Xử lý sự kiện đóng cửa sổ với Bộ công cụ ánh sáng WPF / MVVM


145

Tôi muốn xử lý Closingsự kiện (khi người dùng nhấp vào nút 'X' phía trên bên phải) của cửa sổ của tôi để cuối cùng hiển thị thông báo xác nhận hoặc / và hủy đóng.

Tôi biết làm thế nào để làm điều này trong mã phía sau: đăng ký vào Closingsự kiện của cửa sổ sau đó sử dụng thuộc CancelEventArgs.Canceltính.

Nhưng tôi đang sử dụng MVVM nên tôi không chắc đó là cách tiếp cận tốt.

Tôi nghĩ rằng cách tiếp cận tốt sẽ là liên kết Closingsự kiện với một CommandViewModel của tôi.

Tôi đã thử rằng:

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

Với một liên kết RelayCommandtrong ViewModel của tôi nhưng nó không hoạt động (mã của lệnh không được thực thi).


3
Cũng quan tâm đến câu trả lời tốt đẹp để trả lời cho điều này.
Sekhat

3
Tôi đã tải xuống mã từ codeplex và gỡ lỗi nó tiết lộ: "Không thể truyền đối tượng thuộc loại 'System.ComponentModel.CatteryEventArss' để gõ 'System.Windows.RoutedEventArss'." Nó hoạt động tốt nếu bạn không muốn
Hủy bỏEventArss

Tôi đoán mã của bạn không hoạt động vì điều khiển bạn đã kích hoạt không kích hoạt sự kiện Đóng. Bối cảnh dữ liệu của bạn không phải là một cửa sổ ... Đây có thể là một mẫu dữ liệu có lưới hoặc một cái gì đó, không có sự kiện Đóng. Vì vậy, câu trả lời của dbkk là câu trả lời tốt nhất trong trường hợp này. Tuy nhiên, tôi thích cách tiếp cận Tương tác / Sự kiện hơn khi sự kiện có sẵn.
NielW

Mã bạn có sẽ hoạt động tốt trong một sự kiện được tải, chẳng hạn.
NielW

Câu trả lời:


126

Tôi chỉ đơn giản là liên kết trình xử lý trong hàm tạo View:

MyWindow() 
{
    // Set up ViewModel, assign to DataContext etc.
    Closing += viewModel.OnWindowClosing;
}

Sau đó thêm trình xử lý vào ViewModel:

using System.ComponentModel;

public void OnWindowClosing(object sender, CancelEventArgs e) 
{
   // Handle closing logic, set e.Cancel as needed
}

Trong trường hợp này, bạn có được chính xác không có gì ngoại trừ sự phức tạp bằng cách sử dụng một mẫu phức tạp hơn với nhiều hướng hơn (5 dòng thêm của Commandmẫu XAML cộng ).

Câu thần chú "không có mã phía sau" không phải là mục tiêu của chính nó, vấn đề là tách rời ViewModel khỏi Chế độ xem . Ngay cả khi sự kiện bị ràng buộc theo mã phía sau của Chế độ xem, sự kiện ViewModelkhông phụ thuộc vào Chế độ xem và logic đóng có thể được kiểm tra đơn vị .


4
Tôi thích giải pháp này : chỉ cần móc vào một nút ẩn :)
Stewol

3
Đối với những người mới bắt đầu mvvm không sử dụng MVVMLight và tìm kiếm cách thông báo cho ViewModel về sự kiện Đóng, các liên kết cách thiết lập dataContext chính xác và cách lấy đối tượng viewModel trong Chế độ xem có thể thú vị. Làm cách nào để có được một tham chiếu đến ViewModel trong Chế độ xem? Làm cách nào để đặt ViewModel trên cửa sổ trong xaml bằng thuộc tính datacontext ... Tôi mất vài giờ, làm thế nào một sự kiện đóng cửa sổ đơn giản có thể được xử lý trong ViewModel.
MarkusEgle

18
Giải pháp này không liên quan trong môi trường MVVM. Mã phía sau không nên biết về ViewModel.
Jacob

2
@Jacob Tôi nghĩ vấn đề là nhiều hơn khi bạn có một trình xử lý sự kiện biểu mẫu trong ViewModel của bạn, kết hợp ViewModel với một triển khai UI cụ thể. Nếu họ sẽ sử dụng mã phía sau, họ nên kiểm tra CanExecute và sau đó gọi Execute () trên một thuộc tính ICommand thay thế.
Chim bồ câu ác

