WPF (7) Basic features of the Prism framework

Reference documents:

1. Basic features of the Prism framework

1.BindableBase

​In the previous article, in order to provide logic/data binding support for the front-end View in the MVVM framework, so that the back-end data has the ability to notify UI data changes, we manually implemented the INotifyPropertyChanged interface and encapsulated it as the ViewModelBase base class .
In the Prism framework, Prism extends the binding notification function of WPF. Provides the base class BindableBase that has implemented the INotifyPropertyChanged interface and encapsulated it . And use the CallerMemberName feature to automatically obtain the attribute name, which solves the problem of cumbersome calling of attribute change events. At the same time, operations such as filtering equal values ​​are performed inside the method. The source code is as follows:

namespace Prism.Mvvm
{
    
    
    /// <summary>
    /// Implementation of <see cref="INotifyPropertyChanged"/> to simplify models.
    /// </summary>
    public abstract class BindableBase : INotifyPropertyChanged
    {
    
    
        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Checks if a property already matches a desired value. Sets the property and
        /// notifies listeners only when necessary.
        /// </summary>
        /// <typeparam name="T">Type of the property.</typeparam>
        /// <param name="storage">Reference to a property with both getter and setter.</param>
        /// <param name="value">Desired value for the property.</param>
        /// <param name="propertyName">Name of the property used to notify listeners. This
        /// value is optional and can be provided automatically when invoked from compilers that
        /// support CallerMemberName.</param>
        /// <returns>True if the value was changed, false if the existing value matched the
        /// desired value.</returns>
        protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
        {
    
    
            if (EqualityComparer<T>.Default.Equals(storage, value)) return false;

            storage = value;
            RaisePropertyChanged(propertyName);

            return true;
        }

        /// <summary>
        /// Checks if a property already matches a desired value. Sets the property and
        /// notifies listeners only when necessary.
        /// </summary>
        /// <typeparam name="T">Type of the property.</typeparam>
        /// <param name="storage">Reference to a property with both getter and setter.</param>
        /// <param name="value">Desired value for the property.</param>
        /// <param name="propertyName">Name of the property used to notify listeners. This
        /// value is optional and can be provided automatically when invoked from compilers that
        /// support CallerMemberName.</param>
        /// <param name="onChanged">Action that is called after the property value has been changed.</param>
        /// <returns>True if the value was changed, false if the existing value matched the
        /// desired value.</returns>
        protected virtual bool SetProperty<T>(ref T storage, T value, Action onChanged, [CallerMemberName] string propertyName = null)
        {
    
    
            if (EqualityComparer<T>.Default.Equals(storage, value)) return false;

            storage = value;
            onChanged?.Invoke();
            RaisePropertyChanged(propertyName);

            return true;
        }

        /// <summary>
        /// Raises this object's PropertyChanged event.
        /// </summary>
        /// <param name="propertyName">Name of the property used to notify listeners. This
        /// value is optional and can be provided automatically when invoked from compilers
        /// that support <see cref="CallerMemberNameAttribute"/>.</param>
        protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
        {
    
    
            OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
        }

        /// <summary>
        /// Raises this object's PropertyChanged event.
        /// </summary>
        /// <param name="args">The PropertyChangedEventArgs</param>
        protected virtual void OnPropertyChanged(PropertyChangedEventArgs args)
        {
    
    
            PropertyChanged?.Invoke(this, args);
        }
    }
}

​ When we need to make binding notifications for data, we only need to directly inherit the BindableBase class. There are two types of attribute update notification methods:

  • SetProperty
    private string _fieldName;
    public string PropertyName
    {
    
    
        get {
    
     return _fieldName; }
        set {
    
     SetProperty(ref _fieldName, value); }
    }
  • RaisePropertyChanged
    private string _fieldName;
    public string PropertyName
    {
    
    
        get {
    
     return _fieldName; }
        set 
        {
    
     
        	if(_fileName != value)
        	{
    
    
        		_fieldName = value;
        		RaisePropertyChanged();
        	}
        }
    }

2.DelegateCommand

​In the previous article, in order to achieve the logical separation of the front and back ends in the MVVM framework, we manually implemented the ICommand interface and encapsulated the custom command as RelayCommand<T> for transferring logic/behavior between the front and back ends. In the Prism framework, Prism provides the command base class DelegateCommandBase and its subclass DelegateCommand that has implemented the ICommand interface and encapsulated it. It not only includes all the basic functions of our manual implementation of the command class, but also expands some new features. Simplified command calling method.

