Fermer la fenêtre de ViewModel

95

Je crée une connexion en utilisant un window controlpour permettre à un utilisateur de se connecter à une WPFapplication que je crée.

Jusqu'à présent, j'ai créé une méthode qui vérifie si l'utilisateur a entré les informations d'identification correctes pour le usernameet passworddans un textboxsur l'écran de connexion, bindingdeux properties.

J'ai réalisé cela en créant une boolméthode, comme ça;

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

J'ai aussi un commandque je bindsur mon bouton dans le même xamlgenre;

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

Lorsque j'entre le nom d'utilisateur et le mot de passe, il exécute le code approprié, qu'il soit correct ou faux. Mais comment puis-je fermer cette fenêtre à partir du ViewModel lorsque le nom d'utilisateur et le mot de passe sont corrects?

J'ai déjà essayé d'utiliser un dialog modalmais cela n'a pas tout à fait fonctionné. De plus, dans mon app.xaml, j'ai fait quelque chose comme ce qui suit, qui charge d'abord la page de connexion, puis une fois true, charge l'application réelle.

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

Question: Comment puis-je fermer la connexion à Window controlpartir du ViewModel?

Merci d'avance.

WPFNoob
la source

Réponses:

149

Vous pouvez transmettre la fenêtre à votre ViewModel en utilisant le CommandParameter. Voir mon exemple ci-dessous.

J'ai implémenté une CloseWindowméthode qui prend un Windows comme paramètre et le ferme. La fenêtre est transmise au ViewModel via CommandParameter. Notez que vous devez définir un x:Namepour la fenêtre qui doit être fermée. Dans ma fenêtre XAML, j'appelle cette méthode via Commandet transmets la fenêtre elle-même en tant que paramètre au ViewModel en utilisant CommandParameter.

Command="{Binding CloseWindowCommand, Mode=OneWay}" 
CommandParameter="{Binding ElementName=TestWindow}"

VoirModèle

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

Vue

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

Notez que j'utilise le framework léger MVVM, mais le principe s'applique à chaque application wpf.

Cette solution enfreint le modèle MVVM, car le modèle de vue ne doit rien savoir de l'implémentation de l'interface utilisateur. Si vous souhaitez suivre strictement le paradigme de programmation MVVM, vous devez abstraire le type de vue avec une interface.

Solution conforme MVVM (anciennement EDIT2)

l'utilisateur Crono mentionne un point valide dans la section commentaire:

Passer l'objet Window au modèle de vue rompt le modèle MVVM à mon humble avis, car cela oblige votre VM à savoir dans quoi il est affiché.

Vous pouvez résoudre ce problème en introduisant une interface contenant une méthode close.

Interface:

public interface ICloseable
{
    void Close();
}

Votre ViewModel remanié ressemblera à ceci:

VoirModèle

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

Vous devez référencer et implémenter l' ICloseableinterface dans votre vue

Vue (code derrière)

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

Réponse à la question initiale: (ancien EDIT1)

Votre bouton de connexion (paramètre de commande ajouté):

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

Votre code:

 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();
     }
 }
Joël
la source
1
Merci pour la mise à jour @Joel. Une dernière question, en raison de la méthode prenant un paramètre de Window, et quand j'appelle cette méthode dans ma commande, elle attend un paramètre, est-ce que je créerais un paramètre Window local qui est appelé pour la méthode, par exemple; private void LoginExecute(){this.CheckLogin();}<- CheckLogin doit prendre un paramètre.
WPFNoob
désolé je ne comprends pas, pouvez-vous clarifier un peu votre question?
Joel
14
Si vous n'aimez pas nommer vos fenêtres, vous pouvez également lier le paramètre comme ceci:CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
Jacco Dieleman
33
Passer l' Windowobjet au modèle de vue casse le modèle MVVM à mon humble avis, car cela force votre VM à savoir dans quoi il est affiché. Et si la vue était un onglet ancré dans une interface MDI à la place? La bonne façon de faire cela à mon humble avis est de passer une sorte d'interface IUIHost qui implémente une méthode Close, et d'avoir la vue que vous voulez montrer à votre vm l'implémenter.
Crono
2
Ce n'est pas grave car l'interface cache l'implémentation concrète du ViewModel. Le ViewModel ne sait rien sur la vue, sauf qu'il implémente une méthode Close (). Ainsi, la vue peut être n'importe quoi: une fenêtre WPF, un formulaire WinForms, une application UWP ou même une grille WPF. Il dissocie la vue du modèle de vue.
Joel
34

En restant MVVM, je pense que l'utilisation de Behaviors du Blend SDK (System.Windows.Interactivity) ou d'une demande d'interaction personnalisée de Prism pourrait très bien fonctionner pour ce genre de situation.

Si vous suivez la voie du comportement, voici l'idée générale:

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

