Chiudi finestra da ViewModel


95

Sto creando un accesso utilizzando a window controlper consentire a un utente di accedere a WPFun'applicazione che sto creando.

Finora, ho creato un metodo che controlla se l'utente ha inserito le credenziali corrette per il usernamee passwordin a textboxnella schermata di accesso, bindingdue properties.

Ho ottenuto questo risultato creando un boolmetodo, in questo modo;

public bool CheckLogin()
{
    var user = context.Users.Where(i => i.Username == this.Username).SingleOrDefault();

    if (user == null)
    {
        MessageBox.Show("Unable to Login, incorrect credentials.");
        return false;
    }
    else if (this.Username == user.Username || this.Password.ToString() == user.Password)
    {
        MessageBox.Show("Welcome " + user.Username + ", you have successfully logged in.");

        return true;
    }
    else
    {
        MessageBox.Show("Unable to Login, incorrect credentials.");
        return false;
    }
}

public ICommand ShowLoginCommand
{
    get
    {
        if (this.showLoginCommand == null)
        {
            this.showLoginCommand = new RelayCommand(this.LoginExecute, null);
        }
        return this.showLoginCommand;
    }
}

private void LoginExecute()
{
    this.CheckLogin();
} 

Ho anche un commandche ho bindal mio pulsante in questo xamlmodo;

<Button Name="btnLogin" IsDefault="True" Content="Login" Command="{Binding ShowLoginCommand}" />

Quando inserisco il nome utente e la password, esegue il codice appropriato, che sia giusto o sbagliato. Ma come posso chiudere questa finestra dal ViewModel quando sia il nome utente che la password sono corretti?

Ho già provato a usare a dialog modalma non ha funzionato del tutto. Inoltre, all'interno del mio app.xaml, ho fatto qualcosa di simile al seguente, che carica prima la pagina di accesso, quindi, una volta vero, carica l'applicazione effettiva.

private void ApplicationStart(object sender, StartupEventArgs e)
{
    Current.ShutdownMode = ShutdownMode.OnExplicitShutdown;

    var dialog = new UserView();

    if (dialog.ShowDialog() == true)
    {
        var mainWindow = new MainWindow();
        Current.ShutdownMode = ShutdownMode.OnMainWindowClose;
        Current.MainWindow = mainWindow;
        mainWindow.Show();
    }
    else
    {
        MessageBox.Show("Unable to load application.", "Error", MessageBoxButton.OK);
        Current.Shutdown(-1);
    }
}

Domanda: come posso chiudere il login Window controlda ViewModel?

Grazie in anticipo.


Risposte:


150

Puoi passare la finestra al tuo ViewModel usando il CommandParameter. Vedi il mio esempio qui sotto.

Ho implementato un CloseWindowmetodo che prende un Windows come parametro e lo chiude. La finestra viene passata al ViewModel tramite CommandParameter. Nota che devi definire un x:Nameper la finestra che dovrebbe essere chiusa. Nella mia finestra XAML chiamo questo metodo tramite Commande passo la finestra stessa come parametro al ViewModel utilizzando CommandParameter.

Command="{Binding CloseWindowCommand, Mode=OneWay}" 
CommandParameter="{Binding ElementName=TestWindow}"

ViewModel

public RelayCommand<Window> CloseWindowCommand { get; private set; }

public MainViewModel()
{
    this.CloseWindowCommand = new RelayCommand<Window>(this.CloseWindow);
}

private void CloseWindow(Window window)
{
    if (window != null)
    {
       window.Close();
    }
}

Visualizza

<Window x:Class="ClientLibTestTool.ErrorView"
        x:Name="TestWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:localization="clr-namespace:ClientLibTestTool.ViewLanguages"
        DataContext="{Binding Main, Source={StaticResource Locator}}"
        Title="{x:Static localization:localization.HeaderErrorView}"
        Height="600" Width="800"
        ResizeMode="NoResize"
        WindowStartupLocation="CenterScreen">
    <Grid> 
        <Button Content="{x:Static localization:localization.ButtonClose}" 
                Height="30" 
                Width="100" 
                Margin="0,0,10,10" 
                IsCancel="True" 
                VerticalAlignment="Bottom" 
                HorizontalAlignment="Right" 
                Command="{Binding CloseWindowCommand, Mode=OneWay}" 
                CommandParameter="{Binding ElementName=TestWindow}"/>
    </Grid>
