2013-03-17 8 views
13

में देखने के लिए कुछ कमांड दें मान लें कि मेरे पास कुछ उपयोगकर्ता नियंत्रण है। उपयोगकर्ता नियंत्रण में कुछ बच्चे खिड़कियां हैं। और उपयोगकर्ता नियंत्रण उपयोगकर्ता किसी प्रकार की बाल विंडो बंद करना चाहता है। वहाँ के पीछे उपयोगकर्ता नियंत्रण कोड में एक विधि है:एमवीवीएम

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

आप ViewModel सार्वजनिक संपत्ति से छुटकारा पाने के लिए अपने ViewModel पर UserControl का DataContext भी सेट कर सकते हैं। इसके लिए ईवेंट पंजीकरण पर कुछ कास्टिंग की आवश्यकता होगी लेकिन एमवीवीएम में एक अच्छी प्रथा है क्योंकि आपको UserControl.DataContext को ViewModel पर किसी भी तरह से सेट करने की आवश्यकता है। साथ ही, कुछ सत्यापन करना सुनिश्चित करें कि आपके ChildWindowsCloseRequested इसे कॉल करने से पहले शून्य नहीं है, या आपको अपवाद मिलेगा। –

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

नोट: मैं इसे एक साथ बहुत ही खतरनाक रूप से फेंक रहा हूं; आप निश्चित रूप से इस विचार को अपनी विशिष्ट जरूरतों पर ट्यून करना चाहते हैं।

लेकिन anywho, मूल आधार अपने आदेश WindowViewModel वस्तुओं पर काम कर सकते हैं, उचित IsOpen ध्वज को चालू है, और प्रबंधक वर्ग खोलने/कोई भी नई विंडो बंद करने को संभालती है। संभव यह करने के लिए तरीके के दर्जनों रहे हैं, लेकिन यह अतीत में मेरे लिए एक चुटकी में काम किया है (जब वास्तव में लागू किया और मेरे फोन पर एक साथ फेंक दिया नहीं, कि है)

31

मुझे लगता है मैं सिर्फ एक नहीं बल्कि अच्छा पाया इस समस्या के लिए एमवीवीएम समाधान। मैंने एक ऐसा व्यवहार लिखा है जो एक प्रकार की संपत्ति WindowType और एक बुलियन संपत्ति Open का खुलासा कर रहा है। बाद में डेटाबाइंडिंग व्यूमोडेल को दृश्य के बारे में कुछ भी जानने के बिना आसानी से खिड़कियां खोलने और बंद करने की अनुमति देता है।

प्यार व्यवहार होना चाहिए ...:)

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, 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

+0

व्यवहार के लिए +1 – chrisw

+0

@adabyron, आप अपना उत्तर डाउनलोड करने योग्य स्रोत कोड के रूप में क्यों नहीं देते? – RobinAtTech

4

शुद्धियों के लिए एक उचित तरीका एक सेवा बना रहा है जो आपके नेविगेशन को संभालता है। संक्षिप्त सारांश: एक नेविगेशन सेवा बनाएं, नेविगेशन सेवा पर अपना विचार पंजीकृत करें और नेविगेशन सेवा को नेविगेट करने के लिए दृश्य मॉडल के भीतर से उपयोग करें।

उदाहरण:

class NavigationService 
{ 
    private Window _a; 

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

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

NavigationService आप के लिए एक संदर्भ प्राप्त करने के लिए उसके ऊपर (अर्थात INavigationService) पर एक अमूर्त बनाने के लिए और रजिस्टर कर सकता/एक आईओसी के माध्यम से समझ गया। अधिक सटीक रूप से आप दो अमूर्त भी बना सकते हैं, जिसमें पंजीकरण के तरीकों (दृश्य द्वारा उपयोग किया जाता है) और एक जिसमें एक्ट्यूएटर (दृश्य मॉडल द्वारा उपयोग किया जाता है) शामिल है।

एक अधिक विस्तृत उदाहरण आप गिल क्लीरेन के कार्यान्वयन जो काफी हद तक आईओसी पर निर्भर करता है की जाँच कर सकता है के लिए:

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

1

इस सवाल का जवाब अधिकांश पर शुरू एक राज्य चर शामिल है कि ViewModel द्वारा नियंत्रित किया जाता है और दृश्य इस चर में परिवर्तनों पर कार्य करता है। राज्य के आदेश के लिए यह अच्छा है कि खिड़की खोलना या बंद करना, या बस कुछ नियंत्रण दिखाना या छिपाना। यह स्टेटलेस इवेंट कमांड के लिए अच्छा काम नहीं करता है। आप सिग्नल के बढ़ते किनारे पर कुछ क्रियाएं ट्रिगर कर सकते हैं लेकिन सिग्नल को कम (झूठा) फिर से सेट करने की आवश्यकता है या फिर कभी ट्रिगर नहीं होगा।

मैंने व्यू कॉमांड पैटर्न के बारे में एक लेख लिखा है जो इस समस्या को हल करता है। यह मूल रूप से नियमित कमांड की विपरीत दिशा है जो व्यू से वर्तमान व्यू मॉडेल में जाता है। इसमें एक इंटरफ़ेस शामिल है जो प्रत्येक व्यूमोडेल वर्तमान में जुड़े हुए दृश्यों को कमांड भेजने के लिए कार्यान्वित कर सकता है। जब डेटाकॉन्टेक्स्ट संपत्ति बदलती है तो प्रत्येक असाइन किए गए व्यूमोडेल के साथ पंजीकरण करने के लिए एक दृश्य बढ़ाया जा सकता है। यह पंजीकरण ViewModel में दृश्यों की सूची में दृश्य जोड़ता है। जब भी ViewModel को दृश्य में कमांड चलाने की आवश्यकता होती है, तो यह सभी पंजीकृत दृश्यों के माध्यम से जाता है और यदि मौजूद है तो कमांड चलाता है। यह दृश्य वर्ग में ViewCommand विधियों को खोजने के लिए प्रतिबिंब का उपयोग करता है, लेकिन विपरीत दिशा में बाध्यकारी करता है।

देखें वर्ग म ViewCommand विधि:

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

यह एक ViewModel से कहा जाता है:

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

लेख उपलब्ध on my website और एक पुराने संस्करण on CodeProject में है।

शामिल कोड (बीएसडी लाइसेंस) कोड obfuscation के दौरान नामकरण विधियों की अनुमति देने के उपायों प्रदान करता है।