web-dev-qa-db-fra.com

Comment le ViewModel doit-il fermer le formulaire?

J'essaie d'apprendre WPF et le problème MVVM, mais je me suis heurté à un problème . Cette question est similaire mais pas tout à fait la même que celle-ci (manipulation-dialogues dans wpf-with-mvvm) ...

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

Ce formulaire contient 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 comporte également une commande "Connexion" liée au bouton "Connexion" du formulaire, agan en utilisant la liaison de données normale.

Lorsque la commande "Connexion" est activée, elle appelle une fonction du ViewModel qui s'éteint et envoie des données sur le réseau pour se connecter. Une fois cette fonction terminée, 2 actions sont exécutées:

  1. La connexion était invalide - nous montrons simplement un MessageBox et tout va bien

  2. Le login était valide, nous devons fermer le formulaire de login et le renvoyer true car sa DialogResult...

Le problème est que ViewModel ne sait rien de la vue réelle. Comment peut-il fermer la vue et lui dire de renvoyer un DialogResult particulier? Je pourrais coller du code dans CodeBehind, et/ou passer de View à ViewModel, mais il semblerait que cela viderait complètement le principe de MVVM ...


Mettre à jour

En fin de compte, je viens de violer la "pureté" du modèle MVVM et de faire en sorte que View publie un événement Closed et expose une méthode Close. Le ViewModel appelle alors simplement view.Close. La vue n'est connue que via une interface et câblée via un conteneur IOC. Par conséquent, aucune testabilité ni possibilité de maintenance ne sont perdues.

Il semble plutôt ridicule que la réponse acceptée soit à -5 voix! Bien que je sois bien conscient des sentiments positifs que l'on éprouve en résolvant un problème en étant "pur", je ne suis sûrement pas le seul à penser que 200 lignes d'événements, de commandes et de comportements sont simplement nécessaires pour éviter une méthode à une ligne dans le nom de "motifs" et de "pureté" est un peu ridicule ....

231
Orion Edwards

La réponse de Thejuan m'a inspiré pour écrire 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 l'avait bien compris et avait fait de DialogResult une propriété de dépendance en premier lieu. Il suffit de placer une propriété bool? DialogResult sur votre ViewModel et d'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 comme il se doit.

Voici le code pour 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 aussi posté ceci sur mon blog .

308
Joe White

De mon point de vue, la question est plutôt bonne car la même approche serait utilisée non seulement pour la fenêtre "Connexion", mais pour tout type de fenêtre. J'ai passé en revue beaucoup de suggestions et aucune ne me convient. Veuillez consulter ma suggestion tirée de l’article article sur les modèles de conception MVVM .

Chaque classe ViewModel doit hériter de WorkspaceViewModel qui possède l'événement RequestClose et la propriété CloseCommand du type ICommand. L'implémentation par défaut de la propriété CloseCommand déclenchera l'événement RequestClose.

Afin de fermer la fenêtre, la méthode OnLoaded de votre fenêtre doit être remplacée:

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

ou OnStartup méthode 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();
    }

J'imagine que RequestClose event et CloseCommand, l'implémentation de la propriété dans WorkspaceViewModel sont assez clairs, mais je vais leur montrer à être 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 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. Ne me traite pas mal pour ces sources! Si je les avais hier, cela m'aurait épargné quelques heures ...

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

65
Budda

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

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

18
Adam Mills

Il y a beaucoup de commentaires qui discutent les avantages et les inconvénients de MVVM ici. Pour moi, 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. Il semble que les gens soient prêts à renoncer à tous les principes les plus importants de la conception de logiciels juste pour l’adapter au MVVM.

Cela dit, je pense que votre cas pourrait être un bon ajustement avec un peu de refactoring.

Dans la plupart des cas que j'ai rencontrés, WPF vous permet de vous passer de SANS Windows. Vous pourriez peut-être essayer d'utiliser Frames et Pages au lieu de Windows avec DialogResults.

