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


145

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

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

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

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

나는 그것을 시도했다 :

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

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


3
또한 이것에 대한 답변에 대한 좋은 답변에 관심이 있습니다.
Sekhat

3
코드 플렉스에서 코드를 다운로드하여 디버깅했습니다. " 'System.ComponentModel.CancelEventArgs'유형의 객체를 'System.Windows.RoutedEventArgs'유형으로 캐스트 할 수 없습니다." 당신이 경우 잘 작동 하지 않는 CancelEventArgs 원하지만 그 질문에 대답하지 않습니다 ...
데이비드 Hollinshead

트리거를 연결 한 컨트롤에 Closing 이벤트가 없기 때문에 코드가 작동하지 않는 것 같습니다. 데이터 컨텍스트가 창이 아닙니다. 아마도 그리드 또는 무언가가 있거나 닫는 이벤트가없는 데이터 템플릿 일 것입니다. 따라서 dbkk의 대답은이 경우 가장 좋은 대답입니다. 그러나 이벤트가 사용 가능한 경우 Interaction / EventTrigger 접근 방식을 선호합니다.
NielW

예를 들어 코드는 Loaded 이벤트에서 잘 작동합니다.
NielW

답변:


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의 5 개의 추가 라인 + Command패턴) 더 정교한 패턴을 사용하여 복잡성을 제외하고는 아무것도 얻을 수 없습니다 .

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


4
나는 솔루션을 좋아 한다 : 그냥 숨겨진 버튼에 연결 :)
Benjol

3
MVVMLight를 사용하지 않고 ViewModel에 Closing 이벤트에 대해 알리는 방법을 검색하는 mvvm 초보자의 경우 dataContext를 올바르게 설정하는 방법과 View에서 viewModel 객체를 얻는 방법에 대한 링크가 흥미로울 수 있습니다. View에서 ViewModel에 대한 참조를 얻는 방법은 무엇입니까? 그리고 나는 데이터 컨텍스트 속성을 사용하여 XAML에서 창에 뷰 모델을 설정하려면 어떻게 ... 그것은 나에게 간단한 창 닫기 이벤트는 뷰 모델에서 처리 할 수있는 방법을 몇 시간을했다.
MarkusEgle

18
이 솔루션은 MVVM 환경과 관련이 없습니다. 코드 뒤에 ViewModel에 대해 알아야합니다.
Jacob

2
@ Jacob 문제는 ViewModel에서 특정 UI 구현에 커플 링하는 폼 이벤트 핸들러를 얻는 것보다 더 큰 문제라고 생각합니다. 코드 숨김을 사용하려면 CanExecute를 확인한 다음 ICommand 속성에서 Execute ()를 대신 호출해야합니다.
Evil Pigeon

14
@Jacob 코드 숨김은 XAML 코드와 마찬가지로 ViewModel 멤버에 대해 잘 알 수 있습니다. 또는 ViewModel 속성에 바인딩을 만들 때 무엇을하고 있다고 생각하십니까? 이 솔루션은 코드 숨김 자체에서 닫는 논리를 처리하지 않지만 ViewModel에서 (EvilPigeon과 같은 ICommand를 사용하는 것이 좋지만 바인딩 할 수 있기 때문에) MVVM에 완벽하게 적합합니다. 그것에)
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으로 향하는 것이 더 좋습니다.
Tri Q Tran

16
이것은 WPF 및 MVVM의 간극을 강조하는 시나리오입니다.
Damien

1
무엇이 i들어 <i:Interaction.Triggers>있고 어떻게 얻는 지 언급하면 정말 도움이 될 것입니다.
Andrii Muzychuk 2016 년

1
@Chiz, 루트 요소에서 다음과 같이 선언해야하는 네임 스페이스입니다. 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
}

모두 제일 좋다.


이것은이 문제에서 언급 된 다른 것 중 가장 좋은 솔루션입니다. 감사합니다 !
Jacob

이것이 내가 찾던 것입니다. 감사!
Nikki Punjabi

20
... 이것은 ViewModel과 View 사이에 긴밀한 결합을 만듭니다. -1.
PiotrK

6
이것은 최선의 대답이 아닙니다. MVVM을 중단합니다.
Safiron

1
@Craig 기본 창 또는 사용중인 창에 대한 엄격한 참조가 필요합니다. 훨씬 쉽지만 뷰 모델이 분리되지 않았 음을 의미합니다. MVVM 머저리를 만족시키는 지 여부는 문제가되지 않지만 MVVM 패턴이 제대로 작동하지 않도록해야한다면 전혀 사용할 필요가 없습니다.
Alex

16

다음은 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();
    }
}

간단하고 깨끗하며 깨끗합니다. ViewModel은 뷰의 특정 사항을 알 필요가 없으므로 우려가 분리됩니다.
Bernhard Hiller

문맥은 항상 null입니다!
Shahid Od

@ShahidOd ViewModel IClosingOnClosing메소드를 구현하는 것이 아니라 인터페이스 를 구현해야합니다 . 그렇지 않으면 DataContext as IClosing캐스트가 실패하고 돌아옵니다null
Erik White

10

이런, 여기에 많은 코드가있는 것 같습니다. 위의 통계는 최소한의 노력으로 올바른 접근 방식을 가졌습니다. 여기 내 적응이 있습니다 (MVVMLight를 사용하지만 인식 할 수 있어야합니다) ... 아 그리고 PassEventArgsToCommand = "True" 는 위에 표시된대로 분명히 필요합니다.

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

8

질문자는 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
        }

3
닫기 이벤트를 취소하는 데 필요한 CancelEventArgs에 액세스 할 수 없습니다. 전달 된 객체는 뷰 모델이며, 기술적으로는 WindowClosing 명령이 실행되는 뷰 모델과 동일합니다.
stephenbayer

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을 false로 true로 설정 함). 그러나 MVVM 스타일 로이 작업을 수행하고 싶습니다. 혼란을 드려 죄송합니다
Olivier Payen

1

기본적으로 창 이벤트는 MVVM에 할당되지 않을 수 있습니다. 일반적으로 닫기 버튼은 사용자에게 "저장 : 예 / 아니오 / 취소"를 요청하는 대화 상자를 표시하며 MVVM에 의해 달성되지 않을 수 있습니다.

Model.Close.CanExecute ()를 호출하고 이벤트 속성에서 부울 결과를 설정하는 OnClosing 이벤트 핸들러를 유지할 수 있습니다. 따라서 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 툴킷 사용 :

뷰 모델에 종료 명령 이 있다고 가정합니다 .

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

안녕하세요, 코드를 이해하는 데 도움이되도록 코드와 함께 약간의 설명을 추가하십시오. 코드 만
답답해

op는이를 위해 코드 숨김 이벤트 코드 사용에 관심이 없다고 명시 적으로 언급했습니다.
Fer García
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.