WPF / MVVM लाइट टूलकिट के साथ विंडो क्लोजिंग इवेंट को हैंडल करना


145

मैं इस Closingघटना को संभालना चाहता हूं (जब कोई उपयोगकर्ता मेरी विंडो के ऊपरी दाहिने 'X' बटन पर क्लिक करता है) ताकि अंत में एक पुष्टिकरण संदेश प्रदर्शित किया जा सके या / और समापन को रद्द किया जा सके।

मुझे पता है कि कोड-पीछे में यह कैसे करना है: Closingखिड़की की घटना की सदस्यता लें फिर CancelEventArgs.Cancelसंपत्ति का उपयोग करें ।

लेकिन मैं MVVM का उपयोग कर रहा हूं इसलिए मुझे यकीन नहीं है कि यह अच्छा तरीका है।

मुझे लगता है कि अच्छा दृष्टिकोण मेरे ViewModel में Closingघटना को बांधने के लिए होगा Command

मैंने कोशिश की है कि:

<i:Interaction.Triggers>
    <i:EventTrigger EventName="Closing">
        <cmd:EventToCommand Command="{Binding CloseCommand}" />
    </i:EventTrigger>
</i:Interaction.Triggers>

RelayCommandमेरे ViewModel में एक संबद्ध के साथ लेकिन यह काम नहीं करता है (कमांड कोड निष्पादित नहीं किया गया है)।


3
इसके उत्तर के लिए अच्छे उत्तर में भी रुचि है।
सेखत

3
मैंने कोडप्लेक्स से कोड डाउनलोड किया और यह डीबगिंग से पता चला: "टाइप करने के लिए ऑब्जेक्ट 'System.ComponentModel.CancelEventArgs' टाइप करने के लिए 'System.Windows.RoutedEventArgs' टाइप करने में असमर्थ।" यदि आप रद्द नहीं करना चाहते हैं तो यह ठीक काम करता है , लेकिन यह आपके प्रश्न का उत्तर नहीं देता है ...
डेविड हॉलिंसहेड

मुझे लगता है कि आपका कोड काम नहीं कर रहा है क्योंकि आपके द्वारा ट्रिगर को नियंत्रित करने से आपके पास एक समापन कार्यक्रम नहीं है। आपका डेटा संदर्भ कोई विंडो नहीं है ... यह संभवतः ग्रिड या कुछ और के साथ एक डेटा टेम्पलेट है, जिसमें कोई समापन घटना नहीं है। तो dbkk का जवाब इस मामले में सबसे अच्छा जवाब है। हालाँकि, मैं इंटरेक्शन / EventTrigger दृष्टिकोण पसंद करता हूँ जब घटना उपलब्ध है।
नीलवे

आपके पास कोड एक लोड की गई घटना पर ठीक काम करेगा, उदाहरण के लिए।
नीलवे

जवाबों:


126

मैं बस व्यूअर में हैंडलर को संबद्ध करूंगा:

MyWindow() 
{
    // Set up ViewModel, assign to DataContext etc.
    Closing += viewModel.OnWindowClosing;
}

फिर हैंडलर को इसमें जोड़ें ViewModel:

using System.ComponentModel;

public void OnWindowClosing(object sender, CancelEventArgs e) 
{
   // Handle closing logic, set e.Cancel as needed
}

इस मामले में, आप अधिक अप्रत्यक्ष पैटर्न (एक्सएएमएल प्लस Commandपैटर्न के 5 अतिरिक्त लाइनों ) के साथ अधिक विस्तृत पैटर्न का उपयोग करके जटिलता के अलावा कुछ भी नहीं हासिल करते हैं ।

"शून्य कोड-पीछे" मंत्र अपने आप में लक्ष्य नहीं है, बिंदु ViewModel को व्यू से अलग करना है । जब घटना दृश्य के कोड-पीछे में बंधी होती है, तब ViewModelभी दृश्य पर निर्भर नहीं होती है और समापन तर्क इकाई-परीक्षण किया जा सकता है


4
मुझे यह समाधान पसंद है : बस एक छिपे हुए बटन में हुक करें :)
बेंजोल

3
Mvvm शुरुआती के लिए MVVMLight का उपयोग नहीं कर रहा है और क्लोजिंग इवेंट के बारे में ViewModel को सूचित करने के तरीके की खोज कर रहा है, लिंक कैसे DataContext को सही तरीके से सेट करें और ViewModel को व्यू में कैसे प्राप्त करें, यह दिलचस्प हो सकता है। व्यू में ViewModel का संदर्भ कैसे प्राप्त करें? और कैसे मैं datamontext संपत्ति का उपयोग कर xaml में एक विंडो पर एक ViewModel सेट करूँ ... मुझे कई घंटे लगे, कैसे एक साधारण विंडो समापन घटना को ViewModel में संभाला जा सकता है।
मार्कसइगल

