c# - ViewModel फ़ॉर्म को बंद कैसे करना चाहिए?




wpf mvvm (17)

आप ViewModel को उस ईवेंट का पर्दाफाश कर सकते हैं जिसे दृश्य पंजीकृत करता है। फिर, जब व्यूमोडेल दृश्य को बंद करने के लिए अपना समय तय करता है, तो वह उस घटना को सक्रिय करता है जिससे दृश्य बंद हो जाता है। यदि आप एक विशिष्ट परिणाम मूल्य वापस पास करना चाहते हैं, तो उसके लिए आपके पास व्यूमोडेल में एक संपत्ति होगी।

मैं डब्ल्यूपीएफ और एमवीवीएम समस्या सीखने की कोशिश कर रहा हूं, लेकिन एक झगड़ा मारा है। यह प्रश्न समान है लेकिन इस तरह के समान नहीं है (हैंडलिंग-संवाद-इन-डब्ल्यूपीएफ-साथ-एमवीवीएम) ...

मेरे पास एमवीवीएम पैटर्न का उपयोग करके लिखा गया एक "लॉगिन" फ़ॉर्म है।

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

जब "लॉगिन" कमांड आग लगती है, तो यह व्यूमोडेल में एक फ़ंक्शन का आह्वान करता है जो लॉग ऑफ करने के लिए नेटवर्क पर डेटा भेजता है और भेजता है। जब यह फ़ंक्शन पूरा होता है, तो 2 क्रियाएं होती हैं:

  1. लॉगिन अमान्य था - हम सिर्फ एक संदेशबॉक्स दिखाते हैं और सब ठीक है

  2. लॉगिन मान्य था, हमें लॉगिन फॉर्म को बंद करने की आवश्यकता है और इसे अपने DialogResult रूप में सच कर दिया है ...

समस्या यह है कि व्यूमोडेल वास्तविक दृश्य के बारे में कुछ नहीं जानता है, तो यह दृश्य को कैसे बंद कर सकता है और इसे एक विशेष संवाद लॉगौती करने के लिए कह सकता है ?? मैं CodeBehind में कुछ कोड चिपका सकता हूं, और / या व्यूमोडेल के माध्यम से दृश्य को पास कर सकता हूं, लेकिन ऐसा लगता है कि यह पूरी तरह से एमवीवीएम के पूरे बिंदु को हराने के लिए ...

अद्यतन करें

अंत में मैंने एमवीवीएम पैटर्न की "शुद्धता" का उल्लंघन किया और दृश्य को एक Closed घटना प्रकाशित की, और एक Close विधि का पर्दाफाश किया। view.Close तब बस कॉल को कॉल view.Close । बंद view.Close । दृश्य केवल एक इंटरफ़ेस के माध्यम से जाना जाता है और एक आईओसी कंटेनर के माध्यम से वायर्ड होता है, इसलिए कोई टेस्टेबिलिटी या रखरखाव खो नहीं जाता है।

ऐसा लगता है कि स्वीकार्य उत्तर -5 वोट पर है! जबकि मैं अच्छी भावनाओं के बारे में अच्छी तरह से जानता हूं कि "शुद्ध" होने पर किसी समस्या को हल करके, निश्चित रूप से मैं अकेला नहीं हूं जो सोचता है कि 200 लाइनों की घटनाओं, आज्ञाओं और व्यवहारों में केवल एक पंक्ति विधि से बचने के लिए "पैटर्न" और "शुद्धता" का नाम थोड़ा हास्यास्पद है ....


एक और समाधान है IotifyProperty के साथ संपत्ति बनाने के लिए डायलॉग रेसल्ट जैसे दृश्य मॉडल में बदल दिया गया है, और उसके बाद कोड में इसे लिखने के लिए:

public class SomeWindow: ChildWindow
{
    private SomeViewModel _someViewModel;