14
@Jacob Mã phía sau có thể biết về các thành viên ViewModel tốt, chỉ cần ike mã XAML làm được. Hoặc bạn nghĩ bạn đang làm gì khi tạo một thuộc tính Binding to ViewModel? Giải pháp này hoàn toàn tốt cho MVVM, miễn là bạn không xử lý logic đóng trong chính mã phía sau, nhưng trong ViewModel (mặc dù sử dụng ICommand, như EvilPigeon gợi ý, có thể là một ý tưởng hay vì bạn cũng có thể liên kết với nó)
almulo

81

Mã này hoạt động tốt:

ViewModel.cs:

public ICommand WindowClosing
{
    get
    {
        return new RelayCommand<CancelEventArgs>(
            (args) =>{
                     });
    }
}

và trong XAML:

<i:Interaction.Triggers>
    <i:EventTrigger EventName="Closing">
        <command:EventToCommand Command="{Binding WindowClosing}" PassEventArgsToCommand="True" />
    </i:EventTrigger>
</i:Interaction.Triggers>

giả sử rằng:

  • ViewModel được gán cho một DataContexttrong các thùng chứa chính.
  • xmlns:command="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.SL5"
  • xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

1
Quên: để nhận các đối số sự kiện trong lệnh, hãy sử dụng PassEventArgsToCommand = "True"
Stas

2
+1 cách tiếp cận đơn giản và thông thường. Sẽ tốt hơn nữa khi đi qua PRISM.
Tri Q Trần

16
Đây là một kịch bản làm nổi bật các lỗ hổng trong WPF và MVVM.
Damien

1
Nó sẽ thực sự hữu ích để đề cập đến những gì itrong <i:Interaction.Triggers>và làm thế nào để có được nó.
Andrii Muzychuk

1
@Chiz, đó là một không gian tên bạn nên khai báo trong phần tử gốc như thế này: xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Stas

34

Tùy chọn này thậm chí còn dễ dàng hơn, và có thể phù hợp với bạn. Trong hàm tạo Mô hình Chế độ xem của bạn, bạn có thể đăng ký sự kiện đóng Cửa sổ chính như thế này:

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

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

Tất cả tốt nhất.


Đây là giải pháp tốt nhất trong số những vấn đề khác được đề cập trong vấn đề này. Cảm ơn bạn !
Jacob

Đây là những gì tôi đang tìm kiếm. Cảm ơn!
Nikki Punjabi

20
... Và điều này tạo ra sự kết hợp chặt chẽ giữa ViewModel và View. -1.
PiotrK

6
Đây không phải là câu trả lời tốt nhất. Nó phá vỡ MVVM.
Safiron

1
@Craig Nó yêu cầu một tham chiếu cứng đến cửa sổ chính hoặc bất kỳ cửa sổ nào nó được sử dụng cho. Nó dễ dàng hơn nhiều, nhưng điều đó có nghĩa là mô hình khung nhìn không bị tách rời. Đây không phải là câu hỏi về việc có thỏa mãn các mọt sách MVVM hay không, nhưng nếu mẫu MVVM phải bị phá vỡ để làm cho nó hoạt động, thì không có lý do gì để sử dụng nó cả.
Alex

16

Dưới đây là câu trả lời theo mẫu MVVM nếu bạn không muốn biết về Cửa sổ (hoặc bất kỳ sự kiện nào của nó) trong ViewModel.

public interface IClosing
{
    /// <summary>
    /// Executes when window is closing
    /// </summary>
    /// <returns>Whether the windows should be closed by the caller</returns>
    bool OnClosing();
}

Trong ViewModel, thêm giao diện và triển khai

public bool OnClosing()
{
    bool close = true;

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

    return close;
}

Trong cửa sổ tôi thêm sự kiện kết thúc. Mã phía sau này không phá vỡ mẫu MVVM. Chế độ xem có thể biết về chế độ xem!

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

Đơn giản, rõ ràng và sạch sẽ. ViewModel không cần biết chi tiết cụ thể về chế độ xem, do đó mối quan tâm được tách biệt.
Bernhard Hiller

bối cảnh luôn là null!
Shahid Od

@ShahidOd ViewModel của bạn cần triển khai IClosinggiao diện, không chỉ thực hiện OnClosingphương thức. Nếu không, các DataContext as IClosingdiễn viên sẽ thất bại và trở lạinull
Erik White

10

