在我们使用WPF过程中,不可避免并且超级喜欢使用MVVM框架。

那么,使用MVVM的出发点是视觉与业务逻辑分离,即UI与数据分离

诸如下面的问题:

删除操作,假如需要先执行一部分数据的处理,然后删除界面列表中的子项,之后再执行其它数据的处理。请问此业务该放置于Xaml.cs文件,还是ViewModel中呢?

再如弹窗,提示框,设置列表的滚动等等。

此上一些操作,我们不应该把业务代码直接挪到cs文件中,因为删除操作绝大部分的代码都是数据的处理。所以,数据的部分放置在ViewModel中,一些交互放在cs文件中,就是很合理及有必要了。

单元测试,UI与交互的那部分mock模拟有点难度,也没必要去模拟。那么,我们是应该把数据与交互拆开,减少之间的耦合性。这样添加单元测试则更容易。

交互与数据分离 - 描述

首先MVVM,通过View与ViewModel的绑定,我们实现了UI与业务逻辑的分离。通俗一点,我们熟悉的Xaml与ViewModel文件中,代码之间的隔离。在此不详述~

而MVVM,不只是界面与逻辑,其实逻辑还可以拆分成交互与数据

即:Xaml 》Xaml.cs 》ViewModel

是的,按照上面的结构图,我们分成三部分:

  • 界面 用于界面呈现 ---- 如页面/控件/样式/模板等其它资源的初始化,动画的触发等。
  • 交互 用于与用户确认的交互或者界面复杂逻辑的处理 ---- 如弹窗/提示框/复杂动画的处理/设置列表的滚动等其它界面元素的视觉处理。
  • 数据 只是数据的处理 ---- 增删改查导入导出保存等只针对数据的操作,界面状态属性的保存与触发更改(绑定)。

交互与数据分离是怎样的?比如删除:

1. 界面删除按钮,绑定ViewModel中的DeleteCommand,当我们点击删除时,触发DeleteCommand.Execute

2. ViewModel中,先执行数据状态的判断,然后执行交互通知ShowDeleteWaringAction,调用xaml.cs文件中的确认提示框

3. 在Xaml.cs中添加依赖属性ShowDeleteWaring,绑定ViewModel中的ShowDeleteWaringAction.Progress。在属性更改中,处理提示框确认逻辑。

4. ViewModel中,等待ShowDeleteWaring弹框完成后,继续执行下面的业务。

5. 还有类似上面步骤的删除动画。。。

交互与数据分离 - 实现

使用场景:在WPF框架下开发时,一种基于MVVM的UI分离方案

解决方案:在业务逻辑处理过程中,新建一个交互处理线程,通知界面完成交互处理,同时后台逻辑保持同步等待。界面完成交互处理后,回调并执行后续的业务逻辑。

实现方案:

  • View中的依赖属性DependencyProperty,绑定ViewModel中属性“UIDelegateOperation”中的交互处理进度“UIDelegateProress”
  • 每次在ViewModel执行业务逻辑需要调用交互处理时,由UIDelegateOperation创建一个新的交互进度“UIDelegateProress”,触发属性变更,并设置“UIDelegateOperation”同步等待。
  • 当View中的属性变更事件执行完成后,回调并唤醒”UIDelegateOperation“,继续完成后面的业务逻辑。

1. 界面

在Xaml中添加附加属性,删除动画DeleteCoursewaresAnimation,删除确认框ShowDeleteWaring。并绑定ViewModel中对应的属性

 <UserControl.Style>
<Style TargetType="editing:CloudListView">
<Setter Property="DeleteCoursewaresAnimation" Value="{Binding DeleteCoursewaresAnimation.DelegateProgress}" />
<Setter Property="ShowDeleteWaringShow" Value="{Binding ShowDeleteWaring.DelegateProgress}" />
</Style>
</UserControl.Style>

界面ListBox,列表子项ListBoxItemr的DataTemplate模板中,删除按钮绑定ViewModel中的DeleteCommand

 <Button x:Name="DeleteButton"