    public SomeWindow()
    {
        InitializeComponent();

        this.Loaded += SomeWindow_Loaded;
        this.Closed += SomeWindow_Closed;
    }

    void SomeWindow_Loaded(object sender, RoutedEventArgs e)
    {
        _someViewModel = this.DataContext as SomeViewModel;
        _someViewModel.PropertyChanged += _someViewModel_PropertyChanged;
    }

    void SomeWindow_Closed(object sender, System.EventArgs e)
    {
        _someViewModel.PropertyChanged -= _someViewModel_PropertyChanged;
        this.Loaded -= SomeWindow_Loaded;
        this.Closed -= SomeWindow_Closed;
    }

    void _someViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == SomeViewModel.DialogResultPropertyName)
        {
            this.DialogResult = _someViewModel.DialogResult;
        }
    }
}

सबसे महत्वपूर्ण टुकड़ा _someViewModel_PropertyChangedSomeViewModel में कुछ सार्वजनिक SomeViewModel स्ट्रिंग हो SomeViewModel

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


क्यों न केवल विंडो को कमांड पैरामीटर के रूप में पास करें?

सी#:

 private void Cancel( Window window )
  {
     window.Close();
  }

  private ICommand _cancelCommand;
  public ICommand CancelCommand
  {
     get
     {
        return _cancelCommand ?? ( _cancelCommand = new Command.RelayCommand<Window>(
                                                      ( window ) => Cancel( window ),
                                                      ( window ) => ( true ) ) );
     }
  }

XAML:

<Window x:Class="WPFRunApp.MainWindow"
        x:Name="_runWindow"
...
   <Button Content="Cancel"
           Command="{Binding Path=CancelCommand}"
           CommandParameter="{Binding ElementName=_runWindow}" />

जहां आपको विंडो बंद करने की आवश्यकता है, बस इसे व्यूमोडेल में रखें:

टा-डा

  foreach (Window window in Application.Current.Windows)
        {
            if (window.DataContext == this)
            {
                window.Close();
                return;
            }
        }

बड़ी संख्या में उत्तरों को जोड़ने के लिए, मैं निम्नलिखित जोड़ना चाहता हूं। यह मानते हुए कि आपके पास ViewModel पर आईसीओएमएंड है, और आप चाहते हैं कि वह कमांड अपनी विंडो (या उस मामले के लिए कोई अन्य कार्रवाई) बंद कर दे, तो आप निम्न की तरह कुछ उपयोग कर सकते हैं।

var windows = Application.Current.Windows;
for (var i=0;i< windows.Count;i++ )
    if (windows[i].DataContext == this)
        windows[i].Close();

यह सही नहीं है, और परीक्षण करना मुश्किल हो सकता है (क्योंकि यह स्थैतिक मॉक / स्टब करना मुश्किल है) लेकिन यह अन्य समाधानों की तुलना में क्लीनर (आईएमएचओ) है।

एरिक


मान लें कि आपका लॉगिन संवाद पहली विंडो है जो बनाई गई है, इसे अपने लॉगिन व्यू मॉडेल क्लास के अंदर आज़माएं:

    void OnLoginResponse(bool loginSucceded)
    {
        if (loginSucceded)
        {
            Window1 window = new Window1() { DataContext = new MainWindowViewModel() };
            window.Show();

            App.Current.MainWindow.Close();
            App.Current.MainWindow = window;
        }
        else
        {
            LoginError = true;
        }
    }

मैं इस तरह से जाऊंगा:

using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;    
using GalaSoft.MvvmLight.Messaging; 

// View

public partial class TestCloseWindow : Window
{
    public TestCloseWindow() {
        InitializeComponent();
        Messenger.Default.Register<CloseWindowMsg>(this, (msg) => Close());
    }
}

// View Model

public class MainViewModel: ViewModelBase
{
    ICommand _closeChildWindowCommand;

