5

私はPrismにかなり慣れていません。現在、概念実証プロジェクトとしてPrismを使用して、既存のアプリケーションの1つを書き直しています。

アプリケーションは、ViewModelの最初のアプローチでMVVMを使用します。ViewModelはコンテナーによって解決され、IViewResolverサービスは、(特に名前の規則を使用して)どのビューに接続する必要があるかを判断します。

現時点でのコード(タブコントロールにビューを追加するため)は次のようになります。

var vm = (get ViewModel from somewhere)
IRegion reg = _regionManager.Regions["MainRegion"];
var vw = _viewResolver.FromViewModel(vm); // Spins up a view and sets its DataContext
reg.Add(vw);
reg.Activate(vw);

これはすべて正常に機能しますが、Prismナビゲーションフレームワークを使用してこれらすべてのことを実行し、次のようなことができるようにしたいと思います。

_regionManager.RequestNavigate(
    "MainRegion", 
    new Uri("NameOfMyViewModel", UriKind.Relative)
);

そして、PrismにViewModel + Viewを起動させ、DataContextを設定して、ビューを領域に挿入します。

ViewModelタイプを参照するDataTemplatesを作成することで、ある程度の成功を収めました。例:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Module01">
<DataTemplate DataType="{x:Type local:TestViewModel}">
<local:TestView />
</DataTemplate>
</ResourceDictionary>

...そして、モジュールが初期化されるときに、関連するリソースディクショナリをアプリケーションリソースに追加するようにモジュールに指示しますが、それは少しごみのようです。

Prismからビューの作成を効果的に引き継ぐ方法はありますか?そうすれば、RequestNavigate呼び出されたときに、提供されたものを見て、Uriそれに基づいてビュー/ビューモデルを起動できますか?あなたが自分でビューを提供することを可能にするデリゲートを必要とする過負荷がありRegionManager.RegisterViewWithRegionます、そして私はそのようなものを求めていると思います。

私は自分IRegionBehaviorFactoryで提供する必要があるかもしれないと思いますが、何が関係しているのかわかりません(または私が正しい道を進んでいるとしても!)。

助けていただければ幸いです。

--注:元々はプリズムコードプレックスサイトに投稿されました

4

2 に答える 2

8

確かにあなたはそれを行うことができます。プラグを差し込む場所を知っているだけであれば、Prismv4は本当に拡張可能であることがわかりました。

この場合、独自のカスタム実装が必要ですIRegionNavigationContentLoader

ブートストラッパーで設定する方法は次のとおりです(例は、UnityBootstrapper私自身のプロジェクトの1つのサブクラスからのものです)。

protected override void ConfigureContainer()
{
    // IMPORTANT: Due to the inner workings of UnityBootstrapper, accessing
    // ServiceLocator.Current here will throw an exception!
    // If you want access to IServiceLocator, resolve it from the container directly.
    base.ConfigureContainer();

    // Set up our own content loader, passing it a reference to the service locator
    // (it will need this to resolve ViewModels from the container automatically)
    this.Container.RegisterInstance<IRegionNavigationContentLoader>(
       new ViewModelContentLoader(this.Container.Resolve<IServiceLocator>()));
}

それViewModelContentLoader自体はコードを再利用するために派生しRegionNavigationContentLoader、次のようになります。

public class ViewModelContentLoader : RegionNavigationContentLoader
{
    private readonly IServiceLocator serviceLocator;

    public ViewModelContentLoader(IServiceLocator serviceLocator)
        : base(serviceLocator)
    {
        this.serviceLocator = serviceLocator;
    }

    // THIS IS CALLED WHEN A NEW VIEW NEEDS TO BE CREATED
    // TO SATISFY A NAVIGATION REQUEST
    protected override object CreateNewRegionItem(string candidateTargetContract)
    {
        // candidateTargetContract is e.g. "NameOfMyViewModel"

        // Just a suggestion, plug in your own resolution code as you see fit
        var viewModelType = this.GetTypeFromName(candidateTargetContract);
        var viewModel = this.serviceLocator.GetInstance(viewModelType);

        // get ref to viewResolver somehow -- perhaps from the container?
        var view = _viewResolver.FromViewModel(vm);

        return view;
    }

    // THIS IS CALLED TO DETERMINE IF THERE IS ANY EXISTING VIEW
    // THAT CAN SATISFY A NAVIGATION REQUEST
    protected override IEnumerable<object> 
    GetCandidatesFromRegion(IRegion region, string candidateNavigationContract)
    {
        if (region == null) {
            throw new ArgumentNullException("region");
        }

        // Just a suggestion, plug in your own resolution code as you see fit
        var viewModelType = this.GetTypeFromName(candidateNavigationContract);

        return region.Views.Where(v =>
            ViewHasDataContract((FrameworkElement)v, viewModelType) ||
            string.Equals(v.GetType().Name, candidateNavigationContract, StringComparison.Ordinal) ||
            string.Equals(v.GetType().FullName, candidateNavigationContract, StringComparison.Ordinal));
    }

    // USED IN MY IMPLEMENTATION OF GetCandidatesFromRegion
    private static bool 
    ViewHasDataContract(FrameworkElement view, Type viewModelType)
    {
        var dataContextType = view.DataContext.GetType();

        return viewModelType.IsInterface
           ? dataContextType.Implements(viewModelType)
           : dataContextType == viewModelType 
                   || dataContextType.GetAncestors().Any(t => t == viewModelType);
    }

    // USED TO MAP STRINGS OF VIEWMODEL TYPE NAMES TO ACTUAL TYPES
    private Type GetTypeFromName(string typeName)
    {
        // here you need to map the string type to a Type object, e.g.
        // "NameOfMyViewModel" => typeof(NameOfMyViewModel)

        return typeof(NameOfMyViewModel); // hardcoded for simplicity
    }
}
于 2011-09-18T16:14:23.253 に答える
2

「ViewModelファーストアプローチ」に関する混乱を防ぐには、「コントローラーアプローチ」を使用しますが、「ViewModelファーストアプローチ」は使用しません。「ViewModelの最初のアプローチ」とは、ViewModelにViewを挿入するが、サードパーティのコンポーネント(コントローラー)を介してViewModelとViewの両方を接続する場合です。 「最良」ですが、)最も緩く結合されたアプローチ。

しかし、あなたの質問に答えるには:考えられる解決策は、上記で説明したことを正確に実行するPrismRegionManagerの拡張機能を作成することです。

    public static class RegionManagerExtensions
    {            
        public static void AddToRegion<TViewModel>(
               this IRegionManager regionManager, string region)
        {
            var viewModel = ServiceLocator.Current.GetInstance<TViewModel>();
            FrameworkElement view;

            // Get View depending on your conventions

            if (view == null) throw new NullReferenceException("View not found.");

            view.DataContext = viewModel;
            regionManager.AddToRegion(region, view);
            regionManager.Regions[region].Activate(view);

        }
    }

次に、このメソッドを次のように呼び出すことができます。

regionManager.AddToRegion<IMyViewModel>("MyRegion");
于 2011-09-17T12:28:30.540 に答える