Command="{Binding ElementName=TheCloudDocsList,Path=DataContext.DeleteCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource TemplatedParent},Path=DataContext }"
Content="删除" Style="{StaticResource Style.Button}" />

 2. ViewModel

ViewModel调用UIDelegateOperation交互处理时,根据是否需要同步等待,调用不同的函数 Start(),StartAsync(),StartWithResult(),StartWithResultAsync();

删除业务中,除了数据处理,还有俩个交互(删除确认框,删除元素动画)。

通过在同步调用删除确认框/删除元素动画后,再继续往下执行业务。

属性和字段字义:

定义命令

自定义命令,可以详细之前写的博客:自定义Command

 private DelegateCommand<CoursewareListItem> _deleteCommand = null;
/// <summary>
/// 删除
/// </summary>
public DelegateCommand<CoursewareListItem> DeleteCommand
{
get
{
if (_deleteCommand == null)
{
_deleteCommand = new DelegateCommand<CoursewareListItem>(DeleteCourseware_OnExecute); }
return _deleteCommand;
}
}

提示框确认交互/删除动画交互

 /// <summary>
/// 弹出删除确认窗口
/// </summary>
public IUIDelegateOperation<List<CoursewareListItem>, MessageResult> ShowDeleteWaring { get; set; } = new IUIDelegateOperation<List<CoursewareListItem>, MessageResult>(); /// <summary>
/// 删除动画
/// </summary>
public IUIDelegateOperation<List<CoursewareListItem>> DeleteCoursewaresAnimation { get; set; } = new IUIDelegateOperation<List<CoursewareListItem>>();

删除逻辑:

 /// <summary>
/// 删除
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
private async void DeleteCourseware_OnExecute(CoursewareListItem item)
{
await DeleteCoursewares(new List<CoursewareListItem>() { item });
}
private async Task DeleteCoursewares(List<CoursewareListItem> items)
{
if (items.Count == )
{
return;
} //弹出删除确认窗口
var messageResult = await ShowDeleteWaringShow.ExecuteWithResultAsync(items);
if (messageResult == MessageResult.Positive)
{
//删除服务器数据
Response deleteResponse = await WebService.DeleteItemAsync(items); //删除失败
if (!deleteResponse.Success)
{
Notification.ShowInfo(deleteResponse.Message);
return;
}
//删除动画
await DeleteCoursewaresAnimation.ExecuteAsync(items); //界面删除子项
items.ForEach(item => ItemsSource.Remove(item)); //退出编辑模式
if (DocListState == EditStatus.Editing)
{
DocListState = EditStatus.Normal;
}
}
}

3. Xaml.cs后台

  • 添加依赖属性后,通过属性变更触发,来完成弹出提示框/删除动画等交互。
  • 执行交互时,需要同步等待时,应将动画执行等转化为同步逻辑。

添加依赖属性 - 删除窗口

属性变更触发方法,应该是一个异步方法,里面的逻辑应该为同步执行。这样ViewModel中才能同步等待交互的完成,并执行之后的逻辑。

 /// <summary>
/// 删除窗口
/// </summary>
public static readonly DependencyProperty ShowDeleteWaringShowProperty = DependencyProperty.Register(
"ShowDeleteWaringShow", typeof(UIDelegateProgress<List<CoursewareListItem>, MessageResult>), typeof(CloudListView), new PropertyMetadata(default(UIDelegateProgress<List<CoursewareListItem>, MessageResult>),
(d, e) => ((UIDelegateProgress<List<CoursewareListItem>, MessageResult>)e.NewValue)?.StartAsync(((CloudListView)d).ShowDeleteWaringShow))); private async Task<MessageResult> ShowDeleteWaringShow(List<CoursewareListItem> items)
{
var cmd = await DeleteWaringShow(items);
return cmd.Result;
} public static void SetShowDeleteWaringShow(DependencyObject element, UIDelegateProgress<List<CoursewareListItem>, MessageResult> value)
{
element.SetValue(ShowDeleteWaringShowProperty, value);
} public static UIDelegateProgress<List<CoursewareListItem>, MessageResult> GetShowDeleteWaringShow(DependencyObject element)
{
return (UIDelegateProgress<List<CoursewareListItem>, MessageResult>)element.GetValue(ShowDeleteWaringShowProperty);
}