2.1 DelegateCommand source code

​ The other definitions in the DelegateCommand class are not much different from the implementation of our conventionally encapsulated ICommand. The point is that two methods with Expression parameters, ObservesProperty and ObservesCanExecute, are added to this class. Both methods are called internally. A method called ObservesPropertyInternal. So what do these two methods do? In the previous article WPF (6) Command command model source code analysis , we analyzed the command model of ICommand: if a custom command wants to achieve real-time refresh of the command state, it must manually trigger the CanExecuteChanged event or use the CommandManager proxy; and in In DelegateCommand, a more flexible method is used, by encapsulating ObservesProperty and ObservesCanExecute two methods to monitor the change of the target property value, automatically trigger the CanExecuteChanged event, and refresh the command status.

//1.不带泛型的 DelegateCommand
namespace Prism.Commands
{
    
    
    /// <summary>
    /// An <see cref="ICommand"/> whose delegates do not take any parameters for <see cref="Execute()"/> and <see cref="CanExecute()"/>.
    /// </summary>
    /// <see cref="DelegateCommandBase"/>
    /// <see cref="DelegateCommand{T}"/>
    public class DelegateCommand : DelegateCommandBase
    {
    
    
        //命令所需执行的委托
        Action _executeMethod;
        //判断命令是否可用所执行的委托
        Func<bool> _canExecuteMethod;

        /// <summary>
        /// Creates a new instance of <see cref="DelegateCommand"/> with the <see cref="Action"/> to invoke on execution.
        /// </summary>
        /// <param name="executeMethod">The <see cref="Action"/> to invoke when <see cref="ICommand.Execute(object)"/> is called.</param>
        public DelegateCommand(Action executeMethod)
            : this(executeMethod, () => true)
        {
    
    

        }

        /// <summary>
        /// Creates a new instance of <see cref="DelegateCommand"/> with the <see cref="Action"/> to invoke on execution
        /// and a <see langword="Func" /> to query for determining if the command can execute.
        /// </summary>
        /// <param name="executeMethod">The <see cref="Action"/> to invoke when <see cref="ICommand.Execute"/> is called.</param>
        /// <param name="canExecuteMethod">The <see cref="Func{TResult}"/> to invoke when <see cref="ICommand.CanExecute"/> is called</param>
        public DelegateCommand(Action executeMethod, Func<bool> canExecuteMethod)
            : base()
        {
    
    
            if (executeMethod == null || canExecuteMethod == null)
                throw new ArgumentNullException(nameof(executeMethod), Resources.DelegateCommandDelegatesCannotBeNull);

            _executeMethod = executeMethod;
            _canExecuteMethod = canExecuteMethod;
        }

        ///<summary>
        /// Executes the command.
        ///</summary>
        public void Execute()
        {
    
    
            _executeMethod();
        }

        /// <summary>
        /// Determines if the command can be executed.
        /// </summary>
        /// <returns>Returns <see langword="true"/> if the command can execute,otherwise returns <see langword="false"/>.</returns>
        public bool CanExecute()
        {
    
    
            return _canExecuteMethod();
        }

        /// <summary>
        /// Handle the internal invocation of <see cref="ICommand.Execute(object)"/>
        /// </summary>
        /// <param name="parameter">Command Parameter</param>
        protected override void Execute(object parameter)
        {
    
    
            Execute();
        }

        /// <summary>
        /// Handle the internal invocation of <see cref="ICommand.CanExecute(object)"/>
        /// </summary>
        /// <param name="parameter"></param>
        /// <returns><see langword="true"/> if the Command Can Execute, otherwise <see langword="false" /></returns>
        protected override bool CanExecute(object parameter)
        {
    
    
            return CanExecute();
        }

        /// <summary>
        /// Observes a property that implements INotifyPropertyChanged, and automatically calls DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications.
        /// </summary>
        /// <typeparam name="T">The object type containing the property specified in the expression.</typeparam>
        /// <param name="propertyExpression">The property expression. Example: ObservesProperty(() => PropertyName).</param>
        /// <returns>The current instance of DelegateCommand</returns>
        public DelegateCommand ObservesProperty<T>(Expression<Func<T>> propertyExpression)
        {
    
    
            ObservesPropertyInternal(propertyExpression);
            return this;
        }