18
यह समाधान MVVM वातावरण में अप्रासंगिक है। पीछे कोड ViewModel के बारे में पता नहीं होना चाहिए।
जैकब

2
@ जैकोब मुझे लगता है कि समस्या यह है कि आपको अपने ViewModel में एक फॉर्म इवेंट हैंडलर मिलता है, जो ViewModel को एक विशिष्ट UI कार्यान्वयन के लिए तैयार करता है। यदि वे पीछे कोड का उपयोग करने जा रहे हैं, तो उन्हें CanExecute की जांच करनी चाहिए और उसके बाद ICommand प्रॉपर्टी पर Execute () कॉल करना चाहिए।
ईविल कबूतर

14
@Jacob कोड-पीछे ViewModel सदस्यों के बारे में ठीक-ठीक जान सकता है, बस XAML कोड करता है। या जब आप किसी ViewModel प्रॉपर्टी से बाइंडिंग बनाते हैं तो आपको क्या लगता है? यह समाधान MVVM के लिए पूरी तरह से ठीक है, जब तक कि आप कोड के पीछे के लॉजिक को अपने आप में संभाल नहीं लेते, लेकिन ViewModel में (हालांकि एक ICommand का उपयोग करते हुए, जैसे कि EvilPigeon सुझाव देता है, एक अच्छा विचार हो सकता है क्योंकि आप भी बाँध सकते हैं। यह करने के लिए)
almulo

81

यह कोड ठीक काम करता है:

ViewModel.cs:

public ICommand WindowClosing
{
    get
    {
        return new RelayCommand<CancelEventArgs>(
            (args) =>{
                     });
    }
}

और XAML में:

<i:Interaction.Triggers>
    <i:EventTrigger EventName="Closing">
        <command:EventToCommand Command="{Binding WindowClosing}" PassEventArgsToCommand="True" />
    </i:EventTrigger>
</i:Interaction.Triggers>

मानाकि:

  • ViewModel को DataContextमुख्य कंटेनर में सौंपा गया है ।
  • xmlns:command="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.SL5"
  • xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

1
भूल गए: कमांड उपयोग में तर्क वितर्क प्राप्त करने के लिए PassEventArgsToCommand = "True"
Stas

2
+1 सरल और पारंपरिक दृष्टिकोण। PRISM को सौंपना और भी बेहतर होगा।
ट्राई क्यू ट्रान

16
यह एक ऐसा परिदृश्य है जो WPF और MVVM में गैपिंग होल्स पर प्रकाश डालता है।
डेमियन

1
यह वास्तव में क्या है उल्लेख करने के लिए मददगार होगा iमें <i:Interaction.Triggers>और कैसे इसे पाने के लिए।
एंड्री मुजिकुक

1
@ शीज़, यह एक नेमस्पेस है जिसे आपको रूट एलिमेंट में इस तरह से घोषित करना चाहिए: xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Stas

34

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

Application.Current.MainWindow.Closing += new CancelEventHandler(MainWindow_Closing);

void MainWindow_Closing(object sender, CancelEventArgs e)
{
            //Your code to handle the event
}

शुभकामनाएं।


यह इस मुद्दे में वर्णित अन्य के बीच सबसे अच्छा समाधान है। धन्यवाद !
जैकब

यह वही है जिसे मैं देख रहा था। धन्यवाद!
निक्की पंजाबी

20
... और यह ViewModel और View के बीच चुस्त युग्मन बनाता है। -1।
पियोट्र्क

6
यह सबसे अच्छा जवाब नहीं है। यह MVVM को तोड़ता है।
सफिरॉन

1
@ क्रेग इसे मुख्य विंडो के लिए एक कठिन संदर्भ की आवश्यकता होती है, या जिस भी विंडो के लिए इसका उपयोग किया जा रहा है। यह बहुत आसान है, लेकिन इसका मतलब यह है कि व्यू मॉडल को डिकूप नहीं किया गया है। यह MVVM नर्ड को संतुष्ट करने का सवाल नहीं है या नहीं, लेकिन अगर इसे काम करने के लिए MVVM पैटर्न को तोड़ना पड़े, तो इसका उपयोग करने का कोई मतलब नहीं है।
एलेक्स

