85 votes

WPF MVVM : Comment fermer une fenêtre ?

J'ai un bouton qui ferme ma fenêtre lorsqu'il est cliqué :

<Button x:Name="buttonOk"  IsCancel="True">Ok</Button>

Cela fonctionne bien jusqu'à ce que j'ajoute une commande au bouton, par exemple

<Button x:Name="buttonOk" Command="{Binding SaveCommand}" IsCancel="True">Ok</Button>

Maintenant, il ne se ferme pas vraisemblablement parce que je gère la commande. Je peux résoudre ce problème en ajoutant un gestionnaire d'événement et en appelant this.Close(), c'est-à-dire

<Button x:Name="buttonOk" Click="closeWindow" Command="{Binding SaveCommand}" IsCancel="True">Ok</Button>

mais j'ai maintenant du code dans mon code arrière, c'est-à-dire la méthode "SaveCommand". J'utilise le modèle MVVM et SaveCommand est le seul code dans mon code arrière.

Comment puis-je procéder différemment pour ne pas utiliser le code derrière ?

74voto

Jonathan Shay Points 82

Je viens de terminer un article de blog sur ce même sujet. En résumé, ajoutez une propriété Action à votre ViewModel avec des accesseurs get et set. Définissez ensuite l'action dans le constructeur de votre vue. Enfin, invoquez votre action dans la commande liée qui doit fermer la fenêtre.

Dans le ViewModel :

public Action CloseAction  { get; set;}

et dans le constructeur de la vue :

private View()
{
    InitializeComponent();
    ViewModel vm = new ViewModel();
    this.DataContext = vm;
    if ( vm.CloseAction == null )
        vm.CloseAction = new Action( () => this.Close() );
}

Enfin, dans n'importe quelle commande liée qui doit fermer la fenêtre, nous pouvons simplement invoquer

CloseAction(); // Calls Close() method of the View

Cela a fonctionné pour moi, m'a semblé être une solution assez élégante, et m'a épargné un tas de codage.

22voto

Jon Mitchell Points 1391

Malheureusement, l'affichage des fenêtres est un véritable casse-tête en MVVM. Vous devez donc effectuer un certain travail d'infrastructure ou utiliser un cadre MVVM tel que le suivant Cinch . Si vous voulez investir le temps nécessaire pour le faire vous-même voici un lien sur la façon dont Cinch procède.

C'est bien que vous essayiez de garder toute logique en dehors de la vue, mais ce n'est pas la fin du monde si vous le faites. Dans ce cas, cela ne semble pas poser trop de problèmes.

20voto

Simone Points 99

J'ai eu un problème similaire et j'ai trouvé la solution suivante qui fonctionne pour moi :

XAML :

<Button Name="okButton" Command="{Binding OkCommand}" CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}">OK</Button>

ViewModel :

public ICommand OkCommand
{
    get
    {
        if (_okCommand == null)
        {
            _okCommand = new ActionCommand<Window>(DoOk, CanDoOk);
        }
        return _okCommand ;
    }
}

void DoOk(Window win)
{
    <!--Your Code-->
    win.DialogResult = true;
    win.Close();
}

bool CanDoOk(Window win) { return true; }

13voto

Personnellement, j'utiliserais un comportement pour faire ce genre de choses :

public class WindowCloseBehaviour : Behavior<Window>
{
    public static readonly DependencyProperty CommandProperty =
      DependencyProperty.Register(
      "Command",
      typeof(ICommand),
      typeof(WindowCloseBehaviour));

    public static readonly DependencyProperty CommandParameterProperty =
          DependencyProperty.Register(
          "CommandParameter",
          typeof(object),
          typeof(WindowCloseBehaviour));

    public static readonly DependencyProperty CloseButtonProperty =
      DependencyProperty.Register(
      "CloseButton",
      typeof(Button),
      typeof(WindowCloseBehaviour),
      new FrameworkPropertyMetadata(
                null,
                OnButtonChanged));

    public ICommand Command
    {
        get { return (ICommand)GetValue(CommandProperty); }
        set { SetValue(CommandProperty, value); }
    }

    public object CommandParameter
    {
        get { return GetValue(CommandParameterProperty); }
        set { SetValue(CommandParameterProperty, value); }
    }

    public Button CloseButton
    {
        get { return (Button)GetValue(CloseButtonProperty); }
        set { SetValue(CloseButtonProperty, value); }
    }

    private static void OnButtonChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var window = (Window)((WindowCloseBehaviour)d).AssociatedObject;
        ((Button) e.NewValue).Click += (s, e1) =>
                                           {
                                               var command = ((WindowCloseBehaviour)d).Command;
                                               var commandParameter = ((WindowCloseBehaviour)d).CommandParameter;
                                               if (command != null)
                                               {
                                                   command.Execute(commandParameter);                                                      
                                               }
                                               window.Close();
                                           };
    }
}

Vous pouvez ensuite l'attacher à votre fenêtre et au bouton pour faire le travail :

<Window x:Class="WpfApplication6.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:local="clr-namespace:WpfApplication6"
        Title="Window1" Height="300" Width="300">
    <i:Interaction.Behaviors>
        <local:WindowCloseBehaviour CloseButton="{Binding ElementName=closeButton}"/>
    </i:Interaction.Behaviors>
    <Grid>
        <Button Name="closeButton">Close</Button>
    </Grid>
</Window>

J'ai ajouté Command et CommandParameter ici pour que vous puissiez exécuter une commande avant la fermeture de la fenêtre.

9voto

Ilya Smagin Points 1833

Pour les petites applications, j'utilise mon propre contrôleur d'application pour afficher, fermer et disposer des fenêtres et des DataContexts. C'est un point central de l'interface utilisateur d'une application.

C'est quelque chose comme ça :

//It is singleton, I will just post 2 methods and their invocations
public void ShowNewWindow(Window window, object dataContext = null, bool dialog = true)
{
    window.DataContext = dataContext;
    addToWindowRegistry(dataContext, window);

    if (dialog)
        window.ShowDialog();
    else
        window.Show();

}

public void CloseWindow(object dataContextSender)
{
    var correspondingWindows = windowRegistry.Where(c => c.DataContext.Equals(dataContextSender)).ToList();
    foreach (var pair in correspondingWindows)
    {
        pair.Window.Close();              
    }
}

et leurs invocations à partir de ViewModels :

// Show new Window with DataContext
ApplicationController.Instance.ShowNewWindow(
                new ClientCardsWindow(),
                new ClientCardsVM(),
                false);

// Close Current Window from viewModel
ApplicationController.Instance.CloseWindow(this);

Bien sûr, vous pouvez trouver certaines restrictions dans ma solution. Encore une fois : Je l'utilise pour des petits projets, et c'est suffisant. Si vous êtes intéressé, je peux poster le code complet ici ou ailleurs/

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