        /// <summary>
        /// Observes a property that is used to determine if this command can execute, and if it implements INotifyPropertyChanged it will automatically call DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications.
        /// </summary>
        /// <param name="canExecuteExpression">The property expression. Example: ObservesCanExecute(() => PropertyName).</param>
        /// <returns>The current instance of DelegateCommand</returns>
        public DelegateCommand ObservesCanExecute(Expression<Func<bool>> canExecuteExpression)
        {
    
    
            _canExecuteMethod = canExecuteExpression.Compile();
            ObservesPropertyInternal(canExecuteExpression);
            return this;
        }
    }
}
//2.带泛型的 DelegateCommand
namespace Prism.Commands
{
    
    
    public class DelegateCommand<T> : DelegateCommandBase
    {
    
    
        public DelegateCommand(Action<T> executeMethod);

        public DelegateCommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod);


        public bool CanExecute(T parameter);

        public void Execute(T parameter);

        public DelegateCommand<T> ObservesCanExecute(Expression<Func<bool>> canExecuteExpression);

        public DelegateCommand<T> ObservesProperty<TType>(Expression<Func<TType>> propertyExpression);

        protected override bool CanExecute(object parameter);

        protected override void Execute(object parameter);
    }
}

​ ObservesPropertyInternal This method will pass the current Expression parameter into a local variable of type _observedPropertiesExpressions of HashSet. If the variable exists in the current collection, an exception will be thrown to avoid repeated addition. If it has not been added, it will be added to the current collection. in maintenance. Then, after adding it to the collection, a new method is called. This PropertyObserver.Observes method will pass the RaiseCanExecuteChanged method in the current DelegateCommandBase as a parameter to the Observes method in the PropertyObserver class. As the name implies, PropertyObserver is a method used to A class that monitors changes in Propery properties.

​ Through Expression, the PropertyObserver.Observes() method is called internally and the RaiseCanExecuteChaned method is passed in as a delegate. The Expression is saved in a linked list in the PropertyObserver, and each node subscribes to the OnPropertyChanged event. In this way, when the property Expression changes, the current RaiseCanExecuteChanged delegate can be called, so as to finally trigger the CanExecuteChanged event of the command to achieve the purpose of refreshing the command status.

namespace Prism.Commands
{
    
    
    /// <summary>
    /// An <see cref="ICommand"/> whose delegates can be attached for <see cref="Execute"/> and <see cref="CanExecute"/>.
    /// </summary>
    public abstract class DelegateCommandBase : ICommand, IActiveAware
    {
    
    
        private SynchronizationContext _synchronizationContext;
        private readonly HashSet<string> _observedPropertiesExpressions = new HashSet<string>();
 
        /// <summary>
        /// Creates a new instance of a <see cref="DelegateCommandBase"/>, specifying both the execute action and the can execute function.
        /// </summary>
        protected DelegateCommandBase()
        {
    
    
            _synchronizationContext = SynchronizationContext.Current;
        }
 
        /// <summary>
        /// Occurs when changes occur that affect whether or not the command should execute.
        /// </summary>
        public virtual event EventHandler CanExecuteChanged;
 
        /// <summary>
        /// Raises <see cref="ICommand.CanExecuteChanged"/> so every
        /// command invoker can requery <see cref="ICommand.CanExecute"/>.
        /// </summary>
        protected virtual void OnCanExecuteChanged()
        {
    
    
            var handler = CanExecuteChanged;
            if (handler != null)
            {
    
    
                if (_synchronizationContext != null && _synchronizationContext != SynchronizationContext.Current)
                    _synchronizationContext.Post((o) => handler.Invoke(this, EventArgs.Empty), null);
                else
                    handler.Invoke(this, EventArgs.Empty);
            }
        }
 
        /// <summary>
        /// Raises <see cref="CanExecuteChanged"/> so every command invoker
        /// can requery to check if the command can execute.
        /// </summary>
        /// <remarks>Note that this will trigger the execution of <see cref="CanExecuteChanged"/> once for each invoker.</remarks>
        [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate")]
        public void RaiseCanExecuteChanged()
        {
    
    
            OnCanExecuteChanged();
        }
 
      
        /// <summary>
        /// Observes a property that implements INotifyPropertyChanged, and automatically calls DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications.
        /// </summary>
        /// <typeparam name="T">The object type containing the property specified in the expression.</typeparam>
        /// <param name="propertyExpression">The property expression. Example: ObservesProperty(() => PropertyName).</param>
        protected internal void ObservesPropertyInternal<T>(Expression<Func<T>> propertyExpression)
        {
    
    
            if (_observedPropertiesExpressions.Contains(propertyExpression.ToString()))
            {
    
    
                throw new ArgumentException($"{
      
      propertyExpression.ToString()} is already being observed.",
                    nameof(propertyExpression));
            }
            else
            {
    
    
                _observedPropertiesExpressions.Add(propertyExpression.ToString());
                PropertyObserver.Observes(propertyExpression, RaiseCanExecuteChanged);
            }
        }
    }
}