16

यहाँ MVVM- पैटर्न के अनुसार एक उत्तर है यदि आप ViewModel में विंडो (या इसके किसी भी ईवेंट) के बारे में जानना नहीं चाहते हैं।

public interface IClosing
{
    /// <summary>
    /// Executes when window is closing
    /// </summary>
    /// <returns>Whether the windows should be closed by the caller</returns>
    bool OnClosing();
}

ViewModel में इंटरफ़ेस और कार्यान्वयन जोड़ें

public bool OnClosing()
{
    bool close = true;

    //Ask whether to save changes och cancel etc
    //close = false; //If you want to cancel close

    return close;
}

विंडो में मैं समापन कार्यक्रम जोड़ता हूं। यह कोड MVVM पैटर्न को नहीं तोड़ता है। व्यू व्यूमॉडल के बारे में जान सकते हैं!

void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
    IClosing context = DataContext as IClosing;
    if (context != null)
    {
        e.Cancel = !context.OnClosing();
    }
}

सरल, स्पष्ट और साफ। ViewModel को दृश्य की बारीकियों की जानकारी नहीं है, इसलिए चिंताएँ अलग रहती हैं।
बर्नहार्ड हिलर

संदर्भ हमेशा शून्य है!
शाहिद ओड

@ शहीदो आपका ViewModel IClosingइंटरफ़ेस को लागू करने की आवश्यकता है , न कि केवल OnClosingविधि को लागू करने के लिए । अन्यथा DataContext as IClosingकलाकार असफल हो जाएंगे और वापस लौटेंगेnull
एरिक व्हाइट

10

गीज़, ऐसा लगता है कि इसके लिए यहां बहुत सारे कोड चल रहे हैं। ऊपर के स्टास में न्यूनतम प्रयास के लिए सही दृष्टिकोण था। (MVVMLight का उपयोग कर, लेकिन पहचानने योग्य होना चाहिए) यहाँ मेरी रूपांतर है ... ओह और PassEventArgsToCommand = "सही" है निश्चित रूप से के रूप में ऊपर संकेत की जरूरत है।

(लॉरेंट बुगियन का क्रेडिट http://blog.galasoft.ch/archive/2009/10/18/clean-shutdown-in-silverlight-and-wpf-applications.aspx )

   ... MainWindow Xaml
   ...
   WindowStyle="ThreeDBorderWindow" 
    WindowStartupLocation="Manual">



<i:Interaction.Triggers>
    <i:EventTrigger EventName="Closing">
        <cmd:EventToCommand Command="{Binding WindowClosingCommand}" PassEventArgsToCommand="True" />
    </i:EventTrigger>
</i:Interaction.Triggers> 

दृश्य मॉडल में:

///<summary>
///  public RelayCommand<CancelEventArgs> WindowClosingCommand
///</summary>
public RelayCommand<CancelEventArgs> WindowClosingCommand { get; private set; }
 ...
 ...
 ...
        // Window Closing
        WindowClosingCommand = new RelayCommand<CancelEventArgs>((args) =>
                                                                      {
                                                                          ShutdownService.MainWindowClosing(args);
                                                                      },
                                                                      (args) => CanShutdown);

शटडाउन सर्विस में

    /// <summary>
    ///   ask the application to shutdown
    /// </summary>
    public static void MainWindowClosing(CancelEventArgs e)
    {
        e.Cancel = true;  /// CANCEL THE CLOSE - let the shutdown service decide what to do with the shutdown request
        RequestShutdown();
    }

रिक्वेस्टशूटडाउन कुछ इस तरह दिखता है, लेकिन मूल रूप सेसुपरस्टूडडाउन या जो भी नाम दिया गया है, वह तय करता है कि आवेदन को बंद करना है या नहीं (जो वैसे भी खिड़की को बंद कर देगा):

...
...
...
    /// <summary>
    ///   ask the application to shutdown
    /// </summary>
    public static void RequestShutdown()
    {

        // Unless one of the listeners aborted the shutdown, we proceed.  If they abort the shutdown, they are responsible for restarting it too.

        var shouldAbortShutdown = false;
        Logger.InfoFormat("Application starting shutdown at {0}...", DateTime.Now);
        var msg = new NotificationMessageAction<bool>(
            Notifications.ConfirmShutdown,
            shouldAbort => shouldAbortShutdown |= shouldAbort);

        // recipients should answer either true or false with msg.execute(true) etc.

        Messenger.Default.Send(msg, Notifications.ConfirmShutdown);

        if (!shouldAbortShutdown)
        {
            // This time it is for real
            Messenger.Default.Send(new NotificationMessage(Notifications.NotifyShutdown),
                                   Notifications.NotifyShutdown);
            Logger.InfoFormat("Application has shutdown at {0}", DateTime.Now);
            Application.Current.Shutdown();
        }
        else
            Logger.InfoFormat("Application shutdown aborted at {0}", DateTime.Now);
    }
    }

