参照文書:
- Prism 公式ドキュメントPrism ライブラリ
- Prism GitHub アドレスGitHub - PrismLibrary/Prism
- 兄貴ブログプリズムコレクション - エッセイカテゴリー - トレースg - ブログガーデン (cnblogs.com)
- Big Brother ブログPrism Framework シリーズ ノート
1. Prism フレームワークの基本機能
1.バインド可能なベース
前の記事では、MVVM フレームワークのフロントエンド ビューにロジック/データ バインディング サポートを提供し、バックエンド データが UI データの変更を通知できるようにするために、INotifyPropertyChanged インターフェイスを手動で実装し、カプセル化しました。これを ViewModelBase基本クラスとして扱います。
Prism フレームワークでは、Prism は WPF のバインディング通知機能を拡張します。INotifyPropertyChanged インターフェイスを実装し、カプセル化した基本クラスBindableBaseを提供します。そして、CallerMemberName機能を利用して属性名を自動的に取得することで、属性変更イベントの呼び出しが煩わしい問題を解決すると同時に、等しい値のフィルタリングなどの操作をメソッド内で実行しています。ソースコードは以下のとおりです。
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);
}
}
}
データのバインディング通知を行う必要がある場合、BindableBase クラスを直接継承するだけで済みます。属性更新の通知方法には次の 2 種類があります。
- 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.デリゲートコマンド
前の記事では、MVVM フレームワークでフロント エンドとバック エンドの論理的分離を実現するために、ICommand インターフェイスを手動で実装し、フロントとバックの間でロジック/動作を転送するためにカスタム コマンドを RelayCommand<T> としてカプセル化しました。終わります。Prism フレームワークでは、Prism はコマンド ベース クラスDelegateCommandBaseと、 ICommand インターフェイスを実装してカプセル化したそのサブクラスDelegateCommandを提供します。これには、コマンド クラスの手動実装の基本機能がすべて含まれているだけでなく、いくつかの新機能も拡張されています。コマンドの呼び出し方法を簡略化しました。
2.1 DelegateCommand のソース コード
DelegateCommand クラスの他の定義は、従来のカプセル化された ICommand の実装とあまり変わりません。重要なのは、Expression パラメーターを持つ 2 つのメソッド、ObservesProperty と ObservesCanExecute がこのクラスに追加されていることです。どちらのメソッドも内部的に呼び出されます。 PropertyInternal を観察します。では、これら 2 つの方法は何をするのでしょうか? 前の記事WPF (6) コマンド コマンド モデルのソース コード分析では、ICommand のコマンド モデルを分析しました。カスタム コマンドがコマンド状態のリアルタイム更新を実現したい場合は、CanExecuteChanged イベントを手動でトリガーするか、CommandManager を使用する必要があります。 DelegateCommand では、ObservesProperty と ObservesCanExecute の 2 つのメソッドをカプセル化することで、ターゲット プロパティ値の変更を監視し、自動的に CanExecuteChanged イベントをトリガーし、コマンド ステータスを更新する、より柔軟なメソッドが使用されます。
//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 このメソッドは、現在の Expression パラメータを HashSet の _observedPropertiesExpressions 型のローカル変数に渡します。変数が現在のコレクションに存在する場合、追加の繰り返しを避けるために例外がスローされます。追加されていない場合は、追加されます現在のコレクションへ。メンテナンス中。次に、それをコレクションに追加した後、新しいメソッドが呼び出されます。この PropertyObserver.Observes メソッドは、現在の DelegateCommandBase の RaiseCanExecuteChanged メソッドをパラメータとして PropertyObserver クラスの Observes メソッドに渡します。名前が示すように、PropertyObserver はメソッドですPropery プロパティの変更を監視するクラス。
Expression を通じて、PropertyObserver.Observes() メソッドが内部的に呼び出され、RaiseCanExecuteChaned メソッドがデリゲートとして渡されます。Expression は PropertyObserver のリンク リストに保存され、各ノードは OnPropertyChanged イベントをサブスクライブします。このようにして、プロパティ Expression が変更されると、現在の RaiseCanExecuteChanged デリゲートを呼び出すことができ、最終的にコマンドの CanExecuteChanged イベントをトリガーして、コマンド ステータスを更新するという目的を達成できます。
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 DelegateCommandの使用
(1) 基本的な使い方
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) 実行可能チェック
- RaiseCanExecuteChanged 手動更新
プロパティ値が変更されると、RaiseCanExecuteChanged の呼び出しを通じて CanExecuteChanged イベントを手動でトリガーし、CanExecute 状態を更新します
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 プロパティの監視
ObservesProperty は、ターゲット プロパティ値の変化を監視するために使用されます。監視対象のプロパティ値が変化すると、自動的に状態を確認し、RaiseCanExecuteChangedメソッドを実行し、CanExecuteChangedイベントをトリガーし、ICommandのCanExecuteデリゲートを実行して、CanExecute状態を更新します。ObservesProperty は複数の条件チェックもサポートしています。
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 リスナー
ObservesCanExecute は、ターゲットのプロパティを監視するためにも使用されます。ただし、ObservesProperty とは異なり、ObservesCanExecute は CheckExecute デリゲート検査メソッドを指定する必要はありませんが、コマンドの使用可能なステータスをターゲット プロパティ値に直接バインドします。ターゲット プロパティ値が true になると、コマンドは使用可能になります。それ以外の場合は、コマンドは使用できなくなります。 、ObservesCanExecute はターゲット プロパティをバインドします。 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. コンテナーと依存関係の注入
Spring と同様に、Prism フレームワークのコア コンポーネントはコンテナと依存関係の注入です。つまり、コンテナはフレームワーク全体のすべてのクラスのオブジェクトとそのライフサイクルを管理するために使用され、参照する場合、インターフェイス フレームワークを挿入するだけで、インターフェイスの種類に応じて特定のインスタンスを自動的に見つけることができ、コストを節約できます。新しい操作では、プログラムの不安定な部分を分離して 1 か所で管理し、ソフトウェア設計プロセスでIOC コンテナ、ソフトウェア設計の柔軟性と拡張性。PrismApplication のソースコードの一部は次のとおりです。
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 界面解析
Prism IOC コンテナに関連するいくつかの基本的なインターフェイスは Prism.Core で定義されており、そのクラス図の構造は次のとおりです。
- IContainerRegistry インターフェイス: このインターフェイスの機能は主に、一連のタイプと対応するエンティティをコンテナーに挿入することです (さまざまなライフ サイクル タイプを含む)。さらに、このインターフェイスは、オブジェクトが登録されているかどうかを判断するための IsRegistered メソッドも定義します。 。
- IContainerProvider インターフェイス: このインターフェイスは、IContainerRegistryインターフェイスの正反対であり、主に、タイプまたは名前によってコンテナから以前に登録/管理されているオブジェクトを取得します。
- ContainerLocator : ContainerLocator は Prism.Core で定義された静的クラスで、グローバルに一意な IContainerExtension オブジェクトを定義/維持するために使用されます。
- IContainerExtension : このインターフェイスは、IContainerRegistry と IContainerProvider の 2 つのインターフェイスを同時に継承します。これは、継承されたオブジェクトが型オブジェクトを登録する機能と型オブジェクトを解析する機能の両方を備えていること、および一部のコンテナ終了操作用の FinalizeExtension メソッドも定義していることを示します。
3.2 カスタムサービスの登録と挿入
カスタム サービス/コンポーネントの場合、App.xaml.csで継承されたPrismApplicationBaseクラスのRegisterTypesメソッドをオーバーライドし、IContainerRegistry.Register一連のメソッドを通じてコンポーネントをコンテナに登録できます。IContainerRegistry は、さまざまなライフサイクル タイプに応じて、さまざまな基本的な登録方法 (一時登録、シングルトン登録など)を提供します。
(1)一時登録
一時的な登録では、登録するたびに新しいインスタンスが作成されます。つまり、マルチインスタンス モードです。次のように使用されます。
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)シングルトン登録
シングルトン登録では、アプリケーションのライフサイクル全体、つまりシングルトン モードでインスタンスが 1 つだけ作成され、インスタンスは使用されるときにのみ作成されます。次のように使用されます。
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) 注射と使用
上記の内容を使用すると、フォームのコンストラクターを通じてオブジェクトを直接注入できます。注入方法は次のとおりです。
- コンストラクター型のインジェクション
private readonly MD5Provider _provider1;
private readonly MD5Provider _provider2;
public IndexViewModel(MD5Provider provider1, MD5Provider provider2)
{
_provider1 = provider1;
_provider2 = provider2;
}
- コンテナ解析オブジェクトのインジェクション
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 従来のビューとビューモデルの関連付け
クラシック WPF では、通常、DataContext を使用して View にバインドされた ViewModel を指定し、データ接続を確立します。これを指定するには、2 つの一般的な方法があります。この指定方法では View と ViewModel の関係を確立できますが、この方法では View と ViewModel の関係のコーディング方法が固定されるため、コードの結合がある程度改善され、後のメンテナンスが困難になり、開発原則が崩れます。
- XAML 構成
<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>
- コード構成
namespace WPF_Demo
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
//声明本Window的上下文DataContext
this.DataContext = null;
}
}
}
4.2 Prism ViewModelLocator の関連付け
ViewModelLocator は、Prism フレームワークによって提供される「ビューモデル ロケーター」です。ViewModelLocator は、指定した標準に従って関連付けルールに名前を付けることができ、プロジェクト プログラムの初期化時に、手動で指定することなく、DataContext を通じて対応する View-ViewModel 関係を自動的に確立できます。これにより、コード開発の複雑さが軽減される一方で、DataContext が明示的にバインドされなくなるため、コードの結合が軽減されます。その使用手順は次のとおりです。
- View では、このステートメントにより、現在の View が ViewModel を自動的に組み立てることができます。つまり、ViewModelLocator.AutoWireViewModel = True に設定されます。
<Window x:Class="Demo.Views.MainWindow" ... xmlns:prism="http://prismlibrary.com/" prism:ViewModelLocator.AutoWireViewModel="True">
- 命名規則に従って View と ViewModel を配置し、名前を付けます。
Prism が初期化されるとき、View の AutoWireViewModel が True に設定されると、ViewModelLocationProvider クラスは自動的に AutoWireViewModelChanged メソッドを呼び出し、作成したルールに従って対応する ViewModel を検索および解析し、DataContext 関係を確立します。その解析シーケンスは次のとおりです。
- まず、ViewModelLocationProvider.Register メソッドを通じてユーザーが手動で登録した ViewModel を解析します。
- 解析の最初のステップが失敗した場合は、基本的な規則ルール (デフォルト ルール、カスタム ルール) に従って ViewModel の検索と解析を続けます。
(1) デフォルトの命名規則
Prism ViewModelLocator のデフォルトの命名規則は次のとおりです。
View と ViewModel は同じアセンブリ内にあります
View は対応する.Viewsサブ名前空間に配置され、ViewModel は対応する.ViewModelsサブ名前空間に配置されます (たとえば、abViews は abViewModels に対応します)。親空間も一貫している必要があることに注意してください。
ViewModel のクラス名は、ビューの名前に対応し、「ViewModel」で終わる必要があります (ViewA -> ViewAViewModel、MainView -> MainViewModel など)。
注: a.Views+a.ViewModels と bcViews+bcViewModels は、対応する名前の ViewModel が対応するアセンブリ パッケージの下にある限り、競合することなく共存できます。各 View は、初期化時にデフォルトの命名規則に従って解析されます。パッケージの下の ViewModel に追加します。
(2) カスタム命名規則
ViewModelLocator のデフォルトの命名規則に従いたくない場合は、命名規則のオーバーライドをカスタマイズでき、ViewModelLocator はカスタム規則に従って View を ViewModel に関連付けます。このクラスは、独自の名前付け関連付けルールを設定するために使用できるViewModelLocationProvider
静的メソッドを提供します。継承された PrismApplication クラスのメソッドをオーバーライドしてから、メソッドにカスタム命名規則ロジックを提供するSetDefaultViewTypeToViewModelTypeResolver
必要があります。App.xaml.cs
ConfigureViewModelLocator
ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver
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的映射关系
});
}
各ビュー ページが初期化されるとき、現在のビューが設定されている場合prism:ViewModelLocator.AutoWireViewModel="True"
、ConfigureViewModelLocator
初期化されたビューごとに書き換えられたメソッドが呼び出され、対応する ViewModel の位置が解析され、上記のカスタム マッピング ルールに従って関連付けられます。
(3) カスタム登録
プロジェクト開発ではほとんどの場合、ViewModelLocator
デフォルトの命名規則に従いますが、場合によっては、デフォルトの規則に準拠しないビューとビューモデルの関連付けがいくつか存在します。この時点で、ViewModelLocationProvider.Register
メソッドを使用して ViewModel の特定のビューへのマッピングを直接登録できます。現時点では、Prism が初期化されると、まず ViewModelLocationProvider.Register メソッドを通じてユーザーが手動で登録した ViewModel を解析し、それが見つからない場合はデフォルトの解析ルールに進みます。View-ViewModel マッピングを手動で指定する方が高速かつ効率的です。
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>();
}