2.2 Use of DelegateCommand

(1) Basic use

namespace CommandTest.ViewModels
{
    
    
    public class MainViewModel : BindableBase
    {
    
    
    	//1.委托不带参数
    	public DelegateCommand InitializeCommand {
    
     get; private set; }
    	//2.委托带参数(泛型DelegateCommand)
    	public DelegateCommand<Object> InitializeParamsCommand {
    
     get; private set; }
    	//3.构造函数-初始化
    	public MainViewModel()
        {
    
    
            InitializeCommand = new DelegateCommand(Execute1);
            InitializeParamsCommand = new DelegateCommand<Object>(Execute2);
        }
        
        //4.委托方法
        private void Execute1()
        {
    
    
        	//do something... ...
        }
        
        private void Execute2(Object obj)
        {
    
    
        	//do something... ...
        }
    }  
}

(2) Executable checks

  • RaiseCanExecuteChanged manual refresh

​ When the property value changes, manually trigger the CanExecuteChanged event through the call of RaiseCanExecuteChanged to update the CanExecute state

namespace CommandTest.ViewModels
{
    
    
    public class MainViewModel : BindableBase
    {
    
    
    	//1.DelegateCommand属性声明
    	public DelegateCommand InitializeCommand {
    
     get; private set; }
    	//2.构造函数-初始化
    	public MainViewModel()
        {
    
    
            InitializeCommand = new DelegateCommand(DoExecute,CanExecute);
        }
        //3.命令委托
        private void DoSave()
		{
    
    
    		//执行逻辑
		}
		
		private bool CanExecute()
		{
    
    
    		//能否执行的逻辑
    		return Param < 200;
		}
        
        private int _param = 100;
		public int Param
		{
    
    
    		get{
    
     return _param;}
    		set
    		{
    
    
        		SetProperty(ref _param, value);
        		InitializeCommand.RaiseCanExecuteChanged();//手动触发
    		}
		}
    }  
}
  • ObservesProperty property monitoring

​ ObservesProperty is used to monitor changes in the target property value. When the monitoring target property value changes, it will automatically check the state, execute the RaiseCanExecuteChanged method, trigger the CanExecuteChanged event, execute the CanExecute delegate of ICommand, and update the CanExecute state. ObservesProperty also supports multiple conditional checks.

namespace CommandTest.ViewModels
{
    
    
    public class MainViewModel : BindableBase
    {
    
    
    	//1.DelegateCommand属性声明
    	public DelegateCommand InitializeCommand {
    
     get; private set; }
    	//2.构造函数-初始化
    	public MainViewModel()
        {
    
    
        	//ObservesProperty 监听Param1和Param2
            InitializeCommand = new DelegateCommand(DoExecute,CanExecute)
            	.ObservesProperty(()=>Param1)
        		.ObservesProperty(()=>Param2);
        }
        //3.命令委托
        private void DoSave()
		{
    
    
    		//执行逻辑
		}
		
		private bool CanExecute()
		{
    
    
    		//能否执行的逻辑(多条件)
    		return Param1 < 200 && Param2 != String.Empty;
		}
        //4.属性值声明
        private int _param1;
		public int Param1
		{
    
    
    		get{
    
     return _param1;}
    		set
    		{
    
    
        		SetProperty(ref _param1, value);
    		}
		}
		
		private string _param2;
		public string Param2
		{
    
    
    		get{
    
     return _param2;}
    		set
    		{
    
    
        		SetProperty(ref _param2, value);
    		}
		}
    }  
}
  • ObservesCanExecute listener

​ ObservesCanExecute is also used to monitor target properties. However, unlike ObservesProperty, ObservesCanExecute does not need to specify the CheckExecute delegate inspection method, but directly binds the available status of the command to the target property value. If the target property value becomes true, the command is available, otherwise it will be unavailable, and ObservesCanExecute binds the target property Must be of type bool.