添加依赖属性 - 删除动画

 public static readonly DependencyProperty DeleteCoursewaresAnimationProperty = DependencyProperty.Register(
"DeleteCoursewaresAnimation", typeof(UIDelegateProgress<List<CoursewareListItem>>), typeof(CloudListView), new PropertyMetadata(default(UIDelegateProgress<List<CoursewareListItem>>),
(d, e) => ((UIDelegateProgress<List<CoursewareListItem>>)e.NewValue)?.StartAsync(((CloudListView)d).ExecuteDeleteCoursewaresAnimation))); private async Task ExecuteDeleteCoursewaresAnimation(List<CoursewareListItem> coursewares)
{
List<Storyboard> storyboards = new List<Storyboard>();
foreach (var courseware in coursewares)
{
var listBoxItem = DocumentsControl.ItemContainerGenerator.ContainerFromItem(courseware) as ListBoxItem;
var border = listBoxItem?.VisualDescendant<Border>();
var storyboard = (Storyboard)border?.Resources["ItemRemovedStoryboard"];
if (storyboard == null)
{
//如果找不到storyBoard,则中断动画的执行。因为删除多个Item,只执行一半的动画,界面会闪现俩次。
return;
}
storyboards.Add(storyboard);
}
//删除界面课件
await AsynchronousTransferHelper.ExecuteStoryboradAsync(storyboards);
} public static void SetDeleteCoursewaresAnimation(DependencyObject element, UIDelegateProgress<List<CoursewareListItem>> value)
{
element.SetValue(DeleteCoursewaresAnimationProperty, value);
} public static UIDelegateProgress<List<CoursewareListItem>> GetDeleteCoursewaresAnimation(DependencyObject element)
{
return (UIDelegateProgress<List<CoursewareListItem>>)element.GetValue(DeleteCoursewaresAnimationProperty);
}

动画的执行,怎么转为有同步等待呢?动画完成只有通过触发事件Completed才能确定。

如何将动画转化为同步,可参考之前写的博客:C# 异步转同步

 /// <summary>
/// 执行动画
/// </summary>
/// <param name="storyboard"></param>
/// <returns></returns>
public static async Task ExecuteStoryboradAsync([NotNull] Storyboard storyboard)
{
if (storyboard == null) throw new ArgumentNullException(nameof(storyboard)); AutoResetEvent autoResetEvent = new AutoResetEvent(false); storyboard.Completed += OnStoryboardCompleted;
storyboard.Begin(); void OnStoryboardCompleted(object sender, EventArgs e)
{
storyboard.Completed -= OnStoryboardCompleted;
autoResetEvent.Set();
} await Task.Run(() => { autoResetEvent.WaitOne(); });
}

4. 交互处理辅助类 UIDelegateOperation 

在UIDelegateOperation内部,每次调用时,都会新建一个UIDelegateProgress(委托进度)。委托进度,是界面交互的处理~

UIDelegateOperation:

 /// <summary>