</Window>

Nota che sto usando il framework MVVM light, ma il principio si applica a ogni applicazione wpf.

Questa soluzione viola il pattern MVVM, perché il modello di visualizzazione non dovrebbe sapere nulla sull'implementazione dell'interfaccia utente. Se vuoi seguire rigorosamente il paradigma di programmazione MVVM devi astrarre il tipo di vista con un'interfaccia.

Soluzione conforme MVVM (Ex EDIT2)

l'utente Crono cita un punto valido nella sezione commenti:

Il passaggio dell'oggetto Window al modello di visualizzazione interrompe il pattern MVVM IMHO, perché costringe il tuo VM a sapere in cosa viene visualizzato.

Puoi risolvere questo problema introducendo un'interfaccia contenente un metodo di chiusura.

Interfaccia:

public interface ICloseable
{
    void Close();
}

Il tuo ViewModel refactoring sarà simile a questo:

ViewModel

public RelayCommand<ICloseable> CloseWindowCommand { get; private set; }

public MainViewModel()
{
    this.CloseWindowCommand = new RelayCommand<IClosable>(this.CloseWindow);
}

private void CloseWindow(ICloseable window)
{
    if (window != null)
    {
        window.Close();
    }
}

Devi fare riferimento e implementare l' ICloseableinterfaccia nella tua vista

Visualizza (codice sottostante)

public partial class MainWindow : Window, ICloseable
{
    public MainWindow()
    {
        InitializeComponent();
    }
}

Risposta alla domanda originale: (ex EDIT1)

Il tuo pulsante di accesso (aggiunto CommandParameter):

<Button Name="btnLogin" IsDefault="True" Content="Login" Command="{Binding ShowLoginCommand}" CommandParameter="{Binding ElementName=LoginWindow}"/>

Il tuo codice:

 public RelayCommand<Window> CloseWindowCommand { get; private set; } // the <Window> is important for your solution!

 public MainViewModel() 
 {
     //initialize the CloseWindowCommand. Again, mind the <Window>
     //you don't have to do this in your constructor but it is good practice, thought
     this.CloseWindowCommand = new RelayCommand<Window>(this.CloseWindow);
 }

 public bool CheckLogin(Window loginWindow) //Added loginWindow Parameter
 {
    var user = context.Users.Where(i => i.Username == this.Username).SingleOrDefault();

    if (user == null)
    {
        MessageBox.Show("Unable to Login, incorrect credentials.");
        return false;
    }
    else if (this.Username == user.Username || this.Password.ToString() == user.Password)
    {
        MessageBox.Show("Welcome "+ user.Username + ", you have successfully logged in.");
        this.CloseWindow(loginWindow); //Added call to CloseWindow Method
        return true;
    }
    else
    {
        MessageBox.Show("Unable to Login, incorrect credentials.");
        return false;
    }
 }

 //Added CloseWindow Method
 private void CloseWindow(Window window)
 {
     if (window != null)
     {
         window.Close();
     }
 }

1
Grazie per l'aggiornamento @Joel. Un'ultima domanda, a causa del metodo che accetta un parametro di Window, e quando chiamo quel metodo all'interno del mio comando, si aspetta un parametro, creerei un parametro Window locale che viene chiamato per il metodo, ad esempio; private void LoginExecute(){this.CheckLogin();}<- CheckLogin deve contenere un parametro.
WPF Noob

scusa non ho capito, potresti chiarire un po 'la tua domanda?
Joel

14
Se non ti piace dare un nome alle finestre, puoi anche associare il parametro in questo modo:CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
Jacco Dieleman

33
Il passaggio Windowdell'oggetto al modello di visualizzazione interrompe il pattern MVVM IMHO, perché costringe il tuo VM a sapere in cosa viene visualizzato. E se invece la visualizzazione fosse una scheda ancorata in un'interfaccia MDI? Il modo corretto per fare questo IMHO è passare un qualche tipo di interfaccia IUIHost che implementa un metodo Close e avere qualunque vista tu voglia mostrare alla tua macchina virtuale che lo implementa.
Crono

