WPF / MVVM Light Toolkitでウィンドウを閉じるイベントを処理する


145

Closing最終的に確認メッセージを表示するか、終了をキャンセルするために、ウィンドウのイベント(ユーザーが右上の「X」ボタンをクリックしたとき)を処理したいと思います。

コードビハインドでこれを行う方法を知っています。Closingウィンドウのイベントにサブスクライブし、CancelEventArgs.Cancelプロパティを使用します。

しかし、私はMVVMを使用しているので、それが良いアプローチであるかどうかはわかりません。

ClosingイベントをCommandViewModelのにバインドするのが良い方法だと思います。

私はそれを試しました:

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

RelayCommandViewModelに関連付けられていますが、機能しません(コマンドのコードは実行されません)。


3
これに答えるいい答えにも興味があります。
セハト

3
コードプレックスからコードをダウンロードしてデバッグしたところ、「タイプ「System.ComponentModel.CancelEventArgs」のオブジェクトをタイプ「System.Windows.RoutedEventArgs」にキャストできません。」CancelEventArgsが必要ない場合でも問題なく機能しますが、それでも質問の答えにはなりません...
David Hollinshead

トリガーをアタッチしたコントロールにClosingイベントがないため、コードが機能しないと思います。データコンテキストはウィンドウではありません...グリッドなどのデータテンプレートであり、Closingイベントがありません。したがって、この場合はdbkkの答えが最良の答えです。ただし、イベントが使用可能な場合は、Interaction / EventTriggerアプローチを使用します。
NielW 2014年

あなたが持っているコードは、例えば、Loadedイベントでうまく機能します。
NielW 2014年

回答:


126

ハンドラーをViewコンストラクターに関連付けるだけです。

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
}

この場合、より間接的な(XAMLとCommandパターンの5行が追加された)より複雑なパターンを使用することで、複雑さ以外はまったく何も得られません。

「ゼロコードビハインド」のマントラはそれ自体が目標ではなく、目的はViewModelをViewから切り離すことです。イベントがビューのコードビハインドでバインドされている場合でも、イベントはビューにViewModel依存せず、終了ロジックを単体テストできます


4
私はこのソリューションが好きです:隠しボタンにフックするだけです:)
Benjol 2013

3
mvvm初心者がMVVMLightを使用せず、ViewModelにClosingイベントを通知する方法を検索する場合、dataContextを正しく設定する方法とViewでviewModelオブジェクトを取得する方法のリンクが興味深いかもしれません。ビューでViewModelへの参照を取得するにはどうすればよいですか?そして、datacontextプロパティを使用して、xamlのウィンドウにViewModelを設定するにはどうすればよいですか。ViewModelで単純なウィンドウ終了イベントを処理するには、数時間かかりました。
MarkusEgle 2014

18
このソリューションは、MVVM環境では無関係です。背後のコードは、ViewModelを知らないはずです。
Jacob

2
@Jacob問題は、ViewModelを特定のUI実装に結合するViewModelでフォームイベントハンドラーを取得することだけではないと思います。コードビハインドを使用する場合は、CanExecuteを確認してから、ICommandプロパティでExecute()を呼び出す必要があります。
イービルピジョン

14
@Jacobコードビハインドは、XAMLコードが行うように、ViewModelメンバーについて問題なく認識できます。または、ViewModelプロパティへのバインディングを作成するときに何をしていると思いますか?このソリューションは、コードビハインド自体で終了ロジックを処理しない限り、MVVMには完全に適切ですが、ViewModelで(EvilPigeonが示唆するようにICommandを使用することもできますが、バインドすることもできるためです。それに)
almulo 2015

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に向かう方が良いでしょう。
Tri Q Tran

16
これは、WPFとMVVMのギャップホールを強調する1つのシナリオです。
ダミアン

1
何がi入っ<i:Interaction.Triggers>ているか、どうやってそれを手に入れるかについて言及することは本当に役に立ちます。
Andrii Muzychuk 2016年

1
@Chiz、それはあなたがこのようなルート要素で宣言する必要があり、名前空間があります: xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
スタース

34