/// UI交互处理-提供可调用UI交互的操作
/// </summary>
public class UIDelegateOperation : BindableObject, IUIDelegateAction
{
private UIDelegateProgress _delegateProgress; public UIDelegateProgress DelegateProgress
{
get => _delegateProgress;
private set
{
_delegateProgress = value;
OnPropertyChanged();
}
} /// <summary>
/// 执行
/// </summary>
public void Execute()
{
var delegateProgress = new UIDelegateProgress();
delegateProgress.ProgressCompleted += () =>
{
_delegateProgress = null;
};
DelegateProgress = delegateProgress;
} /// <summary>
/// 异步执行
/// 交互处理完成并回调
/// </summary>
public async Task ExecuteAsync()
{
AutoResetEvent autoResetEvent = new AutoResetEvent(false); var delegateProgress = new UIDelegateProgress();
delegateProgress.ProgressCompleted += () =>
{
_delegateProgress = null; autoResetEvent.Set();
};
DelegateProgress = delegateProgress;
await Task.Run(() => { autoResetEvent.WaitOne(); });
}
} /// <summary>
/// UI交互处理-提供可同步调用UI交互的操作
/// </summary>
/// <typeparam name="T">输入/输出类型</typeparam>
public class UIDelegateAction<T> : BindableObject, IUIDelegateAction<T>
{
private UIDelegateProgress<T> _delegateProgress; public UIDelegateProgress<T> DelegateProgress
{
get => _delegateProgress;
private set
{
_delegateProgress = value;
OnPropertyChanged();
}
}
/// <summary>
/// 执行
/// </summary>
public void Execute(T parameter)
{
var delegateProgress = new UIDelegateProgress<T>(parameter);
delegateProgress.ProgressCompleted += () =>
{
_delegateProgress = null;
};
DelegateProgress = delegateProgress;
}
/// <summary>
/// 异步执行
/// 交互处理完成并回调
/// </summary>
public async Task ExecuteAsync(T parameter)
{
AutoResetEvent autoResetEvent = new AutoResetEvent(false); var delegateProgress = new UIDelegateProgress<T>(parameter);
delegateProgress.ProgressCompleted += () =>
{
_delegateProgress = null; autoResetEvent.Set();
};
DelegateProgress = delegateProgress; await Task.Run(() => { autoResetEvent.WaitOne(); });
} /// <summary>
/// 异步执行并返回结果
/// </summary>
public async Task<T> ExecuteWithResultAsync()
{
AutoResetEvent autoResetEvent = new AutoResetEvent(false); var delegateProgress = new UIDelegateProgress<T>();
delegateProgress.ProgressCompleted += () =>
{
_delegateProgress = null; autoResetEvent.Set();
};
DelegateProgress = delegateProgress; await Task.Run(() => { autoResetEvent.WaitOne(); }); return delegateProgress.Result;
}
} /// <summary>
/// UI交互处理-提供可同步调用UI交互的操作
/// </summary>
/// <typeparam name="TInput">输入类型</typeparam>
/// <typeparam name="TOut">输出类型</typeparam>
public class UIDelegateAction<TInput, TOut> : BindableObject, IUIDelegateAction<TInput, TOut>
{
private UIDelegateProgress<TInput, TOut> _delegateProgress; public UIDelegateProgress<TInput, TOut> DelegateProgress
{
get => _delegateProgress;
private set
{
_delegateProgress = value;
OnPropertyChanged();
}
}
/// <summary>
/// 执行
/// </summary>
public void Execute(TInput parameter)
{
var delegateProgress = new UIDelegateProgress<TInput, TOut>(parameter);
delegateProgress.ProgressCompleted += () =>
{
_delegateProgress = null;
};
DelegateProgress = delegateProgress;
} /// <summary>
/// 执行并返回结果
/// </summary>
public TOut ExecuteWithResult(TInput parameter)
{
var delegateProgress = new UIDelegateProgress<TInput, TOut>(parameter);
delegateProgress.ProgressCompleted += () =>
{
_delegateProgress = null;
};
DelegateProgress = delegateProgress;
return delegateProgress.Result;
} /// <summary>
/// 异步执行并返回结果
/// </summary>
public async Task<TOut> ExecuteWithResultAsync(TInput parameter)
{
var delegateProgress = new UIDelegateProgress<TInput, TOut>(parameter);
await SetDelegateProgress(delegateProgress);
return delegateProgress.Result;
}
private async Task SetDelegateProgress(UIDelegateProgress<TInput, TOut> delegateProgress)
{
AutoResetEvent autoResetEvent = new AutoResetEvent(false); delegateProgress.ProgressCompleted += () =>
{
_delegateProgress = null;
autoResetEvent.Set();
};
DelegateProgress = delegateProgress;
await Task.Run(() => { autoResetEvent.WaitOne(); });
}
} /// <summary>
/// UI交互处理接口
/// </summary>
public interface IUIDelegateAction
{ UIDelegateProgress DelegateProgress { get; } /// <summary>
/// 执行
/// </summary>
void Execute(); /// <summary>
/// 异步执行
/// </summary>
Task ExecuteAsync();
} /// <summary>
/// UI交互处理接口
/// </summary>
/// <typeparam name="T">输入/输出类型</typeparam>
public interface IUIDelegateAction<T>
{
UIDelegateProgress<T> DelegateProgress { get; } /// <summary>
/// 执行
/// </summary>
void Execute(T parameter); /// <summary>
/// 异步执行
/// </summary>
Task ExecuteAsync(T parameter); /// <summary>
/// 异步执行并返回结果
/// </summary>
Task<T> ExecuteWithResultAsync();
} /// <summary>
/// UI交互处理接口
/// </summary>
/// <typeparam name="TInput">输入类型</typeparam>
/// <typeparam name="TOut">输出类型</typeparam>
public interface IUIDelegateAction<TInput, TOut>
{
UIDelegateProgress<TInput, TOut> DelegateProgress { get; } /// <summary>
/// 执行
/// </summary>
void Execute(TInput parameter); /// <summary>
/// 执行并返回结果
/// </summary>
TOut ExecuteWithResult(TInput parameter); /// <summary>
/// 异步执行并返回结果
/// </summary>
Task<TOut> ExecuteWithResultAsync(TInput parameter);
}