2
Va bene perché l'interfaccia nasconde l'implementazione concreta al ViewModel. Il ViewModel non sa nulla della vista tranne che implementa un metodo Close (). Pertanto, la visualizzazione potrebbe essere qualsiasi cosa: una finestra WPF, un modulo WinForms, un'applicazione UWP o anche una griglia WPF. Disaccoppia la vista dal viewmodel.
Joel

34

Rimanendo MVVM, penso che l'utilizzo di Behaviors da Blend SDK (System.Windows.Interactivity) o una richiesta di interazione personalizzata da Prism potrebbe funzionare davvero bene per questo tipo di situazione.

Se segui il percorso del comportamento, ecco l'idea generale:

public class CloseWindowBehavior : Behavior<Window>
{
    public bool CloseTrigger
    {
        get { return (bool)GetValue(CloseTriggerProperty); }
        set { SetValue(CloseTriggerProperty, value); }
    }

    public static readonly DependencyProperty CloseTriggerProperty =
        DependencyProperty.Register("CloseTrigger", typeof(bool), typeof(CloseWindowBehavior), new PropertyMetadata(false, OnCloseTriggerChanged));

    private static void OnCloseTriggerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var behavior = d as CloseWindowBehavior;

        if (behavior != null)
        {
            behavior.OnCloseTriggerChanged();
        }
    }

    private void OnCloseTriggerChanged()
    {
        // when closetrigger is true, close the window
        if (this.CloseTrigger)
        {
            this.AssociatedObject.Close();
        }
    }
}

Quindi nella tua finestra, legheresti semplicemente CloseTrigger a un valore booleano che verrebbe impostato quando desideri che la finestra si chiuda.

<Window x:Class="TestApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
        xmlns:local="clr-namespace:TestApp"
        Title="MainWindow" Height="350" Width="525">
    <i:Interaction.Behaviors>
        <local:CloseWindowBehavior CloseTrigger="{Binding CloseTrigger}" />
    </i:Interaction.Behaviors>

    <Grid>

    </Grid>
</Window>

Infine, il tuo DataContext / ViewModel avrebbe una proprietà che avresti impostato quando volevi che la finestra si chiudesse in questo modo:

public class MainWindowViewModel : INotifyPropertyChanged
{
    private bool closeTrigger;

    /// <summary>
    /// Gets or Sets if the main window should be closed
    /// </summary>
    public bool CloseTrigger
    {
        get { return this.closeTrigger; }
        set
        {
            this.closeTrigger = value;
            RaisePropertyChanged(nameof(CloseTrigger));
        }
    }

    public MainWindowViewModel()
    {
        // just setting for example, close the window
        CloseTrigger = true;
    }

