WPF / MVVM Işık Araç Kiti ile pencere kapatma olayını işleme


145

ClosingSonunda bir onay mesajı görüntülemek ve / ve kapatmayı iptal etmek için penceremdeki olayı (bir kullanıcı 'X' düğmesini tıklattığında) işlemek istiyorum .

Bunun arkasındaki kodda nasıl yapılacağını biliyorum: Closingpencerenin olay abone sonra CancelEventArgs.Cancelözelliği kullanın .

Ama MVVM kullanıyorum, bu yüzden iyi bir yaklaşım olduğundan emin değilim.

Bence iyi yaklaşım Closingolayı CommandViewModel'imde bir cihaza bağlamak olacaktır .

Bunu denedim:

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

RelayCommandBenim ViewModel ile ilişkili ancak çalışmıyor (komutun kodu yürütülmez).


3
Ayrıca buna cevap vermek için güzel bir cevapla ilgileniyor.
Sekhat

3
Ben kodplex kodu indirdim ve hata ayıklama: "'System.ComponentModel.CancelEventArgs' türü 'System.Windows.RoutedEventArgs' yazmak için nesne yayınlanamadı." Eğer eğer cezayı çalışır yok CancelEventArgs istiyorum ama bu sorunuza cevap vermez ...
David hollinshead

Tetikleyicinize eklediğiniz denetimde bir Kapanma olayı olmadığından kodunuzun çalışmadığını tahmin ediyorum. Veri içeriğiniz bir pencere değil ... Muhtemelen Kapanma etkinliği olmayan bir ızgaraya veya başka bir şeye sahip bir veri şablonu. Bu durumda dbkk'ın cevabı en iyi cevaptır. Ancak, etkinlik kullanılabilir olduğunda Etkileşim / EventTrigger yaklaşımını tercih ederim.
NielW

Örneğin, bir Loaded olayında sahip olduğunuz kod düzgün çalışır.
NielW

Yanıtlar:


126

Ben sadece View yapıcısında işleyiciyi ilişkilendirir:

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

Ardından işleyiciyi şunlara ekleyin ViewModel:

using System.ComponentModel;

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

Bu durumda, daha fazla dolaylı (5 ekstra XAML artı Commanddeseni satırı) daha ayrıntılı bir desen kullanarak karmaşıklık dışında hiçbir şey kazanamazsınız .

"Sıfır kod arkası" mantra kendi içinde amaç değildir, amaç ViewModel'i View'den ayırmaktır . Olay Görünümün arkasındaki kodda bağlı olsa bile, Görünüme bağlı ViewModeldeğildir ve kapanış mantığı birim test edilebilir .


4
Ben bu çözümü seviyorum : sadece gizli bir düğmeye kanca :)
Benjol

3
MVVMLight kullanmayan ve ViewModel'i Kapanma olayı hakkında nasıl bilgilendireceğinizi arayan mvvm yeni başlayanlar için, dataContext öğesinin doğru şekilde nasıl ayarlanacağı ve ViewModel nesnesinin Görünümde nasıl elde edileceği ilginç olabilir. Görünümdeki ViewModel'e nasıl başvurulur? ve datacontext özelliğini kullanarak xaml'deki bir pencerede bir ViewModel'i nasıl ayarlayabilirim ... Birkaç saat sürdü, ViewModel'de basit bir pencere kapatma olayının nasıl ele alınabileceği.
MarkusEgle

18
Bu çözüm MVVM ortamında önemsizdir. Arkasındaki kod ViewModel hakkında bilmemelidir.
Jacob

2
@Jacob Sorunun, ViewModel'inizde ViewModel'i belirli bir UI uygulamasına bağlayan bir form olay işleyicisi almanızın daha fazla olduğunu düşünüyorum. Arkasındaki kodu kullanacaklarsa, CanExecute öğesini kontrol etmeli ve bunun yerine bir ICommand özelliğinde Execute () yöntemini çağırmalıdırlar.
Evil Pigeon

14
@Jacob Arkadaki kod, ViewModel üyeleri hakkında gayet iyi biliyor, sadece XAML kodunda olduğu gibi. Veya bir ViewModel özelliğine bağlama oluştururken ne yaptığınızı düşünüyorsunuz? Bu çözüm, MVVM için, arkasındaki kodun kendisinde, ancak ViewModel'de (ancak EvilPigeon'un önerdiği gibi bir ICommand kullanmakla birlikte, bağlayabileceğiniz için iyi bir fikir olabilir) işlemediğiniz sürece MVVM için mükemmel bir çözümdür. to)
almulo

81

Bu kod gayet iyi çalışıyor:

ViewModel.cs:

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

ve XAML'de:

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

varsayalım:

  • ViewModel DataContextana kaptan birine atanır .
  • xmlns:command="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.SL5"
  • xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

1
Unuttum: komutta olay bağımsız değişkenleri almak için PassEventArgsToCommand = "True" kullanın
Stas

2
+1 basit ve geleneksel yaklaşım. PRISM'e gitmek daha iyi olurdu.
Tri Q Tran

16
Bu, WPF ve MVVM'deki boşluk deliklerini vurgulayan bir senaryodur.
Damien

1
O ne söz gerçekten yararlı olacağını iiçinde <i:Interaction.Triggers>ve nasıl elde etmek.
Andrii Muzychuk

1
@Chiz, bu gibi kök öğesinde bildirmeniz gereken bir ad alanı: xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Stas

34

Bu seçenek daha da kolaydır ve belki sizin için uygundur. Görünüm Modeli yapıcısında, Ana Pencere kapanış etkinliğine şu şekilde abone olabilirsiniz:

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

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

