常规绑定——ViewModel属性绑定到任何不可编辑的视图元素的属性。由于元素是不可编辑的,所以不需要将更新通知发送回绑定属性(单向绑定)。
数据绑定—— 一个Model属性(数据字段)绑定到编辑器属性。如果用户可以更改编辑器值,则需要更新绑定属性(双向绑定)。
属性依赖——来自同一个ViewModel的两个属性被绑定。
1. 常规绑定
如果你需要将数据从一个属性传递到另一个ViewModel的属性,您可以使用标准的DataBindings API,或者我们建议使用DevExpress MvvmContext.SetBinding方法。
例如,视图有一个没有文本的LabelControl。一个视图模型有一个可绑定的字符串“LabelText”属性。使用以下任何方法将属性值传递给此标签。
[POCOViewModel()]
public class Form1ViewModel {
public Form1ViewModel() {
LabelText = "Value stored in ViewModel";
public virtual string LabelText { get; set; }
var fluent = mvvmContext1.OfType<Form1ViewModel>();
fluent.SetBinding(labelControl1, l => l.Text, x=>x.LabelText);
Form1ViewModel viewModel = mvvmContext1.GetViewModel<Form1ViewModel>();
labelControl1.DataBindings.Add("Text", viewModel, "LabelText");
2. 更新 POCO ViewModel中的通知
如果绑定属性的值可能更改,则务必将此更改通知相关属性。为此,向依赖属性发送更新通知。如果你使用POCO视图模型,DevExpress框架可以发送这些通知。
2.1 什么是POCO ViewModel?
在MVVM应用程序中,每个视图都有一个相关的视图模型。当您使用DevExpress MVVM框架时,您应该向每个视图添加一个MvvmContext组件,并将该组件指向与该视图相关的视图模型。我们建议在设计时通过组件的智能标记菜单进行此操作。
您也可以在代码中使用ViewModelType属性来完成此操作。
mvvmContext.ViewModelType = typeof(ViewModel);
框架将分配给MvvmContext组件的每个视图模型视为POCO(Plain Old CRL Object)视图模型。POCO视图模型有许多命名和语法约定。如果你遵循它们,这个框架会预测你想做什么,并相应地采取行动。例如,更新通知自动发送到(来自)“正确”声明的属性。
创建一个public virtual auto-implemented 的属性,以允许框架向该属性发送或从该属性发送更新通知。还可以将属性setter声明为protected。
public virtual string Name { get; set; }
public virtual int ID { get; protected set; }
框架会忽略具有支持字段的属性。用DevExpress.Mvvm.DataAnnotations.BindableProperty属性注解来标记,就能够绑定这样的属性了。如下:
using DevExpress.Mvvm.DataAnnotations;
string name;
[BindableProperty]
public virtual string Name {
get { return name; }
set { name = value; }
在可绑定属性例子中,一个Label标签显示TextEdit编辑器的值。TextEdit被绑定到自动实现的虚拟Text属性(存储原始编辑器值),Label标签被绑定到Title(存储格式化的"Text"值)。
由于“Text”属性遵循POCO的命名约定,文本到文本的绑定是双向的:当ViewModel属性改变时,编辑器更新它的值,而当用户修改编辑器文本时,ViewModel属性更新它的值。标签到标题的绑定是单向的,因为“Title”属性没有公共设置方法。在此设置中,我们不需要对“Title”进行双向绑定,因为用户无法更改标签文本。
var fluent = mvvmContext.OfType<ViewModel>();
fluent.SetBinding(editor, ed => ed.EditValue, x => x.Text);
fluent.SetBinding(label, lbl => lbl.Text, x => x.Title);
public class ViewModel {
public virtual string Text { get; set; }
public string Title {
get {
if(Text == null)
return "Title: (Null)";
if(Text.Length == 0)
return "Title: (Empty)";
if(string.IsNullOrWhiteSpace(Text))
return "Title: (Whitespace)";
return "Title: " + Text;
上面的代码演示了“Title”和“Text”属性之间的区别,它并不完整。例子模块还使用属性依赖更新"Title”的同时,“Text”也更改了;运行演示程序 以查看完整的代码。
2.2 绑定嵌套ViewModel和Non-POCO ViewModel的属性
如果需要绑定嵌套的ViewModel属性,请使用DevExpress.Mvvm.POCO.ViewModelSource.Create方法创建此嵌套ViewModel的实例,你可以通过父ViewModel访问该ViewModel。View绑定语法使用相同的SetBinding方法。
public class NestedViewModel {
public virtual string Text { get; set; }
public class ViewModelWithChild {
public ViewModelWithChild() {
Child = ViewModelSource.Create<NestedViewModel>();
public NestedViewModel Child {
get;
private set;
var fluent = mvvmContext.OfType<ViewModelWithChild>();
fluent.SetBinding(editor, ed => ed.EditValue, x => x.Child.Text);
如果不使用POCO Model,框架不会自动发送更新通知。在这种情况下,要发送通知,请实现INotifyPropertyChanged接口或创建**-PropertyName-Changed事件。注意,您不能使用mvvmContext.ViewModelType属性,您应该调用mvvmContext.SetViewModel** 方法将一个ViewModel实例传递给组件。
public class ObjectWithTextAndTitle {
string textCore;
public string Text {
get { return textCore; }
set {
if(textCore == value) return;
textCore = value;
OnTextChanged();
protected virtual void OnTextChanged() {
RaiseTextChanged();
protected void RaiseTextChanged() {
var handler = TextChanged;
if(handler != null) handler(this, EventArgs.Empty);
public event EventHandler TextChanged;
mvvmContext.SetViewModel(typeof(ObjectWithTextAndTitle), viewModelInstance);
var fluent = mvvmContext.OfType<ObjectWithTextAndTitle>();
fluent.SetBinding(editor, ed => ed.EditValue, x => x.Text);
3. 数据绑定
要将编辑器绑定到Model属性,请向View添加BindingSource并使用标准的DataBindings API。可选的updateMode参数允许您指定当编辑器值发生更改时,属性是否更新其值,以及(如果是)是立即更新还是在验证编辑器时更新。
editor.DataBindings.Add(...);
Binding of Entity Properties demo中,定义了一个自定义实体类。该类的实例用作数据记录,并具有ID和文本字段。两个数据字段都绑定到编辑器,BindingSource组件存储活动Entity对象。
mvvmContext.ViewModelType = typeof(ViewModel);
var fluentApi = mvvmContext.OfType<ViewModel>();
BindingSource entityBindingSource = new BindingSource();
entityBindingSource.DataSource = typeof(Entity);
fluentApi.SetObjectDataSourceBinding(entityBindingSource, x => x.Entity, x => x.Update());
idEditor.DataBindings.Add(new Binding("EditValue", entityBindingSource, "ID"));
textEditor.DataBindings.Add(
new Binding("EditValue", entityBindingSource, "Text", true, DataSourceUpdateMode.OnPropertyChanged));
public class ViewModel {
public virtual Entity Entity {
get;
set;
public class Entity {
public Entity(int id) {
this.ID = id;
this.Text = "Entity " + id.ToString();
public int ID { get; private set; }
public string Text { get; set; }
4. 属性依赖
属性依赖关系是来自同一视图模型的两个属性之间的关系。当一个属性更改时,另一个属性将更新其值。
在“MVVM最佳实践”例子中,多个模块演示了以下设置:
两个TextEdit控件绑定到ViewModel“Operand1”和“operand2”属性。
当用户更改TextEdit值时,操作数属性刷新它们的值。
当操作数属性更改时,它们会更新数值“Result”属性(依赖项#1)。
“Result”属性更新字符串“ResultText”属性(依赖项#2)。
将View元素绑定到ViewModel属性的代码对于使用示例UI的每个例子模块都是相同的。
mvvmContext.ViewModelType = typeof(MultViewModel);
var fluentAPI = mvvmContext.OfType<MultViewModel>();
fluentAPI.SetBinding(editor1, e => e.EditValue, x => x.Operand1);
fluentAPI.SetBinding(editor2, e => e.EditValue, x => x.Operand2);
fluentAPI.SetBinding(resultLabel, l => l.Text, x => x.ResultText);
但是,属性依赖关系在每个模块中声明不同。
4.1 OnPropertyChanged 方法
在POCO ViewModel中,您可以声明OnXChanged方法,其中X是一个属性名。当相关属性的值发生变化时,框架调用这些方法。
Run the Demo
public class MultViewModel {
public virtual int Operand1 { get; set; }
public virtual int Operand2 { get; set; }
public virtual int Result { get; set; }
public virtual string ResultText { get; set; }
protected void OnOperand1Changed() {
UpdateResult();
protected void OnOperand2Changed() {
UpdateResult();
protected void OnResultChanged() {
UpdateResultText();
void UpdateResult() {
Result = Operand1 * Operand2;
void UpdateResultText() {
ResultText = string.Format("The result is: {0:n0}", Result);
4.2 自定义更新方法
如果你的更新方法没有被调用"On...Changed", 使用DevExpress.Mvvm.DataAnnotations.BindableProperty 属性注解来告诉框架,在属性值更改时应该调用此方法。在下面的代码示例中,DevExpress.Mvvm.POCO.RaisePropertyChanged 是DevExpress的一个扩展方法,它向依赖属性发送更新通知。
Run the Demo
public class SumViewModel {
[BindableProperty(OnPropertyChangedMethodName = "NotifyResultAndResultTextChanged")]
public virtual int Operand1 { get; set; }
[BindableProperty(OnPropertyChangedMethodName = "NotifyResultAndResultTextChanged")]
public virtual int Operand2 { get; set; }
public int Result {
get { return Operand1 + Operand2; }
public string ResultText {
get { return string.Format("The result is: {0:n0}", Result); }
protected void NotifyResultAndResultTextChanged() {
this.RaisePropertyChanged(x => x.Result);
this.RaisePropertyChanged(x => x.ResultText);
4.3 依赖属性
标记依赖属性可以使用 [DevExpress.Mvvm.DataAnnotations.DependsOnProperties] 属性注解来处理。注意,与前面的示例不同,下面的代码只使用一个依赖项:“ResultText”同时依赖于“操作数”属性。不能使用此属性创建链式依赖关系。
public class MultViewModelEx {
public virtual int Operand1 { get; set; }
public virtual int Operand2 { get; set; }
[DependsOnProperties("Operand1", "Operand2")]
public string ResultText {
get { return string.Format("The result is: {0:n0}", Operand1 * Operand2); }
4.4 元数据类
在这种方法中,您将创建自定义更新方法,并使用单独的元数据类将属性与这些方法关联起来。如果 [BindableProperty] 属性注解,通过名称引用更新方法,则OnPropertyChangedCall方法使用lambda表达式检索方法。重命名自定义更新方法时,元数据类显示编译错误。
[System.ComponentModel.DataAnnotations.MetadataType(typeof(Metadata))]
public class SumViewModel_MetaPOCO {
public virtual int Operand1 { get; set; }
public virtual int Operand2 { get; set; }
public virtual int Result { get; set; }
public string ResultText {
get { return string.Format("The result is: {0:n0}", Result); }
protected void NotifyResultAndResultTextChanged() {
Result = Operand1 + Operand2;
this.RaisePropertyChanged(x => x.Result);
this.RaisePropertyChanged(x => x.ResultText);
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());
5. 集合绑定
要使用data source的记录来填充多项控件,请使用SetItemsSourceBinding方法。
var fluentApi = mvvmContext1.OfType<ViewModelClass>();
fluentApi.SetItemsSourceBinding(
Target
ItemSelector,
SourceSelector,
MatchExpression,
CreateExpression,
DisposeExpression,
ChangeExpression
Target——需要填充的目标UI元素。
ItemSelector——检索应该从数据源填充的UI元素的项集合的表达式。
SourceSelector——定位其项应用于填充目标的数据源的表达式。
MatchExpression——比较数据源项和目标子项的表达式。当您更改或删除一个数据源记录时,框架运行此表达式以确定它是否应该更新相应的目标集合项。
CreateExpression——当出现新的数据源记录时,创建新的目标集合项的表达式。
DisposeExpression——当目标集合项的相关数据源记录被删除时,对其进行处理的表达式。
ChangeExpression——指定当匹配表达式得出与数据源记录不同的项时,如何更新目标集合项。
在MVVM最佳实践例子中,以下代码使用自定义实体类的对象填充列表框。SetBinding方法将编辑器的SelectedItem属性与检索相应实体对象的ViewModel SelectedEntity属性绑定。
mvvmContext.ViewModelType = typeof(ViewModel);
var fluentApi = mvvmContext.OfType<ViewModel>();
fluentApi.SetItemsSourceBinding(
listBox,
lb => lb.Items,
x => x.Entities,
(item, entity) => object.Equals(item.Value, entity),
entity => new ImageListBoxItem(entity),
null,
(item, entity) => {
((ImageListBoxItem)item).Description = entity.Text;
fluentApi.SetBinding(listBox, lb => lb.SelectedValue, x => x.SelectedEntity);
public class ViewModel {
public virtual Entity SelectedEntity { get; set; }
public virtual ObservableCollection<Entity> Entities { get; set;}
protected void OnSelectedEntityChanged() {
this.RaiseCanExecuteChanged(x => x.Remove());
protected void OnEntitiesChanged() {
SelectedEntity = Entities.FirstOrDefault();
public class Entity {
public Entity(int id) {
this.ID = id;
this.Text = "Entity " + id.ToString();
public int ID { get; private set; }
public string Text { get; set; }
6. 触发器
触发器允许您在ViewModel属性更改时修改UI(视图)。在DevExpress的例子中,复选框被绑定到ViewModel的“IsActive”属性。当这个属性的值改变时,触发器会改变UI元素(标签)的背景颜色。
public class ViewModel {
public virtual bool IsActive { get; set; }
var fluent = mvvmContext.OfType<ViewModel>();
fluent.SetBinding(checkEdit, c => c.Checked, x => x.IsActive);
fluent.SetTrigger(x => x.IsActive, (active) => {
if(active)
label.Appearance.BackColor = Color.LightPink;
label.Appearance.BackColor = Color.Empty;