Ensuite, dans votre fenêtre, vous lieriez simplement le CloseTrigger à une valeur booléenne qui serait définie lorsque vous vouliez que la fenêtre se ferme.

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

Enfin, votre DataContext / ViewModel aurait une propriété que vous définiriez lorsque vous vouliez que la fenêtre se ferme comme ceci:

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

(définissez votre Window.DataContext = new MainWindowViewModel ())

Steve Van Treeck
la source
Merci pour la réponse @Steve, vous avez mentionné la liaison du CloseTrigger à une booleanvaleur. Lorsque vous avez dit cela, vouliez-vous que je crée un DataTriggerpour y parvenir?
WPFNoob
Désolé, j'aurais dû être plus explicite - j'aurais une propriété sur mon viewmodel (dans l'exemple ci-dessus, une appelée CloseTrigger) qui serait définie sur true, ce qui finirait par déclencher le comportement. J'ai mis à jour la réponse
Steve Van Treeck
Cela a fonctionné, mais j'ai dû changer la façon dont mon application se chargeait. Parce que j'utilisais une fenêtre pour mon application principale, elle a également tué toutes les fenêtres enfants. Merci.
WPFNoob
Définir une propriété sur true pour effectuer une action est une odeur IMO.
Josh Noe
33

Je mets généralement un événement sur le modèle de vue lorsque je dois le faire, puis je le connecte au Window.Close()lors de la liaison du modèle de vue à la fenêtre

public class LoginViewModel
{
    public event EventHandler OnRequestClose;

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

Et lors de la création de la fenêtre de connexion

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

loginWindow.ShowDialog(); 
ChrisO
la source
11
Le délégué anonyme est rapidement écrit, mais il convient de noter que l'événement ne peut pas être désinscrit (ce qui peut ou non être un problème). Habituellement mieux avec un gestionnaire d'événements à part entière.
Mathieu Guindon
C'est ce que j'aime le plus. Il est de toute façon difficile d'éviter un traitement spécial lors de l'affichage de la fenêtre (par exemple Loaded, ContentRenderedpour la fenêtre principale, les services de dialogue, etc.), y ajouter un peu via l'événement ViewModel est assez propre pour moi. 3 lignes de code n'ont pas vraiment besoin de solution de réutilisation. PS: le pur MVVM est de toute façon pour les nerds.
Sinatr
Garçon, cela m'a aidé.
Dimitri
C'est bien mieux que la réponse acceptée, car elle ne rompt pas le modèle MVVM.
Spook
22

il est peut-être tard, mais voici ma réponse

foreach (Window item in Application.Current.Windows)
{
    if (item.DataContext == this) item.Close();
}
Ahmed Abuelnour
la source
1
pourquoi n'est-ce pas la vraie réponse?
user2529011
1
@ user2529011 certains, au moins, se plaindraient que le viewmodel ne devrait rien savoir sur Application.Current.Windows
soutient gusmally Monica
-1. Le modèle de vue ne doit rien savoir du tout sur la vue. Vous pouvez aussi bien l'écrire dans le code derrière pour cette question.
Alejandro
13

Eh bien, voici quelque chose que j'ai utilisé dans plusieurs projets. Cela peut ressembler à un hack, mais cela fonctionne très 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;
    }
}

Vous pouvez maintenant vous lier DialogResultà une machine virtuelle et définir sa valeur de propriété. Le Windowse ferme lorsque la valeur est définie.

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

Ceci est un résumé de ce qui fonctionne dans notre environnement de production

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

Comme vous pouvez le voir, je déclare d'abord l'espace de noms xmlns:hlp="clr-namespace:AC.Frontend.Helper"et ensuite la liaison hlp:AttachedProperties.DialogResult="{Binding DialogResult}".