8

प्रश्नकर्ता को एसटीएएस उत्तर का उपयोग करना चाहिए, लेकिन उन पाठकों के लिए जो प्रिज्म का उपयोग करते हैं और कोई गलासॉफ्ट / एमवीएमलाइट नहीं है, वे कोशिश कर सकते हैं कि मैंने क्या उपयोग किया है:

विंडो या usercontrol के लिए शीर्ष पर परिभाषा में, आदि नामस्थान को परिभाषित करते हैं:

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

और उस परिभाषा के ठीक नीचे:

<i:Interaction.Triggers>
        <i:EventTrigger EventName="Closing">
            <i:InvokeCommandAction Command="{Binding WindowClosing}" CommandParameter="{Binding}" />
        </i:EventTrigger>
</i:Interaction.Triggers>

आपके दृष्टिकोण में संपत्ति:

public ICommand WindowClosing { get; private set; }

अपने व्यूअमोडल कंस्ट्रक्टर में डेलिकेटेट संलग्न करें:

this.WindowClosing = new DelegateCommand<object>(this.OnWindowClosing);

अंत में, आपका कोड जिसे आप नियंत्रण / खिड़की / जो भी हो, के करीब पहुंचाना चाहते हैं:

private void OnWindowClosing(object obj)
        {
            //put code here
        }

3
यह CancelEventArgs तक पहुंच नहीं देता है जो समापन कार्यक्रम को रद्द करने के लिए आवश्यक है। ऑब्जेक्ट पास किया गया दृश्य मॉडल है, जो तकनीकी रूप से एक ही दृश्य मॉडल है जिसे WindowClosing कमांड से निष्पादित किया जा रहा है।
स्टीफनबेयर

4

मुझे आपके App.xaml.cs फ़ाइल के भीतर एक इवेंट हैंडलर का उपयोग करने के लिए लुभाया जाएगा जो आपको आवेदन बंद करने या न करने के बारे में निर्णय लेने की अनुमति देगा।

उदाहरण के लिए आप अपनी App.xaml.cs फ़ाइल में निम्न कोड जैसा कुछ कर सकते हैं:

protected override void OnStartup(StartupEventArgs e)
{
    base.OnStartup(e);
    // Create the ViewModel to attach the window to
    MainWindow window = new MainWindow();
    var viewModel = new MainWindowViewModel();

    // Create the handler that will allow the window to close when the viewModel asks.
    EventHandler handler = null;
    handler = delegate
    {
        //***Code here to decide on closing the application****
        //***returns resultClose which is true if we want to close***
        if(resultClose == true)
        {
            viewModel.RequestClose -= handler;
            window.Close();
        }
    }
    viewModel.RequestClose += handler;

    window.DataContaxt = viewModel;

    window.Show();

}

तो अपने MainWindowViewModel कोड के भीतर आप निम्नलिखित हो सकते हैं:

#region Fields
RelayCommand closeCommand;
#endregion

#region CloseCommand
/// <summary>
/// Returns the command that, when invoked, attempts
/// to remove this workspace from the user interface.
/// </summary>
public ICommand CloseCommand
{
    get
    {
        if (closeCommand == null)
            closeCommand = new RelayCommand(param => this.OnRequestClose());

        return closeCommand;
    }
}
#endregion // CloseCommand

#region RequestClose [event]

/// <summary>
/// Raised when this workspace should be removed from the UI.
/// </summary>
public event EventHandler RequestClose;

/// <summary>
/// If requested to close and a RequestClose delegate has been set then call it.
/// </summary>
void OnRequestClose()
{
    EventHandler handler = this.RequestClose;
    if (handler != null)
    {
        handler(this, EventArgs.Empty);
    }
}

#endregion // RequestClose [event]