    public ICommand CloseChildWindowCommand {
        get {
            return _closeChildWindowCommand?? (_closeChildWindowCommand = new RelayCommand(() => {
                Messenger.Default.Send(new CloseWindowMsg());
        }));
        }
    }
}

public class CloseWindowMsg
{
}

मैं एक सरल संलग्न संपत्ति लिखने के लिए थेजुआन के जवाब से प्रेरित था। कोई शैलियों, कोई ट्रिगर नहीं; इसके बजाय, आप बस यह कर सकते हैं:

<Window ...
        xmlns:xc="clr-namespace:ExCastle.Wpf"
        xc:DialogCloser.DialogResult="{Binding DialogResult}">

यह लगभग उतना ही साफ है जितना कि डब्ल्यूपीएफ टीम ने इसे सही तरीके से प्राप्त कर लिया था और डायलॉग रिसेट को पहली जगह पर एक निर्भरता संपत्ति बना दिया था। बस एक bool? DialogResult डाल दिया bool? DialogResult अपने ViewModel पर bool? DialogResult प्रॉपर्टी और INOTifyPropertyChanged को लागू करें, और voilà, आपका व्यूमोडेल केवल एक संपत्ति सेट करके विंडो बंद कर सकता है (और इसके डायलॉग रीसेट सेट कर सकता है)। एमवीवीएम जैसा होना चाहिए।

DialogCloser के लिए कोड यहां दिया गया है:

using System.Windows;

namespace ExCastle.Wpf
{
    public static class DialogCloser
    {
        public static readonly DependencyProperty DialogResultProperty =
            DependencyProperty.RegisterAttached(
                "DialogResult",
                typeof(bool?),
                typeof(DialogCloser),
                new PropertyMetadata(DialogResultChanged));

        private static void DialogResultChanged(
            DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            var window = d as Window;
            if (window != null)
                window.DialogResult = e.NewValue as bool?;
        }
        public static void SetDialogResult(Window target, bool? value)
        {
            target.SetValue(DialogResultProperty, value);
        }
    }
}

मैंने इसे अपने ब्लॉग पर भी पोस्ट किया है।


मैंने जो व्हाइट के जवाब और एडम मिल्स के जवाब से कुछ कोड को मिश्रित किया, क्योंकि मुझे प्रोग्रामेटिक रूप से बनाई गई विंडो में उपयोगकर्ता नियंत्रण दिखाने की आवश्यकता थी। तो DialogCloser विंडो पर नहीं होना चाहिए, यह उपयोगकर्ता नियंत्रण पर ही हो सकता है

<UserControl ...
    xmlns:xw="clr-namespace:Wpf"
    xw:DialogCloser.DialogResult="{Binding DialogResult}">

और डायलॉगक्लोजर को उपयोगकर्ता नियंत्रण की खिड़की मिल जाएगी यदि यह खिड़की से जुड़ा हुआ नहीं था।

namespace Wpf
{
  public static class DialogCloser
  {
    public static readonly DependencyProperty DialogResultProperty =
        DependencyProperty.RegisterAttached(
            "DialogResult",
            typeof(bool?),
            typeof(DialogCloser),
            new PropertyMetadata(DialogResultChanged));

    private static void DialogResultChanged(
        DependencyObject d,
        DependencyPropertyChangedEventArgs e)
    {
      var window = d.GetWindow();
      if (window != null)
        window.DialogResult = e.NewValue as bool?;
    }

    public static void SetDialogResult(DependencyObject target, bool? value)
    {
      target.SetValue(DialogResultProperty, value);
    }
  }

  public static class Extensions
  {
    public static Window GetWindow(this DependencyObject sender_)
    {
      Window window = sender_ as Window;        
      return window ?? Window.GetWindow( sender_ );
    }
  }
}

मैंने जो व्हाइट के समाधान को लागू किया, लेकिन कभी-कभी समस्याओं में भाग गया " डायलॉग रेसल्ट को विंडो के निर्माण के बाद ही सेट किया जा सकता है और संवाद के रूप में दिखाया जा सकता है " त्रुटियां।

