Skip to main content
A newer version of this page is available. .

Data Bindings and Notifications

  • 22 minutes to read

This document describes data bindings and notifications that alert the application when bound objects change their values.

  • Concepts - describes the traditional ‘WinForms way’ of data binding.
  • Base Classes - illustrates how to create bindable properties that support change notifications.
  • POCO Properties - introduces POCO (Plain Old CLR Objects) classes, supported by the DevExpress MVVM Framework.
  • POCO Dependencies - illustrates how to implement dependent properties that update their values when other related properties are modified.
  • Meta-POCO Bindings - explains how to move OnPropertyChanged callback declarations into separate metadata classes.
  • Collection Bindings - describes how to populate collections from the required source.
  • UI Triggers - explains how to use triggers to make separate UI elements to update their visual states in accordance with each other.
  • Legacy and Nested ViewModels - demonstrates how to work with child and legacy ViewModels.
  • Value Converters - this group illustrates how to convert bindable property values.
  • Formatting Bound Values - illustrates how to format bound values.
  • Binding Multiple Properties - shows how to bind multiple properties to the same UI element.

Concepts

The WinForms platform has embedded support for data bindings: to bind your control to the required data, you need to add the related binding to the Control.DataBindings collection. For instance, if you have an editor (for example, a text box) and a simple string Title property in your ViewModel, the standard code for binding looks like this:


editor.DataBindings.Add("EditValue", viewModel, "Title", true, DataSourceUpdateMode.OnPropertyChanged)

In real-life applications, your UI element and its bindable property both must be able to update themselves at runtime. For example, the Title property from the code above should update its value if an end-user changes the text within the editor. To do so, a parent View Model must implement the INotifyPropertyChanged interface for your ViewModel classes.


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"));            
        }
    }
}

The DevExpress MVVM Framework can assist you with creating and binding properties.

Base Classes

The first step in simplifying your code is to derive your ViewModels from classes declared in the DevExpress MVVM Framework: BindableBase and ViewModelBase. These classes already implement the INotifyPropertyChanged interface and allow you to shorten the code above:


public class SampleViewModel : BindableBase {
    string titleValue;
    public string Title {
        get { return titleValue; }
        set { SetProperty(ref titleValue, value, "Title");}
    }
}

Here, the bindable property is referred to using its string name. This approach can cause problems if the property is renamed - the compiler does not show any errors, and the control is not able to find the target property. The best way to accomplish this task is using POCO classes.

POCO Properties

Note

MVVM Best Practices Demo The text below has a related example in the DevExpress ‘MVVM Best Practices’ demo.

Group: API Code Examples

Module: Data Bindings

Example: POCO Bindable Properties

18.2 Demo Center: Launch the demo

The DevExpress MVVM Framework saves you a lot of time and lines of code, turning the above-mentioned code into this:


public class SampleViewModel {
    public virtual string Title { get; set; }
}

These simple classes with simple properties are called POCO (Plain Old CLR Objects). DevExpress MVVM Framework recognizes such classes and automatically upgrades them to something bigger. In the code above, the POCO class contains the string Title property. To allow the DevExpress MVVM Framework to make this property bindable, declare it auto-implemented and virtual. Binding these properties require a single line of code.


mvvmContext.SetBinding(editor, e => e.EditValue, "Title");

You can do the same using the Fluent API.


var fluentAPI = mvvmContext.OfType<ViewModel>();
fluentAPI.SetBinding(editor, e => e.EditValue, x => x.Title);

When working with POCO ViewModels, there is no need to implicitly create their instances. Instead, use one of the following approaches:

  • Use the DevExpress.Mvvm.POCO.ViewModelSource class’s Create method as shown below.

    
    //SampleViewModel.cs 
    public static SampleViewModel Create() {
        return ViewModelSource.Create<SampleViewModel>();
    }
    
    //MainViewModel.cs 
    var sampleViewModel = SampleViewModel.Create();
    
  • Use the MvvmContext‘s ViewModelType property. The MvvmContext component is designed to create POCO ViewModels and ensures that your ViewModel refers all required interfaces to be able to use their features.

    
    mvvmContext.ViewModelType = typeof(WindowsFormsApplication1.ViewModels.SampleViewModel);
    

POCO Dependencies

Note

MVVM Best Practices Demo The text below has a related example in the DevExpress ‘MVVM Best Practices’ demo.