Le AttachedPropertyressemble à ça. Ce n'est pas la même chose que j'ai postée hier, mais à mon humble avis, cela ne devrait avoir aucun effet.

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
la source
Non, ce n'est pas une question idiote. Il suffit de mettre la déclaration de la liaison dans l' <Window />élément comme je l'ai illustré dans mon snipped. J'étais juste trop paresseux pour écrire le reste (déclarations d'espace de noms, etc.), qui y sont généralement également déclarés.
DHN
1
Veuillez vous référer à ma modification. J'ai publié le code de production, donc je suis sûr qu'il fonctionne. Cela a l'air un peu différent, mais le code que j'ai publié hier devrait également fonctionner.
DHN
Merci d'avoir clarifié cela. Il s'est avéré que j'appelais le mauvais espace de noms: S. Dois-je juste créer un datatriggeret l'attribuer au bouton pour le faire fonctionner? Encore une fois désolé pour la question nooby.
WPFNoob
Merci - eh bien, je suis juste conscient que je pose trop de questions qui peuvent sembler idiotes et stupides et perdre le temps des gens! Mais revenons à ma question. Après tout ce que vous avez mentionné, comment fermer la fenêtre? Utilisez un DataTrigger¬ and setting value vrai?
WPFNoob
1
Eh bien c'est la partie, je vous laisse. ; o) Pensez à la DataContextdu Dialog. Je m'attendrais à ce que la machine virtuelle définie comme DataContextfournisse une commande, qui définit la propriété DialogResultou tout ce que vous avez lié trueou false, de sorte que le se Dialogferme.
DHN
13

Moyen facile

public interface IRequireViewIdentification
{
    Guid ViewID { get; }
}

Implémenter dans ViewModel

public class MyViewVM : IRequireViewIdentification
{
    private Guid _viewId;

    public Guid ViewID
    {
        get { return _viewId; }
    }

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

Ajouter un assistant de gestionnaire de fenêtres général

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

Et fermez-le comme ça dans viewmodel

WindowManager.CloseWindow(ViewID);
RassK
la source
Une très belle solution.
DonBoitnott
J'ai changé le WindowManager alittle pour définir le dialogresult lors de la fermeture du win public static void CloseWindow (Guid id, bool dialogResult) {foreach (Window window in Application.Current.Windows) {var w_id = window.DataContext as IRequireViewIdentification; if (w_id! = null && w_id.ViewID.Equals (id)) {window.DialogResult = dialogResult; window.Close (); }}} appelez-le comme: WindowManager.CloseWindow (_viewId, true);
lebhero
Belle solution, mais fait un couplage étroit entre viewmodel et WindowManager, qui à son tour est étroitement couplé avec View(en termes de PresentationFramework). Ce serait mieux si WindowManagerun service était passé à viewmodel via une interface. Ensuite, vous pourrez (par exemple) migrer facilement votre solution vers une plate-forme différente.
Spook
4

Voici un exemple simple utilisant MVVM Light Messenger au lieu d'un événement. Le modèle de vue envoie un message de fermeture lorsqu'un bouton est cliqué:

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

    public RelayCommand QuitCommand { get; private set; }

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

Ensuite, il est reçu dans le code derrière la fenêtre.

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

    private void HandleCloseMessage(CloseMessage closeMessage)
    {
        Close();
    }
Hamish Gunn
la source
Pouvez-vous s'il vous plaît des conseils, où je peux trouver la mise en œuvre de CloseMessage?
Roman O
CloseMessage est juste une classe vide, utilisée pour identifier le type de message envoyé. (Il peut également contenir des informations de message complexes, ce qui n'est pas nécessaire ici.)
IngoB
4

Et ça ?

VoirModèle:

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

Dans votre ViewModel, utilisez CloseAction () pour fermer la fenêtre comme dans l'exemple ci-dessus.

Vue:

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());
}
Anon
la source
3

Je sais que c'est un vieux post, probablement personne ne ferait défiler jusqu'ici, je sais que non. Donc, après des heures à essayer différentes choses, j'ai trouvé ce blog et mec l'a tué. Le moyen le plus simple de le faire, l'a essayé et cela fonctionne comme un charme.

Blog

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

...

ajoutez une propriété Action au ViewModel, mais définissez-la à partir du fichier code-behind de la vue. Cela nous permettra de définir dynamiquement une référence sur le ViewModel qui pointe vers la vue.

Sur le ViewModel, nous ajouterons simplement:

public Action CloseAction { get; set; }

Et sur la vue, nous la définirons comme telle:

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
la source
Le lien est cassé: /
soutient gusmally Monica
@gusmally êtes-vous sûr? Je l'ai ouvert normalement, réessayez jkshay.com
...
2

Vous pouvez créer un nouveau gestionnaire d'événements dans le ViewModel comme ceci.

public event EventHandler RequestClose;

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

Définissez ensuite RelayCommand pour 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();
    }

Then In XAML file set

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

Définissez le DataContext dans le fichier xaml.cs et abonnez-vous à l'événement que nous avons créé.

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
la source
J'ai utilisé un MVVM Light Messenger au lieu de l'événement.
Hamish Gunn
1

Ma méthode proposée est de déclarer l'événement dans ViewModel et d'utiliser blend InvokeMethodAction comme ci-dessous.

Exemple de vue

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 L'interface fermable est comme ci-dessous mais ne nécessite pas d'effectuer cette action. ICloseable vous aidera à créer un service de vue générique, donc si vous construisez une vue et un ViewModel par injection de dépendances, vous pouvez faire

internal interface ICloseable
{
    event EventHandler CloseRequested;
}

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

Et ci-dessous est Xaml, vous pouvez utiliser ce xaml même si vous n'implémentez pas d'interface, il n'aura besoin que de votre modèle de vue pour déclencher 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
la source
1