Dans votre cas, ma suggestion serait que LoginFormViewModel gère LoginCommand et si le login est invalide, définissez une propriété sur LoginFormViewModel sur une valeur appropriée (false ou une valeur énumérée telle que UserAuthenticationStates.FailedAuthentication). Vous feriez de même pour une connexion réussie (true ou une autre valeur enum). Vous utiliseriez alors une DataTrigger qui répond aux différents états d'authentification de l'utilisateur et vous pourriez utiliser une simple Setter pour modifier la propriété Source de la Frame

Avoir votre fenêtre de connexion retournée un DialogResult Je pense que c'est là que vous êtes confus; que DialogResult est vraiment une propriété de votre ViewModel. Dans mon expérience certes limitée avec WPF, quand quelque chose ne va pas, c'est généralement parce que je pense à la façon dont j'aurais fait la même chose avec WinForms. 

J'espère que cela pourra aider.

15
Stimul8d

En supposant que votre boîte de dialogue de connexion soit la première fenêtre créée, essayez ceci dans 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;
        }
    }
9
Jim Wallace

La façon dont je le gérerais est d’ajouter un gestionnaire d’événements dans mon ViewModel. Une fois l'utilisateur connecté, je déclencherais l'événement. Dans ma vue, j'attacherais à cet événement et quand il serait déclenché, je fermerais la fenêtre. 

6
Billy Jacobs

Il s'agit d'une solution simple et propre. Vous ajoutez un événement au ViewModel et demandez à la fenêtre de se fermer quand cet événement est déclenché.

Pour plus de détails, voir mon article de blog, Fermer la fenêtre de ViewModel .

5
Shimmy

Voici ce que j’ai fait au début, ce qui fonctionne, mais cela semble assez long et laid (tout ce qui est statique n’est jamais bon)

1: App.xaml.cs

public partial class App : Application
{
    // create a new global custom WPF Command
    public static readonly RoutedUICommand LoggedIn = new RoutedUICommand();
}

2: LoginForm.xaml

// bind the global command to a local eventhandler
<CommandBinding Command="client:App.LoggedIn" Executed="OnLoggedIn" />

3: LoginForm.xaml.cs

// implement the local eventhandler in codebehind
private void OnLoggedIn( object sender, ExecutedRoutedEventArgs e )
{
    DialogResult = true;
    Close();
}

4: LoginFormViewModel.cs

// fire the global command from the viewmodel
private void OnRemoteServerReturnedSuccess()
{
    App.LoggedIn.Execute(this, null);
}

Plus tard, j'ai ensuite supprimé tout ce code et je viens d'avoir la LoginFormViewModel appeler la méthode Close dans sa vue. Cela a fini par être beaucoup plus agréable et facile à suivre. IMHO le but des modèles est de donner aux gens un moyen plus facile de comprendre ce que fait votre application, et dans ce cas, MVVM le rendait beaucoup plus difficile à comprendre que si je ne l'avais pas utilisé, et était maintenant un anti -modèle.

4
Orion Edwards

C’est probablement très tard, mais j’ai rencontré le même problème et j’ai trouvé une solution qui fonctionne pour moi.

Je n'arrive pas à comprendre comment créer une application sans dialogues (c'est peut-être juste un blocage mental). J'étais donc dans une impasse avec MVVM et je montrais un dialogue. Je suis donc tombé sur cet article CodeProject:

http://www.codeproject.com/KB/WPF/XAMLDialog.aspx

Ce qui est un UserControl qui permet fondamentalement à une fenêtre d'être dans l'arborescence visuelle d'une autre fenêtre (non autorisée dans xaml). Il expose également un objet booléen DependencyProperty appelé IsShowing.

Vous pouvez définir un style similaire à, généralement dans un dictionnaire resourced, qui affiche la boîte de dialogue lorsque la propriété Content du contrôle! = Null via des déclencheurs:

<Style TargetType="{x:Type d:Dialog}">
    <Style.Triggers>
        <Trigger Property="HasContent"  Value="True">
            <Setter Property="Showing" Value="True" />
        </Trigger>
    </Style.Triggers>
