256 votes

Comment le ViewModel doit-il fermer le formulaire ?

J'essaie d'apprendre WPF et le problème MVVM, mais j'ai rencontré un problème. Cette question est similaire mais pas tout à fait la même comme celui-ci (handling-dialogs-in-wpf-with-mvvm) ...

J'ai un formulaire "Login" écrit en utilisant le modèle MVVM.

Ce formulaire possède un ViewModel qui contient le nom d'utilisateur et le mot de passe, qui sont liés à la vue dans le XAML à l'aide de liaisons de données normales. Il possède également une commande "Login" qui est liée au bouton "Login" du formulaire, là encore à l'aide de liaisons de données normales.

Lorsque la commande "Login" est activée, elle invoque une fonction dans le ViewModel qui envoie des données sur le réseau pour permettre la connexion. Lorsque cette fonction s'achève, il y a deux actions :

  1. Le login n'était pas valide - nous affichons simplement une MessageBox et tout va bien.

  2. La connexion étant valide, nous devons fermer le formulaire de connexion et faire en sorte qu'il renvoie true en tant que DialogResult ...

Le problème est que le ViewModel ne sait rien de la vue actuelle, alors comment fermer la vue et lui demander de renvoyer un DialogResult particulier ? Je pourrais mettre du code dans le CodeBehind, et/ou passer la vue au ViewModel, mais cela semble aller à l'encontre de l'idée même de MVVM...


Mise à jour

En fin de compte, j'ai violé la "pureté" du modèle MVVM et j'ai fait en sorte que la vue publie un fichier Closed et d'exposer un Close méthode. Le ViewModel n'aurait alors qu'à appeler view.Close . La vue n'est connue que par l'intermédiaire d'une interface et est reliée à un conteneur IOC, ce qui permet d'éviter toute perte de testabilité ou de maintenabilité.

Il semble assez stupide que la réponse acceptée soit à -5 voix ! Bien que je sois conscient des bons sentiments que l'on éprouve en résolvant un problème tout en étant "pur", je ne suis sûrement pas le seul à penser que 200 lignes d'événements, de commandes et de comportements juste pour éviter une méthode d'une ligne au nom des "modèles" et de la "pureté" est un peu ridicule.....

0 votes

J'ai utilisé les comportements joints pour fermer la fenêtre. Liez une propriété "signal" sur votre ViewModel au comportement attaché (j'utilise en fait un déclencheur). Lorsqu'elle vaut true, le comportement ferme la fenêtre. http://adammills.wordpress.com/2009/07/01/window-close-from-xaml/

333voto

Joe White Points 32629

J'ai été inspiré par Réponse de Thejuan pour rédiger une propriété attachée plus simple. Pas de styles, pas de déclencheurs ; à la place, vous pouvez simplement faire ceci :

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

C'est presque aussi propre que si l'équipe WPF avait eu raison de faire de DialogResult une propriété de dépendance dès le départ. Il suffit de mettre une propriété bool? DialogResult sur votre ViewModel et implémenter INotifyPropertyChanged, et voilà, votre ViewModel peut fermer la fenêtre (et définir son DialogResult) simplement en définissant une propriété. MVVM tel qu'il devrait être.

Voici le code de 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);
        }
    }
}

J'ai également posté ceci sur mon blog .

67voto

Budda Points 5575

De mon point de vue, la question est assez bonne car la même approche serait utilisée non seulement pour la fenêtre "Login", mais pour n'importe quel type de fenêtre. J'ai examiné un grand nombre de suggestions et aucune ne me convient. Je vous invite à examiner ma suggestion, qui est tirée de la page Article sur le modèle de conception MVVM .

Chaque classe ViewModel doit hériter de la classe WorkspaceViewModel qui dispose de la RequestClose et CloseCommand de la propriété ICommand type. L'implémentation par défaut de la fonction CloseCommand soulèvera la propriété RequestClose événement.

Afin d'obtenir la fermeture de la fenêtre, le OnLoaded de votre fenêtre doit être surchargée :

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

o OnStartup de votre application :

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

Je suppose que RequestClose et CloseCommand dans l'application de la propriété WorkspaceViewModel sont assez clairs, mais je les montrerai pour qu'ils soient cohérents :

public abstract class WorkspaceViewModel : ViewModelBase
// There's nothing interesting in ViewModelBase as it only implements the INotifyPropertyChanged interface
{
    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;
    }
}

Et le code source du 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. Ne me traitez pas mal pour ces sources ! Si je les avais eues hier, j'aurais gagné quelques heures...

P.P.S. Tous les commentaires et suggestions sont les bienvenus.

18voto

Adam Mills Points 1645

J'ai utilisé les comportements joints pour fermer la fenêtre. Liez une propriété "signal" sur votre ViewModel au comportement attaché (j'utilise en fait un déclencheur) Lorsqu'il vaut true, le comportement ferme la fenêtre.

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

15voto

Stimul8d Points 4730

Il y a beaucoup de commentaires sur les avantages et les inconvénients de MVVM ici. Pour ma part, je suis d'accord avec Nir ; il s'agit d'utiliser le modèle de manière appropriée et MVVM ne convient pas toujours. Les gens semblent prêts à sacrifier tous les principes les plus importants de la conception de logiciels JUSTE pour qu'ils s'adaptent à MVVM.

Cela dit, je pense que votre cas pourrait être bien adapté avec un peu de remaniement.

Dans la plupart des cas que j'ai rencontrés, WPF vous permet de vous passer de multiples Window s. Vous pourriez peut-être essayer d'utiliser Frame et Page au lieu de Windows avec DialogResult s.

Dans votre cas, je vous suggère d'avoir LoginFormViewModel gérer les LoginCommand et si le login n'est pas valide, définir une propriété sur LoginFormViewModel à une valeur appropriée ( false ou une valeur d'énumération comme UserAuthenticationStates.FailedAuthentication ). Vous feriez de même pour une connexion réussie ( true ou une autre valeur de l'énumération). Vous utiliserez alors un DataTrigger qui répond aux différents états d'authentification de l'utilisateur et pourrait utiliser un simple Setter pour modifier la Source de la propriété Frame .

Le fait que votre fenêtre de connexion renvoie un DialogResult Je pense que c'est là que vous vous méprenez ; que DialogResult est en réalité une propriété de votre ViewModel. D'après mon expérience, certes limitée, de WPF, lorsque quelque chose ne me semble pas correct, c'est généralement parce que je pense à la façon dont j'aurais fait la même chose dans WinForms.

J'espère que cela vous aidera.

10voto

Jim Wallace Points 738

En supposant que votre dialogue de connexion soit la première fenêtre créée, essayez ceci à l'intérieur de votre classe LoginViewModel :

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X