2013-03-17 6 views
13

私はユーザーコントロールを持っているとしましょう。ユーザーコントロールにはいくつかの子ウィンドウがあります。また、ユーザーコントロールユーザーは、いくつかの種類の子ウィンドウを閉じたいと考えています。後ろにユーザコントロールコードのメソッドがあります:MVVMで表示するコマンドを与えてください

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

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

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

この問題を解決する正しい方法は何ですか?ビューモデルは、子ウィンドウが閉じられなければならないことを要求するためにこれを達成するために

答えて

2

一つの方法は、次のようになります。

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 
    } 

    ... 
} 

だからここにビューモデルは、子ウィンドウがビューのいずれかの知識がなくても閉じていることを確認することができます。

+3

UserControlのDataContextをViewModelに設定して、ViewModelパブリックプロパティを削除することもできます。これは、イベント登録時にいくつかのキャストが必要ですが、MVVMでは、ViewModelにUserControl.DataContextを設定する必要があるため、良い方法です。また、ChildWindowsCloseRequestedが呼び出す前にnullでないことを確認してください。そうしないと例外が発生します。 –

+0

真実、私は私の答えを更新します、歓声。 –

4

私はそれのために恐ろしい名前です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フラグを適切に切り替え、マネージャークラスは新しいウィンドウの開閉を処理するという基本的な前提があります。 の可能性がありますこれを行う方法が、過去に私のためにピンチで働いています(実際に実装され、私の携帯電話で一緒に投げられなかった場合)

31

私はちょっといいこの問題に対するMVVMの解決策。私は型のプロパティWindowTypeとブール型のプロパティOpenを公開している動作を書いています。 DataBindingを使用すると、ビューについて何も知らなくても、ViewModelでウィンドウを簡単に開閉できます。

好きな行動...:)

enter image description here

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> 

YellowWindow(ブラック/パープル問わず):

<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に、アクションコマンド:

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); 
      } 
     } 
    } 
} 

オープンCloseWindowBehavior:

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 
      } 
     } 
    } 
} 
+0

ああ、私は行動が大好き... – JerKimball

+0

行動のために+1 – chrisw

+0

@adabyron、あなたは答えをダウンロード可能なソースコードとして教えてください。 – RobinAtTech

4

あなたのナビゲーションを処理するサービスを作成するのは合理的な方法です。簡単な概要:NavigationServiceを作成し、NavigationServiceでビューを登録し、ビューモデル内からNavigationServiceを使用してナビゲートします。

例:

class NavigationService 
{ 
    private Window _a; 

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

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

あなたはそれの上に(すなわちINavigationService)の抽象化を行い、それがIoCを経由して取得/登録することができのNavigationServiceへの参照を取得します。より適切には、2つの抽象化(ビューで使用される)と、ビューモデルで使用されるアクチュエータを含む1つの抽象化を作成することもできます。あなたが頻繁にのIoCに依存ギルCleerenの実装をチェックアウトすることができ、より詳細な例については

http://www.silverlightshow.net/video/Applied-MVVM-in-Win8-Webinar.aspxはこの質問への回答のほとんどは、その状態変数を伴う午後12時36分30秒

1

で始まりますViewModelによって制御され、この変数への変更にViewが作用します。 ステートフルコマンドは、ウィンドウの開閉や、一部のコントロールの表示や非表示に便利です。しかし、ステートレスイベントコマンドでもうまくいきません。信号の立ち上がりで何らかの動作をトリガすることができますが、信号を再びLow(false)に設定する必要があります。そうしないと、再度トリガしません。

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

ViewクラスでVIEWコマンド方法:

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

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

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

記事はon my website利用可能と古いバージョンon CodeProjectです。

コード(BSDライセンス)は、コードの難読化中に名前を変更するメソッドを許可するための手段を提供します。

関連する問題