18

ユーザーコントロールがあるとしましょう。ユーザー コントロールにはいくつかの子ウィンドウがあります。また、ユーザー コントロールのユーザーは、何らかの種類の子ウィンドウを閉じたいと考えています。ユーザー コントロール コード ビハインドには次のメソッドがあります。

public void CloseChildWindows(ChildWindowType type)
{
   ...
}

しかし、ビューに直接アクセスできないため、このメソッドを呼び出すことはできません。

私が考えている別の解決策は、何らかの方法でユーザー コントロール ViewModel をそのプロパティの 1 つとして公開することです (そのため、それをバインドして ViewModel に直接コマンドを与えることができます)。しかし、ユーザー コントロールのユーザーには、ユーザー コントロールの ViewModel について何も知られたくありません。

では、この問題を解決する正しい方法は何ですか?

4

5 に答える 5

46

この問題に対するかなり優れたMVVMソリューションを見つけたと思います。WindowTypeタイププロパティとブールプロパティを公開する動作を記述しましたOpen。後者のデータバインディングを使用すると、ViewModelは、ビューについて何も知らなくても、ウィンドウを簡単に開閉できます。

お奨めの行動が大好きです...:)

ここに画像の説明を入力してください

Xaml:

<UserControl x:Class="WpfApplication1.OpenCloseWindowDemo"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WpfApplication1"
             xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">

    <UserControl.DataContext>
        <local:ViewModel />
    </UserControl.DataContext>
    <i:Interaction.Behaviors>
        <!-- TwoWay binding is necessary, otherwise after user closed a window directly, it cannot be opened again -->
        <local:OpenCloseWindowBehavior WindowType="local:BlackWindow" Open="{Binding BlackOpen, Mode=TwoWay}" />
        <local:OpenCloseWindowBehavior WindowType="local:YellowWindow" Open="{Binding YellowOpen, Mode=TwoWay}" />
        <local:OpenCloseWindowBehavior WindowType="local:PurpleWindow" Open="{Binding PurpleOpen, Mode=TwoWay}" />
    </i:Interaction.Behaviors>
    <UserControl.Resources>
        <Thickness x:Key="StdMargin">5</Thickness>
        <Style TargetType="Button" >
            <Setter Property="MinWidth" Value="60" />
            <Setter Property="Margin" Value="{StaticResource StdMargin}" />
        </Style>
        <Style TargetType="Border" >
            <Setter Property="Margin" Value="{StaticResource StdMargin}" />
        </Style>
    </UserControl.Resources>

    <Grid>
        <StackPanel>
            <StackPanel Orientation="Horizontal">
                <Border Background="Black" Width="30" />
                <Button Content="Open" Command="{Binding OpenBlackCommand}" CommandParameter="True" />
                <Button Content="Close" Command="{Binding OpenBlackCommand}" CommandParameter="False" />
            </StackPanel>
            <StackPanel Orientation="Horizontal">
                <Border Background="Yellow" Width="30" />
                <Button Content="Open" Command="{Binding OpenYellowCommand}" CommandParameter="True" />
                <Button Content="Close" Command="{Binding OpenYellowCommand}" CommandParameter="False" />
            </StackPanel>
            <StackPanel Orientation="Horizontal">
                <Border Background="Purple" Width="30" />
                <Button Content="Open" Command="{Binding OpenPurpleCommand}" CommandParameter="True" />
                <Button Content="Close" Command="{Binding OpenPurpleCommand}" CommandParameter="False" />
            </StackPanel>
        </StackPanel>
    </Grid>
</UserControl>

イエローウィンドウ(ブラック/パープル同様):

<Window x:Class="WpfApplication1.YellowWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="YellowWindow" Height="300" Width="300">
    <Grid Background="Yellow" />
</Window>

ViewModel、ActionCommand:

using System;
using System.ComponentModel;
using System.Windows.Input;

namespace WpfApplication1
{
    public class ViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        private bool _blackOpen;
        public bool BlackOpen { get { return _blackOpen; } set { _blackOpen = value; OnPropertyChanged("BlackOpen"); } }

        private bool _yellowOpen;
        public bool YellowOpen { get { return _yellowOpen; } set { _yellowOpen = value; OnPropertyChanged("YellowOpen"); } }

        private bool _purpleOpen;
        public bool PurpleOpen { get { return _purpleOpen; } set { _purpleOpen = value; OnPropertyChanged("PurpleOpen"); } }

        public ICommand OpenBlackCommand { get; private set; }
        public ICommand OpenYellowCommand { get; private set; }
        public ICommand OpenPurpleCommand { get; private set; }


        public ViewModel()
        {
            this.OpenBlackCommand = new ActionCommand<bool>(OpenBlack);
            this.OpenYellowCommand = new ActionCommand<bool>(OpenYellow);
            this.OpenPurpleCommand = new ActionCommand<bool>(OpenPurple);
        }

        private void OpenBlack(bool open) { this.BlackOpen = open; }
        private void OpenYellow(bool open) { this.YellowOpen = open; }
        private void OpenPurple(bool open) { this.PurpleOpen = open; }

    }

    public class ActionCommand<T> : ICommand
    {
        public event EventHandler CanExecuteChanged;
        private Action<T> _action;

        public ActionCommand(Action<T> action)
        {
            _action = action;
        }

        public bool CanExecute(object parameter) { return true; }

        public void Execute(object parameter)
        {
            if (_action != null)
            {
                var castParameter = (T)Convert.ChangeType(parameter, typeof(T));
                _action(castParameter);
            }
        }
    }
}

OpenCloseWindowBehavior:

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;

namespace WpfApplication1
{
    public class OpenCloseWindowBehavior : Behavior<UserControl>
    {
        private Window _windowInstance;

        public Type WindowType { get { return (Type)GetValue(WindowTypeProperty); } set { SetValue(WindowTypeProperty, value); } }
        public static readonly DependencyProperty WindowTypeProperty = DependencyProperty.Register("WindowType", typeof(Type), typeof(OpenCloseWindowBehavior), new PropertyMetadata(null));

        public bool Open { get { return (bool)GetValue(OpenProperty); } set { SetValue(OpenProperty, value); } }
        public static readonly DependencyProperty OpenProperty = DependencyProperty.Register("Open", typeof(bool), typeof(OpenCloseWindowBehavior), new PropertyMetadata(false, OnOpenChanged));

        /// <summary>
        /// Opens or closes a window of type 'WindowType'.
        /// </summary>
        private static void OnOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var me = (OpenCloseWindowBehavior)d;
            if ((bool)e.NewValue)
            {
                object instance = Activator.CreateInstance(me.WindowType);
                if (instance is Window)
                {
                    Window window = (Window)instance;
                    window.Closing += (s, ev) => 
                    {
                        if (me.Open) // window closed directly by user
                        {
                            me._windowInstance = null; // prevents repeated Close call
                            me.Open = false; // set to false, so next time Open is set to true, OnOpenChanged is triggered again
                        }
                    }; 
                    window.Show();
                    me._windowInstance = window;
                }
                else
                {
                    // could check this already in PropertyChangedCallback of WindowType - but doesn't matter until someone actually tries to open it.
                    throw new ArgumentException(string.Format("Type '{0}' does not derive from System.Windows.Window.", me.WindowType));
                }
            }
            else 
            {
                if (me._windowInstance != null)
                    me._windowInstance.Close(); // closed by viewmodel
            }
        }
    }
}
于 2013-03-20T00:14:51.200 に答える
6

私は過去にこの種の状況に対処するために、その恐ろしい名前であるの概念を取り入れたので、WindowManager少しだけ恐ろしいものではないが、基本的な考え方は次のとおりです。WindowViewModel

public class WindowManager
{
    public WindowManager()
    {
        VisibleWindows = new ObservableCollection<WindowViewModel>();
        VisibleWindows.CollectionChanged += OnVisibleWindowsChanged;            
    }
    public ObservableCollection<WindowViewModel> VisibleWindows {get; private set;}
    private void OnVisibleWindowsChanged(object sender, NotifyCollectionChangedEventArgs args)
    {
        // process changes, close any removed windows, open any added windows, etc.
    }
}

public class WindowViewModel : INotifyPropertyChanged
{
    private bool _isOpen;
    private WindowManager _manager;
    public WindowViewModel(WindowManager manager)
    {
        _manager = manager;
    }
    public bool IsOpen 
    { 
        get { return _isOpen; } 
        set 
        {
            if(_isOpen && !value)
            {
                _manager.VisibleWindows.Remove(this);
            }
            if(value && !_isOpen)
            {
                _manager.VisibleWindows.Add(this);
            }
            _isOpen = value;
            OnPropertyChanged("IsOpen");
        }
    }    

    public event PropertyChangedEventHandler PropertyChanged = delegate {};
    private void OnPropertyChanged(string name)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(name));
    }
}

注:私はこれを非常に無計画に一緒に投げています。もちろん、このアイデアを特定のニーズに合わせて調整することもできます。

ただし、基本的な前提は、コマンドがWindowViewModelオブジェクトを処理し、フラグを適切に切り替え、IsOpenマネージャークラスが新しいウィンドウの開閉を処理できることです。これを行うには数十の可能な方法がありますが、過去にはピンチで機能していました(実際に実装され、私の電話で一緒に投げられなかった場合、つまり)

于 2013-03-19T20:12:24.600 に答える
5

純粋主義者にとって合理的な方法は、ナビゲーションを処理するサービスを作成することです。簡単な要約: NavigationService を作成し、NavigationService にビューを登録し、ビュー モデル内から NavigationService を使用してナビゲートします。

例:

class NavigationService
{
    private Window _a;

    public void RegisterViewA(Window a) { _a = a; }

    public void CloseWindowA() { a.Close(); }
}

NavigationService への参照を取得するには、その上に抽象化 (INavigationService など) を作成し、IoC を介して登録/取得できます。より適切には、2 つの抽象化を作成することもできます。1 つは登録メソッド (ビューで使用) を含み、もう 1 つはアクチュエータ (ビューモデルで使用) を含みます。

より詳細な例については、IoC に大きく依存する Gill Cleeren の実装を確認できます。

http://www.silverlightshow.net/video/Applied-MVVM-in-Win8-Webinar.aspx 00:36:30 開始

于 2013-03-21T13:10:21.613 に答える
4

これを実現する 1 つの方法は、view-model が子ウィンドウを閉じるように要求することです。

public class ExampleUserControl_ViewModel
{
    public Action ChildWindowsCloseRequested;

    ...
}

次に、ビューはそのビューモデルのイベントをサブスクライブし、起動されたときにウィンドウを閉じるようにします。

public class ExampleUserControl : UserControl
{
    public ExampleUserControl()
    {
        var viewModel = new ExampleUserControl_ViewModel();
        viewModel.ChildWindowsCloseRequested += OnChildWindowsCloseRequested;

        DataContext = viewModel;
    }

    private void OnChildWindowsCloseRequested()
    {
        // ... close child windows
    }

    ...
}

したがって、ここでビューモデルは、ビューの知識がなくても子ウィンドウを確実に閉じることができます。

于 2013-03-17T19:47:18.760 に答える
2

この質問に対するほとんどの回答には、ViewModel によって制御される状態変数が含まれており、View はこの変数の変更に基づいて動作します。これは、ウィンドウを開いたり閉じたり、単にコントロールを表示したり隠したりするようなステートフル コマンドに適しています。ただし、ステートレス イベント コマンドではうまく機能しません。信号の立ち上がりエッジで何らかのアクションをトリガーすることはできますが、信号を再度 Low (false) に設定する必要があります。

この問題を解決するViewCommand パターンに関する記事を書きました。これは基本的に、ビューから現在のビューモデルに移動する通常のコマンドの逆方向です。これには、現在接続されているすべてのビューにコマンドを送信するために各ビューモデルが実装できるインターフェイスが含まれます。View は、その DataContext プロパティが変更されたときに、割り当てられた各 ViewModel に登録するように拡張できます。この登録により、ViewModel のビューのリストにビューが追加されます。ViewModel がビューでコマンドを実行する必要がある場合は常に、登録されているすべてのビューを調べて、存在する場合はそれらに対してコマンドを実行します。これはリフレクションを利用して View クラスの ViewCommand メソッドを見つけますが、Binding は反対方向に行います。

View クラスの ViewCommand メソッド:

public partial class TextItemView : UserControl
{
    [ViewCommand]
    public void FocusText()
    {
        MyTextBox.Focus();
    }
}

これは ViewModel から呼び出されます。

private void OnAddText()
{
    ViewCommandManager.Invoke("FocusText");
}

この記事は、私の Web サイトとCodeProjectの古いバージョンで入手できます。

含まれているコード (BSD ライセンス) は、コードの難読化中にメソッドの名前を変更できるようにする手段を提供します。

于 2015-09-08T07:29:00.387 に答える