• 常规绑定——ViewModel属性绑定到任何不可编辑的视图元素的属性。由于元素是不可编辑的,所以不需要将更新通知发送回绑定属性(单向绑定)。
  • 数据绑定—— 一个Model属性(数据字段)绑定到编辑器属性。如果用户可以更改编辑器值,则需要更新绑定属性(双向绑定)。
  • 属性依赖——来自同一个ViewModel的两个属性被绑定。
  • 1. 常规绑定

    如果你需要将数据从一个属性传递到另一个ViewModel的属性,您可以使用标准的DataBindings API,或者我们建议使用DevExpress MvvmContext.SetBinding方法。
    例如,视图有一个没有文本的LabelControl。一个视图模型有一个可绑定的字符串“LabelText”属性。使用以下任何方法将属性值传递给此标签。

    //ViewModel code
    [POCOViewModel()]
    public class Form1ViewModel {
        public Form1ViewModel() {
            LabelText = "Value stored in ViewModel";
        public virtual string LabelText { get; set; }
    //View code
    //选项#1 (推荐): SetBinding method
    var fluent = mvvmContext1.OfType<Form1ViewModel>();
    fluent.SetBinding(labelControl1, l => l.Text, x=>x.LabelText);
    //选项 #2: DataBindings
    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);
    //ViewModel层 代码
    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方法。

    //嵌套 ViewModel
    public class NestedViewModel {
        public virtual string Text { get; set; }
    //父 ViewModel
    public class ViewModelWithChild {
        public ViewModelWithChild() {
            Child = ViewModelSource.Create<NestedViewModel>();
        public NestedViewModel Child {
            get;
            private set;
    //View层 代码
    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实例传递给组件。

    //ViewModel code
    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;
    //View code
    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对象。

    //View
    mvvmContext.ViewModelType = typeof(ViewModel);
    var fluentApi = mvvmContext.OfType<ViewModel>();
    // Create a BindingSource and populate it with a data object.
    //When a user modifies this object, the "Update" method is called
    BindingSource entityBindingSource = new BindingSource();
    entityBindingSource.DataSource = typeof(Entity);
    fluentApi.SetObjectDataSourceBinding(entityBindingSource, x => x.Entity, x => x.Update());
    // Data Bindings
    idEditor.DataBindings.Add(new Binding("EditValue", entityBindingSource, "ID"));
    textEditor.DataBindings.Add(
        new Binding("EditValue", entityBindingSource, "Text", true, DataSourceUpdateMode.OnPropertyChanged));
    //ViewModel
    public class ViewModel {
        //...
        public virtual Entity Entity {
            get;
            set;
        //...
    //Model
    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表达式检索方法。重命名自定义更新方法时,元数据类显示编译错误。

    //View Model code
    [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);
        //Metadata class
        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属性绑定。

    //View code
    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);
    //ViewModel code
    public class ViewModel {
        public virtual Entity SelectedEntity { get; set; }
        public virtual ObservableCollection<Entity> Entities { get; set;}
        protected void OnSelectedEntityChanged() {
            //"Remove" is a custom ViewModel method that deletes a selected entity
            this.RaiseCanExecuteChanged(x => x.Remove());
        protected void OnEntitiesChanged() {
            SelectedEntity = Entities.FirstOrDefault();
    //Model code
    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元素(标签)的背景颜色。

    //ViewModel code
    public class ViewModel {
        public virtual bool IsActive { get; set; }
    //ViewModel code
    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;
    

    分类:
    后端
    标签: