web-dev-qa-db-de.com

Behandeln des Fensterschließereignisses mit WPF / MVVM Light Toolkit

Ich möchte das Ereignis "Schließen" (wenn ein Benutzer auf die obere rechte Schaltfläche "X" klickt) in meinem Fenster behandeln, um schließlich eine Bestätigungsmeldung anzuzeigen oder/und das Schließen abzubrechen.

Ich weiß, wie man das im Code-Behind macht: Abonniere das "Closing" -Ereignis des Fensters und benutze dann die "CancelEventArgs.Cancel" -Eigenschaft.

Aber ich benutze MVVM und bin mir nicht sicher, ob es der richtige Ansatz ist.

Ich denke, der gute Ansatz wäre, das Closing-Ereignis an einen Befehl in meinem ViewModel zu binden.

Ich habe das versucht:

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

Mit einem zugeordneten RelayCommand in meinem ViewModel, aber es funktioniert nicht (der Code des Befehls wird nicht ausgeführt).

135
Olivier Payen

Ich würde den Handler einfach dem View-Konstruktor zuordnen:

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

Fügen Sie dann den Handler zum ViewModel hinzu:

using System.ComponentModel;

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

In diesem Fall erhalten Sie nur Komplexität, wenn Sie ein komplexeres Muster mit mehr Indirektion verwenden (5 zusätzliche Zeilen XML plus Befehlsmuster).

Das "Zero Code-Behind" -Mantra ist nicht das eigentliche Ziel, sondern der Punkt ist ViewModel von der Ansicht entkoppeln. Auch wenn das Ereignis im Code-Behind der Ansicht gebunden ist, hängt das ViewModel nicht von der Ansicht und der Abschlusslogik ab kann Unit-getestet werden.

116
dbkk

Dieser Code funktioniert gut:

ViewModel.cs:

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

und in XAML:

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

vorausgesetzt, dass

  • ViewModel ist einem DataContext des Hauptcontainers zugeordnet.
  • xmlns:command="clr-namespace:GalaSoft.MvvmLight.Command;Assembly=GalaSoft.MvvmLight.Extras.SL5"
  • xmlns:i="clr-namespace:System.Windows.Interactivity;Assembly=System.Windows.Interactivity"
75
Stas

Diese Option ist noch einfacher und möglicherweise für Sie geeignet. In Ihrem View Model-Konstruktor können Sie das Abschlussereignis des Hauptfensters wie folgt abonnieren:

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

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

Alles Gute.

33
PILuaces

Hier ist eine Antwort gemäß dem MVVM-Muster, wenn Sie nichts über das Fenster (oder eines seiner Ereignisse) im ViewModel wissen möchten.

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

Fügen Sie im ViewModel die Schnittstelle und die Implementierung hinzu

public bool OnClosing()
{
    bool close = true;

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

    return close;
}

Im Fenster füge ich das Abschlussereignis hinzu. Dieser Code unterbricht das MVVM-Muster nicht. Die Ansicht kann über das Ansichtsmodell Bescheid wissen!

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

Meine Güte, scheint eine Menge Code dafür hier los zu sein. Stas oben hatte den richtigen Ansatz für minimalen Aufwand. Hier ist meine Anpassung (mit MVVMLight sollte aber erkennbar sein) ... Oh und das PassEventArgsToCommand = "True" ist unbedingt benötigt wie oben angegeben.

(Dank an Laurent Bugnion 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> 

Im Ansichtsmodell:

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

im ShutdownService

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

RequestShutdown sieht ungefähr so ​​aus, aber im Grunde entscheidet RequestShutdown oder wie auch immer es heißt, ob die Anwendung heruntergefahren werden soll oder nicht (wodurch das Fenster ohnehin fröhlich geschlossen wird):

...
...
...
    /// <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);
    }
    }
10
AllenM

Der Fragesteller sollte die STAS-Antwort verwenden, aber für Leser, die Prisma und kein Galasoft/MVVMlight verwenden, möchten sie möglicherweise versuchen, was ich verwendet habe:

Definieren Sie in der Definition oben für Fenster oder Benutzersteuerung usw. den Namespace:

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

Und genau unterhalb dieser Definition:

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

Eigenschaft in Ihrem Ansichtsmodell:

public ICommand WindowClosing { get; private set; }

Hängen Sie den delegate-Befehl in Ihrem Viewmodel-Konstruktor an:

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

Schließlich Ihren Code, den Sie erreichen möchten, über das Steuerelement/Fenster/was auch immer:

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

Ich wäre versucht, einen Ereignishandler in Ihrer App.xaml.cs-Datei zu verwenden, mit dem Sie entscheiden können, ob Sie die Anwendung schließen möchten oder nicht.

In Ihrer Datei "App.xaml.cs" könnte dann beispielsweise der folgende Code enthalten sein:

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

}

Dann könnten Sie in Ihrem MainWindowViewModel-Code Folgendes haben:

#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]
4
ChrisBD

Ich habe damit nicht viel getestet, aber es scheint zu funktionieren. Folgendes habe ich mir ausgedacht:

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
Brian Ortiz

MVVM Light Toolkit verwenden:

Angenommen, es gibt einen Exit Befehl im Ansichtsmodell:

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

Dies wird in der Ansicht erhalten:

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

Andererseits behandle ich das Ereignis Closing in MainWindow mit der Instanz von ViewModel:

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

CancelBeforeClose prüft den aktuellen Status des Ansichtsmodells und gibt true zurück, wenn das Schließen abgebrochen werden soll.

Hoffe es hilft jemandem.

1
Ron

Wir verwenden dazu AttachedCommandBehavior. Sie können ein beliebiges Ereignis an einen Befehl in Ihrem Ansichtsmodell anhängen und dabei den dahinter stehenden Code vermeiden.

Wir verwenden es in unserer gesamten Lösung und haben fast keinen Code mehr

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

1
Chris Adams

Grundsätzlich kann ein Fensterereignis nicht MVVM zugewiesen werden. Im Allgemeinen wird über die Schaltfläche Schließen ein Dialogfeld angezeigt, in dem der Benutzer gefragt wird, ob er speichern möchte: Ja/Nein/Abbrechen. Dies wird möglicherweise nicht von der MVVM erreicht.

Sie können den OnClosing-Ereignishandler beibehalten, in dem Sie Model.Close.CanExecute () aufrufen und das boolesche Ergebnis in der Ereigniseigenschaft festlegen. Rufen Sie nach dem Aufruf von CanExecute (), wenn true, OR im Ereignis OnClosed, Model.Close.Execute () auf.

1
Echtelion