</Style>

Dans la vue où vous souhaitez afficher la boîte de dialogue, procédez comme suit:

<d:Dialog Content="{Binding Path=DialogViewModel}"/>

Et dans votre ViewModel, tout ce que vous avez à faire est de définir la propriété sur une valeur (Remarque: la classe ViewModel doit prendre en charge INotifyPropertyChanged pour que la vue sache que quelque chose s'est passé).

ainsi:

DialogViewModel = new DisplayViewModel();

Pour faire correspondre le ViewModel à la View, vous devriez avoir quelque chose comme ceci dans un dictionnaire doté de ressources:

<DataTemplate DataType="{x:Type vm:DisplayViewModel}">
    <vw:DisplayView/>
</DataTemplate>

Avec tout cela, vous obtenez un code à une ligne pour afficher le dialogue. Le problème que vous obtenez est que vous ne pouvez pas vraiment fermer la boîte de dialogue avec uniquement le code ci-dessus. C'est pourquoi vous devez créer un événement dans une classe de base ViewModel dont DisplayViewModel hérite et à la place du code ci-dessus, écrivez ceci.

        var vm = new DisplayViewModel();
        vm.RequestClose += new RequestCloseHandler(DisplayViewModel_RequestClose);
        DialogViewModel = vm;

Ensuite, vous pouvez gérer le résultat du dialogue via le rappel.

Cela peut sembler un peu complexe, mais une fois que les bases sont posées, c'est assez simple. Encore une fois c'est ma mise en œuvre, je suis sûr qu'il y en a d'autres :)

J'espère que ça aide, ça m'a sauvé.

3
Jose

Ok, donc cette question a presque 6 ans et je ne trouve toujours pas ce que je pense être la bonne réponse, alors permettez-moi de partager mes "2 cents" ...

J'ai en fait deux façons de le faire, la première est la plus simple ... la seconde à droite, donc si vous cherchez la bonne, sautez # 1 et passez à # 2:

1. Rapide et facile (mais pas complet)

Si je n'ai qu'un petit projet, je crée parfois juste un CloseWindowAction dans le ViewModel:

        public Action CloseWindow { get; set; } // In MyViewModel.cs

Et quiconque crée la vue, ou dans le code de la vue derrière, je viens de définir la méthode que l'action appellera:

(rappelez-vous que MVVM concerne la séparation de la vue et du modèle de vue ... le code de la vue behin est toujours la vue et tant qu'il existe une séparation correcte, vous ne violez pas le modèle)}

Si un ViewModel crée une nouvelle fenêtre:

private void CreateNewView()
{
    MyView window = new MyView();
    window.DataContext = new MyViewModel
                             {
                                 CloseWindow = window.Close,
                             }; 
    window.ShowDialog();
}

Ou, si vous le souhaitez dans votre fenêtre principale, placez-le simplement sous le constructeur de votre vue:

public MyView()
{
    InitializeComponent();           
    this.DataContext = new MainViewModel
                           {
                                CloseWindow = this.Close
                           };
}

lorsque vous souhaitez fermer la fenêtre, appelez simplement l’action sur votre ViewModel.


2. La bonne façon

Maintenant, la bonne façon de le faire est d'utiliser Prism (IMHO), et tout ce qui le concerne peut être trouvé ici .

Vous pouvez créer une demande d'interaction, la remplir avec les données dont vous aurez besoin dans votre nouvelle fenêtre, la déjeuner, la fermer et même recevoir les données. Tout cela est encapsulé et approuvé par MVVM. Vous obtenez même un statut indiquant comment la fenêtre a été fermée, comme si l'utilisateur Canceled ou Accepted (bouton OK) la fenêtre et données étaient restitués si vous en aviez besoin. C'est un peu plus compliqué et la réponse n ° 1, mais c'est beaucoup plus complet et un modèle recommandé par Microsoft.

Le lien que j'ai donné contient tous les extraits de code et les exemples, je ne vais donc pas me soucier de placer du code ici, il suffit de lire l'article de téléchargement du Prism Quick Start et de l'exécuter, c'est vraiment simple de comprendre un peu plus comment faites-le fonctionner, mais les avantages sont plus importants que de simplement fermer une fenêtre.

3
mFeinstein
public partial class MyWindow: Window
{
    public ApplicationSelection()
    {
      InitializeComponent();

      MyViewModel viewModel = new MyViewModel();

      DataContext = viewModel;

      viewModel.RequestClose += () => { Close(); };

    }
}

public class MyViewModel
{

  //...Your code...

  public event Action RequestClose;

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

  public void SomeFunction()
  {
     //...Do something...
     Close();
  }
}
3
Amir Twito

Pour votre information, j'ai rencontré le même problème et je pense avoir trouvé un moyen de contourner le problème qui ne nécessite ni globals ni statique, bien que ce ne soit peut-être pas la meilleure réponse. Je laisse les gars décider par vous-même.

Dans mon cas, le ViewModel qui instancie l'affichage de la fenêtre (appelons-le ViewModelMain) connaît également le LoginFormViewModel (en utilisant la situation ci-dessus à titre d'exemple). 