    protected void RaisePropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

(imposta il tuo Window.DataContext = new MainWindowViewModel ())


Grazie per la risposta @Steve, hai menzionato l'associazione di CloseTrigger a un booleanvalore. Quando hai detto questo, intendevi per me creare un DataTriggerper ottenerlo?
WPF Noob

Mi spiace, avrei dovuto essere più esplicito: avrei una proprietà sul mio viewmodel (nell'esempio sopra, uno chiamato CloseTrigger) che verrebbe impostata su true, il che finirebbe per attivare il comportamento. Ho aggiornato la risposta
Steve Van Treeck il

Ha funzionato, ma ho dovuto cambiare il modo in cui la mia applicazione veniva caricata. Poiché stavo usando una finestra per la mia applicazione principale, ha ucciso anche tutte le finestre figlio. Grazie.
WPF Noob

L'impostazione di una proprietà su true per eseguire un'azione è puzzolente IMO.
Josh Noe

33

Di solito metto un evento sul modello di visualizzazione quando ho bisogno di farlo e poi lo collego a Window.Close()quando associo il modello di visualizzazione alla finestra

public class LoginViewModel
{
    public event EventHandler OnRequestClose;

    private void Login()
    {
        // Login logic here
        OnRequestClose(this, new EventArgs());
    }
}

E durante la creazione della finestra di accesso

var vm = new LoginViewModel();
var loginWindow = new LoginWindow
{
    DataContext = vm
};
vm.OnRequestClose += (s, e) => loginWindow.Close();

loginWindow.ShowDialog(); 

11
Il delegato anonimo viene scritto rapidamente, ma vale la pena notare che non è possibile annullare la registrazione dell'evento (il che può essere o meno un problema). Di solito è meglio con un gestore di eventi a tutti gli effetti.
Mathieu Guindon

Questo mi piace di più. È comunque difficile evitare elaborazioni speciali durante la visualizzazione della finestra (ad esempio Loaded, ContentRenderedper la finestra principale, i servizi di dialogo, ecc.), Aggiungerne un po 'tramite l'evento ViewModel è abbastanza pulito come per me. 3 righe di codice non richiedono alcuna soluzione di riutilizzabilità. PS: il MVVM puro è comunque per i nerd.
Sinatr

Ragazzo, questo mi ha aiutato.
Dimitri

Questo è molto meglio della risposta accettata, perché non interrompe il pattern MVVM.
Spook

22

potrebbe essere tardi, ma ecco la mia risposta

foreach (Window item in Application.Current.Windows)
{
    if (item.DataContext == this) item.Close();
}

1
perché questa non è la risposta effettiva?
user2529011

1
@ user2529011 alcuni, almeno, si lamenterebbero del fatto che il viewmodel non dovrebbe sapere nulla di Application.Current.Windows
supporta gusmally Monica

-1. Il modello di visualizzazione non dovrebbe sapere nulla della visualizzazione. Puoi anche scriverlo nel codice dietro per quella materia.
Alejandro

13

Bene, ecco qualcosa che ho usato in diversi progetti. Può sembrare un hack, ma funziona bene.

public class AttachedProperties : DependencyObject //adds a bindable DialogResult to window
{
    public static readonly DependencyProperty DialogResultProperty = 
        DependencyProperty.RegisterAttached("DialogResult", typeof(bool?), typeof(AttachedProperties), 
        new PropertyMetaData(default(bool?), OnDialogResultChanged));

    public bool? DialogResult
    {
        get { return (bool?)GetValue(DialogResultProperty); }
        set { SetValue(DialogResultProperty, value); }
    }

    private static void OnDialogResultChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var window = d as Window;
        if (window == null)
            return;

        window.DialogResult = (bool?)e.NewValue;
    }
}

Ora puoi eseguire il binding DialogResulta una VM e impostarne il valore di una proprietà. La Windowsi chiuderà, quando il valore è impostato.

<!-- Assuming that the VM is bound to the DataContext and the bound VM has a property DialogResult -->
<Window someNs:AttachedProperties.DialogResult={Binding DialogResult} />

Questo è un riassunto di ciò che è in esecuzione nel nostro ambiente di produzione

<Window x:Class="AC.Frontend.Controls.DialogControl.Dialog"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:DialogControl="clr-namespace:AC.Frontend.Controls.DialogControl" 
        xmlns:hlp="clr-namespace:AC.Frontend.Helper"
        MinHeight="150" MinWidth="300" ResizeMode="NoResize" SizeToContent="WidthAndHeight"
        WindowStartupLocation="CenterScreen" Title="{Binding Title}"
        hlp:AttachedProperties.DialogResult="{Binding DialogResult}" WindowStyle="ToolWindow" ShowInTaskbar="True"
        Language="{Binding UiCulture, Source={StaticResource Strings}}">
        <!-- A lot more stuff here -->
</Window>

Come puoi vedere, dichiaro xmlns:hlp="clr-namespace:AC.Frontend.Helper"prima lo spazio dei nomi e poi l'associazione hlp:AttachedProperties.DialogResult="{Binding DialogResult}".

Gli AttachedPropertyassomiglia a questo. Non è lo stesso che ho postato ieri, ma IMHO non dovrebbe avere alcun effetto.

public class AttachedProperties
{
    #region DialogResult

    public static readonly DependencyProperty DialogResultProperty =
        DependencyProperty.RegisterAttached("DialogResult", typeof (bool?), typeof (AttachedProperties), new PropertyMetadata(default(bool?), OnDialogResultChanged));

    private static void OnDialogResultChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var wnd = d as Window;
        if (wnd == null)
            return;

        wnd.DialogResult = (bool?) e.NewValue;
    }

    public static bool? GetDialogResult(DependencyObject dp)
    {
        if (dp == null) throw new ArgumentNullException("dp");

        return (bool?)dp.GetValue(DialogResultProperty);
    }

