Dex MVVM


目录

BindableBase

ViewModelBase

POCO ViewModels

Messenger

Asynchronous Commands

Behaviors

Services 

DXDataTemplateSelector 

Data Annotation

 

Services mechanism. The ViewModelBase.GetService method, which employs ISupportServices interface, allows you to access services registered in a View.

<UserControl x:Class="ViewModelBaseSample.View" 
             xmlns:dxmvvm="http://schemas.devexpress.com/winfx/2008/xaml/mvvm" 
             xmlns:ViewModels="clr-namespace:ViewModelBaseSample.ViewModels" ...> 
    <UserControl.DataContext> 
        <ViewModels:ViewModel/> 
    UserControl.DataContext> 
    <dxmvvm:Interaction.Behaviors>
        <dxmvvm:MessageBoxService/>
    dxmvvm:Interaction.Behaviors>
    ...
UserControl>
public class ViewModel : ViewModelBase {
    public IMessageBoxService MessageBoxService { get { return GetService(); } }
}

If it is necessary to use several services of the same type, assign the Name property for these services and access a specific service by its name.


        "service1"/>
        "service2"/>
    

public IMessageBoxService MessageBoxService1 { get { return GetService("service1"); } }
    public IMessageBoxService MessageBoxService2 { get { return GetService("service2"); } }

When developing applications using the loosely-coupled View Models architecture, it is necessary to organize interaction between modules. DevExpress MVVM Framework provides several capabilities for this purpose (see the below list).

  • Passing data between ViewModels (ISupportParameter)
  • ViewModel relationships (ISupportParentViewModel)
  • Messenger

View Models inherited from the ViewModelBase can be related to each other with a parent-child relationship. This is achieved with the ISupportParentViewModel interface that is implemented in the ViewModelBase class. In this case, child View Models may access Services registered in the main View Model. 这种似乎只能传服务,无法传数据

public class DetailViewModel : ViewModelBase {
    public IMessageBoxService MessageBoxService { get { return GetService(); } }
}
public class MainViewModel : ViewModelBase {
    public IMessageBoxService MessageBoxService { get { return GetService(); } }
    public DetailViewModel DetailViewModel { get; private set; }
    public MainViewModel() {
        DetailViewModel = new DetailViewModel();
        ((ISupportParentViewModel)DetailViewModel).ParentViewModel = this;
    }
}

或者

<Grid x:Name="LayoutRoot">
        ...
         
        <View:DetailView dxmvvm:ViewModelExtensions.ParentViewModel="{Binding DataContext, ElementName=LayoutRoot}"/>
        <ContentControl>
            <ContentControl.ContentTemplate>
                <DataTemplate>
                    <View:DetailView dxmvvm:ViewModelExtensions.ParentViewModel="{Binding DataContext, Source={x:Reference LayoutRoot}}"/>
                DataTemplate>
            ContentControl.ContentTemplate>
        ContentControl>
        ...
    Grid>

ISupportParentViewModel interface is automatically implemented when you create a POCO object with the ViewModelSource class.

The POCO mechanism does not generate the ISupportParameter interface implementation automatically, but you can implement this interface in your POCO View Model manually.

ViewModelBase class implements the ISupportParameter interface, which can be used for passing initial data to View Models. 这种可以传数据,应该也可以传服务

public class MainViewModel : ViewModelBase {
    public DetailViewModel DetailViewModel { get; private set; }
    public MainViewModel() {
        DetailViewModel = new DetailViewModel();
        ((ISupportParameter)DetailViewModel).Parameter = "Document 1";
    }
}

When the Parameter property is set, the ViewModelBase.OnParameterChanged virtual method is invoked. You can override the ViewModelBase.OnParameterChanged virtual method to process passed data.

public class DetailViewModel : ViewModelBase {
    protected override void OnParameterChanged(object parameter) {
        base.OnParameterChanged(parameter);
        if(IsInDesignMode) {
            //...
        } else {
            //...
        }
    }
}

when View Models do not know about each other. In this case, you should set the ViewModelExtensions.Parameter attached property in XAML.

<Grid x:Name="LayoutRoot">
        ...
        
        <View:DetailView dxmvvm:ViewModelExtensions.Parameter="{Binding DataContext, ElementName=LayoutRoot}"/>
        <ContentControl>
            <ContentControl.ContentTemplate>
                <DataTemplate>
                    <View:DetailView dxmvvm:ViewModelExtensions.Parameter="{Binding DataContext, Source={x:Reference LayoutRoot}}"/>
                DataTemplate>
            ContentControl.ContentTemplate>
        ContentControl>
        ...
    Grid> 

The ViewModelBase class implements the ICustomTypeDescriptor interface to provide the capability to automatically create command properties based on methods (with the Command attribute) at runtime.

<Button Command="{Binding SaveCommand}"/>

public class ViewModel : ViewModelBase {
    [Command]
    public void Save() {
        //...
    }
    public bool CanSave() {
        //...
    }
}

IDataErrorInfo interface based on defined attributes or Fluent API.

To enable this feature, apply the POCOViewModel attribute for your View Model and set the POCOViewModel.ImplementIDataErrorInfo parameter to True.

//Attribute-based approach
[POCOViewModel(ImplementIDataErrorInfo = true)] 
public class LoginViewModel { 
    [Required(ErrorMessage = "Please enter the user name.")] 
    public virtual string UserName { get; set; }
}

//Fluent API
[POCOViewModel(ImplementIDataErrorInfo = true)]
[MetadataType(typeof(LoginViewModel.Metadata))]
public class LoginViewModel {
   public class Metadata : IMetadataProvider {
       void IMetadataProvider.BuildMetadata(MetadataBuilder builder) {
           builder.Property(x => x.UserName).
               Required(() => "Please enter the user name.");
        }
    }
    public virtual string UserName { get; set; }
}

Messenger.Default property returns a default Messenger instance. The default messenger is not multi-thread safe and stores weak references.

    // Sender ViewModel
    public class SenderViewModel {
      // The DevExpress MVVM framework generates commands for public methods.
      public void SendMessage() {
          Messenger.Default.Send("Hello world!");
      }
    }