1
विस्तृत उत्तर के लिए धन्यवाद। हालाँकि, मुझे नहीं लगता कि मेरी समस्या का समाधान हो रहा है: जब उपयोगकर्ता ऊपरी दाहिने 'X' बटन पर क्लिक करता है, तो मुझे विंडो बंद करने की आवश्यकता होती है। कोड-पीछे में ऐसा करना आसान होगा (मैं सिर्फ क्लोजिंग इवेंट को लिंक करूँगा और CancelEventArgs.Cancel को झूठे के सच में सेट करूँगा) लेकिन मैं MVVM स्टाइल में ऐसा करना चाहूंगा। भ्रम के लिए खेद है
ओलिवियर पायेन

1

मूल रूप से, विंडो इवेंट को MVVM को नहीं सौंपा जा सकता है। सामान्य तौर पर, उपयोगकर्ता को "सेव: यस / नो / कैंसल" करने के लिए क्लोज बटन एक डायलॉग बॉक्स दिखाता है, और यह एमवीवीएम द्वारा प्राप्त नहीं किया जा सकता है।

आप OnClosing ईवेंट हैंडलर रख सकते हैं, जहाँ आप Model.Close.CanExecute () कॉल करते हैं और ईवेंट प्रॉपर्टी में बूलियन परिणाम सेट करते हैं। इसलिए CanExecute () कॉल के बाद यदि सही है, या OnClosed घटना में, मॉडल को कॉल करें। Close.Exute ()


1

मैंने इसके साथ बहुत परीक्षण नहीं किया है लेकिन यह काम करने लगता है। यहाँ मैं क्या लेकर आया हूँ:

namespace OrtzIRC.WPF
{
    using System;
    using System.Windows;
    using OrtzIRC.WPF.ViewModels;

    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        private MainViewModel viewModel = new MainViewModel();
        private MainWindow window = new MainWindow();

        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);

            viewModel.RequestClose += ViewModelRequestClose;

            window.DataContext = viewModel;
            window.Closing += Window_Closing;
            window.Show();
        }

        private void ViewModelRequestClose(object sender, EventArgs e)
        {
            viewModel.RequestClose -= ViewModelRequestClose;
            window.Close();
        }

        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            window.Closing -= Window_Closing;
            viewModel.RequestClose -= ViewModelRequestClose; //Otherwise Close gets called again
            viewModel.CloseCommand.Execute(null);
        }
    }
}

1
इस परिदृश्य में क्या होगा जहां वीएम समापन को रद्द करना चाहता है?
ट्राई ट्रान ट्रैन

1

हम इसके लिए AttachedCommandBehavior का उपयोग करते हैं। आप किसी भी कोड के पीछे किसी भी घटना को अपने व्यू मॉडल पर कमांड से जोड़ सकते हैं।

हम अपने संपूर्ण समाधान में इसका उपयोग करते हैं और लगभग शून्य कोड पीछे छोड़ देते हैं

http://marlongrech.wordpress.com/2008/12/13/attachedcommandbehavior-v2-aka-acb/


1

MVVM लाइट टूलकिट का उपयोग:

यह मानते हुए कि मॉडल में एक निकास आदेश है:

ICommand _exitCommand;
public ICommand ExitCommand
{
    get
    {
        if (_exitCommand == null)
            _exitCommand = new RelayCommand<object>(call => OnExit());
        return _exitCommand;
    }
}

void OnExit()
{
     var msg = new NotificationMessageAction<object>(this, "ExitApplication", (o) =>{});
     Messenger.Default.Send(msg);
}

यह देखने में प्राप्त होता है:

Messenger.Default.Register<NotificationMessageAction<object>>(this, (m) => if (m.Notification == "ExitApplication")
{
     Application.Current.Shutdown();
});

दूसरी ओर, मैं ViewModel के उदाहरण का उपयोग करके Closingईवेंट को संभालता हूं MainWindow:

private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{ 
    if (((ViewModel.MainViewModel)DataContext).CancelBeforeClose())
        e.Cancel = true;
}

CancelBeforeClose देखने के मॉडल की वर्तमान स्थिति की जाँच करता है और यदि बंद किया जाना चाहिए तो यह सच है।

आशा है कि यह किसी की मदद करता है।


-2
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
    {
        MessageBox.Show("closing");
    }

नमस्ते, कोड के साथ थोड़ा स्पष्टीकरण भी जोड़ें क्योंकि यह आपके कोड को समझने में मदद करता है। कोड केवल जवाब पर सिकोड़ी रहे हैं
भार्गव राव

सेशन ने स्पष्ट रूप से कहा कि वह इसके लिए कोड-पीछे इवेंट कोड का उपयोग करने में रुचि नहीं रखता था।
फेर गार्सिया
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.