namespace CommandTest.ViewModels
{
    
    
    public class MainViewModel : BindableBase
    {
    
    
    	//1.DelegateCommand属性声明
    	public DelegateCommand InitializeCommand {
    
     get; private set; }
    	//2.构造函数-初始化
    	public MainViewModel()
        {
    
    
            InitializeCommand = new DelegateCommand(DoExecute)
				.ObservesCanExecute(() => DoCondition);
        }
        //3.命令委托
        private void DoExecute()
		{
    
    
    		//执行逻辑
		}
		
        //4.属性值声明
		public bool DoCondition
		{
    
    
    		get{
    
     return Age < 200 && string.IsNullOrEmpty(Name);}
		}
    }  
}

3. Containers and Dependency Injection

​ Similar to Spring, the core components of the Prism framework are containers and dependency injection. That is, a container is used to manage the objects and their life cycles of all classes in the entire framework, and when referencing, only by injecting the interface framework can automatically find a specific instance according to the interface type, which will help us save a lot of object creation The new operation isolates the unstable parts of the program and manages them in one place, and realizes dependency injection through the IOC container in the software design process to achieve the ultimate inversion of control to the greatest extent, thus ensuring great flexibility in software design and scalability. Part of the source code of PrismApplication is as follows:

namespace Prism
{
    
    
    /// <summary>
    /// Base application class that provides a basic initialization sequence
    /// </summary>
    /// <remarks>
    /// This class must be overridden to provide application specific configuration.
    /// </remarks>
    public abstract class PrismApplicationBase : Application
    {
    
    
        IContainerExtension _containerExtension;
        IModuleCatalog _moduleCatalog;

        /// <summary>
        /// The dependency injection container used to resolve objects
        /// </summary>
        public IContainerProvider Container => _containerExtension;

        /// <summary>
        /// Raises the System.Windows.Application.Startup event.
        /// </summary>
        /// <param name="e">A System.Windows.StartupEventArgs that contains the event data.</param>
        protected override void OnStartup(StartupEventArgs e)
        {
    
    
            base.OnStartup(e);
            InitializeInternal();
        }

        /// <summary>
        /// Run the initialization process.
        /// </summary>
        void InitializeInternal()
        {
    
    
            ConfigureViewModelLocator();
            Initialize();
            OnInitialized();
        }

        /// <summary>
        /// Runs the initialization sequence to configure the Prism application.
        /// </summary>
        protected virtual void Initialize()
        {
    
    
            ContainerLocator.SetContainerExtension(CreateContainerExtension);
            //获取并初始化全局静态的容器
            _containerExtension = ContainerLocator.Current;
            //初始化模块
            _moduleCatalog = CreateModuleCatalog();
            //注册初始化服务
            RegisterRequiredTypes(_containerExtension);
            RegisterTypes(_containerExtension);
            _containerExtension.FinalizeExtension();

            ConfigureModuleCatalog(_moduleCatalog);

            var regionAdapterMappings = _containerExtension.Resolve<RegionAdapterMappings>();
            ConfigureRegionAdapterMappings(regionAdapterMappings);

            var defaultRegionBehaviors = _containerExtension.Resolve<IRegionBehaviorFactory>();
            ConfigureDefaultRegionBehaviors(defaultRegionBehaviors);

            RegisterFrameworkExceptionTypes();

            var shell = CreateShell();
            if (shell != null)
            {
    
    
                MvvmHelpers.AutowireViewModel(shell);
                RegionManager.SetRegionManager(shell, _containerExtension.Resolve<IRegionManager>());
                RegionManager.UpdateRegions();
                InitializeShell(shell);
            }

            InitializeModules();
        }
        
        //... ...
}

3.1 Interface Analysis

Some basic interfaces related to Prism IOC container are defined in Prism.Core, and their class diagram structure is as follows:
insert image description here