public class ReceiverViewModel {
    public virtual string ReceivedMessage { get; protected set; }
    // Subscribe to the Messenger. 
    // Run the 'OnMessage' method when a message is received.
    protected ReceiverViewModel() {
        Messenger.Default.Register<string>(this, OnMessage);
    }
    void OnMessage(string message) {
        ReceivedMessage = "Received: " + message;
    }
}

the following code replaces the Default Messenger with a multi-thread safe Messenger:

public partial class App : Application {
    public App() {
        Messenger.Default = new Messenger(isMultiThreadSafe: true, actionReferenceType: ActionReferenceType.WeakReference);
    }
}

When you subscribe to a message of a custom type, you can invoke your handler if a message descendant is received

public class InheritedMessage : MyMessage {
    // ...
}
public class Recipient {
    public Recipient() {
        // Inherited messages are not processed with this subscription
        Messenger.Default.Register(
            recipient: this, 
            action: OnMessage);
        // Inherited messages are processed with this subscription
        Messenger.Default.Register(
            recipient: this, 
            receiveInheritedMessagesToo: true,
            action: OnMessage);
    }
    void SendMessages() {
        Messenger.Default.Send(new MyMessage());
        Messenger.Default.Send(new InheritedMessage());
    }
    void OnMessage(MyMessage message) {
        // ...
    }
}

You can invoke message handlers when a message with a particular token is received. 

public enum MessageToken { Type1, Type2 }
public class Recipient {
    public Recipient() {
        Messenger.Default.Register(
            recipient: this, 
            token: MessageToken.Type1,
            action: OnMessage1);
        Messenger.Default.Register(
            recipient: this, 
            token: MessageToken.Type2,
            action: OnMessage2);
    }
    void SendMessages() {
        Messenger.Default.Send(message: new MyMessage(), token: MessageToken.Type1);
        Messenger.Default.Send(message: new MyMessage(), token: MessageToken.Type2);
    }
    void OnMessage1(MyMessage message) {
        //...
    }
    void OnMessage2(MyMessage message) {
        //...
    }
}

The weak reference messenger (like the Default messenger) imposes a limitation for lambda expressions with outer variables (closures).

The weak reference messenger refers to a lambda expression with a weak reference. If the garbage collector collects the lambda expression object, the message handler is not invoked.

    public Recipient(string text) {
        // WARNING!
        // The lambda may be collected and never called.
        Messenger.Default.Register(this, x => {
            var str = text;
            //...
        });
    }

the lambda method refers to the text variable that is defined outside the lambda. In this case, this lambda can be collected and never called.

所以最好定义一个方法而不是用lamda

POCO ViewModels and ViewModelBase descendants can automatically generate asynchronous commands for methods marked with the async keyword.

[AsyncCommand(UseCommandManager = false)]
public async Task Calculate() {
    for(int i = 0; i <= 100; i++) {
        Progress = i;
        await Task.Delay(20);
    }
}

You can reference your asynchronous method when invalidating an auto-generated asynchronous command:

// For ViewModelBase descendants:
RaiseCanExecuteChanged(() => Calculate())

// For POCO ViewModels:
this.RaiseCanExecuteChanged(x => x.Calculate());

The AsyncCommand and AsyncCommand classes provide the IsExecuting property. While the command execution task is working, this property equals True and the AsyncCommand.CanExecute method always returns False, no matter what you implemented in the CanExecute delegate. 

You can disable this behavior by setting the AsyncCommand.AllowMultipleExecution property to True. In this case, the AsyncCommand.CanExecute method returns a value based on your CanExecute delegate implementation.

The AsynCommands provide the IsCancellationRequested property, which you can check in the execution method to implement canceling the command execution.

public AsyncCommand MyAsyncCommand { get; private set; }

public MyViewModel() {
    MyAsyncCommand = new AsyncCommand(Calculate);
}

Task Calculate() {
    return Task.Factory.StartNew(CalculateCore);
}
void CalculateCore() {
    for(int i = 0; i <= 100; i++) {
        if(myAsyncCommand.IsCancellationRequested) return;
        Progress = i;
        Thread.Sleep(TimeSpan.FromSeconds(0.1));
    }
}

The AsyncCommand.IsCancellationRequested property is set to True when AsyncCommand.CancelCommand is invoked.

<StackPanel Orientation="Vertical">
    <ProgressBar Minimum="0" Maximum="100" Value="{Binding Progress}" Height="20"/>
    <Button Content="Calculate" Command="{Binding MyAsyncCommand}"/>
    <Button Content="Cancel" Command="{Binding MyAsyncCommand.CancelCommand}"/>
StackPanel>
public AsyncCommand MyAsyncCommand { get; private set; }

public MyViewModel() {
    MyAsyncCommand = new AsyncCommand(Calculate);
}
Task Calculate() {
    return Task.Factory.StartNew(CalculateCore, MyAsyncCommand.CancellationTokenSource.Token).
        ContinueWith(x => MessageBoxService.Show(x.IsCanceled.ToString()));
}
void CalculateCore() {
    for(int i = 0; i <= 100; i++) {
        MyAsyncCommand.CancellationTokenSource.Token.ThrowIfCancellationRequested();
        Progress = i;
        Thread.Sleep(TimeSpan.FromSeconds(0.1));
    }
}

POCO ViewModels can automatically create AsyncCommands based on a public function returning a Task object (the function should have one parameter or be parameterless). POCO cannot create AsyncCommands for methods that return objects of Task type.

To access the IsExecuting and IsCancellationRequested command properties, you can use the DevExpress.Mvvm.POCO.POCOViewModelExtensions class, which provides the GetAsyncCommand method.

[POCOViewModel]
public class ViewModel {
    public virtual int Progress { get; set; }
    public Task Calculate() {
        return Task.Factory.StartNew(CalculateCore);
    }
    void CalculateCore() {
        for(int i = 0; i <= 100; i++) {
            if(this.GetAsyncCommand(x => x.Calculate()).IsCancellationRequested) return;
            Progress = i;
            Thread.Sleep(TimeSpan.FromSeconds(0.1));
        }
    }
}

