2013-03-17 33 views
13

Hãy tưởng tượng tôi có một số điều khiển người dùng. Điều khiển người dùng có một số cửa sổ con. Và người dùng kiểm soát người dùng muốn đóng cửa sổ con của một số loại. Có một phương pháp trong mã kiểm soát người dùng phía sau:Đưa ra một số lệnh để xem trong MVVM

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

Nhưng tôi không thể gọi phương thức này vì tôi không có quyền truy cập trực tiếp vào chế độ xem.

Một giải pháp khác mà tôi nghĩ là bằng cách nào đó để lộ điều khiển người dùng ViewModel là một trong những thuộc tính của nó (vì vậy tôi có thể liên kết nó và đưa lệnh trực tiếp vào ViewModel). Nhưng tôi không muốn người dùng kiểm soát người dùng biết gì về điều khiển người dùng ViewModel.

Vậy cách nào đúng để giải quyết vấn đề này?

Trả lời

2

Một cách để đạt được điều này sẽ cho xem mô hình để yêu cầu các cửa sổ con nên được đóng lại:

public class ExampleUserControl_ViewModel 
{ 
    public Action ChildWindowsCloseRequested; 

    ... 
} 

Quan điểm sau đó sẽ đăng ký vào trường hợp của mình xem mô hình, và chăm sóc bế mạc các cửa sổ khi nó bị sa thải.

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

     DataContext = viewModel; 
    } 

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

    ... 
} 

Vì vậy, ở đây, chế độ xem có thể đảm bảo cửa sổ con được đóng mà không có kiến ​​thức về chế độ xem.

+3

Bạn cũng có thể đặt DataContext của UserControl thành ViewModel, loại bỏ thuộc tính công khai ViewModel. Điều này sẽ yêu cầu một số đúc trên đăng ký sự kiện nhưng là một thực hành tốt kể từ trong MVVM bạn cần phải thiết lập UserControl.DataContext để ViewModel anyways. Ngoài ra, hãy chắc chắn để thực hiện một số xác nhận rằng ChildWindowsCloseRequested của bạn không phải là null trước khi gọi nó, hoặc bạn sẽ nhận được một ngoại lệ. –

+0

True, tôi sẽ cập nhật câu trả lời của tôi, cổ vũ. –

4

tôi đã xử lý loại này tình hình trong quá khứ bằng cách đưa vào các khái niệm về một WindowManager, mà là một khủng khiếp tên cho nó, vì vậy chúng ta hãy ghép nó với một WindowViewModel, mà chỉ là một chút ít khủng khiếp - nhưng ý tưởng cơ bản là:

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

lưu ý: Tôi chỉ đang ném điều này với nhau rất haphazardly; bạn dĩ nhiên muốn điều chỉnh ý tưởng này theo nhu cầu cụ thể của bạn.

Tuy nhiên, tiền đề cơ bản là lệnh của bạn có thể hoạt động trên các đối tượng WindowViewModel, chuyển đổi cờ IsOpen một cách thích hợp và lớp người quản lý xử lý việc mở/đóng bất kỳ cửa sổ mới nào. Có hàng chục thể cách để làm điều này, nhưng nó đã làm việc trong một pinch cho tôi trong quá khứ (khi thực sự thực hiện và không ném với nhau trên điện thoại của tôi, đó là)

31

tôi cảm thấy tôi chỉ tìm thấy một khá tốt đẹp Giải pháp MVVM cho vấn đề này. Tôi đã viết một hành vi phơi bày thuộc tính loại WindowType và thuộc tính boolean Open. DataBinding thứ hai cho phép ViewModel mở và đóng các cửa sổ một cách dễ dàng, mà không biết gì về View.

Gotta yêu hành vi ...:)

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 (Black/Tím như nhau):

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

Mở 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

Ah, tôi yêu các hành vi ... – JerKimball

+0

+1 cho hành vi – chrisw

+0

@adabyron, Tại sao bạn không đưa ra câu trả lời dưới dạng mã nguồn có thể tải xuống? – RobinAtTech

4

Một cách hợp lý để người thuần túy đang tạo ra một dịch vụ xử lý điều hướng của bạn. Tóm tắt ngắn gọn: tạo một NavigationService, đăng ký chế độ xem của bạn tại NavigationService và sử dụng NavigationService từ bên trong mô hình khung nhìn để điều hướng.

Ví dụ:

class NavigationService 
{ 
    private Window _a; 

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

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

Để có được một tài liệu tham khảo để NavigationService bạn có thể làm cho một sự trừu tượng trên đầu trang của nó (ví dụ: INavigationService) và đăng ký/nhận được nó thông qua một IoC. Đúng hơn, bạn thậm chí có thể tạo ra hai phép trừu tượng, một phương thức chứa các phương thức đăng ký (được sử dụng bởi khung nhìn) và một phương thức chứa các bộ truyền động (được sử dụng bởi mô hình khung nhìn).

Đối với một ví dụ chi tiết hơn bạn có thể kiểm tra việc thực hiện của Gill CLEEREN phụ thuộc nhiều vào IoC:

http://www.silverlightshow.net/video/Applied-MVVM-in-Win8-Webinar.aspx bắt đầu từ 00:36:30

1

Hầu hết các câu trả lời cho câu hỏi này liên quan đến một biến trạng thái đó được điều khiển bởi ViewModel và View hoạt động trên các thay đổi đối với biến này. Điều này tốt cho các lệnh trạng thái như mở hoặc đóng cửa sổ hoặc chỉ hiển thị hoặc ẩn một số điều khiển. Nó không hoạt động tốt cho lệnh sự kiện không trạng thái. Bạn có thể kích hoạt một số hành động trên các cạnh tăng của tín hiệu nhưng cần phải thiết lập tín hiệu để thấp (false) một lần nữa hoặc nó sẽ không bao giờ kích hoạt một lần nữa.

Tôi đã viết một bài viết về mẫu ViewCommand giải quyết vấn đề này. Về cơ bản nó là hướng ngược lại của các lệnh thông thường đi từ View đến ViewModel hiện tại. Nó bao gồm một giao diện mà mỗi ViewModel có thể thực hiện để gửi các lệnh tới tất cả các khung nhìn hiện đang được kết nối. Một View có thể được mở rộng để đăng ký với mỗi ViewModel được gán khi thuộc tính DataContext của nó thay đổi. Đăng ký này thêm View vào danh sách Views trong ViewModel. Bất cứ khi nào ViewModel cần chạy một lệnh trong một khung nhìn, nó đi qua tất cả các khung nhìn đã đăng ký và chạy lệnh trên chúng nếu nó tồn tại. Điều này làm cho việc sử dụng sự phản chiếu để tìm các phương thức ViewCommand trong lớp View, nhưng như vậy sẽ ràng buộc theo hướng ngược lại.

Phương pháp này ViewCommand trong lớp View:

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

này được gọi từ một ViewModel:

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

bài viết này có sẵn on my website và trong một phiên bản cũ on CodeProject.

Mã được bao gồm (giấy phép BSD) cung cấp các biện pháp cho phép đổi tên phương thức trong quá trình giải mã mã.

Các vấn đề liên quan