code

WPF/MVVM Light Toolkit을 사용한 창 닫기 이벤트 처리

starcafe 2023. 4. 23. 10:59
반응형

WPF/MVVM Light Toolkit을 사용한 창 닫기 이벤트 처리

그 일을 .Closing이벤트(사용자가 오른쪽 위 'X' 버튼을 클릭했을 때)를 클릭하여 최종적으로 확인 메시지를 표시하거나 닫기를 취소합니다.

비하인드로 .서브스크라이브에 .Closing합니다.CancelEventArgs.Cancel★★★★★★★★★★★★★★★★★★.

하지만 저는 MVVM을 사용하고 있기 때문에 이것이 좋은 방법인지 잘 모르겠습니다.

은 '를 하는 합니다.ClosingCommand참조할 수 있습니다.

저도 해봤어요.

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

있는 「」를 사용해 .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
}

이 보다 + 을이외에는 얻을 수 있는 .Command□□□□□□□□★

코드 배후에 제로(zero code-behind)라는 주문 자체가 목적이 아니라 View Model을 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>

다음을 전제로 합니다.

  • 은 View Model에 .DataContext츠미야
  • xmlns:command="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.SL5"
  • xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

이 옵션은 훨씬 더 쉽고 고객에게 적합할 수 있습니다.View Model 생성자에서 다음과 같이 기본 창 닫기 이벤트를 구독할 수 있습니다.

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

View Model에서 인터페이스와 구현을 추가합니다.

public bool OnClosing()
{
    bool close = true;

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

    return close;
}

Window에서 Closing 이벤트를 추가합니다.뒤에 있는 이 코드는 MVVM 패턴을 파괴하지 않습니다.뷰는 뷰 모델에 대해 알 수 있습니다!

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

이런, 여기에 많은 암호들이 있는 것 같네요.위의 Stas는 최소한의 노력으로 올바른 접근 방식을 취했습니다.여기 적응이 있습니다(MVMLight를 사용하지만 인식할 수 있어야 함).Oh 및 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);

셧다운 서비스로

    /// <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를 사용하지 않는 독자는 내가 사용한 것을 시험해 보고 싶을 것이다.

창 또는 사용자 제어 등에 대한 맨 위의 정의에서 네임스페이스를 정의합니다.

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

뷰 모델 생성자에서 delegate 명령 연결:

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

}

그런 다음 Main Window View Model 코드 내에서 다음을 사용할 수 있습니다.

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

기본적으로 윈도우 이벤트는 MVVM에 할당되지 않을 수 있습니다.일반적으로 닫기 버튼에는 사용자에게 "save: yes/no/cancel(저장: 예/아니오/취소)"을 요청하는 대화상자가 표시되며, MVVM에서는 이 작업이 수행되지 않을 수 있습니다.

모델을 호출하는 OnClosing 이벤트 핸들러를 유지할 수 있습니다.canExecute()를 닫고 이벤트 속성에 부울 결과를 설정합니다.따라서 CanExecute() 호출(true인 경우) 후 또는 OnClosed 이벤트에서 모델을 호출합니다.Close.Execute()

이걸로 테스트를 많이 해보진 않았지만 효과가 있는 것 같아요.제가 생각해낸 것은 다음과 같습니다.

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

여기에는 AttachedCommandBehavior를 사용합니다.모든 이벤트를 뷰 모델의 명령에 첨부하여 코드 뒤에 표시되지 않도록 할 수 있습니다.

솔루션 전체에서 사용하고 있으며, 코드 배후에 거의 없음

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

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

저는 른른른른른, 는는 on on를 취급하고 .Closing의 사건MainWindow모델 표시:

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

CancelBeforeClose는 현재 뷰 모델 상태를 확인하고 닫기를 중지해야 하는 경우 true를 반환합니다.

도움이 됐으면 좋겠는데