EventToCommand class is a Behavior that allows you to bind an event to a command. 

The EventToCommand behavior allows you to specify an event using any of the following properties:

  • EventName
  • Event

EventName is useful when a source object provides an event.

Unlike EventName, the Event property is of the RoutedEvent type and can be used to specify attached events. For example:

<dxe:TextEdit>
    <dxmvvm:Interaction.Behaviors>
        <dxmvvm:EventToCommand Command="{Binding LoadedCommand}" Event="FrameworkElement.Loaded" />
        <dxmvvm:EventToCommand Command="{Binding TextChangedCommand}" Event="TextBoxBase.TextChanged" />
    dxmvvm:Interaction.Behaviors>
dxe:TextEdit>

If it's necessary, you can manually specify the source object for the EventToCommand. To do this, bind the EventTriggerBase.SourceObject property, or specify the object's name using the EventTriggerBase.SourceName property.

<UserControl ...>
    ...
    <dxmvvm:Interaction.Behaviors>
        <dxmvvm:EventToCommand SourceName="list" EventName="MouseDoubleClick" Command="{Binding InitializeCommand}"/>
        <dxmvvm:EventToCommand SourceObject="{Binding ElementName=list}" EventName="MouseDoubleClick" Command="{Binding InitializeCommand}"/>
    dxmvvm:Interaction.Behaviors>
    ...
        <ListBox x:Name="list" ... />
    ...
UserControl>

You can provide a parameter to the bound command using the EventToCommandBase.CommandParameter property. Alternatively, you can pass the event's arguments to the command as a parameter by setting the EventToCommand.PassEventArgsToCommand property to true. 此时传递的是'System.Windows.Input.MouseButtonEventArgs' ,实际上没什么用

<ListBox x:Name="list" ...>
    <dxmvvm:Interaction.Behaviors>
        <dxmvvm:EventToCommand EventName="MouseDoubleClick" Command="{Binding EditCommand}" PassEventArgsToCommand="True"/>
    dxmvvm:Interaction.Behaviors>
ListBox>

The EventToCommand behavior allows you to invoke a command only when modifier keys are pressed. Use the EventToCommand.ModifierKeys property to specify modifier keys.

<ListBox ItemsSource="{Binding Persons}">
            <dxmvvm:Interaction.Behaviors>
                <dxmvvm:EventToCommand EventName="MouseLeftButtonUp" Command="{Binding EditCommand}" ModifierKeys="Ctrl+Alt">
                    <dxmvvm:EventToCommand.EventArgsConverter>
                        <Common:ListBoxEventArgsConverter/>
                    dxmvvm:EventToCommand.EventArgsConverter>
                dxmvvm:EventToCommand>
            dxmvvm:Interaction.Behaviors>
            ...
        ListBox>

Set the EventToCommandBase.MarkRoutedEventsAsHandled property to True to mark routed events as handled when the bound command is executed.

<dxmvvm:EventToCommand MarkRoutedEventsAsHandled="True" .../>

The EventToCommand class provides the EventToCommand.AllowChangingEventOwnerIsEnabled property, which is False by default. If you set this property to True, the EventToCommand disables the associated control when the bound command cannot be executed (when the ICommand.CanExecute method returns False). 此时控件被禁用了

<dxmvvm:EventToCommand EventName="MouseDoubleClick" Command="{Binding ShowPersonDetailCommand}" AllowChangingEventOwnerIsEnabled="True">
                            <dxmvvm:EventToCommand.EventArgsConverter>
                                <dxmvvm:ItemsControlMouseEventArgsConverter />
                            dxmvvm:EventToCommand.EventArgsConverter>
                        dxmvvm:EventToCommand>

        public bool CanShowPersonDetail(PersonInfo person)
        {
            return false;
        }

The KeyToCommand class is a special behavior that allows you to bind a KeyGesture to a command.

<TextBox>
    <dxmvvm:Interaction.Behaviors>
        <dxmvvm:KeyToCommand KeyGesture="Enter" Command="{Binding CommitCommand}"/>
    dxmvvm:Interaction.Behaviors>
TextBox>

above is to invoke the ViewModel's CommitCommand when the end-user presses the Enter key while the focus is in the TextBox.

Due to the fact that the KeyToCommand and EventToCommand are inherited from one base class, their overall capabilities are similar to ProcessEventsFromDisabledEventOwner, MarkRoutedEventsAsHandled, UseDispatcher and DispatcherPriority.

FocusBehavior allows you to set the focus to a UI control without utilizing code-behind.

<TextBox Text="This control is focused on button click: ">
    <dxmvvm:Interaction.Behaviors>
        <dxmvvm:FocusBehavior SourceName="button" EventName="Click"/>
    dxmvvm:Interaction.Behaviors>
TextBox>
<Button x:Name="button" Content="Click to focus the TextBox"/>

to make a UI control focused when a specific property is changed, specify the FocusBehavior.PropertyName property.

<TextBox Text="This control is focused when data is loaded">
    <dxmvvm:Interaction.Behaviors>
        <dxmvvm:FocusBehavior SourceObject="{Binding ViewModel}" PropertyName="IsDataLoaded" FocusDelay="0:00:01"/> 
dxmvvm:Interaction.Behaviors>
TextBox>

The SourceObject and SourceName properties specify an object used for processing an event or property change. The SourceObject can be set through binding.

If the associated control shouldn't be focused immediately once it's loaded or the event specified in the EventName property occurs, specify the required delay using the FocusBehavior.FocusDelay property.

ValidationErrorsHostBehavior allows tracking validation errors within a UI container.

