187 votos

¿Cómo debe el ViewModel cerrar el formulario?

Estoy tratando de aprender a WPF y MVVM problema, pero han golpeado un problema. Esta pregunta es similar pero no es lo mismo como este (manejo de diálogos-en-wpf-con-mvvm)...

Tengo un "inicio de Sesión" forma escrita utilizando el patrón MVVM.

Este formulario tiene un ViewModel que contiene el nombre de Usuario y Contraseña, a la cual están vinculados a la vista en el XAML uso normal de enlaces de datos. También tiene un comando "Login" que está enlazado el botón "iniciar Sesión" en la forma, agan el uso normal de enlace de datos.

Cuando el comando "Login" de los incendios, se invoca a una función en el ViewModel que se va y envía los datos a través de la red para iniciar sesión en. Cuando esta función se completa, hay 2 acciones:

  1. El inicio de sesión no válido - nos acaba de mostrar un MessageBox y todo está muy bien

  2. El inicio de sesión válidas, tenemos que cerrar el formulario de inicio de Sesión y tiene que devolver true como su DialogResult...

El problema es que el ViewModel no sabe nada acerca de la visión real, así que ¿cómo es cerrar la vista y decirle que devolver un particular DialogResult?? Me podría pegar un poco de código en el código subyacente, y/o pasar la Vista a través de la ViewModel, pero que parece que sería la derrota el punto de MVVM totalmente...


Actualización

Al final me acaban de violar la "pureza" del patrón MVVM y tenia la Visión de publicar un Closed evento, y exponer Close método. El ViewModel sería entonces simplemente llame view.Close. La vista es sólo conocido a través de una interfaz y el cableado a través de un contenedor de IOC, así que no hay capacidad de prueba o de mantenimiento se pierde.

Me parece bastante tonto que la aceptación de respuesta es de -5 votos! Aunque soy muy consciente de las buenas sensaciones que se obtiene mediante la resolución de un problema, mientras que ser "puro", seguro que no soy el único que piensa que con 200 líneas de eventos, comandos y comportamientos sólo para evitar un método de línea en el nombre de "patrones" y "pureza" es un poco ridículo....

264voto

Joe White Puntos 32629

Me inspiré en Thejuan la respuesta a escribir una simplificación de la propiedad adjunta. Sin estilos, sin desencadenantes; en su lugar, usted puede hacer esto:

<Window ...
        xmlns:xc="clr-namespace:ExCastle.Wpf"
        xc:DialogCloser.DialogResult="{Binding DialogResult}">

Esto es casi tan limpio como si el WPF equipo había conseguido lo correcto, y de hecho DialogResult una propiedad de dependencia en el primer lugar. Sólo hay que poner un bool? DialogResult de la propiedad en el ViewModel y aplicar INotifyPropertyChanged, y listo, tu Perspective puede cerrar la Ventana (y establezca su DialogResult) sólo mediante el establecimiento de una propiedad. MVVM como debe ser.

Este es el código para DialogCloser:

using System.Windows;

namespace ExCastle.Wpf
{
    public static class DialogCloser
    {
        public static readonly DependencyProperty DialogResultProperty =
            DependencyProperty.RegisterAttached(
                "DialogResult",
                typeof(bool?),
                typeof(DialogCloser),
                new PropertyMetadata(DialogResultChanged));

        private static void DialogResultChanged(
            DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            var window = d as Window;
            if (window != null)
                window.DialogResult = e.NewValue as bool?;
        }
        public static void SetDialogResult(Window target, bool? value)
        {
            target.SetValue(DialogResultProperty, value);
        }
    }
}

También he publicado en mi blog.

58voto

Budda Puntos 5575

Desde mi punto de vista la pregunta es bastante bueno, como el mismo enfoque podría utilizarse no sólo para el "Login" de la ventana, pero para cualquier tipo de ellos. He pasado por un montón de sugerencias y nadie para mí está bien. Por favor ver mi especie, que fue tomada desde el patrón de diseño MVVM artículo.

Cada Perspective de la clase debe ser heredado de WorkspaceViewModel que ha RequestClose envent, y CloseCommand de la propiedad de la ICommand tipo. Implementación predeterminada de la CloseCommand de la propiedad aumentará RequestClose evento.

Y con el fin de obtener ventana cerrada de la OnLoaded método de su ventana debe ser overrided:

void CustomerWindow_Loaded(object sender, RoutedEventArgs e)
{
    CustomerViewModel customer = CustomerViewModel.GetYourCustomer();
    DataContext = customer;
    customer.RequestClose += () => { Close(); };
}

