Cerrar ventana de ViewModel

95

Estoy creando un inicio de sesión usando un window controlpara permitir que un usuario inicie sesión en unWPF aplicación que estoy creando.

Hasta ahora, he creado un método que verifica si el usuario ha ingresado las credenciales correctas para usernamey passworden textboxla pantalla de inicio de sesión, bindingdos properties.

Lo he logrado creando un boolmétodo, así;

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

También tengo un commandque yo binda mi botón dentro de lo xamlsimilar;

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

Cuando ingreso el nombre de usuario y la contraseña, ejecuta el código apropiado, ya sea correcto o incorrecto. Pero, ¿cómo puedo cerrar esta ventana desde ViewModel cuando tanto el nombre de usuario como la contraseña son correctos?

Anteriormente intenté usar un dialog modalpero no funcionó del todo. Además, dentro de mi app.xaml, he hecho algo como lo siguiente, que primero carga la página de inicio de sesión y luego, una vez verdadera, carga la aplicación real.

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

Pregunta: ¿Cómo puedo cerrar el inicio Window controlde sesión desde ViewModel?

Gracias por adelantado.

WPFNoob
fuente

Respuestas:

150

Puede pasar la ventana a su ViewModel usando el CommandParameter. Vea mi ejemplo a continuación.

Implementé un CloseWindowmétodo que toma un Windows como parámetro y lo cierra. La ventana se pasa a ViewModel a través de CommandParameter. Tenga en cuenta que debe definir un x:Namepara la ventana que debe estar cerrada. En mi ventana XAML, llamo a este método a través de Commandy paso la propia ventana como un parámetro al ViewModel usando 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();
    }
}

Ver

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

Tenga en cuenta que estoy usando el marco ligero MVVM, pero el principal se aplica a todas las aplicaciones wpf.

Esta solución viola el patrón MVVM, porque el modelo de vista no debería saber nada sobre la implementación de la interfaz de usuario. Si desea seguir estrictamente el paradigma de programación MVVM, debe abstraer el tipo de vista con una interfaz.

Solución conforme a MVVM (antes EDIT2)

el usuario Crono menciona un punto válido en la sección de comentarios:

Pasar el objeto Window al modelo de vista rompe el patrón MVVM en mi humilde opinión, porque obliga a su máquina virtual a saber en qué se está viendo.

Puede solucionar este problema introduciendo una interfaz que contiene un método de cierre.

Interfaz:

public interface ICloseable
{
    void Close();
}

Su ViewModel refactorizado se verá así:

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

Tienes que hacer referencia e implementar la ICloseableinterfaz en tu vista.

Ver (código subyacente)

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

Respuesta a la pregunta original: (anteriormente EDIT1)

Su botón de inicio de sesión (parámetro de comando agregado):

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

Tu codigo:

 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();
     }
 }
Joel
fuente
1
Gracias por la actualización @Joel. Una última pregunta, debido a que el método toma un parámetro de Window, y cuando llamo a ese método dentro de mi comando, espera un parámetro, ¿crearía un parámetro de Windows local que se llame para el método, por ejemplo; private void LoginExecute(){this.CheckLogin();}<- CheckLogin necesita incorporar un parámetro.
WPFNoob
lo siento, no lo entiendo, ¿podrías aclarar un poco tu pregunta?
Joel
14
Si no le gusta nombrar sus ventanas, también puede vincular el parámetro de esta manera:CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
Jacco Dieleman
33
Pasar el Windowobjeto al modelo de vista rompe el patrón MVVM en mi humilde opinión, porque obliga a su máquina virtual a saber en qué se está viendo. ¿Qué pasa si la vista fuera una pestaña acoplada en una interfaz MDI? La forma correcta de hacer esto en mi humilde opinión es pasar algún tipo de interfaz IUIHost que implemente un método Close, y tener la vista que desee que muestre que su vm lo implementa.
Crono
2
Está bien porque la interfaz oculta la implementación concreta al ViewModel. ViewModel no sabe nada sobre la vista, excepto que implementa un método Close (). Por lo tanto, la vista podría ser cualquier cosa: una ventana de WPF, un formulario de WinForms, una aplicación para UWP o incluso una cuadrícula de WPF. Desacopla la vista del modelo de vista.
Joel
34

Siguiendo con MVVM, creo que el uso de Behaviors del Blend SDK (System.Windows.Interactivity) o una solicitud de interacción personalizada de Prism podría funcionar muy bien para este tipo de situación.

Si sigue la ruta del comportamiento, esta es la idea general:

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