<UserControl x:Class="Example.Views.View" ...
    xmlns:ViewModels="clr-namespace:Example.ViewModels"
    xmlns:dxmvvm="http://schemas.devexpress.com/winfx/2008/xaml/mvvm"
    DataContext="{dxmvvm:ViewModelSource Type=ViewModels:ViewModel}">
    <UserControl.Resources>
        <dxmvvm:BooleanNegationConverter x:Key="BooleanNegationConverter"/>
    UserControl.Resources>
    <dxmvvm:Interaction.Behaviors>
        <dxmvvm:ValidationErrorsHostBehavior x:Name="validationErrorsHostBehavior"/>
    dxmvvm:Interaction.Behaviors>
    <Grid>
        <StackPanel Orientation="Vertical" ...>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="First Name: " .../>
                <TextBox Text="{Binding FirstName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, 
                                NotifyOnValidationError=True, ValidatesOnDataErrors=True}" .../>
            StackPanel>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="Last Name: " .../>
                <TextBox Text="{Binding LastName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, 
                                NotifyOnValidationError=True, ValidatesOnDataErrors=True}" .../>
            StackPanel>
            <Button Content="Save" ... IsEnabled="{Binding ElementName=validationErrorsHostBehavior, 
                                                   Path=HasErrors, Converter={StaticResource BooleanNegationConverter}}"/>
        StackPanel>
    Grid>
UserControl>

In the code above, the ValidationErrorsHostBehavior is defined for the root element. That means that the behavior tracks all validation errors within the View.

To enable validation error notification, it is necessary to customize the bindings as shown at the example above: set the NotifyOnValidationError and ValidatesOnDataErrors properties to True. To identify whether a validation error occurs, use the ValidationErrorsHostBehavior.HasErrors property. 

It's often required to show a confirmation box before performing an action. ConfirmationBehavior allows to automate this process.

<Button Content="Close">
    <dxmvvm:Interaction.Behaviors>
        <dxmvvm:ConfirmationBehavior EnableConfirmationMessage="{Binding IsSaved, Converter={StaticResource BooleanNegationConverter}}" 
             Command="{Binding CloseCommand}" MessageText="Are you sure to close the unsaved document?"/>
    dxmvvm:Interaction.Behaviors>
Button>

 the Button (associated control) is not bound to a command directly. Instead, it is necessary to bind the ConfirmationBehavior.Command property.

if EnableConfirmationMessage equals False, the ConfirmationBehavior does not show the confirmation message.

By default, the ConfirmationBehavior uses the DXMessageBoxService for showing a confirmation message. If you need to use a custom IMessageBoxService (for instance, WinUIMessageBoxService), you can define a certain message box service and bind the ConfirmationBehavior.MessageBoxService property as shown below.

<Button Content="Close">
    <dxmvvm:Interaction.Behaviors>
        <dxwui:WinUIMessageBoxService 
             xmlns:dxwui="http://schemas.devexpress.com/winfx/2008/xaml/windowsui"
             x:Name="winUIMessageBoxService"/>
        <dxmvvm:ConfirmationBehavior EnableConfirmationMessage="{Binding IsSaved, Converter={StaticResource BooleanNegationConverter}}" 
             Command="{Binding CloseCommand}" MessageText="Are you sure to close the unsaved document?"
             MessageBoxService="{Binding ElementName=winUIMessageBoxService}"/>
    dxmvvm:Interaction.Behaviors>
Button>

Sometimes, some properties of a UI control are not dependency properties, for instance, the TextBox.SelectedText property. DependencyPropertyBehavior can be used to overcome such issues.

<TextBox Text="Select some text in this box">
    <dxmvvm:Interaction.Behaviors>
        <dxmvvm:DependencyPropertyBehavior PropertyName="SelectedText" EventName="SelectionChanged" Binding="{Binding SelectedText, Mode=TwoWay}"/>
    dxmvvm:Interaction.Behaviors>
TextBox>

The EnumItemsSourceBehavior class allows you to bind an enumeration to the ItemsSource property of any control.

public enum UserRole {
    [Image("pack://application:,,,/Images/Admin.png"), Display(Name = "Admin", Description = "High level of access", Order = 1)]
    Administrator,
    [Image("pack://application:,,,/Images/Moderator.png"), Display(Name = "Moderator", Description = "Average level of access", Order = 2)]
    Moderator,
    [Image("pack://application:,,,/Images/User.png"), Display(Name = "User", Description = "Low level of access", Order = 3)]
    User
}

Image is the DataAnnotation attribute that allows assigning an image to a corresponding member of the enumeration. Use the AllowImages property to control image visibility. 

xmlns:dxe="http://schemas.devexpress.com/winfx/2008/xaml/editors"
xmlns:dxmvvm="http://schemas.devexpress.com/winfx/2008/xaml/mvvm"
...
<dxe:ComboBoxEdit>
    <dxmvvm:Interaction.Behaviors>
        <dxmvvm:EnumItemsSourceBehavior EnumType="{x:Type common:UserRole}"/>
    dxmvvm:Interaction.Behaviors>
dxe:ComboBoxEdit>

图片会自动显示在每一项之前

The CompositeCommandBehavior can be used to aggregate and execute multiple commands

        <Button Content="Register">
            
            <dxmvvm:Interaction.Behaviors>
                <dxmvvm:CompositeCommandBehavior>
                    <dxmvvm:CommandItem Command="{Binding LogCommand}" CommandParameter="Registration"/>
                    <dxmvvm:CommandItem Command="{Binding RegisterCommand}" CommandParameter="{Binding ElementName=userNameTextBox, Path=Text}"/>
                dxmvvm:CompositeCommandBehavior>
            dxmvvm:Interaction.Behaviors>
            
        Button>

The CompositeCommand can only be executed when all of its inner commands can be executed (the CommandItem.CanExecute property is set to true). This is the default behaviour.

Set the CompositeCommandBehavior.CanExecuteCondition property to AnyCommandCanBeExecuted to allow running the CompositeCommand when at least one of its inner commands can be executed (the CommandItem.CanExecute property is set to true).