  • IContainerRegistry interface : The function of this interface is mainly to inject a series of types and corresponding entities into the container (including different life cycle types). In addition, this interface also defines the IsRegistered method to determine whether an object has been registered . register.
  • IContainerProvider interface : This interface is just the opposite of the IContainerRegistry interface, mainly to obtain previously registered/managed objects from the container by type or name.
  • ContainerLocator : ContainerLocator is a static class defined in Prism.Core, used to define/maintain a globally unique IContainerExtension object.
  • IContainerExtension : This interface inherits the two interfaces of IContainerRegistry and IContainerProvider at the same time, indicating that the inherited object has both the ability to register type objects and resolve type objects, and also defines a FinalizeExtension method for some container finalization operations.

3.2 Custom Service Registration and Injection

​ For our custom services/components, we can override the RegisterTypes method of the inherited PrismApplicationBase class in App.xaml.cs , and register the components into the container through the IContainerRegistry.Register series of methods. For different life cycle types, IContainerRegistry provides a variety of different basic registration methods, namely Transient Registering, Singleton Registering, etc.

(1)Transient Registering

​ Transient Registering creates a new instance every time it registers, that is, the multi-instance mode. It is used as follows:

namespace PrismDemo
{
    
    
    /// <summary>
    /// App.xaml 的交互逻辑
    /// </summary>
    public partial class App : PrismApplication
    {
    
    
        protected override Window CreateShell()
        {
    
    
            return Container.Resolve<HomeView>();
        }

        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
    
    
            // Where it will be appropriate to use FooService as a concrete type
            //具体类型注入
			containerRegistry.Register<FooService>();
			//接口类型注入(IBarService接口 -> 实现子类 BarService)
			containerRegistry.Register<IBarService, BarService>();
        }
    }
}

(2)Singleton Registering

​ Singleton Registering creates only one instance in the entire application life cycle, that is, the singleton mode, and the instance will only be created when it is used. It is used as follows:

namespace PrismDemo
{
    
    
    /// <summary>
    /// App.xaml 的交互逻辑
    /// </summary>
    public partial class App : PrismApplication
    {
    
    
        protected override Window CreateShell()
        {
    
    
            return Container.Resolve<HomeView>();
        }

        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
    
    
            // Where it will be appropriate to use FooService as a concrete type
            //具体类型注入
			containerRegistry.RegisterSingleton<FooService>();
			//接口类型注入(IBarService接口 -> 实现子类 BarService)
			containerRegistry.RegisterSingleton<IBarService, BarService>();
        }
    }
}

(3) Injection and use

With the above content, we can directly inject the object through the constructor in the form . The injection method is as follows:

  • constructor type injection
	private readonly MD5Provider _provider1;
	private readonly MD5Provider _provider2;
	
	public IndexViewModel(MD5Provider provider1, MD5Provider provider2)
	{
    
    
	    _provider1 = provider1;
	    _provider2 = provider2;
    }
  • Container Parsing Object Injection
	private readonly MD5Provider _provider1;
	private readonly MD5Provider _provider2;
	private readonly IContainerExtension _container;
	
	public IndexViewModel(IContainerExtension container)
	{
    
    
	    _container = container;
	    _provider1 = container.Resolve<MD5Provider>();
	    _provider2 = container.Resolve<MD5Provider>();
	}

4.ViewModelLocator

4.1 Traditional View-ViewModel Association

​ In classic WPF, we usually use DataContext to specify the ViewModel bound to the View to establish a data connection. There are two common ways to specify it. Although this designation method can establish a View-ViewModel relationship, this method fixes the coding method of the relationship between View and ViewModel, which improves the coupling of the code to a certain extent, makes later maintenance difficult, and breaks our Development principles.

  • XAML configuration
<Window
    x:Class="WPF_MVVM.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:WPF_MVVM"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:vm="clr-namespace:WPF_MVVM.ViewModel"
    Title="MainWindow" Width="300" Height="150"
    DataContext="{x:Static vm:MainWindowViewModel.Instance}"
    mc:Ignorable="d">
    
    <!--或者单独声明
    <Window.DataContext>
        <x:Static vm:MainWindowViewModel.Instance/>
 	</Window.DataContext>
	-->

</Window>
  • code configuration
namespace WPF_Demo
{
    
    

    public partial class MainWindow : Window
    {
    
    
        public MainWindow()
        {
    
    
            InitializeComponent();
			//声明本Window的上下文DataContext
            this.DataContext = null;
        }
    }
}

4.2 Prism ViewModelLocator Association

​ ViewModelLocator is a "view model locator" provided by the Prism framework. ViewModelLocator can name the association rules according to the standard we specified, and automatically establish the corresponding View-ViewModel relationship through DataContext when the project program is initialized, without manual specification. On the one hand, this reduces the complexity of our code development; on the other hand, it no longer explicitly binds the DataContext, thereby reducing code coupling. Its use steps are as follows:

  • In View, the statement allows the current View to automatically assemble the ViewModel, that is, set ViewModelLocator.AutoWireViewModel = True