Donc ce que j'ai fait était de créer une propriété sur le LoginFormViewModel qui était de type ICommand (appelons-le CloseWindowCommand). Ensuite, avant d'appeler .ShowDialog () sur la fenêtre, j'ai défini la propriété CloseWindowCommand sur le LoginFormViewModel sur la méthode window.Close () de la fenêtre instanciée. Ensuite, à l'intérieur de LoginFormViewModel, tout ce que je dois faire est d'appeler CloseWindowCommand.Execute () pour fermer la fenêtre.

C'est un peu une solution de contournement/hack je suppose, mais cela fonctionne bien sans vraiment casser le modèle MVVM.

N'hésitez pas à critiquer ce processus autant que vous le souhaitez, je peux le supporter! :)

3
Chris Walker

Vous pouvez demander à ViewModel d'exposer un événement enregistré par View. Ensuite, lorsque le ViewModel décide de l'heure à laquelle fermer la vue, il déclenche l'événement qui provoque la fermeture de la vue. Si vous souhaitez qu'une valeur de résultat spécifique soit renvoyée, vous aurez une propriété dans le ViewModel pour cela.

2
Abdulla Al-Qawasmeh

Juste pour ajouter au nombre massif de réponses, je veux ajouter le texte suivant. En supposant que vous ayez une commande IC dans votre ViewModel et que vous souhaitiez que cette commande ferme sa fenêtre (ou toute autre action à cet égard), vous pouvez utiliser quelque chose comme ce qui suit.

var windows = Application.Current.Windows;
for (var i=0;i< windows.Count;i++ )
    if (windows[i].DataContext == this)
        windows[i].Close();

Ce n'est pas parfait, et peut être difficile à tester (comme il est difficile de se moquer/stub un statique) mais c'est plus propre (IMHO) que les autres solutions.

Erick

0
Erick T

Une autre solution consiste à créer une propriété avec INotifyPropertyChanged dans View Model, comme DialogResult, puis dans Code Derrière, écrivez ceci:

public class SomeWindow: ChildWindow
{
    private SomeViewModel _someViewModel;

    public SomeWindow()
    {
        InitializeComponent();

        this.Loaded += SomeWindow_Loaded;
        this.Closed += SomeWindow_Closed;
    }

    void SomeWindow_Loaded(object sender, RoutedEventArgs e)
    {
        _someViewModel = this.DataContext as SomeViewModel;
        _someViewModel.PropertyChanged += _someViewModel_PropertyChanged;
    }

    void SomeWindow_Closed(object sender, System.EventArgs e)
    {
        _someViewModel.PropertyChanged -= _someViewModel_PropertyChanged;
        this.Loaded -= SomeWindow_Loaded;
        this.Closed -= SomeWindow_Closed;
    }

    void _someViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == SomeViewModel.DialogResultPropertyName)
        {
            this.DialogResult = _someViewModel.DialogResult;
        }
    }
}