The FunctionBindingBehavior class is a special behavior that allows you to bind the function result to your View.

    public virtual ObservableCollection Points { get; set; }

    public IList GetFilteredItems(DateTime start, DateTime end) {
        return this.Points.Where(x => x.Date.Date >= start && x.Date.Date <= end).ToList();
<dxc:ChartControl ... >
    <dxc:ChartControl.Diagram>
        <dxc:SimpleDiagram2D>
            <dxc:SimpleDiagram2D.Series>
                <dxc:FunnelSeries2D x:Name="Series" ... >
                    ...
                dxc:FunnelSeries2D>
            dxc:SimpleDiagram2D.Series>
        dxc:SimpleDiagram2D>
    dxc:ChartControl.Diagram>
    ...
<dxmvvm:Interaction.Behaviors>
    <dxmvvm:FunctionBindingBehavior         
        Target="{Binding ElementName=Series}"       
        Property="DataSource" 
        Source="{Binding}"
        Function="GetFilteredItems" 
        Arg1="{Binding SelectionRangeStart, ElementName=rangeControl}" 
        Arg2="{Binding SelectionRangeEnd, ElementName=rangeControl}"/>
dxmvvm:Interaction.Behaviors>
dxc:ChartControl>
  • The Target property contains an object whose property will be populated (by default, the target-object is the behavior's associated object). Since the ChartControl shows data in a series, the ChartControl's Series FunnelSeries2D is used as Target.
  • The property of target-object should be specified using the Property property. The FunnelSeries2D shows data specified in the DataSource property. Thus, set Property to DataSource.
  • The Source property contains an object whose function will be bound to the target-property specified in Function (by default, the source-object is an object located in the data context of the associated object.). Due to the fact that the source-function is defined at the ViewModel level, bind the Source property to the ViewModel.
  • The source-function name is specified by the Function property.
  • The FunctionBindingBehavior also provides several properties that correspond to the source-function parameters: Arg1, Arg2... Arg15. You can specify no more than 15 arguments.

By default, the FunctionBindingBehavior automatically re-invokes the Function when one of the arguments is changed (updated). If you need to manually re-invoke the Function, you can use the POCOViewModelExtensions.UpdateFunctionBinding extension method.

public class MainViewModel {
    ...
    public void Update() {
        this.UpdateFunctionBinding(x => x.GetFilteredItems(default(DateTime), default(DateTime)));
    }
}

The MethodToCommandBehavior class is special behavior that allows you to bind a method to a property of the ICommand type.

<dxb:BarManager>
    <dxb:BarManager.Bars>
        <dxb:Bar>
            <dxb:BarButtonItem 
    Content="Descending" 
    Glyph="{dx:DXImage Image=MoveDown_16x16.png}" 
    BarItemDisplayMode="ContentAndGlyph">               
    <dxmvvm:Interaction.Behaviors>
        <dxmvvm:MethodToCommandBehavior 
            Source="{Binding ElementName=gridControl}" 
            Method="SortBy"
            Arg1="{Binding ElementName=gridControl,Path=CurrentColumn}" 
            Arg2="Descending"/>
    dxmvvm:Interaction.Behaviors>
dxb:BarButtonItem>
        dxb:Bar>
    dxb:BarManager.Bars>
    <Grid>
        <dxg:GridControl 
            x:Name="gridControl" 
            ItemsSource="{Binding Users}" 
            AutoGenerateColumns="AddNew" >
            <dxg:GridControl.View>
                <dxg:TableView 
                    x:Name="tableView" 
                    ShowGroupPanel="False" 
                    FadeSelectionOnLostFocus="False"/>
            dxg:GridControl.View>
        dxg:GridControl>
    Grid>
dxb:BarManager>

The ReadOnlyDependencyPropertyBindingBehavior allows you to bind read-only dependency and attached properties to ViewModel properties.

<TreeView>
    <dxmvvm:Interaction.Behaviors>
        <dxmvvm:ReadOnlyDependencyPropertyBindingBehavior Property="SelectedItem" />
    dxmvvm:Interaction.Behaviors>

You can also use the DependencyProperty to bind attached properties.

<TreeView Name="view"
                  ItemsSource="{Binding Menu}">
            <TreeView.Resources>
                <HierarchicalDataTemplate DataType="{x:Type ViewModels:MenuItemViewModel}"
                                          ItemsSource="{Binding Items}">
                    <TextBlock Text="{Binding Title}" />
                HierarchicalDataTemplate>
            TreeView.Resources>
            <dxmvvm:Interaction.Behaviors>
                <dxmvvm:ReadOnlyDependencyPropertyBindingBehavior Binding="{Binding SelectedMenuItem, Mode=OneWayToSource}"
                                                                  DependencyProperty="{x:Static TreeView.SelectedItemProperty}" />
            dxmvvm:Interaction.Behaviors>
        TreeView>

use the the behavior's Binding property to specify a binding to the target ViewModel's property .

Behavior that implements an interface. Although Services are defined in Xaml, the Service interfaces can accessed from the View Model layer.

Certain services that don't need to be associated with a specific control (like NotificationService, DispatcherService, etc) can be registered at the App.xaml level.

<Application.Resources>
  <dx:DXMessageBoxService x:Key="MessageBoxService"/>
Application.Resources>

To access the application services from other than a view model, you can use the static Default property of the DevExpress.Mvvm.ServiceContainer class:

ServiceContainer.Default.GetService();

The use of Services makes it easy to create unit-tests for your View Models. 

[TestFixture]
public class DocumentViewModelTests {
    [Test]
    public void Test() {
        bool serviceIsCalled = false;
        var viewModel = new DocumentViewModel();
        var service = new Mock(MockBehavior.Strict);
        service.
           Setup(foo => foo.Show(
               "Want to save your changes?", "Document", MessageBoxButton.YesNoCancel, 
                MessageBoxImage.None, MessageBoxResult.None)).
           Returns((string text, string caption, MessageBoxButton button,
                MessageBoxImage image, MessageBoxResult none) => {
               serviceIsCalled = true;
               return MessageBoxResult.OK;
           });
        ((ISupportServices)viewModel).ServiceContainer.RegisterService(service.Object);
        viewModel.CloseDocumentCommand.Execute(null);
        Assert.IsTrue(serviceIsCalled);
    }
}

Services become available only when Views to which services are attached are loaded. 

<UserControl x:Class="DXSample.View.MainView" 
    ... 
    DataContext="{dxmvvm:ViewModelSource Type={x:Type ViewModel:MainViewModel}}">
    <Grid>
        <dxwui:NavigationFrame AnimationType="SlideHorizontal">
            <dxmvvm:Interaction.Behaviors>
                <dxmvvm:EventToCommand EventName="Loaded" Command="{Binding OnViewLoadedCommand}" />
                <dxwuin:FrameNavigationService />
            dxmvvm:Interaction.Behaviors>
        dxwui:NavigationFrame>
    Grid>
UserControl>
public class MainViewModel {
    private INavigationService NavigationService { get { return this.GetService(); } }

    public MainViewModel() {  }

    public void OnViewLoaded() {
        NavigationService.Navigate("HomeView", null, this);
    }
}

当请求的实例serviceType可用时,两种方法的行为都相同。不同之处在于serviceType未注册时的行为:

  • GetService- 如果服务未注册,则返回null
  • GetRequiredService- 如果服务未注册,则抛出一个Exception异常。

Some services provide the capability to display child Views (like IDialogService, IDocumentManagerService, etc.). Usually, these services are derived from the ViewServiceBase class. This class provides three properties that can be set in XAML: ViewTemplate, ViewTemplateSelector, and ViewLocator. All services derived from the ViewServiceBase class support these properties and provide three approaches of creating child Views.

  • ViewTemplate and ViewTemplateSelector, tightly-coupled View Models
  • ViewLocator, tightly-coupled View Models
  • ViewLocator, loosely-coupled View Models

tightly-coupled View Models: View Models may have direct links to other View Models and create them. 

Alternatively, a View can be dynamically generated from the passed ViewModel. The DialogService.ViewTemplateSelector property supports this approach. 

public class ChildViewTemplateSelector : DataTemplateSelector {
    public DataTemplate ChildViewTemplate { get; set; }
    public DataTemplate DefaultViewTemplate { get; set; }
    public override DataTemplate SelectTemplate(object item, DependencyObject container) {
        if(item is ChildViewModel)
            return ChildViewTemplate;
        return DefaultViewTemplate;
    }
}
<Common:ChildViewTemplateSelector x:Key="childViewTemplateSelector">
    <Common:ChildViewTemplateSelector.ChildViewTemplate>
        <DataTemplate>
            <View:ChildView/>
        DataTemplate>
    Common:ChildViewTemplateSelector.ChildViewTemplate>
    <Common:ChildViewTemplateSelector.DefaultViewTemplate>
        <DataTemplate>
            <TextBlock Text="Default Template"/>
        DataTemplate>
    Common:ChildViewTemplateSelector.DefaultViewTemplate>
Common:ChildViewTemplateSelector>
<dx:DialogService ViewTemplateSelector="{StaticResource childViewTemplateSelector}" />

This approach is useful if you want to show different Views with different View Models using only one service. Alternatively, you can place several services with different names.

The ViewLocator provides a simple composition mechanism that for creating child Views by names.

public class MainViewModel {
    ...
        ChildViewModel childViewModel = new ChildViewModel();
        DialogService.ShowDialog(
            dialogCommands: new List() { ... },
            title: "Child View",
            documentType: "ChildView",
            viewModel: childViewModel,
        );
    ...
}

The preceding code implicitly calls the default ViewLocator to create the ChildView based on the passed document type parameter.  This approach also implies a tightly-coupled View Model architecture.

The ViewServiceBase class provides the ViewLocator property. By default, this property is null, which means that the service uses the default ViewLocator (ViewLocator.Default). If the default ViewLocator implementation does not meet your requirements, you can change the default ViewLocator (by setting the ViewLocator.Default property) or define a specific ViewLocator at the service level (by setting the ViewServiceBase.ViewLocator property).

If you use the loosely-coupled architecture, you can create child Views, without passing View Models to the child Views.

This approach involves defining the ChildViewModel in the ChildView XAML.

<UserControl x:Class="Example.View.ChildView" ...
    DataContext="{dxmvvm:ViewModelSource ViewModel:ChildViewModel}">
    ...
UserControl>

<UserControl x:Class="Example.View.MainView" ...>
    <dxmvvm:Interaction.Behaviors>
        <dx:DialogService/>
    dxmvvm:Interaction.Behaviors>
    ...
UserControl>
public class MainViewModel {
    ...
        DialogService.ShowDialog(
            dialogCommands: new List() { ... },
            title: "Child View",
            documentType: "ChildView",
            parameter: "Parameter",
            parentViewModel: this,
        );
    ...
}

In the case above, the ChildView is created based on the document type parameter. The ChildViewModel is not passed through the DialogService, because it is already defined in the ChildView XAML.

To implement interaction between View Models, you can pass a parameter to the ChildViewModel through the ISupportParameter interface.

DialogService allows you to show a modal dialog window (ThemedWindow) and get its result.

Use the WinUIDialogService to display a modal window in the Windows 8 or Windows 10 style. The message box stretches to fit the window's width when you use the WinUIMessageBoxService.

IDocumentManagerService is an abstraction of the document manager. By using it, you can implement interaction between your ViewModels, show and control your Views.

The WindowedDocumentUIService is an IDocumentManagerService implementation that allows you to show documents in separate windows.

        <dxmvvm:Interaction.Behaviors>
            <dx:WindowedDocumentUIService DocumentShowMode="Dialog">
                <dx:WindowedDocumentUIService.WindowStyle>
                    <Style TargetType="Window">
                        <Setter Property="Width" Value="400" />
                        <Setter Property="Height" Value="300" />
                        <Setter Property="WindowStyle" Value="ToolWindow" />
                    Style>
                dx:WindowedDocumentUIService.WindowStyle>
            dx:WindowedDocumentUIService>
        dxmvvm:Interaction.Behaviors>

默认窗口为非模态,指定DocumentShowMode="Dialog"使弹出窗口模态

[POCOViewModel]
public class MainViewModel {
    protected IDocumentManagerService DocumentManagerService { get { return this.GetService(); } }
    ...
    public void CreateDocument(object arg) {
        IDocument doc = DocumentManagerService.FindDocument(arg);
        if (doc == null) {
            doc = DocumentManagerService.CreateDocument("DetailedView", arg);
            doc.Id = DocumentManagerService.Documents.Count();
        }
        doc.Show();
    }
}

the FindDocument method to find an already existing document with the specified ViewModel passed via a parameter arg.

    5. public static IDocument FindDocument(this IDocumentManagerService service, object parameter, object parentViewModel);
    6. public static IDocument FindDocument(this IDocumentManagerService service, object viewModel);
    7. public static IDocument FindDocumentById(this IDocumentManagerService service, object id);

Method 5 requires you to implement the ISupportParameter interface by the document's ViewModel and returns the first document with a specified parameter and parent ViewModel. 

Method 6 and Method 7 retrieve and return a document with a specified ViewModel or id accordingly.

1. public static IDocument CreateDocument(this IDocumentManagerService service, object viewModel);
    2. public static IDocument CreateDocument(this IDocumentManagerService service, string documentType, object viewModel);
    3. public static IDocument CreateDocument(this IDocumentManagerService service, string documentType, object parameter, object parentViewModel);    

Method 1 is used when a document View is defined through the ViewServiceBase.ViewTemplate or ViewServiceBase.ViewTemplateSelector property and at the same time, the document View should not contain a View Model, because it is passed through the service.

Method 2 and Method 3 create a document View by implicitly calling the ViewLocator and pass the specified ViewModel to the created View. Method 2 is tightly-coupled, Method 3 is loosely-coupled

TabbedDocumentUIService 

    <dxdo:DockLayoutManager>
        <dxdo:LayoutGroup Orientation="Horizontal">
            <dxdo:LayoutPanel Caption="Users">
                <local:CollectionView>
                    
                    <dxmvvm:Interaction.Behaviors>                       
                        <dxdo:TabbedDocumentUIService DocumentGroup="{Binding ElementName=documnetGroup}"/>
                    dxmvvm:Interaction.Behaviors>
                    
                local:CollectionView>
            dxdo:LayoutPanel>            
            <dxdo:DocumentGroup x:Name="documnetGroup" Caption="Documents" ItemHeight="*"/>
        dxdo:LayoutGroup>
    dxdo:DockLayoutManager>

TabbedWindowDocumentUIService

    <dx:DXTabControl x:Name="tabControl">
        <dx:DXTabItem Header="Persons" AllowHide="False">
            <local:CollectionView>
                
                <dxmvvm:Interaction.Behaviors>
                    <dx:TabbedWindowDocumentUIService Target="{Binding ElementName=tabControl}"/>
                dxmvvm:Interaction.Behaviors>
                
            local:CollectionView>
        dx:DXTabItem>
        <dx:DXTabControl.View>
            <dx:TabControlScrollView AllowHideTabItems="True" />
        dx:DXTabControl.View>
    dx:DXTabControl>

和上面的区别在于TabbedDocumentUIService使用独立的DocumentGroup做容器,而TabbedWindowDocumentUIService需要使用DXTabControl做容器

FrameDocumentUIService

    <dxwui:NavigationFrame AnimationType="SlideHorizontal">
        <dxwui:NavigationFrame.Source>
            <local:CollectionView>
                
                <dxmvvm:Interaction.Behaviors>
                    <dxwuin:FrameDocumentUIService/>
                dxmvvm:Interaction.Behaviors>
                
            local:CollectionView>
        dxwui:NavigationFrame.Source>
    dxwui:NavigationFrame>

DockingDocumentUIService 

    <dxdo:DockLayoutManager>
        <dxdo:LayoutGroup Orientation="Horizontal">
            <dxdo:LayoutPanel Caption="Users">
                <local:CollectionView>
                    
                    <dxmvvm:Interaction.Behaviors>
                        <dxdo:DockingDocumentUIService LayoutGroup="{Binding ElementName=layouGroup}"/>
                    dxmvvm:Interaction.Behaviors>
                    
                local:CollectionView>
            dxdo:LayoutPanel>
            <dxdo:LayoutGroup x:Name="layouGroup" Caption="Panels" ItemHeight="*"/>
        dxdo:LayoutGroup>
    dxdo:DockLayoutManager>

The DispatcherService is an IDispatcherService implementation that allows you to perform actions in a ViewModel using the Dispatcher.

  • Delay - gets or sets the amount of time (a TimeSpine object), to wait before invoking the DispatcherService's BeginInvoke method.

The WindowService allows you to show your view as a window, and control the displayed window from the ViewModel.

弹窗口

The CurrentWindowService allows you to control the associated window at the View Model level.

用来关闭当前窗口

 The CurrentDialogService allows you to control the associated dialog window and specify the dialog result in the Close method at the View Model level.

上面3个似乎都可以用DialogService替代

The FrameNavigationService is an INavigationService implementation that allows you to navigate between Views within a NavigationFrame.

xmlns:dxwui="http://schemas.devexpress.com/winfx/2008/xaml/windowsui"
xmlns:dxmvvm="http://schemas.devexpress.com/winfx/2008/xaml/mvvm" 
xmlns:dxwuin="http://schemas.devexpress.com/winfx/2008/xaml/windowsui/navigation"
...
<dxwui:NavigationFrame>
    <dxmvvm:Interaction.Behaviors>
        <dxwuin:FrameNavigationService />
    dxmvvm:Interaction.Behaviors>
dxwui:NavigationFrame>
public class MainViewModel {
    private INavigationService NavigationService { get { return this.GetService(); } }
    public MainViewModel() { }
    public void OnViewLoaded() {
        NavigationService.Navigate("HomeView", null, this);
    }
}

Depending on the NavigationFrame.NavigationCacheMode property value, a NavigationFrame can cache Views to which it navigates in multiple modes: cache always, cache until the cache size exceeds the defined value, or do not cache at all.

If you are navigating between content-heavy Views with complex structure, you can show a splash screen by using the FrameNavigationService.ShowSplashScreen while the NavigationFrame content is loading.

SplashScreenManagerService

Use the PredefinedSplashScreenType property to specify which one of the three predefined splash screen styles to display. 可以用来显示等待框

    <dxmvvm:Interaction.Behaviors>
        <dx:SplashScreenManagerService x:Name="ThemedSplashScreenService" PredefinedSplashScreenType="Themed" InputBlock="WindowContent"
                                       Owner="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"/>
        <dx:SplashScreenManagerService x:Name="FluentSplashScreenService" PredefinedSplashScreenType="Fluent" InputBlock="WindowContent"
                                       Owner="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"/>
        <dx:SplashScreenManagerService x:Name="WaitIndicatorSplashScreenService" PredefinedSplashScreenType="WaitIndicator" InputBlock="WindowContent"
                                       Owner="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"/>
    dxmvvm:Interaction.Behaviors>
    
    <dxlc:LayoutControl Orientation="Vertical" VerticalAlignment="Top">
        <Button Command="{Binding ShowSplashScreenCommand}" CommandParameter="ThemedSplashScreenService" VerticalAlignment="Top">Show ThemedButton>
        <Button Command="{Binding ShowSplashScreenCommand}" CommandParameter="FluentSplashScreenService" VerticalAlignment="Top">Show FluentButton>
        <Button Command="{Binding ShowSplashScreenCommand}" CommandParameter="WaitIndicatorSplashScreenService" VerticalAlignment="Top">Show WaitIndicatorButton>
    dxlc:LayoutControl>

WizardService

The NotificationService allows you to display popup notifications in your applications. 

<dxmvvm:Interaction.Behaviors>
        <dxmvvm:NotificationService x:Name="notificationService"
            UseWin8NotificationsIfAvailable="True"
            CreateApplicationShortcut="True"
            ApplicationActivator="{x:Type local:MyNotificationActivator}">
            <dxmvvm:NotificationService.ApplicationId>
                <Binding Source="{x:Static local:MainWindow.ApplicationID}" />
            dxmvvm:NotificationService.ApplicationId>
        dxmvvm:NotificationService>
    dxmvvm:Interaction.Behaviors>
  • Create the ApplicationActivator instance and connect it to the service.

  • Specify the ApplicationId.

  • Set the UseWin8NotificationsIfAvailable to true.

  • Set the CreateApplicationShortcut property to true or create an application shortcut based on the custom logic:

public partial class MainWindow : Window {
    public MainWindow() {
        InitializeComponent();
    }
    public static string ApplicationID {
        get { return "FunWithNotifications_19_1"; }
    }
}

[Guid("E343F8F2-CA68-4BF4-BB54-EEA4B3AC4A31"), ComVisible(true)]
public class MyNotificationActivator : ToastNotificationActivator {
    public override void OnActivate(string arguments, Dictionary<string, string> data) {
        MessageBox.Show("Activate it!");
    }
}
[POCOViewModel]
public class MainViewModel {
    //[ServiceProperty(Key = "NotificationService")]
    //protected virtual INotificationService AppNotificationService { get { return null; } }

protected virtual INotificationService AppNotificationService { get { return this.GetRequiredService(); } }

    ...
    public void ShowNotification() {
        INotification notification = AppNotificationService.CreatePredefinedNotification("DevAV Tips & Tricks", 
        "Take user where they want to go with", "DevExpress Map Controls.", 
        new BitmapImage(new Uri("pack://application:,,,/NotificationsSampleApp;component/images/ImageName.png", UriKind.Absolute)));
        notification.ShowAsync();
    }
    ... 
}

Custom Notification

TaskbarButtonService

NotifyIconService is an INotifyIconService implementation that allows you to place a notification icon

ApplicationJumpListService allows you to add your own items to the Window's Jump Lists in accordance with MVVM

Report Services

  • GridReportManagerService - allows you to export data from a GridControl into a report
  • StandaloneReportManagerService - allows you to export data from a data source into a report

The ViewInjectionService is an IViewInjectionService implementation that allows you to integrate any ViewModel (with its View) to controls. see more

 strongly recommend that you use Module Injection Framework (MIF).

Module Injection Framework (MIF) is a set of classes that help organize an MVVM application. It provides the following functionality:

  • Connecting View Models to Views
  • Navigating among different screens or pages in an application
  • Saving and restoring an application's visual and logical states
  • Unit testing

不够简明,用途存疑

DataTemplateSelector ancestor in code-behind. The DXDataTemplateSelector works like WPF triggers.

<DXDataTemplateSelector x:Key="myDataTemplateSelector">
    <DXDataTemplateTrigger Binding="{Binding Priority}" Value="1" Template="{StaticResource importantTaskTemplate}"/>
    <DXDataTemplateTrigger Template="{StaticResource myTaskTemplate}"/>
DXDataTemplateSelector>
...
<ListBox Width="400" Margin="10"
         ItemsSource="{Binding Source={StaticResource myTodoList}}"
         ItemTemplateSelector="{StaticResource myDataTemplateSelector}"
         HorizontalContentAlignment="Stretch"/>
  • When both the Property and Value properties are not specified, the DXDataTemplateTrigger serves as the default template source.
  • You can assign a template to DXDataTemplateTrigger  as a static resource only. Dynamic resources are not supported.

GridControl, TreeListControl, DataLayoutControl and PropertyGridControl recognize Data Annotation attributes, and automatically generate layout and content based on these attributes. These controls support the following attributes from the System.ComponentModel.DataAnnotations library.

You can use the Data Annotation Attributes in POCO View models to implement the IDataErrorInfo interface.

  • CustomValidationAttribute
  • DataTypeAttribute
  • DisplayFormatAttribute
  • EditableAttribute
  • EnumDataTypeAttribute
  • EmailAddressAttribute
  • FileExtensionsAttribute
  • MaxLengthAttribute
  • MetadataTypeAttribute
  • MinLengthAttribute
  • PhoneAttribute
  • RangeAttribute
  • RegularExpressionAttribute
  • RequiredAttribute
  • ScaffoldColumnAttribute
  • ScaffoldTable
  • StringLengthAttribute
  • UrlAttribute
  • ValidationAttribute
  • DateTimeMaskAttribute
  • NumericMaskAttribute
  • RegExMaskAttribute
  • RegularMaskAttribute
  • SimpleMaskAttribute
  • LayoutControlEditorAttribute