Vous pouvez utiliser à Messengerpartir de la boîte à outils MVVMLight. dans votre ViewModelenvoyer un message comme celui-ci:
Messenger.Default.Send(new NotificationMessage("Close"));
puis dans votre code Windows derrière, après InitializeComponent, inscrivez-vous pour ce message comme ceci:

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

vous pouvez en savoir plus sur la boîte à outils MVVMLight ici: Boîte à outils MVVMLight sur Codeplex

Notez qu'il n'y a pas de règle "pas de code-behind du tout" dans MVVM et que vous pouvez vous enregistrer pour les messages dans une vue code-behind.

Amir Oveisi
la source
0

C'est simple. Vous pouvez créer votre propre classe ViewModel pour la connexion - LoginViewModel. Vous pouvez créer une vue var dialog = new UserView (); dans votre LoginViewModel. Et vous pouvez configurer la commande LoginCommand dans le bouton.

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

et

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

Classe 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
la source
3
Oui, c'est aussi une solution valable. Mais si vous voulez vous en tenir à MVVM et au découplage des VM et des vues, vous allez casser le modèle.
DHN
Salut @misak - après avoir essayé de mettre en œuvre votre solution (comme les autres réponses), il lance un Object reference not set to an instance of an object.pour la méthode CloseLoginView. Des suggestions pour résoudre ce problème?
WPFNoob
@WPFNoob - Je présente à nouveau cette solution. L'exemple fonctionne correctement. Voulez-vous envoyer une solution complète de studio visuel par e-mail?
misak
@WPFNoob - Je vois le problème. Vous créez une instance en tant que var dialog = new UserView () ;. Effacer le mot-clé var (instance locale) écrase l'instance globale dans LoginViewModel
misak
0

C'est une façon dont je l'ai fait assez simplement:

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

Je ne vois rien de mal dans la réponse que vous avez choisie, j'ai juste pensé que cela pourrait être un moyen plus simple de le faire!

thestephenstanton
la source
8
Cela nécessite que votre ViewModel connaisse et référence votre View.
AndrewS
@AndrewS pourquoi est-ce mauvais?
thestephenstanton
9
Pour suivre le modèle MVVM, le ViewModel ne doit pas connaître la vue.
MetalMikester
1
Pour développer cela, le but de MVVM est de rendre la plupart de votre unité de code GUI testable. Les vues ont une tonne de dépendances qui les rendent impossibles à tester unitaire. Les ViewModels doivent être testables unitaire, mais si vous leur donnez une dépendance directe sur la vue, ils ne le seront pas.
ILMTitan
Et pour étendre encore plus cela, MVVM correctement écrit vous permet de migrer facilement la solution vers une plate-forme différente. En particulier, vous devriez pouvoir réutiliser vos modèles de vue sans aucune modification. Dans ce cas, si vous déplaçiez votre solution vers Android, cela ne fonctionnerait pas, car Android n'a pas de concept de fenêtre. -1 pour une solution de rupture MVVM.
Spook
0

Vous pouvez traiter window comme un service (par exemple, un service d'interface utilisateur) et se transmettre à viewmodel via une interface , en tant que telle:

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

Cette solution a le plus d'avantages de passer la vue elle-même à viewmodel sans avoir l'inconvénient de casser MVVM, car bien que la vue physique soit passée à viewmodel, ce dernier ne connaît toujours pas le premier, il n'en voit que quelques-uns IMainWindowAccess. Ainsi, par exemple, si nous voulions migrer cette solution vers une autre plate-forme, ce serait seulement une question de mise en œuvre IMainWindowAccesscorrecte pour, par exemple, un Activity.

Je poste la solution ici pour proposer une approche différente de celle des événements (bien que ce soit en fait très similaire), car cela semble un peu plus simple que les événements à implémenter (attacher / détacher, etc.), mais s'aligne toujours bien avec le modèle MVVM.

Effrayer
la source
-1

Vous pouvez fermer la fenêtre actuelle en utilisant simplement le code suivant:

Application.Current.Windows[0].Close();
chandudab
la source
6
Si vous avez plus d'une fenêtre, cela peut fermer une fenêtre incorrecte.
Sasha le
17
Oh mon Dieu! vous avez massacré MVVM
Hossein Shahdoost
-7

System.Environment.Exit (0); en vue le modèle fonctionnerait.

rjain
la source
6
Non, ce ne sera pas le cas. Il quittera l'application et ne fermera pas la fenêtre actuelle.
Tilak
cela a résolu mon problème, car fermer la fenêtre principale (pour moi) == quitter l'application. Toutes les méthodes proposées sauf celle-ci avaient des points délicats lorsqu'elle était appelée à partir de différents threads; mais cette approche ne se soucie pas vraiment de savoir qui est le fil d'appel :) c'était tout ce dont j'avais besoin!
Hamed le
BuAHahahAHahahAha désolé n'a pas pu résister
L.Trabacchin