<Window x:Class="Demo.Views.MainWindow"
    ...
    xmlns:prism="http://prismlibrary.com/"
    prism:ViewModelLocator.AutoWireViewModel="True">
  • Place and name our View and ViewModel according to the naming association rules

​ When Prism is initialized, when the View's AutoWireViewModel is set to True, the ViewModelLocationProvider class will automatically call the AutoWireViewModelChanged method to search and parse its corresponding ViewModel according to the rules we made, and establish a DataContext relationship. Its parsing sequence is as follows:

  • First parse the ViewModel manually registered by the user through the ViewModelLocationProvider.Register method
  • If the first step of parsing fails, continue to search and parse the ViewModel through basic convention rules (default rules, custom rules)

(1) Default naming rules

​ The default naming rules of Prism ViewModelLocator are as follows:

  • View and ViewModel are in the same assembly

  • View is placed in the corresponding .Views sub-namespace, and ViewModel is placed in the corresponding .ViewModels sub-namespace (for example, abViews corresponds to abViewModels). Note that the parent space must also be consistent

  • The class name of the ViewModel must correspond to the name of the View and end with "ViewModel" (such as ViewA -> ViewAViewModel, MainView -> MainViewModel)

prism

Note: a.Views+a.ViewModels and bcViews+bcViewModels can coexist without conflict, as long as the correspondingly named ViewModel can be found under the corresponding assembly package, each View will be parsed according to the default naming rules when it is initialized Corresponds to the ViewModel under the package.

(2) Custom naming rules

​ If you don't want to follow ViewModelLocator's default naming convention, we can customize the naming convention override, and ViewModelLocator will associate View to ViewModel according to the custom convention. ViewModelLocationProviderThe class provides a static method SetDefaultViewTypeToViewModelTypeResolverthat can be used to set its own naming association rules. We need to App.xaml.csoverride the method in the inherited PrismApplication class ConfigureViewModelLocatorand then ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolverprovide custom naming convention logic in the method.

protected override void ConfigureViewModelLocator()
{
    
    
    base.ConfigureViewModelLocator();
	//自定义命名规则
    ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver((viewType) =>
    {
    
    
        var viewName = viewType.FullName.Replace(".Views.", ".CustomNamespace.");
        //视图View全限定名称:viewType.FullName = PrismDemo.Views.HomePage
        //自定义Model存放前缀:Replace = PrismDemo.CustomNamespace.HomePage
        var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;
        //程序集名称:PrismDemo, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
        var viewModelName = $"{
      
      viewName}ViewModel, {
      
      viewAssemblyName}";
        //自定义Model映射:PrismDemo.CustomNamespace.HomePageViewModel, PrismDemo, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
        return Type.GetType(viewModelName);
        //返回当前视图View的映射关系
    });
}

​ When each View page is initialized, if the current View is set prism:ViewModelLocator.AutoWireViewModel="True", the rewritten ConfigureViewModelLocatormethod will be called for each initialized View, and the corresponding ViewModel position will be parsed and associated according to our custom mapping rules above.

(3) Custom registration

ViewModelLocator​ If we follow the default naming convention in most cases in project development , but in a few cases there are some View-ViewModel associations that do not conform to the default convention. At this point we can use ViewModelLocationProvider.Registermethods to directly register the mapping of the ViewModel to a specific view. At this time, when Prism is initialized, it will first parse the ViewModel manually registered by the user through the ViewModelLocationProvider.Register method, and then go to the default rule for parsing if it cannot find it. Manually specifying the View-ViewModel mapping is faster and more efficient.

protected override void ConfigureViewModelLocator()
{
    
    
    base.ConfigureViewModelLocator();
    // Type / Type
	ViewModelLocationProvider.Register(typeof(MainWindow).ToString(), typeof(CustomViewModel));
	// Type / Factory
	ViewModelLocationProvider.Register(typeof(MainWindow).ToString(), () => Container.Resolve<CustomViewModel>());
	// 通用工厂
	ViewModelLocationProvider.Register<MainWindow>(() => Container.Resolve<CustomViewModel>());
	// 通用类型
	ViewModelLocationProvider.Register<MainWindow, CustomViewModel>();
}

Guess you like

Origin blog.csdn.net/qq_40772692/article/details/127576247