    public static void SetDialogResult(DependencyObject dp, object value)
    {
        if (dp == null) throw new ArgumentNullException("dp");

        dp.SetValue(DialogResultProperty, value);
    }

    #endregion
}

No, non è una domanda stupida. Metti semplicemente la dichiarazione del binding <Window />nell'elemento come ho illustrato nel mio snipped. Ero semplicemente troppo pigro per scrivere il resto (dichiarazioni dello spazio dei nomi, ecc.), Che di solito è anche dichiarato lì.
DHN

1
Pls si riferisce alla mia modifica. Ho pubblicato il codice di produzione, quindi sono sicuro che funzioni. Sembra un po 'diverso, ma dovrebbe funzionare anche il codice che ho postato ieri.
DHN

Grazie per aver chiarito tutto. Si è scoperto che stavo chiamando lo spazio dei nomi sbagliato: S. Devo solo creare un datatriggere assegnarlo al pulsante per farlo funzionare? Ancora una volta scusa per la domanda di nessuno.
WPF Noob

Grazie - beh, sono solo consapevole di me che faccio troppe domande che possono sembrare sciocche e stupide e fanno perdere tempo alle persone! Ma tornando alla mia domanda. Dopo tutto quello che hai detto, come faccio a chiudere la finestra? Usa un DataTrigger¬ and setting value vero`?
WPF Noob

1
Bene, questa è la parte che ti lascio. ; o) Pensa al DataContextdi Dialog. Mi aspetto che la VM impostata come DataContextfornisca un comando, che imposta la proprietà DialogResulto qualunque cosa tu abbia associato trueo false, in modo che si Dialogchiuda.
DHN

13

Modo semplice

public interface IRequireViewIdentification
{
    Guid ViewID { get; }
}

Implementare in ViewModel

public class MyViewVM : IRequireViewIdentification
{
    private Guid _viewId;

    public Guid ViewID
    {
        get { return _viewId; }
    }

    public MyViewVM()
    {
        _viewId = Guid.NewGuid();
    }
}

Aggiungi l'helper del window manager generale

public static class WindowManager
{
    public static void CloseWindow(Guid id)
    {
        foreach (Window window in Application.Current.Windows)
        {
            var w_id = window.DataContext as IRequireViewIdentification;
            if (w_id != null && w_id.ViewID.Equals(id))
            {
                window.Close();
            }
        }
    }
}

E chiudilo in questo modo in viewmodel

WindowManager.CloseWindow(ViewID);

Una soluzione molto bella.
DonBoitnott

ho cambiato un po 'il WindowManager per impostare il dialogresult alla chiusura del win public static void CloseWindow (Guid id, bool dialogResult) {foreach (Window window in Application.Current.Windows) {var w_id = window.DataContext as IRequireViewIdentification; if (w_id! = null && w_id.ViewID.Equals (id)) {window.DialogResult = dialogResult; window.Close (); }}} chiamalo come: WindowManager.CloseWindow (_viewId, true);
lebhero

Bella soluzione, anche se rende stretto l'accoppiamento tra viewmodel e WindowManager, che a sua volta è strettamente accoppiato con View(in termini di PresentationFramework). Sarebbe meglio se WindowManagerun servizio fosse passato a viewmodel tramite un'interfaccia. Quindi potresti (dire) migrare facilmente la tua soluzione su una piattaforma diversa.
Spook

4

Ecco un semplice esempio di utilizzo di MVVM Light Messenger invece di un evento. Il modello di visualizzazione invia un messaggio di chiusura quando si fa clic su un pulsante:

    public MainViewModel()
    {
        QuitCommand = new RelayCommand(ExecuteQuitCommand);
    }

    public RelayCommand QuitCommand { get; private set; }

    private void ExecuteQuitCommand() 
    {
        Messenger.Default.Send<CloseMessage>(new CloseMessage());
    }

Quindi viene ricevuto nel codice dietro la finestra.

    public Main()
    {   
        InitializeComponent();
        Messenger.Default.Register<CloseMessage>(this, HandleCloseMessage);
    }

    private void HandleCloseMessage(CloseMessage closeMessage)
    {
        Close();
    }

Potete per favore un consiglio, dove posso trovare l'implementazione di CloseMessage?
Roman O

CloseMessage è solo una classe vuota, utilizzata per identificare il tipo di messaggio inviato. (Potrebbe contenere anche informazioni complesse sui messaggi, che non sono necessarie qui.)
IngoB

4

Che ne dici di questo ?

ViewModel:

class ViewModel
{
    public Action CloseAction { get; set; }
    private void Stuff()
    {
       // Do Stuff
       CloseAction(); // closes the window
    }
}

Nel tuo ViewModel usa CloseAction () per chiudere la finestra proprio come nell'esempio sopra.

Visualizza:

public View()
{
    InitializeComponent();
    ViewModel vm = new ViewModel (); // this creates an instance of the ViewModel
    this.DataContext = vm; // this sets the newly created ViewModel as the DataContext for the View
    if (vm.CloseAction == null)
        vm.CloseAction = new Action(() => this.Close());
}

3

So che questo è un vecchio post, probabilmente nessuno scorrerebbe così lontano, so che non l'ho fatto. Quindi, dopo ore passate a provare cose diverse, ho trovato questo blog e il tipo l'ha ucciso. Il modo più semplice per farlo, l'ho provato e funziona a meraviglia.

Blog

Nel ViewModel:

...

public bool CanClose { get; set; }

private RelayCommand closeCommand;
public ICommand CloseCommand
{
    get
    {
        if(closeCommand == null)
        (
            closeCommand = new RelayCommand(param => Close(), param => CanClose);
        )
    }
}

public void Close()
{
    this.Close();
}

...

aggiungere una proprietà Action al ViewModel, ma definirla dal file code-behind della vista. Questo ci consentirà di definire dinamicamente un riferimento sul ViewModel che punta alla vista.

Sul ViewModel, aggiungeremo semplicemente:

public Action CloseAction { get; set; }

E nella vista, lo definiremo come tale:

public View()
{
    InitializeComponent() // this draws the View
    ViewModel vm = new ViewModel(); // this creates an instance of the ViewModel
    this.DataContext = vm; // this sets the newly created ViewModel as the DataContext for the View
    if ( vm.CloseAction == null )
        vm.CloseAction = new Action(() => this.Close());
}

Il collegamento è interrotto: /
gusmally supporta Monica

@gusmally sei sicuro? L'ho aperto normalmente, riprova jkshay.com/…
Serlok

2

Puoi creare un nuovo gestore di eventi nel ViewModel in questo modo.

public event EventHandler RequestClose;

    protected void OnRequestClose()
    {
        if (RequestClose != null)
            RequestClose(this, EventArgs.Empty);
    }

Quindi definire RelayCommand per ExitCommand.

private RelayCommand _CloseCommand;
    public ICommand CloseCommand
    {
        get
        {
            if(this._CloseCommand==null)
                this._CloseCommand=new RelayCommand(CloseClick);
            return this._CloseCommand;
        }
    }

    private void CloseClick(object obj)
    {
        OnRequestClose();
    }

Quindi In XAML file set

<Button Command="{Binding CloseCommand}" />

Imposta il DataContext nel file xaml.cs e iscriviti all'evento che abbiamo creato.

public partial class MainWindow : Window
{
    private ViewModel mainViewModel = null;
    public MainWindow()
    {
        InitializeComponent();
        mainViewModel = new ViewModel();
        this.DataContext = mainViewModel;
        mainViewModel.RequestClose += delegate(object sender, EventArgs args) { this.Close(); };
    }
}

Ho usato un MVVM Light Messenger al posto dell'evento.
Hamish Gunn

1

Il mio modo offerto è Declare event in ViewModel e utilizzare blend InvokeMethodAction come di seguito.

ViewModel di esempio

public class MainWindowViewModel : BindableBase, ICloseable
{
    public DelegateCommand SomeCommand { get; private set; }
    #region ICloseable Implementation
    public event EventHandler CloseRequested;        

    public void RaiseCloseNotification()
    {
        var handler = CloseRequested;
        if (handler != null)
        {
            handler.Invoke(this, EventArgs.Empty);
        }
    }
    #endregion

    public MainWindowViewModel()
    {
        SomeCommand = new DelegateCommand(() =>
        {
            //when you decide to close window
            RaiseCloseNotification();
        });
    }
}

L'interfaccia chiudibile è la seguente, ma non è necessario eseguire questa azione. ICloseable ti aiuterà a creare un servizio di visualizzazione generico, quindi se costruisci view e ViewModel per iniezione di dipendenze, ciò che puoi fare è

internal interface ICloseable
{
    event EventHandler CloseRequested;
}

Uso di ICloseable

var viewModel = new MainWindowViewModel();
        // As service is generic and don't know whether it can request close event
        var window = new Window() { Content = new MainView() };
        var closeable = viewModel as ICloseable;
        if (closeable != null)
        {
            closeable.CloseRequested += (s, e) => window.Close();
        }

E sotto c'è Xaml, puoi usare questo xaml anche se non implementi l'interfaccia, avrà solo bisogno del tuo modello di visualizzazione per sollevare CloseRquested.

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WPFRx"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" 
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions" 
xmlns:ViewModels="clr-namespace:WPFRx.ViewModels" x:Name="window" x:Class="WPFRx.MainWindow"
    mc:Ignorable="d"
    Title="MainWindow" Height="350" Width="525" 
d:DataContext="{d:DesignInstance {x:Type ViewModels:MainWindowViewModel}}">

<i:Interaction.Triggers>
    <i:EventTrigger SourceObject="{Binding Mode=OneWay}" EventName="CloseRequested" >
        <ei:CallMethodAction TargetObject="{Binding ElementName=window}" MethodName="Close"/>
    </i:EventTrigger>
</i:Interaction.Triggers>

<Grid>
    <Button Content="Some Content" Command="{Binding SomeCommand}" Width="100" Height="25"/>
</Grid>


1

È possibile utilizzare Messengerdal toolkit MVVMLight. nel tuo ViewModelinvia un messaggio come questo:
Messenger.Default.Send(new NotificationMessage("Close"));
poi nel tuo codice di Windows dietro, dopo InitializeComponent, registrati per quel messaggio in questo modo:

Messenger.Default.Register<NotificationMessage>(this, m=>{
    if(m.Notification == "Close") 
    {
        this.Close();
    }
   });

puoi trovare ulteriori informazioni sul toolkit MVVMLight qui: Toolkit MVVMLight su Codeplex

Si noti che non esiste una regola "nessun code-behind" in MVVM e che è possibile eseguire la registrazione per i messaggi in una vista code-behind.


0

È semplice. È possibile creare la propria classe ViewModel per Login - LoginViewModel. Puoi creare la vista var dialog = new UserView (); all'interno del tuo LoginViewModel. E puoi impostare Command LoginCommand nel pulsante.

<Button Name="btnLogin" IsDefault="True" Content="Login" Command="{Binding LoginCommand}" />

e

<Button Name="btnCancel" IsDefault="True" Content="Login" Command="{Binding CancelCommand}" />

Classe ViewModel:

public class LoginViewModel
{
    Window dialog;
    public bool ShowLogin()
    {
       dialog = new UserView();
       dialog.DataContext = this; // set up ViewModel into View
       if (dialog.ShowDialog() == true)
       {
         return true;
       }

       return false;
    }

    ICommand _loginCommand
    public ICommand LoginCommand
    {
        get
        {
            if (_loginCommand == null)
                _loginCommand = new RelayCommand(param => this.Login());

            return _loginCommand;
        }
    }

    public void CloseLoginView()
    {
            if (dialog != null)
          dialog.Close();
    }   

    public void Login()
    {
        if(CheckLogin()==true)
        {
            CloseLoginView();         
        }
        else
        {
          // write error message
        }
    }

    public bool CheckLogin()
    {
      // ... check login code
      return true;
    }
}

3
Sì, è anche una valida soluzione. Ma se vuoi restare fedele a MVVM e al disaccoppiamento di VM e visualizzazioni, romperai lo schema.
DHN

Ciao @misak, dopo aver provato a implementare la tua soluzione (come le altre risposte), genera un Object reference not set to an instance of an object.per il metodo CloseLoginView. Qualche suggerimento su come risolvere questo problema?
WPF Noob

@ WPFNoob - Riporto di nuovo questa soluzione. L'esempio funziona correttamente. Vuoi inviare una soluzione completa di Visual Studio tramite posta elettronica?
misak

@ WPFNoob - Vedo il problema. Stai creando un'istanza come var dialog = new UserView () ;. La parola chiave Clear var (istanza locale) sovrascrive l'istanza globale in LoginViewModel
misak

0

Questo è un modo in cui l'ho fatto abbastanza semplicemente:

YourWindow.xaml.cs

//In your constructor
public YourWindow()
{
    InitializeComponent();
    DataContext = new YourWindowViewModel(this);
}

YourWindowViewModel.cs

private YourWindow window;//so we can kill the window

//In your constructor
public YourWindowViewModel(YourWindow window)
{
    this.window = window;
}

//to close the window
public void CloseWindow()
{
    window.Close();
}

Non vedo nulla di sbagliato nella risposta che hai scelto, ho solo pensato che questo potesse essere un modo più semplice per farlo!


8
Ciò richiede che il tuo ViewModel conosca e faccia riferimento alla tua vista.
AndrewS

@AndrewS perché è così male?
thestephenstanton

9
Per seguire il modello MVVM, ViewModel non dovrebbe conoscere la vista.
MetalMikester

1
Per espandere questo aspetto, lo scopo di MVVM è rendere testabile la maggior parte dell'unità di codice della GUI. Le viste hanno un sacco di dipendenze che le rendono impossibili da testare. I ViewModels dovrebbero essere testabili in unità, ma se gli dai una dipendenza diretta dalla vista, non lo saranno.
ILMTitan

E per espandere ulteriormente questo aspetto, MVVM scritto correttamente consente di migrare facilmente la soluzione su una piattaforma diversa. In particolare, dovresti essere in grado di riutilizzare i tuoi viewmodels senza alcuna modifica. In questo caso, se spostassi la tua soluzione su Android, non funzionerebbe, perché Android non ha il concetto di Finestra. -1 per una soluzione rivoluzionaria MVVM.
Spook

0

Puoi trattare la finestra come un servizio (ad es. Servizio dell'interfaccia utente) e passare a viewmodel tramite un'interfaccia , come tale:

public interface IMainWindowAccess
{
    void Close(bool result);
}

public class MainWindow : IMainWindowAccess
{
    // (...)
    public void Close(bool result)
    {
        DialogResult = result;
        Close();
    }
}

public class MainWindowViewModel
{
    private IMainWindowAccess access;

    public MainWindowViewModel(IMainWindowAccess access)
    {
        this.access = access;
    }

    public void DoClose()
    {
        access.Close(true);
    }
}

Questa soluzione ha i maggiori vantaggi di passare la vista stessa a viewmodel senza avere lo svantaggio di rompere MVVM, perché sebbene la vista fisicamente sia passata a viewmodel, quest'ultimo ancora non conosce il primo, ne vede solo alcuni IMainWindowAccess. Quindi, ad esempio, se volessimo migrare questa soluzione su un'altra piattaforma, sarebbe solo questione di implementarla IMainWindowAccesscorrettamente, diciamo, per un file Activity.

Sto postando la soluzione qui per proporre un approccio diverso rispetto agli eventi (anche se in realtà è molto simile), perché sembra un po 'più semplice degli eventi da implementare (allegare / scollegare ecc.), Ma si allinea comunque bene con il pattern MVVM.


-1

Puoi chiudere la finestra corrente semplicemente usando il codice seguente:

Application.Current.Windows[0].Close();

6
Se hai più di una finestra questo potrebbe chiudere la finestra sbagliata.
Sasha

17
oh Dio! hai massacrato MVVM
Hossein Shahdoost il

-7

System.Environment.Exit (0); nel modello di visualizzazione funzionerebbe.


6
No, non lo farà. Uscirà dall'applicazione e non chiuderà la finestra corrente.
Tilak

questo ha risolto il mio problema, perché chiudendo la mainWindow (per me) == uscire dall'applicazione. Tutti i metodi proposti tranne questo avevano punti difficili quando veniva chiamato da thread diversi; ma a questo approccio non interessa chi è il thread chiamante :) era tutto ciò di cui avevo bisogno!
Hamed

BuAHahahAHahahAha scusa non ho potuto resistere
L.Trabacchin
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.