Le fragment le plus important est _someViewModel_PropertyChanged.DialogResultPropertyName peut être une chaîne publique constante dans SomeViewModel.

J'utilise ce genre d'astuce pour apporter des modifications dans les contrôles d'affichage au cas où cela serait difficile à faire dans ViewModel. OnPropertyChanged dans ViewModel, vous pouvez faire tout ce que vous voulez dans View. ViewModel est toujours «testable par unité» et quelques petites lignes de code dans le code derrière ne font aucune différence.

0
sliwinski.lukas

J'ai lu toutes les réponses mais je dois dire que la plupart d'entre elles ne sont tout simplement pas assez bonnes, voire pire.

Vous pouvez gérer cela magnifiquement avec la classe DialogService dont la responsabilité est d’afficher la boîte de dialogue et d’en retourner le résultat. J'ai créer exemple de projet démontrant son implémentation et son utilisation.

voici les parties les plus importantes:

//we will call this interface in our viewmodels
public interface IDialogService
{
    bool? ShowDialog(object dialogViewModel, string caption);
}

//we need to display logindialog from mainwindow
public class MainWindowViewModel : ViewModelBase
{
    public string Message {get; set;}
    public void ShowLoginCommandExecute()
    {
        var loginViewModel = new LoginViewModel();
        var dialogResult = this.DialogService.ShowDialog(loginViewModel, "Please, log in");

        //after dialog is closed, do someting
        if (dialogResult == true && loginViewModel.IsLoginSuccessful)
        {
            this.Message = string.Format("Hello, {0}!", loginViewModel.Username);
        }
    }
}


public class DialogService : IDialogService
{
    public bool? ShowDialog(object dialogViewModel, string caption)
    {
        var contentView = ViewLocator.GetView(dialogViewModel);
        var dlg = new DialogWindow
        {
            Title = caption
        };
        dlg.PART_ContentControl.Content = contentView;

        return dlg.ShowDialog();
    }
}

N'est-ce pas plus simple? plus simple, plus lisible et enfin plus facile à déboguer que EventAggregator ou d’autres solutions similaires?

comme vous pouvez le constater, dans ma vision des modèles, j’ai utilisé la première approche de ViewModel décrite dans mon post ici: Méthode recommandée pour appeler View from ViewModel dans WPF

Bien sûr, dans le monde réel, le DialogService.ShowDialog doit avoir plus d’option pour configurer la boîte de dialogue, par exemple. boutons et commandes qu'ils doivent exécuter. Il y a différentes façons de le faire, mais c'est hors de portée :)

0
Liero

Créez unDependency Propertydans votreView/ toute UserControl (ou Window que vous voulez fermer). Comme ci-dessous:

 public bool CloseTrigger
        {
            get { return (bool)GetValue(CloseTriggerProperty); }
            set { SetValue(CloseTriggerProperty, value); }
        }

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

        private static void OnCloseTriggerChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
        {
            //write Window Exit Code
        }

Et liez-le à partir de la propriété de votre ViewModel :

<Window x:Class="WpfStackOverflowTempProject.MainWindow"
        xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
        Title="MainWindow"  Width="525"
        CloseTrigger="{Binding Path=CloseWindow,Mode=TwoWay}"

Propriété dansVeiwModel:

private bool closeWindow;

    public bool CloseWindow
    {
        get { return closeWindow; }
        set 
        { 
            closeWindow = value;
            RaiseChane("CloseWindow");
        }
    }

Maintenant, déclenchez l'opération de fermeture en modifiant la valeurCloseWindowdans ViewModel. :)

0
Kylo Ren

Bien que cela ne réponde pas à la question de savoir comment procéder via le modèle de vue, cela montre également comment le faire en utilisant uniquement XAML + le kit de développement intégré.

J'ai choisi de télécharger et d'utiliser deux fichiers du SDK Blend, que vous pouvez regrouper sous la forme d'un package fourni par Microsoft via NuGet. Les fichiers sont:

System.Windows.Interactivity.dll et Microsoft.Expression.Interactions.dll

Microsoft.Expression.Interactions.dll vous offre des fonctionnalités intéressantes telles que la possibilité de définir une propriété ou d'appeler une méthode sur votre modèle de vue ou une autre cible, ainsi que d'autres widgets.

Quelques XAML:

<Window x:Class="Blah.Blah.MyWindow"
    ...
    xmlns:i="http://schemas.Microsoft.com/expression/2010/interactivity"
    xmlns:ei="http://schemas.Microsoft.com/expression/2010/interactions"
  ...>
 <StackPanel>
    <Button x:Name="OKButton" Content="OK">
       <i:Interaction.Triggers>
          <i:EventTrigger EventName="Click">
             <ei:ChangePropertyAction
                      TargetObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
                      PropertyName="DialogResult"
                      Value="True"
                      IsEnabled="{Binding SomeBoolOnTheVM}" />                                
          </i:EventTrigger>
    </Button>
    <Button x:Name="CancelButton" Content="Cancel">
       <i:Interaction.Triggers>
          <i:EventTrigger EventName="Click">
             <ei:ChangePropertyAction
                      TargetObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
                      PropertyName="DialogResult"
                      Value="False" />                                
          </i:EventTrigger>
    </Button>

    <Button x:Name="CloseButton" Content="Close">
       <i:Interaction.Triggers>
                <i:EventTrigger EventName="Click">
                    <!-- method being invoked should be void w/ no args -->
                    <ei:CallMethodAction
                        TargetObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
                        MethodName="Close" />
                </i:EventTrigger>
            </i:Interaction.Triggers>
    </Button>
 <StackPanel>
</Window>

Notez que si vous optez pour un comportement simple OK/Cancel, vous pouvez vous échapper en utilisant les propriétés IsDefault et IsCancel tant que la fenêtre est affichée avec w/Window.ShowDialog ().
J'ai personnellement eu des problèmes avec un bouton dont la propriété IsDefault était définie sur true, mais il était masqué lors du chargement de la page. Il ne semblait pas vouloir jouer correctement après son affichage. Je ne fais donc que définir la propriété Window.DialogResult comme indiqué ci-dessus et cela fonctionne pour moi.

0
Wes

J'ai fini par mélanger la réponse de Joe White et du code de la réponse d'Adam Mills , car je devais afficher un contrôle utilisateur dans une fenêtre créée par programme. Donc, le DialogCloser ne doit pas nécessairement être sur la fenêtre, il peut être sur le contrôle de l'utilisateur lui-même

<UserControl ...
    xmlns:xw="clr-namespace:Wpf"
    xw:DialogCloser.DialogResult="{Binding DialogResult}">

Et DialogCloser trouvera la fenêtre du contrôle utilisateur si elle n’était pas attachée à la fenêtre elle-même.

namespace 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.GetWindow();
      if (window != null)
        window.DialogResult = e.NewValue as bool?;
    }

    public static void SetDialogResult(DependencyObject target, bool? value)
    {
      target.SetValue(DialogResultProperty, value);
    }
  }

  public static class Extensions
  {
    public static Window GetWindow(this DependencyObject sender_)
    {
      Window window = sender_ as Window;        
      return window ?? Window.GetWindow( sender_ );
    }
  }
}
0
Anuroopa Shenoy

J'irais de cette façon:

using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;    
using GalaSoft.MvvmLight.Messaging; 

// View

public partial class TestCloseWindow : Window
{
    public TestCloseWindow() {
        InitializeComponent();
        Messenger.Default.Register<CloseWindowMsg>(this, (msg) => Close());
    }
}

// View Model

public class MainViewModel: ViewModelBase
{
    ICommand _closeChildWindowCommand;

    public ICommand CloseChildWindowCommand {
        get {
            return _closeChildWindowCommand?? (_closeChildWindowCommand = new RelayCommand(() => {
                Messenger.Default.Send(new CloseWindowMsg());
        }));
        }
    }
}

