DevExpress MVVM
- 模型 -定义数据和您的业务逻辑。
- 视图 -指定UI,包括绑定到ViewModel中的属性和命令的所有可视元素(按钮,标签,编辑器等)。
- ViewModel-连接模型和视图。该层是View的抽象,它公开了用于将数据绑定到GUI元素并管理此数据的公共属性和命令。
约定和属性Conventions and Attributes
属性的语法正确,则可以将其视为可绑定的,这些语法规则称为约定。
可绑定属性
所有公共实现的虚拟属性都是可绑定的
public virtual string Test { get; set; }
也可以禁止
[Bindable(false)]
public virtual string Test { get; set; }
带有字段的属性默认不可进行数据绑定,可以使用BindableProperty属性显式标记此类属性,以仍然能够将其用于数据绑定。
using DevExpress.Mvvm.DataAnnotations;
//. . .
string test;
[BindableProperty]
public virtual string Test
{
get { return test; }
set { test = value; }
}
属性依赖
属性依赖项是一种方法,当其相关属性更改或将要更改时,该方法将自动执行。
public virtual string Test { get; set; }
protected void OnTestChanged() {
//do something
}
为了实现属性依赖,属性依赖的方法必须要命名为On
public virtual string Test { get; set; }
protected void OnTestChanged() {
//do something
}
On
public virtual string Test { get; set; }
protected void OnTestChanging(string newValue) {
//do something
}
protected void OnTestChanged(string oldValue) {
//do something
}
自定义调用方法
[BindableProperty(OnPropertyChangingMethodName = "BeforeChange", OnPropertyChangedMethodName = "AfterChange")]
public virtual string Test { get; set; }
protected void BeforeChange() {
//. . .
}
protected void AfterChange() {
//. . .
}
命令Commands
事件处理程序转移到命令-封装特定操作的对象
在POCO ViewModels中void methods带有0或1个参数的都被视为命令
public void DoSomething(object p) {
MessageBox.Show(string.Format("The parameter passed to command is {0}.", p));
}
以 ...Command命名的方法会引发异常,需要在上面添加特定的特性,并通过设定Name来命名
using DevExpress.Mvvm.DataAnnotations;
[Command(Name="DoSomething")]
public void DoSomethingCommand(object p) {
//do something
}
对于每一个命令方法,框架都会生成相应的备用属性,这个属性会被默认命名为方法名Command通过Command特性的name可以为这个备用属性重命名。
[Command(Name = "MyMvvmCommand")]
public void DoSomething(object p) {
//do something
}
命令可以带有一个CanExecute的返回bool方法,用来指示方法是否可以被执行,同时该方法必须命名为Can
//this command will be executed only if "p" equals 4
public void DoSomething(int p) {
MessageBox.Show(string.Format("The parameter passed to command is {0}.", p));
}
public bool CanDoSomething(int p) {
return (2 + 2) == p;
}
可以通过Command特性的CanExecuteMethodName参数修改可以执行方法的名字
[Command(CanExecuteMethodName = "DoSomethingCriteria")]
public void DoSomething(int p) {
MessageBox.Show(string.Format("The parameter passed to command is {0}.", p));
}
public bool DoSomethingCriteria(int p) {
return (2 + 2) == p;
}
当命令刚刚绑定到目标时,首先检查CanExecute子句(以获取目标的初始状态)。以后每次CanExecuteChanged事件通知命令目标有关命令状态更改时,都会重新计算此标准。在基础命令对象级别声明此事件。要从ViewModel级别发送此类通知,请按如下所示调用RaiseCanExecuteChanged扩展方法。
//a bindable property
public virtual bool IsModified { get; protected set; }
//a command
public void Save() {
//. . .
}
//a CanExecute condition
public bool CanSave() {
return IsModified;
}
//the OnChanged method calls the RaiseCanExecuteChanged method for the "Save" command
//this forces the command to update its CanExecute condition
public void OnIsModifiedChanged() {
this.RaiseCanExecuteChanged(x=>x.Save());
}
服务Service
用于为MVVM应用程序中的视图提供特定的UI感知功能的界面
为了解析服务,框架需要重写接口类型的虚拟属性,而且这些属性的名字都必须以Service结尾。
public virtual IMyNotificationService MyService {
get { throw new NotImplementedException(); }
}
public virtual IMyNotificationService AnotherService {
get { throw new NotImplementedException(); }
}
可以使用ServiceProperty特性来显示标注其他服务属性
using DevExpress.Mvvm.DataAnnotations;
//. . .
[ServiceProperty]
public virtual IMyNotificationService MyProvider {
get { throw new NotImplementedException(); }
}
当框架覆盖服务属性时,它将生成相应的GetService <>扩展方法调用。该ServiceProperty特性可以为这个方法传递一个特殊的参数(例如,业务密钥)。
[ServiceProperty(Key="Service1")]
public virtual IMyNotificationService Service {
get { throw new NotImplementedException(); }
}
[ServiceProperty(Key = "Service2")]
public virtual IMyNotificationService AnotherService {
get { throw new NotImplementedException(); }
}
数据绑定 Data binding和通知Notifications
概念
若要将控件绑定到所需的数据,需要将相关的绑定添加到Control.DataBindings集合。例如有一个编辑器(textbox,editor)和viewmodel中的title属性进行绑定
editor.DataBindings.Add("EditValue", viewModel, "Title", true, DataSourceUpdateMode.OnPropertyChanged)
在运行时,必须可以更新UI元素的属性值,所以Viewmodel必须实现INotifyPropertyChanged接口.
public class LoginViewModel : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
string userNameCore;
public string UserName {
get { return userNameCore; }
set {
if(userNameCore == value) return;
this.userNameCore = value;
PropertyChangedEventHandler handler = PropertyChanged;
if(handler != null)
handler(this, new PropertyChangedEventArgs("UserName"));
}
}
}
基类
从DevExpress提供的BindableBase 和 ViewModelBase类中派生ViewModels,这两个类已经实现了INotifyPropertyChanged 接口。
public class SampleViewModel : BindableBase {
string titleValue;
public string Title {
get { return titleValue; }
set { SetProperty(ref titleValue, value, "Title");}
}
}
绑定属性是使用的字符串名字,这样会有一个问题就是如果重新命名了但是编译器不会报错,最好是使用POCO属性
POCO属性
DevExpress MVVM Framework可以将上述代码简化
public class SampleViewModel {
public virtual string Title { get; set; }
}
这个简化的属性称为POCO(Plain Old CLR Objects)属性,该类称为POCO类,DevExpress MVVM Framework可以识别此类自动将该属性扩展,在上面代码里,POCO类包含一个string Title可绑定属性属性。绑定:
mvvmContext.SetBinding(editor, e => e.EditValue, "Title");
使用Fluent API实现相同的功能
var fluentAPI = mvvmContext.OfType<ViewModel>();
fluentAPI.SetBinding(editor, e => e.EditValue, x => x.Title);
使用 POCO ViewModels时,不需要隐式创建实例,有两种方法,
- 使用DevExpress.Mvvm.POCO.ViewModelSource类的Create方法
//SampleViewModel.cs
public static SampleViewModel Create() {
return ViewModelSource.Create<SampleViewModel>();
}
//MainViewModel.cs
var sampleViewModel = SampleViewModel.Create();
- 使用MvvmContext的ViewModelType 属性,MvvmContext 组件创建POCO ViewModel,同时要确保ViewModel 引用了相关的接口
mvvmContext.ViewModelType = typeof(WindowsFormsApplication1.ViewModels.SampleViewModel);
POCO依赖
实例:实例化viewmodel,将两个文本编辑器绑定到Operand1和Operand2,并将一个结果label绑定到ResultText,并使其具有关联的On ... Changed方法。两个文本编辑器的MaskProperties.MaskType 属性必须设置成 MaskType.Numeric.
//View
mvvmContext.ViewModelType = typeof(MultViewModel);
mvvmContext.SetBinding(editor1, e => e.EditValue, "Operand1");
mvvmContext.SetBinding(editor2, e => e.EditValue, "Operand2");
mvvmContext.SetBinding(resultLabel, l => l.Text, "ResultText");
//ViewModel
public class MultViewModel {
public MultViewModel() {
UpdateResultText();
}
public virtual int Operand1 { get; set; }
public virtual int Operand2 { get; set; }
public virtual int Result { get; set; }
public virtual string ResultText { get; set; }
// OnChanged callback is created for the Operand1 property from this method.
protected void OnOperand1Changed() {
UpdateResult();
}
// OnChanged callback is created for the Operand2 property from this method.
protected void OnOperand2Changed() {
UpdateResult();
}
// OnChanged callback is created for the Result property from this method.
protected void OnResultChanged() {
UpdateResultText();
}
void UpdateResult() {
Result = Operand1 * Operand2;
}
void UpdateResultText() {
ResultText = string.Format("The result of operands multiplication is: {0:n0}", Result);
}
}
另一个相同实现使用BindableProperty特性来指向NotifyResultAndResultTextChanged方法
//View
mvvmContext.ViewModelType = typeof(SumViewModel);
mvvmContext.SetBinding(editor1, e => e.EditValue, "Operand1");
mvvmContext.SetBinding(editor2, e => e.EditValue, "Operand2");
mvvmContext.SetBinding(resultLabel, l => l.Text, "ResultText");
//ViewModel
public class SumViewModel {
[BindableProperty(OnPropertyChangedMethodName = "NotifyResultAndResultTextChanged")]
public virtual int Operand1 { get; set; }
[BindableProperty(OnPropertyChangedMethodName = "NotifyResultAndResultTextChanged")]
public virtual int Operand2 { get; set; }
// We raise change notifications for the Result and ResultText properties manually
public int Result {
get { return Operand1 + Operand2; }
}
public string ResultText {
get { return string.Format("The result of operands summarization is: {0:n0}", Result); }
}
protected void NotifyResultAndResultTextChanged() {
this.RaisePropertyChanged(x => x.Result); // change-notification for the Result
this.RaisePropertyChanged(x => x.ResultText); // change-notification for the ResultText
}
}
Meta-POCO绑定
POCO减轻了很多代码量,但重命名后会导致相关绑定失败。
使用Meta-POCO可以解决这个问题,
public virtual int Result { get; set; }
public virtual int Operand1 { get; set; }
public virtual int Operand2 { get; set; }
public string ResultText {
get { return string.Format("The result of operands summarization is: {0:n0}", Result); }
}
Meta-POCO声明:创建一个包含元数据的嵌套类,该嵌套类明确指定将哪些方法用作属性OnPropertyChanged回调的表达式。元数据类还可以使特定的POCO属性不可绑定。该嵌套类必须实现IMetadataProvider接口。下面的代码说明了ViewModel及其元数据声明。
[System.ComponentModel.DataAnnotations.MetadataType(typeof(Metadata))]
//ViewModel
public class SumViewModel_MetaPOCO {
public SumViewModel_MetaPOCO() {
}
protected void NotifyResultAndResultTextChanged() {
Result = Operand1 + Operand2;
}
// Metadata class for the SumViewModel_MetaPOCO
public class Metadata : IMetadataProvider<SumViewModel_MetaPOCO> {
void IMetadataProvider<SumViewModel_MetaPOCO>.BuildMetadata(MetadataBuilder<SumViewModel_MetaPOCO> builder) {
//Metadata
}
}
}
使用BuildMetadata方法,您可以遍历所有ViewModel属性并提供每个属性的行为。例如,在下面的代码中,Operand1和Operand2属性被视为引发OnPropertyChanged回调的可绑定属性。
public class Metadata : IMetadataProvider<SumViewModel_MetaPOCO> {
void IMetadataProvider<SumViewModel_MetaPOCO>.BuildMetadata(MetadataBuilder<SumViewModel_MetaPOCO> builder) {
builder.Property(x => x.Result)
.DoNotMakeBindable();
builder.Property(x => x.Operand1).
OnPropertyChangedCall(x => x.NotifyResultAndResultTextChanged());
builder.Property(x => x.Operand2).
OnPropertyChangedCall(x => x.NotifyResultAndResultTextChanged());
}
}
可以手动引发更改通知
protected void NotifyResultAndResultTextChanged() {
Result = Operand1 + Operand2;
this.RaisePropertyChanged(x => x.Result); // change-notification for the Result
this.RaisePropertyChanged(x => x.ResultText); // change-notification for the ResultText
}
集合绑定
需多数据类型的绑定是集合,ObservableCollection集合对象存储Test实例。该例子公开两个属性ID和Text,添加记录和删除记录两个公共方法。
public class Test {
public int ID { get; set; }
public string Text { get; set; }
}
public class TestViewModel {
public TestViewModel() {
Entities = new ObservableCollection<Test>();
}
public virtual Test SelectedEntity { get; set; }
public virtual ObservableCollection<Test> Entities { get; set; }
public void Add() {
Entities.Add(new Test() { ID = Entities.Count, Text = string.Format("Test {0}", Entities.Count) });
}
public void Remove(string title) {
Test element = Entities.Single<Test>(x => x.Text == title);
if (element != null) Entities.Remove(element);
}
}
利用MvvmContext组件的API,SetItemsSourceBinding 方法 可以绑定集合的每一个实例,并用实例填充tileControl的每一小块
mvvmContext1.ViewModelType = typeof(TestViewModel);
var fluentApi = mvvmContext1.OfType<TestViewModel>();
TileGroup group = tileControl1.Groups[0];
fluentApi.SetItemsSourceBinding(
group, //Target
g => g.Items, //Items Selector
x => x.Entities, //Source Selector
(tile, entity) => object.Equals(tile.Tag, entity), //Match Expression
entity => new TileItem() { Tag = entity }, //Create Expression
null, //Dispose Expression
(tile, entity) => ((TileItem)tile).Text = entity.Text); //Change Expression
此方法的参数说明
- Target - 应该填充的目标. A TileGroup in this example.
- Items Selector - 一个表达式负责在目标中找到项目集合. For this example, you need the TileGroup.Items collection.
- Source Selector - 一个表达式查找应用于填充目标的项目集合.
- Match Expression - 确定数据源项与源元素是否相同的表达式
- Create Expression - 当出现新的原纪录时,该表达式将创建y一个新的目标元素。
- Dispose Expression - 除去目标后.
- Change Expression - when a new source collection record is added or the existing one is modified, the Match expression finds the target element related to this record. After that, you can use the Change expression to modify its properties. In this sample, the newly created TileItem receives the same caption as its related entity record.
行为Behavoirs
对象添加某些功能,而无需从内部对其进行修改。
通信Messenger
View和ViewModel相互通信的方法
视图管理
MVVM框架需要知道它应显示的特定视图