WPF之MVVM(Step1)——自己实现ICommand接口

旧地址:http://blog.canself.com/mvvm_ICommand/

开发WPF应用程序,就不得不提MVVM。下面偶将展示MVVM中简单的实现,其中主要在于ICommand的实现上,不过这种实现方式,应该不会有多少人在开发中使用,在此仅作学习使用。

准备:

界面绘制,简单的以一个输入框TextBox和一个按钮Button组成。

入手

接下来写ViewModel,注意其中ViewModel需要继承接口INotifyPropertyChanged,其主要功能是保证后台属性改变能够通知到前台改变。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class TestViewModel : INotifyPropertyChanged
{

private string teststr;
/// <summary>
/// 待通知字符串
/// </summary>
public string TestStr
{
get { return teststr; }
set
{
teststr = value;
RaiseChanged("TestStr");
}
}

/// <summary>
/// 测试命令
/// </summary>
public ICommand TestCommand { get; set; }


public TestViewModel()
{
TestCommand = new TestCommand(this);
}

#region INotifyPropertyChanged接口实现
public void RaiseChanged(string propertyname)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyname));
}
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}

代码中我们看到在TestViewModel中使用了一个TestCommand类。一下是此类的实现,其主要是ICommand的一个实现【开发中不建议使用】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class TestCommand : ICommand
{

public TestCommand(TestViewModel viemo)
{
viewmodel = viemo;
}

TestViewModel viewmodel{get;set;}

public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}

int i = 0;

/// <summary>
/// 命令是否可用
/// </summary>
/// <param name="parameter"></param>
/// <returns></returns>
public bool CanExecute(object parameter)
{
return true;
}

/// <summary>
/// 命令执行的操作
/// </summary>
/// <param name="parameter"></param>
public void Execute(object parameter)
{
i++;
viewmodel.TestStr = i.ToString();
}

}

最后就是将ViewModel内容绑定到界面啦!

XAML:

1
2
3
4
5
6
7
8
9
10
11
12
13
<Window x:Class="WPF_MVVM.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<TextBox Text="{Binding TestStr}"/>
<Button Grid.Row="1" Content="Test" Command="{Binding TestCommand}" />
</Grid>
</Window>

CodeBehind:

1
2
3
4
5
6
7
8
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new TestViewModel();
}
}

这样一个简单的MVVM程序就实现啦。哦,对了,这里未用到Model,可以说还不是一个完整的MVVM,这个就留给自己去思考吧。

WPF线程(Step2)——BackgroundWorker

旧地址:http://blog.canself.com/thread_backgroudnworker/

在WPF中第二个常用的线程处理方式就是BackgroundWorker。

以下是BackgroundWorker一个简单的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public partial class MainWindow : Window
{
/// <summary>
/// 后台worker
/// </summary>
BackgroundWorker worker = new BackgroundWorker();

public MainWindow()
{
InitializeComponent();
worker.WorkerReportsProgress = true;
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.ProgressChanged += worker_ProgressChanged;
this.btn_test.Click += btn_test_Click;
}

/// <summary>
/// 按钮点击
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void btn_test_Click(object sender, RoutedEventArgs e)
{
worker.RunWorkerAsync();
}

/// <summary>
/// 进度返回处理
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.pb_test.Value = e.ProgressPercentage;
}

/// <summary>
/// 业务逻辑处理
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void worker_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i <= 100; i++)
{
worker.ReportProgress(i);//返回进度
Thread.Sleep(100);
}
}
}

其中要注意的有BackgroundWorker的属性WorkerReportsProgress表示BackgroundWorker是否可以返回进度。事件DoWork中处理自身的业务逻辑,ProgressChanged负责更新界面操作。


WPF线程(Step1)——Dispatcher

旧地址:http://blog.canself.com/thread_dispatcher/

使用WPF开发时经常会遇上自己建立的线程需要更新界面UI内容,从而导致的跨线程问题。

  • 异常内容:-
  • 异常类型:System.InvalidOperationException
  • 异常描述:
1
2
3
“System.InvalidOperationException”类型的未经处理的异常在 WindowsBase.dll 中发生

其他信息: 调用线程无法访问此对象,因为另一个线程拥有该对象。

在WPF中最简便的解决此问题的方法就是使用Dispatcher

1、最便捷的使用Dispatcher

1
2
3
4
5
this.Dispatcher.Invoke(new Action(() => {
//Do Something
//更新UI操作
}));
Thread.Sleep(100);

2、使用控件自身的Dispatcher【在WPF中,控件最后都继承自DispatcherObject】

1
2
3
4
5
6
7
8
9
10
11
if (!this.pb_test.Dispatcher.CheckAccess())
{
//更新UI界面
this.pb_test.Dispatcher.Invoke(
DispatcherPriority.Normal,
new UpdateProgressBarDelegate((int progress) => {
this.pb_test.Value = progress;
}),
i);
Thread.Sleep(100);
}

3、同2,利用当前窗体的Dispatcher

1
2
3
4
5
6
7
8
9
10
if (!Dispatcher.CheckAccess())
{
Dispatcher.Invoke(
DispatcherPriority.Normal,
new UpdateProgressBarDelegate((int progress) => {
this.pb_test.Value = progress;
}),
i);
Thread.Sleep(100);
}