このオプションはさらに簡単で、おそらくあなたに適しています。ビューモデルコンストラクターでは、次のようにメインウィンドウの終了イベントをサブスクライブできます。

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

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

ではごきげんよう。


これは、この問題で言及されている他のソリューションの中で最高のソリューションです。ありがとうございました !
Jacob

これは私が探していたものです。ありがとう!
Nikki Punjabi

20
...これにより、ViewModelとViewの間に密結合が作成されます。-1。
PiotrK 2016

6
これは最良の答えではありません。MVVMが壊れます。
サフィロン2017年

1
@Craigメインウィンドウまたはそれが使用されているウィンドウへのハードリファレンスが必要です。はるかに簡単ですが、ビューモデルが分離されていないことを意味します。それはMVVMオタクを満たすかどうかの問題ではありませんが、MVVMパターンを機能させるために破壊する必要がある場合、それを使用してもまったく意味がありません。
Alex、

16

ViewModelのウィンドウ(またはそのイベントのいずれか)について知りたくない場合のMVVMパターンによる答えは次のとおりです。

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

ウィンドウで、Closingイベントを追加します。このコードビハインドはMVVMパターンを壊しません。ビューはビューモデルについて知ることができます!

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

シンプル、クリア、そしてクリーン。ViewModelはビューの詳細を知っている必要はないため、懸念は分離されたままです。
Bernhard Hiller、2015年

コンテキストは常にnullです。
Shahid Od

@ShahidOd ViewModelはIClosingOnClosingメソッドを実装するだけでなく、インターフェースを実装する必要があります。そうでDataContext as IClosingない場合、キャストは失敗して戻りますnull
エリックホワイト

10

Geez、このためにここで行われている多くのコードのようです。上記のStaは、最小限の労力で適切なアプローチをとっていました。これが私の適応です(MVVMLightを使用していますが、認識できるはずです)...ああ、上記のようにPassEventArgsToCommand = "True"間違いなく必要です。

(Lauren 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> 

ビューモデルで:

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

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は次のようになりますが、基本的には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

質問者はSTAS回答を使用する必要がありますが、prismを使用し、galasoft / mvvmlightを使用しない読者には、私が使用したものを試してみてください。

ウィンドウやユーザーコントロールなどの上部の定義で、名前空間を定義します。

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

ビューモデルコンストラクターにdelegatecommandをアタッチします。

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

最後に、コントロール/ウィンドウ/その他のどこにでも到達したいコード:

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

3
これは、終了イベントをキャンセルするために必要なCancelEventArgsへのアクセスを提供しません。渡されるオブジェクトはビューモデルです。これは、厳密には、WindowClosingコマンドの実行元であるビューモデルと同じです。
stephenbayer 2015

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」ボタンをクリックしたときにウィンドウが閉じるのを処理する必要があります。コードビハインドでこれを行うのは簡単です(Closingイベントをリンクして、CancelEventArgs.Cancelをtrueまたはfalseに設定するだけです)が、これはMVVMスタイルで行いたいです。混乱して申し訳ありません
Olivier Payen

1

基本的に、ウィンドウイベントはMVVMに割り当てられない場合があります。通常、閉じるボタンはダイアログボックスを表示してユーザーに「保存:はい/いいえ/キャンセル」を要求します。これは、MVVMでは実現できない場合があります。

OnClosingイベントハンドラーを保持し、Model.Close.CanExecute()を呼び出して、ブール型の結果をイベントプロパティに設定できます。したがって、CanExecute()呼び出しの後にtrueの場合、またはOnClosedイベントで、Model.Close.Execute()を呼び出します。


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
VMが終了をキャンセルしたい場合、ここで何が起こりますか?
Tri Q Tran


1

MVVM Light Toolkitの使用:

ビューモデルにExitコマンドがあると仮定します。

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 ビューモデルの現在の状態をチェックし、閉じるのを停止する必要がある場合はtrueを返します。

それが誰かを助けることを願っています。


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

こんにちは。コードを理解するのに役立つので、コードとともに少し説明を追加してください。コードのみの回答は眉を
ひそめる

オペレーションは、コードビハインドイベントコードをこれに使用することに興味がないことを明示的に述べました。
Fer Garcia
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.