WPF MVVM – A New Way to Work with Events

WPF Team Blog
13 August 2021

Our latest release (v21.1) includes a series of new MVVM-related enhancements for our WPF product line. These enhancements make it much easier to process control events at the ViewModel level (as you’ll soon see, we added multiple command properties and extended EventToCommand behavior capabilities).

In previous versions, events could be processed in the ViewModel in the following manner:

  • Use the EventToCommand behavior to execute a command in response to a raised event.
  • Use the DXEvent mechanism to bind an event to a ViewModel method.

These techniques did not allow you to specify a return value (for example, if you need to set e.IsValid for the ValidateCell event). Previous versions required that you write complex behaviors or pass UI-dependent event args to your ViewModel.

With v21.1, we addressed this limitation and extended MVVM support on a few fronts.

New Command API in our WPF Data Grid

This release includes multiple properties designed to bind a ViewModel’s commands to a control. These properties are command counterparts for WPF Data Grid events that expect a return value:

ValidateRow event -> ValidateRowCommand property

CustomColumnDisplayText event -> CustomColumnDisplayTextCommand property

You no longer need to write special converters. The ViewModel obtains a typed parameter that exposes UI-independent event args. You can modify this parameter at the ViewModel level and return values back to an event. Consider the following example wherein our WPF Data Grid uses a command to customize cell values:

<dxg:GridControl CustomColumnDisplayTextCommand="{Binding CalculateDisplayTextCommand}"/>
public class ViewModel: ViewModelBase {
    // ...

    [Command]
    public void CalculateDisplayText(ColumnDisplayTextArgs e) {
        if (e.FieldName == "Value")
            e.DisplayText = string.Format("{0:n2}", e.Value);
    }
}

Refer to the following article for a full list of new command properties: Data Grid Command API. Each property includes a command usage example and includes a link to the corresponding event.

New Command API in Virtual Sources

As you probably know, previous versions of our Virtual Sources were only able to use event handlers to implement data operations. We received numerous suggestions in this regard and were asked to improve event processing at the ViewModel level. We listened and with this release, you simply need to declare a virtual source in XAML and bind its new command properties to process these events at the ViewModel level:

<dxg:GridControl.ItemsSource>
    <dxg:InfiniteAsyncSource ElementType="{x:Type local:IssueData}"
        FetchRowsCommand="{Binding FetchIssuesCommand}"
        GetTotalSummariesCommand="{Binding GetTotalSummariesCommand}"
        GetUniqueValuesCommand="{Binding GetUniqueValuesCommand}">
    </dxg:InfiniteAsyncSource>
</dxg:GridControl.ItemsSource>

You can incorporate data operations at the ViewModel level without introducing UI dependencies. Command parameters expose a platform-independent API that allows you to pass data from the ViewModel to the Data Grid:

public class ViewModel : ViewModelBase {
    // ...
    
    [Command]
    public void FetchIssues(DevExpress.Mvvm.Xpf.FetchRowsAsyncArgs args) {
        args.Result = GetIssuesAsync(args);
    }
}

To learn more, please review the following example on GitHub: How to Bind to InfiniteAsyncSource.

EventToCommand - Event Args Back Conversion

If you want to handle an event that does not have a command property, you can use the EventToCommand behavior and bind your command to the event manually.

We extended EventToCommand capabilities to allow you to define back conversion logic. When you pass event args to a command, you can modify them and return values to the event as needed:

<dxe:TextEdit EditValue="{Binding UserName}">
    <dxmvvm:Interaction.Behaviors>
      <dxmvvm:EventToCommand Command="{Binding UserNameValidationCommand}"
                             EventArgsConverter="{local:ValidateEventArgsConverter}"
                             EventName="Validate"/>
    </dxmvvm:Interaction.Behaviors>
</dxe:TextEdit>
public class ValidationArgs {
    public string ErrorContent { get; private set; }
    public object Value { get; }

    public ValidationArgs(object value) => Value = value;
    public void SetError(bool isValid, string errorContent) => ErrorContent = isValid ? null : errorContent;
}

public class ValidateEventArgsConverter: EventArgsConverterBase<ValidationEventArgs> {
    protected override object Convert(object sender, ValidationEventArgs e) => new ValidationArgs(e.Value);
    protected override void ConvertBack(object sender, ValidationEventArgs e, object parameter) {
        var args = parameter as ValidationArgs;
        e.IsValid = args.ErrorContent == null;
        e.ErrorContent = args.ErrorContent;
    }
}

To learn more about this extended behavior, please refer to the following example on GitHub: How to Use Two Way Conversion.

 

We hope that these enhancements will be of value and allow you to maintain clean MVVM patterns within your applications when using DevExpress UI controls. Should you have any questions or need additional assistance, please post your comment/feedback below.

Free DevExpress Products - Get Your Copy Today

The following free DevExpress product offers remain available. Should you have any questions about the free offers below, please submit a ticket via the DevExpress Support Center at your convenience. We'll be happy to follow-up.
No Comments

Please login or register to post comments.