Herşey gönlünce olsun.


Bu, bu sayıda bahsedilen diğer çözümler arasında en iyi çözümdür. Teşekkür ederim !
Jacob

Aradığım şey buydu. Teşekkürler!
Nikki Punjabi

20
... bu da ViewModel ve View arasında sıkı bir bağlantı oluşturur. -1.
PiotrK

6
Bu en iyi cevap değil. MVVM'yi kırıyor.
Safiron

1
@Craig Ana pencereye veya hangi pencere için kullanıldığına dair sert bir başvuru gerektirir. Çok daha kolay, ancak görünüm modelinin ayrıştırılmadığı anlamına geliyor. Bu, MVVM sinirlerini tatmin etmek ya da etmemek meselesi değildir, ancak MVVM paterninin çalışması için kırılması gerekiyorsa, onu kullanmanın bir anlamı yoktur.
Alex

16

ViewModel'de Pencere (veya herhangi bir olayı) hakkında bilgi edinmek istemiyorsanız, MVVM desenine göre bir cevap.

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

ViewModel'e arayüzü ve uygulamayı ekleyin

public bool OnClosing()
{
    bool close = true;

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

    return close;
}

Pencerede Kapanma olayını ekliyorum. Arkasındaki bu kod MVVM modelini bozmaz. View viewmodel hakkında bilgi sahibi olabilir!

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

Basit, temiz ve temiz. ViewModel'in görünümün özelliklerini bilmesine gerek yoktur, bu nedenle endişeler ayrı kalır.
Bernhard Hiller

bağlam her zaman boştur!
Shahid Od

@ShahidOd ViewModel'inizin IClosingyalnızca OnClosingyöntemi uygulamakla kalmaması , arayüzü uygulaması gerekir . Aksi takdirde DataContext as IClosingoyuncu başarısız olur ve geri dönernull
Erik White

10

Tanrım, bunun için burada bir sürü kod var gibi görünüyor. Yukarıdaki stas, minimum çaba için doğru yaklaşıma sahipti. İşte benim uyarlamasıdır (MVVMLight kullanarak ancak tanınabilir olmalıdır) ... Oh ve PassEventArgsToCommand = "True" olduğunu kesinlikle yukarıda belirtildiği gibi gerekli.

(Laurent Bugnion'a kredi 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> 

Görünüm modelinde:

///<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 içinde

    /// <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 aşağıdakine benzer, ancak temeldeRequestShutdown veya adı ne olursa olsun, uygulamanın kapatılıp kapatılmayacağına karar verir (bu pencereyi yine de nadiren kapatacaktır):

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

Asker STAS cevabını kullanmalıdır, ancak prizma kullanan ve galasoft / mvvmlight olmayan okuyucular için kullandıklarımı denemek isteyebilirler:

Pencere veya usercontrol vb. İçin üstteki tanımda ad alanını tanımlayın:

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

Ve bu tanımın hemen altında:

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

Görünüm modelinizde mülk:

public ICommand WindowClosing { get; private set; }

Vekil komutunuzu görünüm modelinizin yapıcısına ekleyin:

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

Son olarak, kontrol etmek istediğiniz pencerenin / pencerenin / her neyse:

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

3
Bu, closing olayını iptal etmek için gereken CancelEventArgs öğesine erişim sağlamaz. Aktarılan nesne, teknik olarak WindowClosing komutunun yürütüldüğü görünüm modeliyle aynı olan görünüm modelidir.
stephenbayer

4

App.xaml.cs dosyanızın içinde uygulamayı kapatıp kapatmayacağınıza karar vermenizi sağlayacak bir olay işleyici kullanmak cazip olurdu.

Örneğin, App.xaml.cs dosyanızda aşağıdaki kod gibi bir şey olabilir:

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

}

Sonra MainWindowViewModel kodunuzda aşağıdakilere sahip olabilirsiniz:

#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
Detaylı cevap için teşekkürler. Ancak, bunun sorunumu çözdüğünü sanmıyorum: Kullanıcı sağ üstteki 'X' düğmesini tıkladığında pencere kapanışını işlemem gerekiyor. Bunun arkasındaki kodda bunu yapmak kolay olurdu (sadece Closing olayını bağlar ve CancelEventArgs.Cancel'i false değerine ayarlayın) ama MVVM tarzında bunu yapmak istiyorum. Karışıklık için üzgünüm
Olivier Payen

1

Temel olarak, window olayı MVVM'ye atanmamış olabilir. Genel olarak, Kapat düğmesi kullanıcıdan "kaydet: evet / hayır / iptal" istemesini isteyen bir İletişim Kutusu gösterir ve bu MVVM tarafından gerçekleştirilemeyebilir.

Model.Close.CanExecute () öğesini çağırdığınız ve event özelliğinde boole sonucunu ayarladığınız OnClosing olay işleyicisini saklayabilirsiniz. Bu nedenle, true olursa CanExecute () çağrısından sonra VEYA OnClosed olayında Model.Close.Execute () yöntemini çağırın


1

Bununla çok fazla test yapmadım ama işe yarıyor gibi görünüyor. İşte ben geldim:

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'nin kapanışı iptal etmek istediği senaryoda burada ne olacak?
Tri Q Tran


1

MVVM Light Toolkit Kullanımı:

Görünüm modelinde bir Çıkış komutu olduğunu varsayarsak :

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

Bu görünümde alınır:

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

Öte yandan, ben ViewModel örneğini kullanarak Closingolay işlemek MainWindow:

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

CancelBeforeClose geçerli görünüm modelini kontrol eder ve kapanma durdurulması gerekiyorsa true değerini döndürür.

Umarım birine yardımcı olur.


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.