o OnStartup método de ustedes aplicación:

    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        MainWindow window = new MainWindow();
        var viewModel = new MainWindowViewModel();
        viewModel.RequestClose += window.Close;
        window.DataContext = viewModel;

        window.Show();
    }

Supongo que RequestClose evento y CloseCommand de la propiedad de la implementación en el WorkspaceViewModel está bastante claro, pero voy a mostrar a la coherencia:

public abstract class WorkspaceViewModel : ViewModelBase // There are nothing interest in ViewModelBase, it only implements INotifyPropertyChanged interface only
{
    RelayCommand _closeCommand;
    public ICommand CloseCommand
    {
        get
        {
            if (_closeCommand == null)
            {
                _closeCommand = new RelayCommand(
                   param => Close(),
                   param => CanClose()
                   );
            }
            return _closeCommand;
        }
    }

    public event Action RequestClose;

    public virtual void Close()
    {
        if ( RequestClose!=null )
        {
            RequestClose();
        }
    }

    public virtual bool CanClose()
    {
        return true;
    }
}

Y el código fuente de la RelayCommand:

public class RelayCommand : ICommand
{
    #region Constructors

    public RelayCommand(Action<object> execute, Predicate<object> canExecute)
    {
        if (execute == null)
            throw new ArgumentNullException("execute");

        _execute = execute;
        _canExecute = canExecute;
    }
    #endregion // Constructors

    #region ICommand Members

    [DebuggerStepThrough]
    public bool CanExecute(object parameter)
    {
        return _canExecute == null ? true : _canExecute(parameter);
    }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public void Execute(object parameter)
    {
        _execute(parameter);
    }

    #endregion // ICommand Members

    #region Fields

    readonly Action<object> _execute;
    readonly Predicate<object> _canExecute;

    #endregion // Fields
}

P.S. no me trates mal por esas fuentes, Si yo tuviera que ayer que me salvaría unas horas...

P.P.S Cualquier comentario o sugerencia son bienvenidos.

15voto

Adam Mills Puntos 1645

He utilizado adjunto comportamientos para cerrar la ventana. Obligar a una "señal" de la propiedad en el ViewModel a las que se adjunta el comportamiento (en realidad, el uso de un disparador) Cuando se establece en true, el comportamiento cierra la ventana.

http://adammills.wordpress.com/2009/07/01/window-close-from-xaml/

12voto

Stimul8d Puntos 4730

Hay un montón de comentarios, argumentando los pros y los contras de MVVM aquí. Para mí, estoy de acuerdo con Nir; es una cuestión de usar el patrón de manera adecuada y MVVM no siempre encajan. La gente parece haber llegado a estar dispuestos a sacrificar todo de los más importantes principios de diseño de software SÓLO para conseguir que se ajuste MVVM.

Dicho esto,..creo que su caso podría ser un buen ajuste con un poco de refactoring.

En la mayoría de los casos me he topado, WPF permite prescindir de múltiples Windows. Tal vez usted podría tratar de usar Frames y Pages en lugar de Windows con DialogResults.

En el caso de mi sugerencia sería tener LoginFormViewModel de manejar la LoginCommand y si el inicio de sesión no es válido, establecer una propiedad en LoginFormViewModel a un valor adecuado (false o algo de valor de enumeración como UserAuthenticationStates.FailedAuthentication). Usted haría lo mismo para un inicio de sesión exitoso (true o algún otro valor de enumeración). A continuación, se utilizaría un DataTrigger que responde a las diversas autenticación de usuario de los estados y podría utilizar un simple Setter a cambio de la Source de la propiedad de la Frame.

Tener tu Ventana de inicio de sesión de devolución de un DialogResult creo que es donde estás consiguiendo confundir; que DialogResult es realmente una propiedad de su ViewModel. En mi, obviamente limitada experiencia con WPF, cuando algo no se siente bien normalmente porque estoy pensando en términos de cómo yo he hecho la misma cosa en WinForms.

Espero que ayude.

7voto

Jim Wallace Puntos 738

Suponiendo que el diálogo de inicio de sesión es la primera ventana que se crea, prueba esta en el interior de su LoginViewModel clase:

    void OnLoginResponse(bool loginSucceded)
    {
        if (loginSucceded)
        {
            Window1 window = new Window1() { DataContext = new MainWindowViewModel() };
            window.Show();

            App.Current.MainWindow.Close();
            App.Current.MainWindow = window;
        }
        else
        {
            LoginError = true;
        }
    }

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X