UIDelegateProgress:

     /// <summary>
/// 委托进度
/// </summary>
public class UIDelegateProgress
{
public event Action ProgressCompleted; /// <summary>
/// UI委托处理
/// </summary>
/// <param name="uiTask"></param>
public async void StartAsync(Func<Task> uiTask)
{
try
{
await uiTask.Invoke();
}
catch (InvalidOperationException e)
{
Log.Error("UI交互处理,产生异常!", e);
}
finally
{
ProgressCompleted?.Invoke();
}
} /// <summary>
/// UI委托处理
/// </summary>
/// <param name="uiTask"></param>
public void Start(Action uiTask)
{
try
{
uiTask.Invoke();
}
catch (InvalidOperationException e)
{
Log.Error("UI交互处理,产生异常!", e);
}
finally
{
ProgressCompleted?.Invoke();
}
}
} /// <summary>
/// 委托进度
/// </summary>
public class UIDelegateProgress<T>
{
public event Action ProgressCompleted; /// <summary>
/// 输入参数
/// </summary>
public T Parameter { get; set; } /// <summary>
/// 输出参数
/// </summary>
public T Result { get; set; } public UIDelegateProgress()
{ }
public UIDelegateProgress(T parameter)
{
Parameter = parameter;
} /// <summary>
/// UI委托处理
/// </summary>
/// <param name="uiTask"></param>
public void Start(Action<T> uiTask)
{
try
{
uiTask.Invoke(Parameter);
}
catch (InvalidOperationException e)
{
Log.Error("UI交互处理,产生异常!", e);
}
finally
{
ProgressCompleted?.Invoke();
}
} /// <summary>
/// UI委托处理
/// </summary>
/// <param name="uiTask"></param>
public async void StartAsync(Func<T, Task> uiTask)
{
try
{
await uiTask.Invoke(Parameter);
}
catch (InvalidOperationException e)
{
Log.Error("UI交互处理,产生异常!", e);
}
finally
{
ProgressCompleted?.Invoke();
}
} /// <summary>
/// UI委托处理
/// </summary>
/// <param name="uiTask"></param>
public void Start(Func<T> uiTask)
{
try
{
Result = uiTask.Invoke();
}
catch (InvalidOperationException e)
{
Log.Error("UI交互处理,产生异常!", e);
}
finally
{
ProgressCompleted?.Invoke();
}
} /// <summary>
/// UI委托处理
/// </summary>
/// <param name="uiTask"></param>
public async void StartAsync(Func<Task<T>> uiTask)
{
try
{
Result = await uiTask.Invoke();
}
catch (InvalidOperationException e)
{
Log.Error("UI交互处理,产生异常!", e);
}
finally
{
ProgressCompleted?.Invoke();
}
}
} /// <summary>
/// 委托进度
/// </summary>
public class UIDelegateProgress<TInput, TOut>
{
public event Action ProgressCompleted; /// <summary>
/// 输入参数
/// </summary>
public TInput Parameter { get; set; } /// <summary>
/// 输出参数
/// </summary>
public TOut Result { get; set; } public UIDelegateProgress(TInput parameter)
{
Parameter = parameter;
} /// <summary>
/// UI委托处理
/// </summary>
/// <param name="uiTask"></param>
public async void StartAsync(Func<TInput, Task<TOut>> uiTask)
{
try
{
Result = await uiTask.Invoke(Parameter);
}
catch (InvalidOperationException e)
{
Log.Error("UI交互处理,产生异常!", e);
}
finally
{
ProgressCompleted?.Invoke();
}
} /// <summary>
/// UI委托处理
/// </summary>
/// <param name="uiTask"></param>
public void Start(Func<TOut> uiTask)
{
try
{
uiTask.Invoke();
}
catch (InvalidOperationException e)
{
Log.Error("UI交互处理,产生异常!", e);
}
finally
{
ProgressCompleted?.Invoke();
}
} /// <summary>
/// UI委托处理
/// </summary>
/// <param name="uiTask"></param>
public void Start(Func<TInput, TOut> uiTask)
{
try
{
Result = uiTask.Invoke(Parameter);
}
catch (InvalidOperationException e)
{
Log.Error("UI交互处理,产生异常!", e);
}
finally
{
ProgressCompleted?.Invoke();
}
}
}