Luego, en su ventana, simplemente vincularía CloseTrigger a un valor booleano que se establecería cuando quisiera que se cerrara la ventana.

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

Finalmente, su DataContext / ViewModel tendría una propiedad que establecería cuando quisiera que la ventana se cerrara así:

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

(establezca su Window.DataContext = new MainWindowViewModel ())

Steve Van Treeck
fuente
Gracias por la respuesta @Steve, mencionaste acerca de vincular CloseTrigger a un booleanvalor. Cuando dijiste eso, ¿quisiste que creara un DataTriggerpara lograrlo?
WPFNoob
Lo siento, debería haber sido más explícito: tendría una propiedad en mi modelo de vista (en el ejemplo anterior, una llamada CloseTrigger) que se establecería en verdadero, lo que terminaría desencadenando el comportamiento. Actualicé la respuesta
Steve Van Treeck
Esto funcionó, pero tuve que cambiar la forma en que se cargaba mi aplicación. Como estaba usando una ventana para mi aplicación principal, también mató a todas las ventanas secundarias. Gracias.
WPFNoob
Establecer una propiedad en true para realizar una acción huele mal en mi opinión.
Josh Noe
33

Por lo general, coloco un evento en el modelo de vista cuando necesito hacer esto y luego lo conecto al Window.Close()al vincular el modelo de vista a la ventana