व्यू बंद होने के बाद मैं व्यूमोडेल को रख रहा था और कभी-कभी मैंने उसी वीएम का उपयोग करके एक नया दृश्य खोला। ऐसा प्रतीत होता है कि पुराना व्यू कचरा इकट्ठा करने से पहले नए दृश्य को बंद करने के परिणामस्वरूप DialogResultChanged बंद खिड़की पर DialogResult संपत्ति सेट करने की कोशिश कर रहा था, इस प्रकार त्रुटि को उत्तेजित कर रहा था।

मेरा समाधान विंडो की IsLoaded संपत्ति की जांच करने के लिए DialogResultChanged बदलने के लिए था:

private static void DialogResultChanged(
    DependencyObject d,
    DependencyPropertyChangedEventArgs e)
{
    var window = d as Window;
    if (window != null && window.IsLoaded)
        window.DialogResult = e.NewValue as bool?;
}

इस परिवर्तन को करने के बाद बंद संवादों के लिए कोई अनुलग्नक अनदेखा कर दिया जाता है।


यह एक सरल और साफ समाधान है - आप ViewModel में एक ईवेंट जोड़ते हैं और उस ईवेंट को निकाल दिए जाने पर विंडो को बंद करने का निर्देश देते हैं।

अधिक जानकारी के लिए मेरे ब्लॉग पोस्ट देखें, ViewModel से विंडो बंद करें


यह शायद बहुत देर हो चुकी है, लेकिन मैं एक ही समस्या में आया और मुझे एक समाधान मिला जो मेरे लिए काम करता है।

मैं यह नहीं समझ सकता कि संवाद के बिना ऐप कैसे बनाया जाए (शायद यह सिर्फ एक दिमाग ब्लॉक है)। तो मैं एमवीवीएम के साथ एक बाधा था और एक संवाद दिखा रहा था। तो मैं इस कोडप्रोजेक्ट आलेख में आया:

http://www.codeproject.com/KB/WPF/XAMLDialog.aspx

जो UserControl है जो मूल रूप से एक विंडो को दूसरी विंडो के दृश्य पेड़ (xaml में अनुमति नहीं) के भीतर होने की अनुमति देता है। यह IsShowing नामक एक बुलियन निर्भरता प्रजनन का भी खुलासा करता है।

आप आमतौर पर एक रिसोर्सिस्की में एक शैली सेट कर सकते हैं, जो मूल रूप से नियंत्रण की सामग्री प्रॉपर्टी जब भी नियंत्रण करता है! = ट्रिगर के माध्यम से शून्य:

<Style TargetType="{x:Type d:Dialog}">
    <Style.Triggers>
        <Trigger Property="HasContent"  Value="True">
            <Setter Property="Showing" Value="True" />
        </Trigger>
    </Style.Triggers>
</Style>

इस दृश्य में जहां आप संवाद प्रदर्शित करना चाहते हैं, बस यह है:

<d:Dialog Content="{Binding Path=DialogViewModel}"/>

और आपके व्यूमोडेल में आपको केवल संपत्ति को एक मान पर सेट करना है (नोट: व्यूमोडेल क्लास को कुछ जानकारी जानने के लिए INOTifyProperty का समर्थन करना चाहिए)।

इस तरह:

DialogViewModel = new DisplayViewModel();

दृश्य के साथ व्यूमोडेल से मिलान करने के लिए आपके पास पुनर्वित्त में ऐसा कुछ होना चाहिए:

<DataTemplate DataType="{x:Type vm:DisplayViewModel}">
    <vw:DisplayView/>
</DataTemplate>