Group: API Code Examples

Module: Data Bindings

Example: POCO Dependencies

18.2 Demo Center: Launch the demo

The ‘Simple Dependencies’ example of the ‘MVVM Best Practices’ demo illustrates how to implement a dependency between two POCO properties. The two editors are bound to integer Operand1 and Operand2 bindable properties, which have associated On…Changed methods. Both methods call the UpdateResult method when an end-user inputs a value to either editor and changes the value of the related bindable property. The UpdateResult method re-calculates the third bindable property, Result. In turn, this property has an OnResultChanged method that updates the string ResultText property bound to the label. The code below illustrates code for this example.


//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);
    }
}

Tip

The MaskProperties.MaskType properties of both editors must be set to MaskType.Numeric. Otherwise, bindings to integer Operand1 and Operand2 properties does not work.

The ‘PropertyChanged notifications’ is a similar example. Here, the [BindableProperty] data attribute is used to point to the NotifyResultAndResultTextChanged method that should be treated as the OnChanged method for both Operand1 and Operand2 bindable properties. This method calls the ViewModel’s RaisePropertyChanged method to update property dependencies for the Result and ResultText properties manually.


//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 Bindings

Note

MVVM Best Practices Demo The text below has a related example in the DevExpress ‘MVVM Best Practices’ demo.

Group: API Code Examples

Module: Data Bindings

Example: POCO Meta-Declarations

18.2 Demo Center: Launch the demo

POCO classes eliminate many problems associated with manually creating and using property bindings and change notifications, except renaming properties. The Framework can relate the property and its On(PropertyName)Changed callback. However, when you rename the property, you should rename its related callback as well.

Use the Meta-POCO approach to overcome this issue. This approach is more complex than regular POCO classes, but also the most efficient since it grants maximum control over your app.

In the related demo example, you can find the same sample with Operand1 and Operand2 bindable properties used to calculate values for the integer Result and string ResultText properties.


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 declaration is simple - create a nested class that contains metadata: expressions that specify explicitly which methods to use as property OnPropertyChanged callbacks. Metadata classes also allow you to make specific POCO properties unbindable. This nested class must implement the IMetadataProvider interface. The code below illustrates the ViewModel and its metadata declarations.


[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
        }
    }
}

Using the BuildMetadata method, you can go over all ViewModel properties and provide each property’s behavior. For instance, in the code below the Operand1 and Operand2 properties are treated as bindable properties that raise the OnPropertyChanged callback. This connection remains even when these properties are renamed. The Result property, in turn, is made non-bindable.


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());
    }
}

Note that this approach gives you the option to raise change notifications manually. For instance, the Result and ResultText properties did not receive their OnChanged callbacks within the metadata builder. You can still raise them manually:


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
 }

Collection Bindings

Note

MVVM Best Practices Demo The text below has a related example in the DevExpress ‘MVVM Best Practices’ demo.

Group: API Code Examples

Module: Data Bindings

Example: Collection Bindings

18.2 Demo Center: Launch the demo

Many controls have item collections that should be populated from an external source and updated when this source changes. The code below illustrates the TestViewModel class that provides the Entities collection. This collection is the ObservableCollection object that stores the Test class instances, which expose two public properties (ID and Text). Additionally, the View Model declares two public methods to add new and remove existing records.


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);
    }
}

Using the MvvmContext component’s API, you can populate a TileControl with tiles, one for each record within the Entities collection using the SetItemsSourceBinding method.


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

This method uses the following parameters:

  • Target - a target element that should be populated. A TileGroup in this example.
  • Items Selector - an expression that locates an items collection within the target. For this example, you need the TileGroup.Items collection.
  • Source Selector - an expression that locates a collection of items that should be used to populate the Target.
  • Match Expression - an expression that determines whether or not the data source item and the source element are the same. This expression is used when you modify an object within the Entities collection or remove it.
  • Create Expression - an expression that creates a new target element when the new source record appears. For this example, a new TileItem is created when a new Test class instance is placed within the Entities collection.
  • Dispose Expression - an expression that disposes of a target element when its related source collection record is removed.
  • 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.

When the binding is ready, you only need to call the collection’s Add or Remove methods and the TileControl immediately adds or removes its TileItems respectively.


var fluentApi = mvvmContext1.OfType<TestViewModel>();
fluentApi.ViewModel.Add();
fluentApi.ViewModel.Remove("Test 3");