public class LoginViewModel
{
    public event EventHandler OnRequestClose;

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

Y al crear la ventana de inicio de sesión

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

loginWindow.ShowDialog(); 
ChrisO
fuente
11
El delegado anónimo se escribe rápidamente, pero vale la pena señalar que el evento no se puede cancelar (lo que puede ser un problema o no). Por lo general, es mejor con un controlador de eventos completo.
Mathieu Guindon
Esto es lo que más me gusta. De todos modos, es difícil evitar un procesamiento especial al mostrar la ventana (por ejemplo Loaded, ContentRenderedpara la ventana principal, los servicios de diálogo, etc.), agregar un poco a través del evento ViewModel es bastante limpio para mí. 3 líneas de código realmente no necesitan ninguna solución de reutilización. PD: MVVM puro es para nerds de todos modos.
Sinatr
Chico, esto me ayudó.
Dimitri
Esto es mucho mejor que la respuesta aceptada, porque no rompe el patrón MVVM.
Spook
22

puede que sea tarde, pero esta es mi respuesta

foreach (Window item in Application.Current.Windows)
{
    if (item.DataContext == this) item.Close();
}
Ahmed Abuelnour
fuente
1
¿Por qué esta no es la respuesta real?
user2529011
1
@ user2529011 algunos, al menos, se quejarían de que el modelo de vista no debería saber nada sobre Application.Current.Windows
gusmally apoya a Monica
-1. El modelo de vista no debería saber nada sobre la vista. También puede escribirlo en el código subyacente.
Alejandro
13

Bueno, aquí hay algo que usé en varios proyectos. Puede parecer un truco, pero funciona bien.

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

Ahora puede vincularse DialogResulta una máquina virtual y establecer su valor de propiedad. Se Windowcerrará cuando se establezca el valor.

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

Este es un resumen de lo que se está ejecutando en nuestro entorno de producción.

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

Como puede ver, xmlns:hlp="clr-namespace:AC.Frontend.Helper"primero declaro el espacio de nombres y luego el enlace hlp:AttachedProperties.DialogResult="{Binding DialogResult}".

Se AttachedPropertyve así. No es lo mismo que publiqué ayer, pero en mi humilde opinión, no debería tener ningún efecto.

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
}
DHN
fuente
No, no es una pregunta tonta. Simplemente coloque la declaración del enlace en el <Window />elemento como lo ilustré en mi recorte. Era demasiado vago para escribir el resto (declaraciones de espacio de nombres, etc.), que generalmente también se declara allí.
DHN
1
Por favor, consulte mi edición. Publiqué el código de producción, así que estoy seguro de que está funcionando. Se ve un poco diferente, pero el código que publiqué ayer también debería funcionar.
DHN
Gracias por aclarar eso. Resultó que estaba llamando al espacio de nombres incorrecto: S. ¿Solo necesito crear datatriggery asignarlo al botón para que funcione? Nuevamente perdón por la pregunta de Nooby.
WPFNoob
Gracias, bueno, soy consciente de que hago demasiadas preguntas que pueden parecer tontas y estúpidas y que hacen perder el tiempo a las personas. Pero volviendo a mi pregunta. Después de todo lo que mencionaste, ¿cómo cierro la ventana? Utilice un DataTrigger¬ and setting value verdadero`?
WPFNoob
1
Bueno, esa es la parte que te dejo a ti. ; o) Piense en el DataContextdel Dialog. Yo esperaría que la VM establecida como DataContextproporcione un comando, que establece la propiedad DialogResulto lo que sea que haya vinculado trueo false, para que se Dialogcierre.
DHN
13

Manera fácil

public interface IRequireViewIdentification
{
    Guid ViewID { get; }
}

Implementar en ViewModel

public class MyViewVM : IRequireViewIdentification
{
    private Guid _viewId;

    public Guid ViewID
    {
        get { return _viewId; }
    }

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

Agregar asistente de administrador de ventanas general

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

Y ciérralo así en viewmodel

WindowManager.CloseWindow(ViewID);
RassK
fuente
Muy buena solucion.
DonBoitnott
cambié el WindowManager un poco para establecer el resultado de diálogo al cerrar el win public static void CloseWindow (Guid id, bool dialogResult) {foreach (Ventana de ventana en Application.Current.Windows) {var w_id = window.DataContext como IRequireViewIdentification; if (w_id! = null && w_id.ViewID.Equals (id)) {window.DialogResult = dialogResult; window.Close (); }}} llámalo como: WindowManager.CloseWindow (_viewId, true);
lebhero
Buena solución, aunque hace un acoplamiento estrecho entre viewmodel y WindowManager, que a su vez está estrechamente acoplado con View(en términos de PresentationFramework). Sería mejor si WindowManagerse pasara un servicio a viewmodel a través de una interfaz. Entonces podría (digamos) migrar su solución a una plataforma diferente fácilmente.
Spook
4

A continuación, se muestra un ejemplo sencillo que utiliza MVVM Light Messenger en lugar de un evento. El modelo de vista envía un mensaje de cierre cuando se hace clic en un botón:

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

    public RelayCommand QuitCommand { get; private set; }

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

Luego se recibe en el código detrás de la ventana.

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

    private void HandleCloseMessage(CloseMessage closeMessage)
    {
        Close();
    }
Hamish Gunn
fuente
¿Puede darme un consejo, dónde puedo encontrar la implementación de CloseMessage?
Roman O
CloseMessage es solo una clase vacía, que se usa para identificar el tipo de mensaje que se envía. (También podría contener información compleja de mensajes, que no es necesaria aquí).
IngoB
4

¿Qué tal esto ?

ViewModel:

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

En su ViewModel, use CloseAction () para cerrar la ventana como en el ejemplo anterior.

Ver:

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

Sé que esta es una publicación antigua, probablemente nadie se desplazaría hasta aquí, sé que no lo hice. Entonces, después de horas de probar cosas diferentes, encontré este blog y el tipo lo mató. La forma más sencilla de hacer esto, probé y funciona como un encanto.

Blog

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

...

agregue una propiedad Action al ViewModel, pero defínela desde el archivo de código subyacente de View. Esto nos permitirá definir dinámicamente una referencia en el ViewModel que apunta a la Vista.

En ViewModel, simplemente agregaremos:

public Action CloseAction { get; set; }

Y en la Vista, lo definiremos como tal:

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());
}
Serlok
fuente
Enlace roto: /
Gusmally apoya a Monica
@gusmally, ¿estás seguro? Lo abrí normalmente, vuelva a intentarlo jkshay.com/…
Serlok
2

Puede crear un nuevo controlador de eventos en ViewModel de esta manera.

public event EventHandler RequestClose;

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

Luego defina RelayCommand para 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();
    }

Luego, en el conjunto de archivos XAML

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

Configure el DataContext en el archivo xaml.cs y suscríbase al evento que creamos.

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(); };
    }
}
Jyotirmaya Prusty
fuente
Usé un MVVM Light Messenger en lugar del evento.
Hamish Gunn
1

Mi forma ofrecida es Declarar evento en ViewModel y usar la combinación InvokeMethodAction como se muestra a continuación.

Modelo de vista de muestra

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

I La interfaz que se puede cerrar es la siguiente, pero no es necesario realizar esta acción. ICloseable ayudará a crear un servicio de vista genérico, por lo que si construye una vista y un modelo de vista por inyección de dependencia, lo que puede hacer es

internal interface ICloseable
{
    event EventHandler CloseRequested;
}

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

Y a continuación se muestra Xaml, puede usar este xaml incluso si no implementa la interfaz, solo necesitará su modelo de vista para generar 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>

Rajnikant
fuente
1

Puede utilizar Messengerdesde el kit de herramientas MVVMLight. en su ViewModelenvíe un mensaje como este:
Messenger.Default.Send(new NotificationMessage("Close"));
luego en su código de Windows detrás, después InitializeComponent, regístrese para ese mensaje como este:

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

puede encontrar más sobre el kit de herramientas MVVMLight aquí: Kit de herramientas MVVMLight en Codeplex

Tenga en cuenta que no hay una "regla de ningún código subyacente" en MVVM y puede registrarse para recibir mensajes en una vista de código subyacente.

Amir Oveisi
fuente
0

Es sencillo. Puede crear su propia clase ViewModel para Login - LoginViewModel. Puede crear la vista var dialog = new UserView (); dentro de su LoginViewModel. Y puede configurar Command LoginCommand en button.

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

y

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

Clase 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;
    }
}
misak
fuente
3
Sí, también es una solución válida. Pero si desea ceñirse a MVVM y el desacoplamiento de VM y vistas, romperá el patrón.
DHN
Hola @misak: al intentar implementar su solución (como las otras respuestas), arroja un Object reference not set to an instance of an object.para el método CloseLoginView. ¿Alguna sugerencia de cómo resolver ese problema?
WPFNoob
@WPFNoob - Vuelvo a colocar esta solución en bandeja. El ejemplo funciona correctamente. ¿Quiere enviar una solución completa de Visual Studio por correo electrónico?
misak
@WPFNoob - Veo el problema. Está creando una instancia como var dialog = new UserView () ;. La palabra clave clara var (instancia local) sobrescribe la instancia global en LoginViewModel
misak
0

Esta es una forma en que lo hice de manera bastante simple:

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

No veo nada malo en la respuesta que eligió, ¡solo pensé que esta podría ser una forma más simple de hacerlo!

thestephenstanton
fuente
8
Esto requiere que su ViewModel conozca y haga referencia a su Vista.
AndrewS
@AndrewS, ¿por qué es tan malo?
thestephenstanton
9
Para seguir el patrón MVVM, ViewModel no debería conocer la View.
MetalMikester
1
Para ampliar esto, el objetivo de MVVM es hacer que la mayor parte de su unidad de código GUI sea comprobable. Las vistas tienen un montón de dependencias que las hacen imposibles de realizar pruebas unitarias. ViewModels debe ser testeable por unidad, pero si les da una dependencia directa en la vista, no lo será.
ILMTitan
Y para expandir esto aún más, MVVM correctamente escrito le permite migrar la solución a una plataforma diferente fácilmente. En particular, debería poder reutilizar sus modelos de vista sin ningún cambio. En este caso, si trasladara su solución a Android, no funcionaría, porque Android no tiene el concepto de ventana. -1 para una solución que rompe MVVM.
Spook
0

Puede tratar la ventana como un servicio (por ejemplo, servicio de interfaz de usuario) y pasar a viewmodel a través de una interfaz , como tal:

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

Esta solución tiene la mayoría de las ventajas de pasar la vista en sí a viewmodel sin tener la desventaja de romper MVVM, porque aunque la vista física se pasa a viewmodel, este último todavía no sabe sobre el primero, solo ve algunos IMainWindowAccess. Entonces, por ejemplo, si quisiéramos migrar esta solución a otra plataforma, solo sería cuestión de implementarla IMainWindowAccesscorrectamente para, digamos, un Activity.

Estoy publicando la solución aquí para proponer un enfoque diferente a los eventos (aunque en realidad es muy similar), porque parece un poco más simple que los eventos de implementar (adjuntar / separar, etc.), pero aún se alinea bien con el patrón MVVM.

Espectro
fuente
-1

Puede cerrar la ventana actual simplemente usando el siguiente código:

Application.Current.Windows[0].Close();
chandudab
fuente
6
Si tiene más de una ventana, esto puede cerrar la ventana incorrecta.
Sasha
17
¡Oh Dios! has matado MVVM
Hossein Shahdoost
-7

System.Environment.Salir (0); en el modelo de vista funcionaría.

rjain
fuente
6
No, no lo hará. Saldrá de la aplicación y no cerrará la ventana actual.
Tilak
esto resolvió mi problema, porque cerrar la ventana principal (para mí) == salir de la aplicación. Todos los métodos propuestos, excepto éste, tenían puntos delicados cuando se llamaba desde diferentes hilos; pero a este enfoque realmente no le importa quién es el hilo de la llamada :) ¡Eso era todo lo que necesitaba!
Hamed
BuAHahahAHahahAha lo siento, no pude resistir
L.Trabacchin