MVVM模式早就久仰大名,wpf采用MVVM模式后,我们只需要给属性赋值即可,不再需要去手动操作界面元素,大大减少了界面的开发工作量。
本章,通过解读一个MVVM的实例,来对MVVM模式有个大概的了解。
首先大概看下结构:
分为Models Views ViewModels 。取每个前面第一个字母 就是MVVM
那么这3个模块都是干嘛的呢?
Models定义了一个类
public class TextConverter
{
private readonly Func<string, string> _convertion;
public TextConverter(Func<string, string> convertion)
{
_convertion = convertion;
}
public string ConvertText(string inputText)
{
return _convertion(inputText);
}
}
大概感觉就是执行了一个外部传递进来的函数,然后进行了一些字符串转换。
再看views类
看到了熟悉的界面配置,点开看了下,果然是界面的设计
下面的重点看下ViewModels
定义了3个类
我们一个一个研读
DelegateCommand类 继承ICommand ,
public class DelegateCommand : ICommand
{
private readonly Action _action;
public DelegateCommand(Action action)
{
_action = action;
}
public void Execute(object parameter)
{
_action();
}
public bool CanExecute(object parameter)
{
return true;
}
#pragma warning disable 67
public event EventHandler CanExecuteChanged { add { } remove { } }
#pragma warning restore 67
}
重点就两个函数 第一个CanExecute 是否可以执行第二个Execute 执行了构造函数传递进来的函数。
ObservableObject 类
public abstract class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChangedEvent(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
其实就是执行了PropertyChangedEventHandler事件,那么到底这个事件是干嘛的呢?其实我们不需要关心,我们等下只要关心哪里增加了这个事件即可。
Presenter类,继承上面定义的ObservableObject类
public class Presenter : ObservableObject
{
private readonly TextConverter _textConverter = new TextConverter(s => s.ToUpper());
private string _someText;
private readonly ObservableCollection<string> _history = new ObservableCollection<string>();
public string SomeText
{
get { return _someText; }
set
{
_someText = value;
RaisePropertyChangedEvent("SomeText");
}
}
public IEnumerable<string> History
{
get { return _history; }
}
public ICommand ConvertTextCommand
{
get { return new DelegateCommand(ConvertText); }
}
private void ConvertText()
{
if (string.IsNullOrWhiteSpace(SomeText)) return;
AddToHistory(_textConverter.ConvertText(SomeText));
SomeText = string.Empty;
}
private void AddToHistory(string item)
{
if (!_history.Contains(item))
_history.Add(item);
}
}
下面看界面
<Window.DataContext>
<ViewModels:Presenter/>
</Window.DataContext>
这个DataContext熟悉,其实就是wpf为MVVM准备的,Presenter是我们的一个类,在上面的VM里面,他把这个类绑定到了界面,就是干这个活。
看下面一个界面,真正的材料都在这里
<UserControl x:Class="MinimalMVVM.Views.ConverterControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ViewModels="clr-namespace:MinimalMVVM.ViewModels"
mc:Ignorable="d"
d:DesignHeight="336"
d:DesignWidth="300"
d:DataContext="{d:DesignInstance ViewModels:Presenter}">
<UserControl.InputBindings>
<KeyBinding Key="Enter" Command="{Binding ConvertTextCommand}"/>
</UserControl.InputBindings>
<StackPanel Height="336">
<Label Foreground="Blue" Margin="5,5,5,0">Text To Convert</Label>
<TextBox Text="{Binding SomeText, UpdateSourceTrigger=PropertyChanged}" Margin="5"/>
<Label Foreground="Blue" Margin="5,5,5,0">History</Label>
<ListBox ItemsSource="{Binding History}" Height="200" Margin="5"/>
<Button Command="{Binding ConvertTextCommand}" Margin="5">Convert</Button>
</StackPanel>
</UserControl>
首先:mc:Ignorable=“d"的意思就是告诉编辑器(vs2017)在项目运行时忽略命名空间d设置的大小。
d:DesignHeight=”" d:DesignWidth="" 只说明了控件设计宽度,实际运行时,会根据窗体而变化
与Width、Height不同
参考这里
明白了
就是设计的时候,先以这个类作为数据绑定的依据,在运行的时候,再根据具体的实例来进行赋值。
<TextBox Text="{Binding SomeText, UpdateSourceTrigger=PropertyChanged}" Margin="5"/>
这里有两个地方需要注意:
第一个是Binding 后面的值,第二个是UpdateSourceTrigger
Binding对应的是这个属性,不难理解。
UpdateSourceTrigger设置的是更新的方式,UpdateSourceTrigger一共包含有四种方式:Default,PropertyChanged,LostFocus,Explicit。
Default: 由不同控件控制. 例如 TextBox, 当 LostFocus 事件触发时,目标绑定发生变化.
PropertyChanged: 意味着当目标控件值发生变化时,源数据立马更新.例如, TextBox是目标绑定,当输入字符时, 源数据也发生变化. 着就意味着当你输入字符创的时候,TextBox的数据Text也在改变.
Explicit: 当UpdateSourceTrigger 设置为 Explicit, 数据源不会自动更新,只有在后代码里面显示的触发。
这里设置的是有值变化,立刻更新。
<ListBox ItemsSource="{Binding History}" Height="200" Margin="5"/>
这里是把ListBox绑定到一个list队列,也好理解。
<Button Command="{Binding ConvertTextCommand}" Margin="5">Convert</Button>
这里的话,是把点击按钮的事件,绑定到了下面这个属性上面
public ICommand ConvertTextCommand
{
get { return new DelegateCommand(ConvertText); }
}