Programing

WPF / MVVM Light Toolkit으로 창 닫기 이벤트 처리

crosscheck 2020. 6. 22. 07:58
반응형

WPF / MVVM Light Toolkit으로 창 닫기 이벤트 처리


결국 확인 메시지를 표시하거나 종료를 취소하기 위해 창의 "닫기"이벤트 (사용자가 오른쪽 상단 'X'단추를 클릭 할 때)를 처리하고 싶습니다.

코드 숨김에서이 ​​작업을 수행하는 방법을 알고 있습니다. 창의 "Closing"이벤트를 구독 한 다음 "CancelEventArgs.Cancel"속성을 사용하십시오.

그러나 MVVM을 사용하고 있으므로 좋은 접근 방법인지 잘 모르겠습니다.

좋은 접근 방식은 Closing 이벤트를 ViewModel의 Command에 바인딩하는 것입니다.

나는 그것을 시도했다 :

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

내 ViewModel에 연결된 RelayCommand가 있지만 작동하지 않습니다 (명령 코드가 실행되지 않음).


처리기를 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
}

이 경우 더 간접적 인 (5 개의 추가 라인 XML+ 명령 패턴) 더 정교한 패턴을 사용하여 복잡성을 제외하고는 아무것도 얻을 수 없습니다 .

"제로 코드 비하인드"만트라 자체는 목표가 아니며, 요점은 ViewModel과 View분리하는 것 입니다. 이벤트가 View의 코드 숨김에 바인딩되어 있어도 View에 ViewModel의존하지 않으며 닫기 로직 을 단위 테스트 할 수 있습니다 .


이 코드는 잘 작동합니다.

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"

이 옵션은 훨씬 쉬우 며 아마도 적합 할 것입니다. 뷰 모델 생성자에서 다음과 같이 메인 창 닫기 이벤트를 구독 할 수 있습니다.

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

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

모두 제일 좋다.


다음은 ViewModel의 Window (또는 해당 이벤트)에 대해 알고 싶지 않은 경우 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();
    }
}

이런, 여기에 많은 코드가있는 것 같습니다. 위의 통계는 최소한의 노력으로 올바른 접근 방식을 가졌습니다. 여기 내 적응은 (MVVMLight를 사용하고 있지만 인식해야한다) ... 아와 PassEventArgsToCommand = "진정한"가 되어 확실히 상기 한 바와 같이 필요합니다.

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

뷰 모델에서 :

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

질문자는 STAS 답변을 사용해야하지만 프리즘을 사용하고 galasoft / mvvmlight를 사용하지 않는 독자에게는 내가 사용한 것을 시도해 볼 수 있습니다.

창 또는 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; }

viewmodel 생성자에 delegatecommand를 첨부하십시오.

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

마지막으로 제어 / 창 / 무엇을 닫을 것인지에 대한 코드 :

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

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

}

Then within your MainWindowViewModel code you could have the following:

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

Basically, window event may not be assigned to MVVM. In general, the Close button show a Dialog box to ask the user "save : yes/no/cancel", and this may not be achieved by the MVVM.

You may keep the OnClosing event handler, where you call the Model.Close.CanExecute() and set the boolean result in the event property. So after the CanExecute() call if true, OR in the OnClosed event, call the Model.Close.Execute()


I haven't done much testing with this but it seems to work. Here's what I came up with:

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

We use AttachedCommandBehavior for this. You can attach any event to a command on your view model avoiding any code behind.

We use it throughout our solution and have almost zero code behind

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


Using MVVM Light Toolkit:

Assuming that there is an Exit command in view model:

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

This is received in the view:

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

On the other hand, I handle Closing event in MainWindow, using the instance of ViewModel:

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

CancelBeforeClose checks the current state of view model and returns true if closing should be stopped.

Hope it helps someone.


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

참고URL : https://stackoverflow.com/questions/3683450/handling-the-window-closing-event-with-wpf-mvvm-light-toolkit

반응형