2013-03-17 72 views
13

讓我們想象我有一些用戶控制。用戶控件有一些子窗口。而用戶控制用戶想要關閉某種類型的子窗口。有一個在後面的用戶控件代碼的方法:給一些MVVM中的視圖命令

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

但我不能調用此方法,因爲我沒有看法的直接訪問。

我想到的另一個解決方案是以某種方式將用戶控件ViewModel作爲其屬性之一(這樣我可以將它綁定並直接給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中,您需要將UserControl.DataContext設置爲ViewModel。此外,請務必在調用ChildWindowsCloseRequested之前執行一些驗證,否則您將收到異常。 – 2013-03-19 19:55:06

+0

沒錯,我會更新我的答案,歡呼。 – 2013-03-20 17:22:46

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輕鬆打開和關閉窗口,而無需瞭解任何有關View的內容。

得到愛的行爲...:)

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> 

視圖模型,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); 
      } 
     } 
    } 
} 

打開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 2013-03-22 20:11:40

+0

+1行爲 – chrisw 2013-03-24 23:24:52

+0

@adabyron,你爲什麼不把你的答案作爲一個可下載的源代碼? – RobinAtTech 2014-11-03 05:31:46

4

純粹主義者的合理方式是創建一個處理導航的服務。簡短摘要:創建NavigationService,在NavigationService上註冊您的視圖,並使用視圖模型內的NavigationService進行導航。

實施例:

class NavigationService 
{ 
    private Window _a; 

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

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

要得到的NavigationService你一個參考可以使在其頂部(即INavigationService)的抽象和寄存器/經由的IoC得到它。更恰當地說,你甚至可以做兩個抽象,一個包含註冊方法(由視圖使用)和一個包含執行器(由視圖模型使用)的抽象。

對於您可以檢查出吉爾Cleeren實施這在很大程度上依賴於國際奧委會更詳細的例子:

http://www.silverlightshow.net/video/Applied-MVVM-in-Win8-Webinar.aspx開始〇時36分30秒

1

大多數回答這個問題涉及到一個狀態變量。由ViewModel控制,並且View對這個變量進行更改。這對於打開或關閉一個窗口,或者只是顯示或隱藏某些控件而言是有用的,如有狀態命令。儘管如此,它對無狀態事件命令不起作用。您可能會在信號的上升沿觸發一些操作,但需要再次將信號設置爲低(錯誤),否則將不再觸發。

我寫了一篇關於ViewCommand pattern的文章,它解決了這個問題。它基本上是從視圖到當前ViewModel的常規命令的反向。它涉及一個接口,每個ViewModel都可以實現向所有當前連接的視圖發送命令。當DataContext屬性更改時,可以擴展View以註冊每個分配的ViewModel。此註冊將視圖添加到ViewModel中的視圖列表。每當ViewModel需要在View中運行命令時,它會遍歷所有已註冊的視圖並在其上運行該命令(如果存在)。這使用反射來查找View類中的ViewCommand方法,但在相反的方向上也是如此。

的ViewCommand方法在視圖類:

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

這是從一個視圖模型稱爲:

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

的文章可以on my website和舊版本on CodeProject

包含的代碼(BSD許可證)提供了一些措施,以允許在代碼混淆期間重命名方法。