UI Triggers

Note

MVVM Best Practices Demo The text below has a related example in the DevExpress ‘MVVM Best Practices’ demo.

Group: API Code Examples

Module: Data Bindings

Example: UI Triggers

18.2 Demo Center: Launch the demo

Usually, when you have a check-box and a label that should say whether a check box is selected or clear, you would bind your check-box value to the IsChecked property, and define the OnIsCheckedChanged method that updates the LabelText property bound to your label. However, this method involves your ViewModel in a process: you have to define properties that would be bound to your UI controls. Avoid writing additional code in a ViewModel and utilize UI triggers when a process takes place entirely within a UI. Triggers update certain UI elements when another element changes, without using bindings.

Use the SetTrigger method of the MvvmContext component to set a trigger.


//View
    mvvmContext.ViewModelType = typeof(UIViewModel);
    // Data binding for the IsActive property
    mvvmContext.SetBinding<CheckEdit, UIViewModel, bool>(checkEdit, c => c.Checked, x => x.IsActive);
    // Property-change Trigger for the IsActive property
    mvvmContext.SetTrigger<UIViewModel, bool>(x => x.IsActive, (active) =>
    {
        label.Text = active ? "Active" : "Inactive";
    });

//ViewModel
public class UIViewModel {
    public virtual bool IsActive { get; set; }
}

Note that in code above, the SetBinding syntax differs from the previous example. Instead of passing the bindable property as a simple string parameter, you define a source ViewModel and use the lambda-expression to obtain this property. This creates a fail-safe code that protects you from situations when the property is renamed, but the compiler shows no errors since the binding syntax is still correct.

With Fluent API, you can do the same but with fewer generic-parameters used.


// Set type of POCO-ViewModel
mvvmContext.ViewModelType = typeof(UIViewModel);
// Data binding for the IsActive property
var fluentAPI = mvvmContext.OfType<UIViewModel>();
fluentAPI.SetBinding(checkEdit, c => c.Checked, x => x.IsActive);
// Property-change Trigger for the IsActive property
fluentAPI.SetTrigger(x => x.IsActive, (active) =>
{
    label.Text = active ? "Active" : "Inactive";
});

Legacy and Nested ViewModels

Note

MVVM Best Practices Demo The text below has a related example in the DevExpress ‘MVVM Best Practices’ demo.

Group: API Code Examples

Module: Data Bindings

Example: Binding to Legacy ViewModels

18.2 Demo Center: Launch the demo

Although POCO-ViewModels provide many useful features, you might already have custom ViewModels (not built according to POCO concepts). If you switch to the DevExpress MVVM Framework, you do not have to modify them, since all the Framework features are still available.

You need to set the MvvmContext to this specific ViewModel type to work with legacy ViewModels.


var legacyViewModel = new LegacyViewModel("Legacy ViewModel");
// initialize the MVVMContext with the specific ViewModel's instance
mvvmContext.SetViewModel(typeof(LegacyViewModel), legacyViewModel);

When this is done, you can use data bindings. The syntax remains the same as before.


mvvmContext.SetBinding(editor, e => e.EditValue, "Title");

Note

MVVM Best Practices Demo The text below has a related example in the DevExpress ‘MVVM Best Practices’ demo.

Group: API Code Examples

Module: Data Bindings

Example: Data-Binding Capabilities (Binding Path)

18.2 Demo Center: Launch the demo

You can also bind your controls to properties from child ViewModels. First, define a ViewModel that serves as a nested ViewModel and optionally a property that retrieves this child model.


public ViewModel() {
    // Create Nested ViewModel as POCO-ViewModel
    Child = ViewModelSource.Create<NestedViewModel>();
}

public NestedViewModel Child { get; private set; }

After that, you can use data bindings, specifying the bindable property as a part of the nested ViewModel.


mvvmContext.SetBinding(editor, e => e.EditValue, "Child.Title");

The fluent API can be applied to create a fail-safe syntax.


var fluent = mvvmContext.OfType<ViewModel>();
fluent.SetBinding(editor, e => e.EditValue, x => x.Child.Title);

Value Converters

Note

MVVM Best Practices Demo The text below has a related example in the DevExpress ‘MVVM Best Practices’ demo.

Group: API Code Examples

Module: Data Bindings

Example: Data-Binding Capabilities (Converters)