Geez, có vẻ như rất nhiều mã đang diễn ra ở đây cho điều này. Stas ở trên đã có cách tiếp cận đúng cho nỗ lực tối thiểu. Đây là sự thích ứng của tôi (sử dụng MVVMLight nhưng có thể nhận ra được) ... Oh và PassEventArgsToCommand = "True" chắc chắn là cần thiết như đã nêu ở trên.

(ghi có vào Laurent Bugnion http://blog.galasoft.ch/archive/2009/10/18/clean-shutdown-in-silverlight-and-wpf-appluggest.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> 

Trong mô hình xem:

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

trong dịch vụ 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 trông giống như sau nhưng về cơ bảnRequestShutdown hoặc bất cứ thứ gì nó được đặt tên quyết định có nên tắt ứng dụng hay không (dù sao cũng sẽ đóng cửa sổ lại):

...
...
...
    /// <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

Người hỏi nên sử dụng câu trả lời STAS, nhưng đối với những độc giả sử dụng lăng kính và không có galasoft / mvvmlight, họ có thể muốn thử những gì tôi đã sử dụng:

Trong định nghĩa ở trên cùng cho cửa sổ hoặc điều khiển người dùng, vv xác định không gian tên:

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

Và ngay dưới định nghĩa đó:

<i:Interaction.Triggers>
        <i:EventTrigger EventName="Closing">
            <i:InvokeCommandAction Command="{Binding WindowClosing}" CommandParameter="{Binding}" />
        </i:EventTrigger>
</i:Interaction.Triggers>

Tài sản trong viewmodel của bạn:

public ICommand WindowClosing { get; private set; }

Đính kèm ủy nhiệm trong hàm tạo viewmodel của bạn:

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

Cuối cùng, mã của bạn mà bạn muốn tiếp cận gần với điều khiển / cửa sổ / bất cứ điều gì:

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

3
Điều này không cung cấp quyền truy cập vào CancEventArss cần thiết để hủy sự kiện kết thúc. Đối tượng được truyền là mô hình khung nhìn, về mặt kỹ thuật là mô hình khung nhìn tương tự mà lệnh WindowCloses đang được thực thi.
stephenbayer

4

Tôi sẽ được khuyến khích sử dụng một trình xử lý sự kiện trong tệp App.xaml.cs của bạn để cho phép bạn quyết định có nên đóng ứng dụng hay không.

Ví dụ: sau đó bạn có thể có một cái gì đó giống như đoạn mã sau trong tệp App.xaml.cs của bạn:

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

}

Sau đó, trong mã MainWindowViewModel của bạn, bạn có thể có những điều sau đây:

#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
Cảm ơn các câu trả lời chi tiết. Tuy nhiên, tôi không nghĩ rằng giải quyết vấn đề của mình: Tôi cần xử lý việc đóng cửa sổ khi người dùng nhấp vào nút 'X' phía trên bên phải. Thật dễ dàng để làm điều này trong mã phía sau (tôi chỉ liên kết sự kiện Đóng và đặt HủyEventArss.Cattery thành đúng) nhưng tôi muốn làm điều này theo kiểu MVVM. Xin lỗi vì sự nhầm lẫn
Olivier Payen

1

Về cơ bản, sự kiện cửa sổ có thể không được gán cho MVVM. Nói chung, nút Đóng hiển thị hộp thoại để yêu cầu người dùng "lưu: có / không / hủy" và điều này có thể không đạt được bởi MVVM.

Bạn có thể giữ trình xử lý sự kiện OnClose, nơi bạn gọi Model.Close.CanExecute () và đặt kết quả boolean trong thuộc tính sự kiện. Vì vậy, sau lệnh gọi CanExecute () nếu đúng, HOẶC trong sự kiện OnCloses, hãy gọi Model.Close.Execute ()


1

Tôi đã không thực hiện nhiều thử nghiệm với điều này nhưng nó dường như hoạt động. Đây là những gì tôi nghĩ ra:

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
Điều gì sẽ xảy ra ở đây trong kịch bản mà VM muốn hủy bỏ việc đóng cửa?
Tri Q Trần


1

Sử dụng Bộ công cụ ánh sáng MVVM:

Giả sử rằng có một lệnh Thoát trong mô hình xem:

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

Điều này được nhận trong chế độ xem:

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

Mặt khác, tôi xử lý Closingsự kiện MainWindowbằng cách sử dụng thể hiện của ViewModel:

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

CancelBeforeClose kiểm tra trạng thái hiện tại của mô hình xem và trả về true nếu đóng nên dừng.

Hy vọng nó sẽ giúp được ai đó.


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.