डायलॉग दिखाने के लिए आप सभी को एक-लाइनर कोड मिलता है। आपको जो समस्या मिलती है वह है कि आप वास्तव में उपर्युक्त कोड के साथ संवाद को बंद नहीं कर सकते हैं। इसलिए यही कारण है कि आपको एक व्यूमोडेल बेस क्लास में एक ईवेंट डालना है जो DisplayViewModel को ऊपर दिए गए कोड के बजाय और उससे प्राप्त होता है, इसे लिखें

        var vm = new DisplayViewModel();
        vm.RequestClose += new RequestCloseHandler(DisplayViewModel_RequestClose);
        DialogViewModel = vm;

फिर आप कॉलबैक के माध्यम से संवाद के परिणाम को संभाल सकते हैं।

यह थोड़ा जटिल प्रतीत हो सकता है, लेकिन आधारभूत ढांचे के बाद, यह बहुत सरल है। फिर यह मेरा कार्यान्वयन है, मुझे यकीन है कि अन्य हैं :)

उम्मीद है कि यह मदद करता है, यह मुझे बचाया।


यहां मैंने जो किया है, वह है जो काम करता है, हालांकि ऐसा लगता है कि यह लंबे समय तक हवादार और बदसूरत है (वैश्विक स्थैतिक कुछ भी अच्छा नहीं है)

1: App.xaml.cs

public partial class App : Application
{
    // create a new global custom WPF Command
    public static readonly RoutedUICommand LoggedIn = new RoutedUICommand();
}

2: LoginForm.xaml

// bind the global command to a local eventhandler
<CommandBinding Command="client:App.LoggedIn" Executed="OnLoggedIn" />

3: LoginForm.xaml.cs

// implement the local eventhandler in codebehind
private void OnLoggedIn( object sender, ExecutedRoutedEventArgs e )
{
    DialogResult = true;
    Close();
}

4: LoginFormViewModel.cs

// fire the global command from the viewmodel
private void OnRemoteServerReturnedSuccess()
{
    App.LoggedIn.Execute(this, null);
}

बाद में मैंने इस कोड को हटा दिया, और केवल LoginFormViewModel को इसके दृश्य पर बंद विधि को कॉल किया गया था। यह बहुत अच्छा और पालन करने में आसान होने के समाप्त हो गया। आईएमएचओ पैटर्न का मुद्दा लोगों को यह समझने का एक आसान तरीका है कि आपका ऐप क्या कर रहा है, और इस मामले में, एमवीवीएम इसे समझने में कहीं अधिक कठिन बना रहा था, अगर मैंने इसका इस्तेमाल नहीं किया था, और अब एक विरोधी- पैटर्न था।


हालांकि यह व्यूमोडेल के माध्यम से ऐसा करने के सवाल का जवाब नहीं देता है, यह दिखाता है कि केवल एक्सएएमएल + मिश्रण एसडीके का उपयोग करके इसे कैसे किया जाए।

मैंने ब्लेंड एसडीके से दो फाइलों को डाउनलोड और उपयोग करना चुना, जिनमें से आप माइक्रोसॉफ्ट से NuGet के माध्यम से एक पैकेज के रूप में कर सकते हैं। फाइलें हैं:

System.Windows.Interactivity.dll और Microsoft.Expression.Interactions.dll

Microsoft.Expression.Interactions.dll आपको अच्छी क्षमताओं जैसे कि संपत्ति सेट करने की क्षमता या आपके व्यूमोडेल या अन्य लक्ष्य पर एक विधि का आह्वान करने की क्षमता देता है और इसमें अन्य विजेट भी हैं।

कुछ एक्सएएमएल:

<Window x:Class="Blah.Blah.MyWindow"
    ...
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
  ...>
 <StackPanel>
    <Button x:Name="OKButton" Content="OK">
       <i:Interaction.Triggers>
          <i:EventTrigger EventName="Click">
             <ei:ChangePropertyAction
                      TargetObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
                      PropertyName="DialogResult"
                      Value="True"
                      IsEnabled="{Binding SomeBoolOnTheVM}" />                                
          </i:EventTrigger>
    </Button>
    <Button x:Name="CancelButton" Content="Cancel">
       <i:Interaction.Triggers>
          <i:EventTrigger EventName="Click">
             <ei:ChangePropertyAction
                      TargetObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
                      PropertyName="DialogResult"
                      Value="False" />                                
          </i:EventTrigger>
    </Button>

    <Button x:Name="CloseButton" Content="Close">
       <i:Interaction.Triggers>
                <i:EventTrigger EventName="Click">
                    <!-- method being invoked should be void w/ no args -->
                    <ei:CallMethodAction
                        TargetObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
                        MethodName="Close" />
                </i:EventTrigger>
            </i:Interaction.Triggers>
    </Button>
 <StackPanel>
</Window>

Note that if you're just going for simple OK/Cancel behavior, you can get away w/ using the IsDefault and IsCancel properties as long as the window is shown w/ Window.ShowDialog().
I personally had problems w/ a button that had the IsDefault property set to true, but it was hidden when the page is loaded. It didn't seem to want to play nicely after it was shown, so I just am setting the Window.DialogResult property as shown above instead and it works for me.


Create a Dependency Property in your View /any UserControl (or Window you want to close). नीचे की तरह:

 public bool CloseTrigger
        {
            get { return (bool)GetValue(CloseTriggerProperty); }
            set { SetValue(CloseTriggerProperty, value); }
        }

        public static readonly DependencyProperty CloseTriggerProperty =
            DependencyProperty.Register("CloseTrigger", typeof(bool), typeof(ControlEventBase), new PropertyMetadata(new PropertyChangedCallback(OnCloseTriggerChanged)));

        private static void OnCloseTriggerChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
        {
            //write Window Exit Code
        }

And bind it from your ViewModel's property :

<Window x:Class="WpfTempProject.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow"  Width="525"
        CloseTrigger="{Binding Path=CloseWindow,Mode=TwoWay}"

Property In VeiwModel :

private bool closeWindow;

    public bool CloseWindow
    {
        get { return closeWindow; }
        set 
        { 
            closeWindow = value;
            RaiseChane("CloseWindow");
        }
    }

Now trigger the close operation by changing the CloseWindow value in ViewModel. :)


Here is the simple bug free solution (with source code), It is working for me.

  1. Derive your ViewModel from INotifyPropertyChanged

  2. Create a observable property CloseDialog in ViewModel

    public void Execute()
    {
        // Do your task here
    
        // if task successful, assign true to CloseDialog
        CloseDialog = true;
    }
    
    private bool _closeDialog;
    public bool CloseDialog
    {
        get { return _closeDialog; }
        set { _closeDialog = value; OnPropertyChanged(); }
    }
    
    public event PropertyChangedEventHandler PropertyChanged;
    
    private void OnPropertyChanged([CallerMemberName]string property = "")
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(property));
        }
    }
    

    }

  3. Attach a Handler in View for this property change

        _loginDialogViewModel = new LoginDialogViewModel();
        loginPanel.DataContext = _loginDialogViewModel;
        _loginDialogViewModel.PropertyChanged += OnPropertyChanged;
    
  4. Now you are almost done. In the event handler make DialogResult = true

    protected void OnPropertyChanged(object sender, PropertyChangedEventArgs args)
    {
        if (args.PropertyName == "CloseDialog")
        {
            DialogResult = true;
        }
    }
    

public partial class MyWindow: Window
{
    public ApplicationSelection()
    {
      InitializeComponent();

      MyViewModel viewModel = new MyViewModel();

      DataContext = viewModel;

      viewModel.RequestClose += () => { Close(); };

    }
}

public class MyViewModel
{

  //...Your code...

  public event Action RequestClose;

  public virtual void Close()
  {
    if (RequestClose != null)
    {
      RequestClose();
    }
  }

  public void SomeFunction()
  {
     //...Do something...
     Close();
  }
}




mvvm