• Dex MVVM


    目录

    BindableBase

    ViewModelBase

    POCO ViewModels

    Messenger

    Asynchronous Commands

    Behaviors

    Services 

    DXDataTemplateSelector 

    Data Annotation

     

    BindableBase

    public class ViewModel : BindableBase {
        public string FirstName {
            get { return GetValue<string>(); } 
            set { SetValue(value); }
        }
    }
    
    public class ViewModel : BindableBase {
        public string FirstName {
            get { return GetValue<string>(); }
            set { SetValue(value, changedCallback: NotifyFullNameChanged); }
        }
    
        public string LastName {
            get { return GetValue<string>(); }
            set { SetValue(value, changedCallback: NotifyFullNameChanged); }
        }
    
        public string FullName { get { return FirstName + " " + LastName; } }
    
        void NotifyFullNameChanged() {
            RaisePropertyChanged(nameof(FullName));
        }
    }

    ViewModelBase

     ViewModelBase class provides the OnInitializeInDesignMode and OnInitializeInRuntime protected virtual methods, which you can override to initialize properties for runtime and design time modes separately.

    public class ViewModel : ViewModelBase {
        protected override void OnInitializeInDesignMode() {
            base.OnInitializeInDesignMode();
            Employees = new List<Employee>() {
                new Employee() { Name = "Employee 1" },
            };
        }
        protected override void OnInitializeInRuntime() {
            base.OnInitializeInRuntime();
            Employees = DatabaseController.GetEmployees();
        }
    }

    The ViewModelBase class implements the ISupportServices interface that maintains the 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<IMessageBoxService>(); } }
    }

    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.

    <dxmvvm:Interaction.Behaviors>
            <dx:DXMessageBoxService x:Name="service1"/>
            <dx:DXMessageBoxService x:Name="service2"/>
        </dxmvvm:Interaction.Behaviors>
    
    public IMessageBoxService MessageBoxService1 { get { return GetService<IMessageBoxService>("service1"); } }
        public IMessageBoxService MessageBoxService2 { get { return GetService<IMessageBoxService>("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).

    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<IMessageBoxService>(); } }
    }
    public class MainViewModel : ViewModelBase {
        public IMessageBoxService MessageBoxService { get { return GetService<IMessageBoxService>(); } }
        public DetailViewModel DetailViewModel { get; private set; }
        public MainViewModel() {
            DetailViewModel = new DetailViewModel();
            ((ISupportParentViewModel)DetailViewModel).ParentViewModel = this;
        }
    }

    或者

    <Grid x:Name="LayoutRoot">
            ...
            <!-- It is necessary to use the ElementName binding mode, because the DetailView's DataContext is a DetailViewModel object. 
            That is why the following regular binding cannot be used in this case: dxmvvm:ViewModelExtensions.Parameter="{Binding}"
            Instead, use one of the following constructions:--> 
            <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">
            ...
            <!-- It is necessary to use the ElementName binding mode, 
            because the DetailView's DataContext is a DetailViewModel object. 
            That is why the following regular binding cannot be used 
            in this case: dxmvvm:ViewModelExtensions.Parameter="{Binding}"
            Instead, use one of the following constructions:-->
            <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() {
            //...
        }
    }

    POCO ViewModels

    POCO View Models allow you to:

    • define bindable properties as simple auto-implemented properties;
    • create methods that function as commands at runtime;
    • make properties and methods implement MVVM-specific interfaces.

    To transform a POCO class into a fully functional ViewModel, create a class instance with the DevExpress.Mvvm.POCO.ViewModelSource.Create method. 

    public class LoginViewModel {
        //This property will be converted to a bindable one
        public virtual string UserName { get; set; }
    
        //SaveAccountSettingsCommand will be created for the SaveAccountSettings and CanSaveAccountSettings methods:
        //SaveAccountSettingsCommand = new DelegateCommand<string>(SaveAccountSettings, CanSaveAccountSettings);
        public void SaveAccountSettings(string fileName) {
            //...
        }
        public bool CanSaveAccountSettings(string fileName) {
            return !string.IsNullOrEmpty(fileName);
        }
    
        //We recommend that you not use public constructors to prevent creating the View Model without the ViewModelSource
        protected LoginViewModel() { }
        //This is a helper method that uses the ViewModelSource class for creating a LoginViewModel instance
        public static LoginViewModel Create() {
            return ViewModelSource.Create(() => new LoginViewModel());
        }
    }

    也可以直接在xaml中创建

    <UserControl x:Class="DXPOCO.Views.LoginView"
        xmlns:dxmvvm="http://schemas.devexpress.com/winfx/2008/xaml/mvvm"
        xmlns:ViewModels="clr-namespace:DXPOCO.ViewModels"
        DataContext="{dxmvvm:ViewModelSource Type=ViewModels:LoginViewModel}"
        ...>
        <Grid>
            <!--...-->
        </Grid>
    </UserControl>

    使用DependsOnProperties

    publicclass DependentPropertiesViewModel {

    public virtual string FirstName { get; set; }
    
            public virtual string LastName { get; set; }
    
            [DependsOnProperties("FirstName", "LastName")]
            public string FullName { get { return FirstName + " " + LastName; } }
        }

    You can define methods that are invoked when properties are changed. These method names should use the following formats: On[PropertyName]Changed and On[PropertyName]Changing.

    public class LoginViewModel {
            public virtual string UserName { get; set; }
            protected void OnUserNameChanged(string oldValue) {
                //...
            }
            protected void OnUserNameChanging(string newValue) {
                //...
            }
        }

    多个属性可以共用同一个方法

    public class ChangeNotificationsViewModel {
            [BindableProperty(OnPropertyChangedMethodName = "OnNameChanged")]
            public virtual string FirstName { get; set; }
    
            [BindableProperty(OnPropertyChangedMethodName = "OnNameChanged")]
            public virtual string LastName { get; set; }
    
            public string FullName { get { return FirstName + " " + LastName; } }
    
            protected void OnNameChanged() {
                this.RaisePropertyChanged(x => x.FullName);
                this.RaiseCanExecuteChanged(x => x.Register());
            }
            
            [Command(UseCommandManager = false)]
            public void Register() {
                MessageBox.Show("Registered");
            }
            public bool CanRegister() {
                return !string.IsNullOrEmpty(FirstName) && !string.IsNullOrEmpty(LastName);
            }
        }

    You can use the BindableProperty attribute to:

    • prevent the POCO mechanism from generating a bindable property for a specified property;
    • specify which method should be invoked when a property value is changing or has been changed. This is useful when the method's name does not match the On[PropertyName]Changed and On[PropertyName]Changing convention.
    public class LoginViewModel {
            [BindableProperty(isBindable: false)]
            public virtual bool IsEnabled { get; set; }
    
            [BindableProperty(OnPropertyChangedMethodName = "Update")]
            public virtual string UserName { get; set; }
            protected void Update() {
                //...
            }
        }

    The POCO mechanism generates commands for all public methods without parameters or with a single parameter. You can use the Command attribute or the Fluent API to control the command generation mechanism.

    public class LoginViewModel {
        [Command(isCommand: false)]
        public void SaveCore() {
            //...
        }
    
        [Command(CanExecuteMethodName = "CanSaveAccountSettings",
            Name = "SaveCommand",
            UseCommandManager = true)]
        public void SaveAccountSettings(string fileName) {
            //...
        }
        public bool CanSaveAccountSettings(string fileName) {
            return !string.IsNullOrEmpty(fileName);
        }
    }

     To update an automatically generated command in a POCO View Model, use the RaiseCanExecuteChanged extension method

    [POCOViewModel]
    public class ViewModel {
        public void GoBack(){
            //...
        }
        public bool CanGoBack(){
            //...
        }
        public void UpdateSaveCommand(){
            this.RaiseCanExecuteChanged(c => c.GoBack());
        }
    }

    Can方法是必需的,即使总是return true

    You can pass parameters to the ViewModel's constructor using any of the following approaches.

    • Use lambda expressions. Lambda expressions work slower, because they are not cached and are newly compiled with each method call.
    ViewModelSource.Create(() => new LoginViewModel(caption: "Login") {
        UserName = "John Smith"
    });
    • Use delegates. This approach works faster than lambda expressions, because the compiled delegate instances can be cached.
    var factory = ViewModelSource.Factory((string caption) => new LoginViewModel(caption));
    factory("Login");

     The POCO mechanism allows you to automatically implement the 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<LoginViewModel> {
           void IMetadataProvider<LoginViewModel>.BuildMetadata(MetadataBuilder<LoginViewModel> builder) {
               builder.Property(x => x.UserName).
                   Required(() => "Please enter the user name.");
            }
        }
        public virtual string UserName { get; set; }
    }

    Messenger

    The static 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<MyMessage>(
                recipient: this, 
                action: OnMessage);
            // Inherited messages are processed with this subscription
            Messenger.Default.Register<MyMessage>(
                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<MyMessage>(
                recipient: this, 
                token: MessageToken.Type1,
                action: OnMessage1);
            Messenger.Default.Register<MyMessage>(
                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<Message>(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

    Asynchronous Commands

    Asynchronous commands can be useful if you need to run a time-consuming operation in a separate thread without freezing the UI.

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

    Behaviors

    The DevExpress.Mvvm.UI.Interactivity.Interaction class implements a Behaviors attached property. This property is read-only and contains a collection of Behaviors. 

    <TextBox Text="This control is focused on startup">
        <dxmvvm:Interaction.Behaviors>
            <dxmvvm:FocusBehavior/>
            <!--Other Behaviors-->
        </dxmvvm:Interaction.Behaviors>
    </TextBox>

    The DevExpress.Mvvm.UI.Interactivity.Interaction class implements a BehaviorsTemplate attached property. This property can be used to apply Behaviors in a style. 

    <Style TargetType="TextBox">
        <Setter Property="dxmvvm:Interaction.BehaviorsTemplate">
            <Setter.Value>
                <DataTemplate>
                    <ContentControl>
                        <dxmvvm:FocusBehavior/>
                    </ContentControl>
                </DataTemplate>
            </Setter.Value>
        </Setter>
    </Style>

    If it is necessary to define more than one Behavior, you can place behaviors on the ItemsControl element, instead of the ContentControl.

    <Style TargetType="TextBox">
        <Setter Property="dxmvvm:Interaction.BehaviorsTemplate">
            <Setter.Value>
                <DataTemplate>
                    <ItemsControl>
                        <dxmvvm:FocusBehavior/>
                        <dxmvvm:EventToCommand EventName="MouseDoubleClick" Command="{Binding EditCommand}"> 
                            <dxmvvm:EventToCommand.EventArgsConverter> 
                                <Common:ListBoxEventArgsConverter/> 
                            </dxmvvm:EventToCommand.EventArgsConverter> 
                        </dxmvvm:EventToCommand> 
                    </ItemsControl>
                </DataTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    using DevExpress.Mvvm.UI;
    using System.Linq;
    public class ListBoxEventArgsConverter : EventArgsConverterBase<MouseEventArgs> {
        protected override object Convert(object sender, MouseEventArgs args) {
            ListBox parentElement = (ListBox)sender;
            DependencyObject clickedElement = (DependencyObject)args.OriginalSource;
            ListBoxItem clickedListBoxItem = 
                LayoutTreeHelper.GetVisualParents(child: clickedElement, stopNode: parentElement)
                .OfType<ListBoxItem>()
                .FirstOrDefault();
            if(clickedListBoxItem != null)
                return (Person)clickedListBoxItem.DataContext;
            return null;
        }
    }

    The 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 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<T>.SourceObject property, or specify the object's name using the EventTriggerBase<T>.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">
                <!--#region !-->
                <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>
                <!--#endregion-->
            </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<DataItem> Points { get; set; }
    
        public IList<DataItem> 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 .

    Services

    A Service is a special 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<IDispatcherService>();

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

    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<UICommand>() { ... },
                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<UICommand>() { ... },
                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<IDocumentManagerService>(); } }
        ...
        public void CreateDocument(object arg) {
            IDocument doc = DocumentManagerService.FindDocument(arg);
            if (doc == null) {
                doc = DocumentManagerService.CreateDocument("DetailedView", arg);
                doc.Id = DocumentManagerService.Documents.Count<IDocument>();
            }
            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>
                        <!--#region !-->
                        <dxmvvm:Interaction.Behaviors>                       
                            <dxdo:TabbedDocumentUIService DocumentGroup="{Binding ElementName=documnetGroup}"/>
                        </dxmvvm:Interaction.Behaviors>
                        <!--#endregion-->
                    </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>
                    <!--#region !-->
                    <dxmvvm:Interaction.Behaviors>
                        <dx:TabbedWindowDocumentUIService Target="{Binding ElementName=tabControl}"/>
                    </dxmvvm:Interaction.Behaviors>
                    <!--#endregion-->
                </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>
                    <!--#region !-->
                    <dxmvvm:Interaction.Behaviors>
                        <dxwuin:FrameDocumentUIService/>
                    </dxmvvm:Interaction.Behaviors>
                    <!--#endregion-->
                </local:CollectionView>
            </dxwui:NavigationFrame.Source>
        </dxwui:NavigationFrame>

    DockingDocumentUIService 

        <dxdo:DockLayoutManager>
            <dxdo:LayoutGroup Orientation="Horizontal">
                <dxdo:LayoutPanel Caption="Users">
                    <local:CollectionView>
                        <!--#region !-->
                        <dxmvvm:Interaction.Behaviors>
                            <dxdo:DockingDocumentUIService LayoutGroup="{Binding ElementName=layouGroup}"/>
                        </dxmvvm:Interaction.Behaviors>
                        <!--#endregion-->
                    </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<INavigationService>(); } }
        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>
        <!--#endregion-->
        <dxlc:LayoutControl Orientation="Vertical" VerticalAlignment="Top">
            <Button Command="{Binding ShowSplashScreenCommand}" CommandParameter="ThemedSplashScreenService" VerticalAlignment="Top">Show Themed</Button>
            <Button Command="{Binding ShowSplashScreenCommand}" CommandParameter="FluentSplashScreenService" VerticalAlignment="Top">Show Fluent</Button>
            <Button Command="{Binding ShowSplashScreenCommand}" CommandParameter="WaitIndicatorSplashScreenService" VerticalAlignment="Top">Show WaitIndicator</Button>
        </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>
    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<INotificationService>(); } }

        ...
        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

    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

    不够简明,用途存疑

    DXDataTemplateSelector 

    The DXDataTemplateSelector allows you to define a simple template selection logic in XAML, so you do not need to create a 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.

    Converters

    ObjectToObjectConverter acts as a dictionary, which maps predefined input values to output values of any type.

    ObjectToObjectConverter provides three properties.

    • Map – this property contains a collection of MapItem objects that map input and output values.
    • DefaultTarget – this property contains an object, which should be returned in case of a failed conversion.
    • DefaultSource – the property is used in a backward conversion. It is returned when the conversion fails.
    <UserControl ...
                xmlns:dxmvvm="http://schemas.devexpress.com/winfx/2008/xaml/mvvm">
    
        <UserControl.DataContext> 
            <ViewModels:ViewModel State="Green"/> 
        </UserControl.DataContext> 
        <UserControl.Resources> 
            <dxmvvm:ObjectToObjectConverter x:Key="ColorStateToStringConverter"> 
                    <dxmvvm:MapItem Source="Red" Target="Ready"/> 
                    <dxmvvm:MapItem Source="Yellow" Target="Steady"/> 
                    <dxmvvm:MapItem Source="Green" Target="Go"/> 
            </dxmvvm:ObjectToObjectConverter> 
        </UserControl.Resources> 
        <Grid> 
            <TextBlock Text="{Binding State, Converter={StaticResource ColorStateToStringConverter}}"/> 
        </Grid>
    </UserControl>

    BooleanToObjectConverter Converts the input Boolean, nullable Boolean or DefaultBoolean value to a value of any type.

    This converter provides three mapping properties: TrueValue, FalseValue, and NullValue. These properties specify objects that should be returned if the input value is TrueFalsenull (or DefaultBoolean.Default) respectively.

    <UserControl ...
                xmlns:dxmvvm="http://schemas.devexpress.com/winfx/2008/xaml/mvvm">
    
        <UserControl.DataContext> 
            <ViewModels:ViewModel IsEnabled="True"/> 
        </UserControl.DataContext> 
        <UserControl.Resources> 
            <dxmvvm:BooleanToObjectConverter x:Key="BooleanToColorConverter"> 
                <dxmvvm:BooleanToObjectConverter.TrueValue> 
                    <SolidColorBrush Color="Green"/> 
                </dxmvvm:BooleanToObjectConverter.TrueValue> 
                <dxmvvm:BooleanToObjectConverter.FalseValue> 
                    <SolidColorBrush Color="Red"/> 
                </dxmvvm:BooleanToObjectConverter.FalseValue> 
            </dxmvvm:BooleanToObjectConverter> 
        </UserControl.Resources> 
        <Grid> 
            <Border Background="{Binding IsEnabled, Converter={StaticResource BooleanToColorConverter}}"/> 
        </Grid>
    </UserControl>

    BooleanToVisibilityConverter An extended version of the standard converter that maps Boolean values to the values of the Visibility type and vice versa.

    • Inverse – allows you to invert the conversion.
    • HiddenInsteadOfCollapsed – if this property is True, the converter returns the Hidden state instead of the Collapsed state when the input value is False.
    <UserControl ...
                xmlns:dxmvvm="http://schemas.devexpress.com/winfx/2008/xaml/mvvm">
    
        <CheckBox x:Name="straightConversionCheckBox" IsChecked="True">Is visible</CheckBox>
        <Button Visibility="{Binding IsChecked, ElementName=straightConversionCheckBox, 
                Converter={dxmvvm:BooleanToVisibilityConverter}}">Test Button</Button>
    </UserControl>

    StringToVisibilityConverter Converts string values to the Visibility type.

    If the input value is Null or String.Empty, the converter returns Visibility.Collapsed; otherwise, it returns Visibility.Visible.

    • Inverse – allows you to invert the conversion.
    • HiddenInsteadOfCollapsed – if this property is True, the converter returns the Hidden state instead of the Collapsed state when the input value is Null or String.Empty.

     

    NumericToBooleanConverter Converts numeric values to the Boolean type.

    If the input value is 0, the converter returns False; otherwise, it returns True.

    NumericToVisibilityConverter Converts numeric values to the Visibility type.

    If the input value is 0, the converter returns Visibility.Collapsed; otherwise, it returns Visibility.Visible.

    • Inverse – allows you to invert the conversion.
    • HiddenInsteadOfCollapsed – if this property is True, the converter returns the Hidden state instead of the Collapsed state when the input value is 0.

    BooleanNegationConverter

    Inverts the input Boolean or DefaultBoolean value.

    DefaultBooleanToBooleanConverter

    Converts between the three-state DefaultBoolean type (DefaultTrue and False) and the nullable Boolean type (nullTrue, and False).

    ObjectToBooleanConverter

    If the input object is null, the converter returns False; otherwise, it returns True.

    StringToBooleanConverter

    If the input string is empty or null, the converter returns False; otherwise, it returns True.

     

    Data Annotation 

    used for customizing data classes, to specify how data is displayed from a data source, define validation rules, and set relationships between data classes.

    The GridControlTreeListControlDataLayoutControl 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

     

  • 相关阅读:
    poj2352树状数组解决偏序问题
    Python中的输入(input)和输出打印
    Python中的输入(input)和输出打印
    IP协议
    IP协议
    Python中的可迭代Iterable和迭代器Iterator
    Python中的可迭代Iterable和迭代器Iterator
    Python中的变量和数据类型
    Python中的变量和数据类型
    TCP的三次握手和四次挥手和UDP协议
  • 原文地址:https://www.cnblogs.com/yetsen/p/13767982.html
Copyright © 2020-2023  润新知