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 IClosing은 OnClosing메소드를 구현하는 것이 아니라 인터페이스 를 구현해야합니다 . 그렇지 않으면 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.