6

MVVM パターンに従う WPF アプリケーションを開発しています。モーダルダイアログを表示するには、次の記事が示唆する方法に従おうとしています。 http://www.codeproject.com/Articles/36745/Showing-Dialogs-When-Using-the-MVVM-Pattern?fid=1541292&fr=26#xx0xx

しかし、この種の記事では、DialogService インターフェイスの ShowDialog メソッドが MainWindowViewModel から呼び出されていることがわかりました。

私のアプリケーションの状況は少し異なります。MainWindow.xaml には、ボタン Add を含む ChildView などのユーザー コントロールが含まれています。MainWindowViewModel には、ChildView にバインドされている ChildVM などの別の ViewModel が含まれています。ChildVM には AddCommand が含まれており、AddCommand に対応する AddExecute メソッドが呼び出されたときに Modal Dialog を表示する必要があります。どうすればそれを達成できますか?

編集されたコード

     private Window FindOwnerWindow(object viewModel)
            {
                    FrameworkElement view = null;

        // Windows and UserControls are registered as view.
        // So all the active windows and userControls are contained in views
        foreach (FrameworkElement viewIterator in views)
        {
            // Check whether the view is an Window
            // If the view is an window and dataContext of the window, matches
            // with the viewModel, then set view = viewIterator
            Window viewWindow = viewIterator as Window;
            if (null != viewWindow)
            {
                if (true == ReferenceEquals(viewWindow.DataContext, viewModel))
                {
                    view = viewWindow;
                    break;
                }

            }
            else
            {
                // Check whether the view is an UserControl
                // If the view is an UserControl and Content of the userControl, matches
                // with the viewModel, then set view = userControl
                // In case the view is an user control, then find the Window that contains the
                // user control and set it as owner
                System.Windows.Controls.UserControl userControl = viewIterator as System.Windows.Controls.UserControl;
                if (null != userControl)
                {
                    if (true == ReferenceEquals(userControl.Content, viewModel))
                    {
                        view = userControl;
                        break;
                    }

                }
            }
        }
        if (view == null)
        {
            throw new ArgumentException("Viewmodel is not referenced by any registered View.");
        }

        // Get owner window
        Window owner = view as Window;
        if (owner == null)
        {
            owner = Window.GetWindow(view);
        }

        // Make sure owner window was found
        if (owner == null)
        {
            throw new InvalidOperationException("View is not contained within a Window.");
        }

        return owner;
        }
4

2 に答える 2

4

わかりました。正しければ、MainWindowViewModelからではなく、別のChildViewModelからモーダルダイアログを開きたいですか?

リンクしたCodeProject記事のMainWindowViewModelのコンストラクターを見てください。

ViewModelには、次の署名を持つコンストラクターがあります。

public MainWindowViewModel(
            IDialogService dialogService,
            IPersonService personService,
            Func<IOpenFileDialog> openFileDialogFactory)

つまり、構築には、モーダルダイアログを表示するサービス、ここでは関係のない別のサービス(personService)、およびファイルを開くための特定のダイアログのファクトリopenFileDialogFactoryが必要です。

記事のコア部分であるサービスを使用するために、単純なServiceLocatorが実装され、デフォルトのコンストラクターが定義されます。これは、ServiceLocatorを使用してViewModelが必要とするサービスのインスタンスを取得します。

public MainWindowViewModel()
            : this(
            ServiceLocator.Resolve<IDialogService>(),
            ServiceLocator.Resolve<IPersonService>(),
            () => ServiceLocator.Resolve<IOpenFileDialog>())
        {}

ServiceLocatorは静的であるため、これが可能です。または、ServiceLocatorを使用して、コンストラクターでサービスのローカルフィールドを設定することもできます。上記のアプローチは、ServiceLocatorを使用したくない場合に、自分でサービスを設定できるため、より優れています。

独自のChildViewModelでもまったく同じことができます。

public ChildViewModel(IDialogService dialogService)
{
    _dialogService = dialogService;
}