저는 이 투고에서 영감을 얻어 이 투고를 저만의 용도로 만들고 있는 라이브러리로 개조했습니다(단, 공개 장소는 https://github.com/RFBCodeWorks/MvvmControls입니다).

핸들러에 전달되는 '송신자' 및 '이벤트args'를 통해 View Model에 View가 어느 정도 노출되지만, 다른 종류의 처리에 필요할 경우에 대비하여 이 방법을 사용했습니다.예를 들어 핸들러가 ViewModel이 아니라 창 열기/닫기 시간을 기록하는 서비스인 경우 해당 서비스는 발신인에 대해 알고 싶어할 수 있습니다.VM이 View에 대해 알고 싶지 않은 경우 송신자 또는 arg를 검사하지 않습니다.

다음은 제가 생각해낸 관련 코드입니다. 코드 이면을 제거하고 xaml 내에서 바인딩을 허용합니다.

Behaviors:WindowBehaviors.IWindowClosingHandler="{Binding ElementName=ThisWindow, Path=DataContext}"
    /// <summary>
    /// Interface that can be used to send a signal from the View to the ViewModel that the window is closing
    /// </summary>
    public interface IWindowClosingHandler
    {
        /// <summary>
        /// Executes when window is closing
        /// </summary>
        void OnWindowClosing(object sender, System.ComponentModel.CancelEventArgs e);

        /// <summary>
        /// Occurs when the window has closed
        /// </summary>
        void OnWindowClosed(object sender, EventArgs e);

    }

    /// <summary>
    /// Attached Properties for Windows that allow MVVM to react to a window Loading/Activating/Deactivating/Closing
    /// </summary>
    public static class WindowBehaviors
    {
        #region < IWindowClosing >

        /// <summary>
        /// Assigns an <see cref="IWindowClosingHandler"/> handler to a <see cref="Window"/>
        /// </summary>
        public static readonly DependencyProperty IWindowClosingHandlerProperty =
            DependencyProperty.RegisterAttached(nameof(IWindowClosingHandler),
                typeof(IWindowClosingHandler),
                typeof(WindowBehaviors),
                new PropertyMetadata(null, IWindowClosingHandlerPropertyChanged)
                );

        /// <summary>
        /// Gets the assigned <see cref="IWindowLoadingHandler"/> from a <see cref="Window"/>
        /// </summary>
        public static IWindowClosingHandler GetIWindowClosingHandler(DependencyObject obj) => (IWindowClosingHandler)obj.GetValue(IWindowClosingHandlerProperty);

        /// <summary>
        /// Assigns an <see cref="IWindowClosingHandler"/> to a <see cref="Window"/>
        /// </summary>
        public static void SetIWindowClosingHandler(DependencyObject obj, IWindowClosingHandler value)
        {
            if (obj is not null and not Window) throw new ArgumentException($"{nameof(IWindowClosingHandler)} property can only be bound to a {nameof(Window)}");
            obj.SetValue(IWindowClosingHandlerProperty, value);
        }

        private static void IWindowClosingHandlerPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Window w = d as Window;
            if (w is null) return;
            if (e.NewValue != null)
            {
                w.Closing += W_Closing;
                w.Closed += W_Closed;
            }
            else
            {
                w.Closing -= W_Closing;
                w.Closed -= W_Closed;
            }
        }

        private static void W_Closed(object sender, EventArgs e)
        {
            GetIWindowClosingHandler(sender as DependencyObject)?.OnWindowClosed(sender, e);
        }

        private static void W_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            GetIWindowClosingHandler(sender as DependencyObject)?.OnWindowClosing(sender, e);
        }

        #endregion
    }

뒤에 있는 코드를 사용하면 쉽게 할 수 있습니다.Main.xaml에서는 다음과 같이 설정됩니다.Closing="Window_Closing"

Main.cs의 경우:

 public MainViewModel dataContext { get; set; }
 public ICommand CloseApp 
   {
      get { return (ICommand)GetValue(CloseAppProperty); }
      set { SetValue(CloseAppProperty, value); }
   }
public static readonly DependencyProperty CloseAppProperty =
DependencyProperty.Register("CloseApp", typeof(ICommand), typeof(MainWindow), new PropertyMetadata(null));

메인.온로드:

dataContext = DataContext as MainViewModel;

메인.창_닫기:

   if (CloseApp != null)
      CloseApp .Execute(this);

Main Window Model에서:

    public ICommand CloseApp => new CloseApp (this);

그리고 마지막으로:

Class Close App : ICommand { public event Handler Can Execute Changed ;

    private MainViewModel _viewModel;

    public CloseApp (MainViewModel viewModel)
    {
        _viewModel = viewModel;
    }


    public bool CanExecute(object parameter)
    {
        return true;
    }

    public void Execute(object parameter)
    {
        Console.WriteLine();
    }
}
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

반응형