18.2 Demo Center: Launch the demo

In the POCO Properties section of this topic, the sample code binds the text editor to the string ‘Title’ property.


mvvmContext.SetBinding(editor, e => e.EditValue, "Title");

The EditValue property is an Object type and functions correctly when binding it to the string bindable property. However, there can be scenarios when the bindable property and the bound editor’s property are a different type. In the demo center example, the integer ‘Progress’ property modifies the TrackBarControl and TextEdit controls’ visual state. To do this, it is bound to the TrackBarControl.Value and TextEdit.Text properties. If the Value property is the same type as the Progress property (Int32), the ‘Text’ property is of a String type. However, same binding expressions are used for both editors.


var fluent = mvvmContext.OfType<ViewModel>();
fluent.SetBinding(trackBar, e => e.Value, x => x.Progress);
fluent.SetBinding(editor, e => e.Text, x => x.Progress);

The second binding works fine because of the default value converter. Whenever there is a type mismatch, the converter tries to find an appropriate replacement for the source type and cast it to this type. For instance, when you enter a value to the text edit, its string ‘Text’ property value is automatically converted to Int32 and can be passed to the integer ‘Progress’ property.

The default converter copes with most tasks when conversion is obvious and simple, like the Int32-String conversion in the example above. For more complex tasks, manually convert values using the SetBinding method overload that takes custom and reverse converters as the two last parameters.


fluent.SetBinding(check, e => e.CheckState, x => x.ModelState,
    modelState =>
    {
        // Convert the ViewModel.State to the editor's CheckState
        switch(modelState) {
            case ViewModel.State.Active:
                return CheckState.Checked;
            case ViewModel.State.Inactive:
                return CheckState.Unchecked;
            default:
                return CheckState.Indeterminate;
        }
    },
    checkState =>
    {
        // Convert back from editor's CheckState to the ViewModel.State
        switch(checkState) {
            case CheckState.Checked:
                return ViewModel.State.Active;
            case CheckState.Unchecked:
                return ViewModel.State.Inactive;
            default:
                return ViewModel.State.Suspended;
        }
    });

Here, the custom value converter transforms the ‘ModelState’ property of the custom State type to the CheckState type, recognized by the check editor. The reverse converter casts the editor’s check state back to the State enumerator value.

You can skip setting the reverse converter if you only need one-way value conversion. For instance, the second binding expression of the same demo center example binds the formatted ‘ModelState’ property, which is originally of the State type, to the string check editor’s caption. Reverse conversion is not required because the caption cannot be modified manually.


fluent.SetBinding(check, e => e.Text, x => x.ModelState, modelState =>
     string.Format("Click to change the current ViewModel state from {0} to {1}", modelState, (ViewModel.State)((1 + (int)modelState) % 3)));

Formatting Bound Values

Note

MVVM Best Practices Demo The text below has a related example in the DevExpress ‘MVVM Best Practices’ demo.

Group: API Code Examples

Module: Data Bindings

Example: Data-Binding Capabilities (Formatting)

18.2 Demo Center: Launch the demo

The code below illustrates how to bind a label control to the “Price” bindable property declared in the View Model.


mvvmContext.SetBinding(label, l => l.Text, "Price");

public class ViewModel {
    public virtual int Price { get; set; }
}

Add a format string parameter to the SetBinding method to display the bound integer value as currency.


mvvmContext.SetBinding(label, l => l.Text, "Price", "Price: {0:C}");

Binding Multiple Properties

Note

MVVM Best Practices Demo The text below has a related example in the DevExpress ‘MVVM Best Practices’ demo.

Group: API Code Examples

Module: Data Bindings

Example: Data-Binding Capabilities (MultiBinding)

18.2 Demo Center: Launch the demo

The code below illustrates how to use the SetMultiBinding method to bind the same editor to two bindable properties, “FirstName” and “Last Name”.


mvvmContext.SetMultiBinding(editForFullName, e => e.Text, new string[] { "FirstName", "LastName" });

You have to use converters to make two-way binding instead, so that changes made inside the editor are passed back to bindable properties. The code below converts separate bindable property values into a joint string and vice versa.


mvvmContext.SetMultiBinding(editForFullName, e => e.EditValue, new string[] { "FirstName", "LastName" },
        jointString => string.Join(", ", jointString), singleString => ((string)singleString).Split(','));