デフォルトのコンストラクターを作成します。これは、ServiceLocatorから解決されたサービスインスタンスを使用して上記のコンストラクターを呼び出します。

public ChildViewModel() : this(ServiceLocator.Resolve<IDialogService>()) {}

そして今、あなたはこのようにあなたのChildViewModelのどこからでもサービスを使うことができます:

_dialogService.ShowDialog<WhateverDialog>(this, vmForDialog);

ビュー自体ではないビューの所有者ウィンドウを見つけるにはFindOwnerWindow、ウィンドウをビュー自体として期待するのではなく、DialogServiceのメソッドを変更してビューの親ウィンドウを見つける必要があります。VisualTreeHelperを使用してこれを行うことができます。

private Window FindOwnerWindow(object viewModel)
    {
        var view = views.SingleOrDefault(v => ReferenceEquals(v.DataContext, viewModel));

        if (view == null)
        {
            throw new ArgumentException("Viewmodel is not referenced by any registered View.");
        }

        DependencyObject owner = view;

        // Iterate through parents until a window is found, 
        // if the view is not a window itself
        while (!(owner is Window))
        {
            owner = VisualTreeHelper.GetParent(owner);
            if (owner == null) 
                throw new Exception("No window found owning the view.");
        }

        // Make sure owner window was found
        if (owner == null)
        {
            throw new InvalidOperationException("View is not contained within a Window.");
        }

        return (Window) owner;
    }

ただし、UserControlに添付プロパティを設定して、UserControlを登録する必要があります。

<UserControl x:Class="ChildView"
             ...
             Service:DialogService.IsRegisteredView="True">
   ...
 </UserControl>

私の知る限り、これは機能します。

追加情報:

同じことを実現するために、PRISMフレームワークを使用します。このフレームワークには、まさにこの種のデカップリング、制御の反転(IoC)および依存性注入(DI)のための多くの機能が付属しています。たぶん、あなたのためにもそれを見てみる価値があります。

お役に立てれば!

コメントを検討するために編集されました。

于 2013-03-05T14:23:21.443 に答える
0

このアイデアが気に入るかどうかを確認してください...私はCastle WindsorとPrismを使用しているため、マイレージは異なる場合がありますが、概念は別のMVVMとIoCと同じである必要があります.

モーダル ダイアログを開きたい MainViewModel.cs から始めます。

var view = ServiceLocator.Current.GetInstance<SomeDialogView>();
view.ShowDialog();

もちろん、 MainView.xaml で設定した内容は尊重されません

WindowStartupLocation="CenterOwner"

ドラット!

しかし、待ってください。ServiceLocator は MainView を提供してくれませんか?

var view = ServiceLocator.Current.GetInstance<SomeDialogView>();
view.Owner = ServiceLocator.Current.GetInstance<MainView>();  // sure, why not?
view.ShowDialog();

私のIoCはビューを「一時的な有効期間」を持つように登録するため、この行は私のIoC構成で例外をスローします。Castle Windsor では、これは各要求にまったく新しいインスタンスが提供されることを意味し、表示されていない新しいインスタンスではなく、 MainView インスタンス自体が必要です。

しかし、「一時的」であるすべてのビューから登録を変更するだけで

container.Register(Classes.FromAssemblyNamed("WhateverAssemblyTheyreIn")
  .InNamespace("WhateverNamespaceTheyreIn")
  .LifestyleTransient());

流暢な Less() と If() を使用して、もう少し識別力を高めます。

container.Register(Classes.FromAssemblyNamed("WhateverAssemblyTheyreIn")
  .InNamespace("WhateverNamespaceTheyreIn")
  .Unless(type => type == typeof(MainView))
  .LifestyleTransient());  // all as before but MainView.

container.Register(Classes.FromAssemblyNamed("WhateverAssemblyTheyreIn")
  .InNamespace("WhateverNamespaceTheyreIn")
  .If(type => type == typeof(MainView))
  .LifestyleSingleton());  // set MainView to singleton!

提供された MainView、私たちが望んでいたものです!

HTH

于 2015-03-22T03:03:20.027 に答える