Demo中,举例了界面的删除操作

https://github.com/Kybs0/MVVM.DataAndInteractionIsolation

InvokeCommandAction

详细请参考Command篇

通过InvokeCommandAction 的使用,WPF任意事件都可以绑定Command,将业务逻辑放在ViewModel中。如:

 <TextBlock>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<i:InvokeCommandAction Command="{Binding MouseLeftButtonDownCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>

关键字:UI分离,交互与数据分离,动画同步,单元测试

WPF MVVM UI分离之《交互与数据分离》的更多相关文章

  1. WPF MVVM UI分离之《交互与数据分离》 基础才是重中之重~delegate里的Invoke和BeginInvoke 将不确定变为确定系列~目录(“机器最能证明一切”) 爱上MVC3系列~全局异常处理与异常日志 基础才是重中之重~lock和monitor的区别 将不确定变成确定~我想监视我的对象,如果是某个值,就叫另一些方法自动运行 将不确定变成确定~LINQ DBML模型可以对

    WPF MVVM UI分离之<交互与数据分离>   在我们使用WPF过程中,不可避免并且超级喜欢使用MVVM框架. 那么,使用MVVM的出发点是视觉与业务逻辑分离,即UI与数据分离 诸如下 ...

  2. 前端的UI设计与交互之数据展示篇

    合适的数据展示方式可以帮助用户快速地定位和浏览数据,以及更高效得协同工作.在设计时有以下几点需要注意:依据信息的重要等级.操作频率和关联程度来编排展示的顺序.注意极端情况下的引导.如数据信息过长,内容 ...

  3. WPF MVVM(Caliburn.Micro) 数据验证

    书接前文 前文中仅是WPF验证中的一种,我们暂且称之为View端的验证(因为其验证规是写在Xaml文件中的). 还有一种我们称之为Model端验证,Model通过继承IDataErrorInfo接口来 ...

  4. WPF MVVM从入门到精通8:数据验证

    原文:WPF MVVM从入门到精通8:数据验证 WPF MVVM从入门到精通1:MVVM模式简介 WPF MVVM从入门到精通2:实现一个登录窗口 WPF MVVM从入门到精通3:数据绑定 WPF M ...

  5. wpf mvvm datagrid数据过滤

    原文:wpf mvvm datagrid数据过滤 datagrid数据过滤,你可以通过设置RowStyle属性,通过将Visibility绑定到ViewModel层的属性来控制是否可见,比如:   & ...

  6. WPF自学入门(十)WPF MVVM简单介绍

     前面文章中,我们已经知道,WPF技术的主要特点是数据驱动UI,所以在使用WPF技术开发的过程中是以数据为核心的,WPF提供了数据绑定机制,当数据发生变化时,WPF会自动发出通知去更新UI. 我们不管 ...

  7. 【转】【WPF】WPF MVVM 简单实例

    1 新建WPF 应用程序WPFMVVMExample 程序结构如下图所示. 2 Model实现 在Model文件夹下新建业务类StudentModel(类文件StudentModel.cs),类的详细 ...

  8. 一个简单的WPF MVVM实例【转载】

    引用地址:http://blog.csdn.net/yl2isoft/article/details/20838149 1 新建WPF 应用程序WPFMVVMExample 程序结构如下图所示. 2  ...

  9. WPF自学入门(十一)WPF MVVM模式Command命令 WPF自学入门(十)WPF MVVM简单介绍

    WPF自学入门(十一)WPF MVVM模式Command命令   在WPF自学入门(十)WPF MVVM简单介绍中的示例似乎运行起来没有什么问题,也可以进行更新.但是这并不是我们使用MVVM的正确方式 ...

随机推荐

  1. 关于ruby gem无法连接到rubygems.org的解决方案

    RubyGems 镜像 - 淘宝网 为什么有这个? 由于国内网络原因(你懂的),导致 rubygems.org 存放在 Amazon S3 上面的资源文件间歇性连接失败.所以你会与遇到 gem ins ...

  2. PyCharm命令行输入

    PyCharm命令行输入 写作原因 网上资料比较杂,版本较老,与现在的版本有区别,所以根据网上资料和自己亲手实验撰写此文. 设置方法 在菜单中按此路径设置: Run->Edit Configur ...

  3. SpringBoot2.0之一 新建项目helloWorld

    SpringBoot 以简单快速很快获得了广大开发者的青睐,本套SpringBoot系列以最新的SpringBoot 2.0为基础,同时会提及不同版本下SpringBoot的修改和变化,如有理解不当的 ...

  4. BAT面试技巧

    很多人都质疑面试前去google一下面试题,是否有用....其实真实情况往往是这样:前台告诉经理,有个面试者来了,经理一拍头:啊!差点忘了!拿起电话:小谢,你有空吧,帮忙面个试! 小谢答应后,goog ...

  5. JTA 原理分析

    JTA 深度历险 - 原理与实现 在 J2EE 应用中,事务是一个不可或缺的组件模型,它保证了用户操作的 ACID(即原子.一致.隔离.持久)属性.对于只操作单一数据源的应用,可以通过本地资源接口实现 ...

  6. 多重影分身——C#中多线程的使用三(调用方法和传参)

    对Thread: 1.使用ThreadStart static void Main(string[] args) { Thread th1=new Thread(new ThreadStart(Say ...

  7. 关闭ipv6的方法

    公司研发反应,几台机器开了一些端口,但是访问一直不通. 检查后发现,发现服务开启的是ipv6的端口,所有首先想到的办法就是关闭ipv6. 关闭ipv6的方法有两种: 第一个是在内核配置文件修改配置(p ...

  8. Python并发编程的几篇文章

    Python几种并发实现方案的性能比较 http://www.elias.cn/Python/PyConcurrency?from=Develop.PyConcurrency python并发编程 h ...

  9. Hive入门学习--HIve简介

    现在想要应聘大数据分析或者数据挖掘岗位,很多都需要会使用Hive,Mapreduce,Hadoop等这些大数据分析技术.为了充实自己就先从简单的Hive开始吧.接下来的几篇文章是记录我如何入门学习Hi ...

  10. redis 设置

    设置成服务命令,redis目录下,执行cmd命令 redis-server --service-install redis.windows-service.conf --loglevel verbos ...