public class CloseWindowMsg
{
}
0
romanoza

Le comportement est le moyen le plus pratique ici. 

  • D'une part, il peut être lié au modèle de vue donné (cela peut indiquer Signaler "fermer le formulaire!")

  • D'un autre côté, il a accès au formulaire lui-même et peut donc s'abonner aux événements spécifiques au formulaire nécessaires, ou afficher une boîte de dialogue de confirmation, ou autre.

Écrire le comportement nécessaire peut être vu comme ennuyeux dès la première fois. Cependant, à partir de maintenant, vous pouvez le réutiliser sur chaque formulaire dont vous avez besoin sous forme d'extrait XAML exact à une ligne. Et si nécessaire, vous pouvez l'extraire en tant qu'assemblage séparé afin de l'inclure dans le prochain projet de votre choix.

0
Yury Schkatula

Pourquoi ne pas simplement passer la fenêtre en tant que paramètre de commande?

C #:

 private void Cancel( Window window )
  {
     window.Close();
  }

  private ICommand _cancelCommand;
  public ICommand CancelCommand
  {
     get
     {
        return _cancelCommand ?? ( _cancelCommand = new Command.RelayCommand<Window>(
                                                      ( window ) => Cancel( window ),
                                                      ( window ) => ( true ) ) );
     }
  }

XAML:

<Window x:Class="WPFRunApp.MainWindow"
        x:Name="_runWindow"
...
   <Button Content="Cancel"
           Command="{Binding Path=CancelCommand}"
           CommandParameter="{Binding ElementName=_runWindow}" />
0
chrislarson

J'ai implémenté la solution de Joe White, mais j'ai rencontré des problèmes avec des erreurs "DialogResult" occasionnelles qui ne peuvent être définies qu'après la création de Window et affichées sous forme d'erreurs de dialogue ".

Je conservais le ViewModel après la fermeture de la vue et occasionnellement, j'ouvrais par la suite une nouvelle vue à l'aide du même ordinateur virtuel. Il semble que la fermeture de la nouvelle vue avant que l'ancienne vue ait été nettoyée ait abouti à DialogResultChanged essayer de définir la propriété DialogResult sur la fenêtre fermée, provoquant ainsi l'erreur.

Ma solution a été de changer DialogResultChanged pour vérifier la propriété IsLoaded de la fenêtre:

private static void DialogResultChanged(
    DependencyObject d,
    DependencyPropertyChangedEventArgs e)
{
    var window = d as Window;
    if (window != null && window.IsLoaded)
        window.DialogResult = e.NewValue as bool?;
}

Après avoir apporté cette modification, toutes les pièces jointes aux dialogues fermés sont ignorées.

0
Jim Hansen

Voici la solution simple sans bug (avec le code source), ça marche pour moi.

  1. Dérivez votre ViewModel de INotifyPropertyChanged

  2. Créer une propriété observable CloseDialog in ViewModel

    public void Execute()
    {
        // Do your task here
    
        // if task successful, assign true to CloseDialog
        CloseDialog = true;
    }
    
    private bool _closeDialog;
    public bool CloseDialog
    {
        get { return _closeDialog; }
        set { _closeDialog = value; OnPropertyChanged(); }
    }
    
    public event PropertyChangedEventHandler PropertyChanged;
    
    private void OnPropertyChanged([CallerMemberName]string property = "")
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(property));
        }
    }
    

  3. Joindre un gestionnaire dans la vue pour ce changement de propriété

        _loginDialogViewModel = new LoginDialogViewModel();
        loginPanel.DataContext = _loginDialogViewModel;
        _loginDialogViewModel.PropertyChanged += OnPropertyChanged;
    
  4. Maintenant vous avez presque fini. Dans le gestionnaire d'événements make DialogResult = true 

    protected void OnPropertyChanged(object sender, PropertyChangedEventArgs args)
    {
        if (args.PropertyName == "CloseDialog")
        {
            DialogResult = true;
        }
    }
    
0
Anil8753