114 votes

Fermer la fenêtre à partir du ViewModel

Je crée une connexion en utilisant un window control pour permettre à un utilisateur de se connecter à un WPF que je suis en train de créer.

Jusqu'à présent, j'ai créé une méthode qui vérifie si l'utilisateur a saisi les informations d'identification correctes pour l'interface utilisateur. username et password dans un textbox sur l'écran de connexion, binding deux properties .

J'y suis parvenu en créant un bool comme suit ;

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 command que je bind à mon bouton dans le xaml comme ça ;

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

Lorsque je saisis le nom d'utilisateur et le mot de passe, il exécute le code approprié, qu'il soit bon ou mauvais. 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 modal mais ça n'a pas vraiment marché. En outre, dans mon app.xaml, j'ai fait quelque chose comme ce qui suit, qui charge d'abord la page de connexion, puis, une fois que c'est vrai, l'application proprement dite.

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 l'ouverture de session Window control depuis le ViewModel ?

Merci d'avance.

177voto

Joel Points 1102

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

J'ai mis en place un CloseWindow Méthode qui prend une fenêtre comme paramètre et la ferme. La fenêtre est passée au ViewModel via CommandParameter . Notez que vous devez définir un x:Name pour la fenêtre qui devrait être proche. Dans ma fenêtre XAML, j'appelle cette méthode via Command et passer la fenêtre elle-même comme un paramètre au ViewModel en utilisant CommandParameter .

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

ViewModel

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

Voir

<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 cadre MVVM light, mais le principe s'applique à toutes les applications wpf.

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

Solution conforme à MVVM (Ancien EDIT2)

l'utilisateur Crono mentionne un point valable dans la section des commentaires :

Passer l'objet Window au modèle de vue rompt le modèle MVVM IMHO, parce que cela oblige votre MVVM à savoir dans quoi il est visualisé.

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

Interface :

public interface ICloseable
{
    void Close();
}

Votre ViewModel remanié ressemblera à ceci :

ViewModel

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 le ICloseable interface dans votre vue

Vue (code derrière)

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

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

Votre bouton de connexion (Ajouté CommandParameter) :

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

54voto

ChrisO Points 1580

En général, je place un événement sur le modèle de vue lorsque j'ai besoin de faire cela, puis je le connecte à l'interface de l'utilisateur. 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();

41voto

Steve Van Treeck Points 143

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

Si vous choisissez 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, il vous suffit de lier le déclencheur de fermeture à une valeur booléenne qui sera définie lorsque vous voudrez fermer la fenêtre.

<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é à définir lorsque vous souhaitez 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éfinir votre Window.DataContext = new MainWindowViewModel())

29voto

Ahmed Abuelnour Points 181

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

foreach (Window item in Application.Current.Windows)
{
    if (item.DataContext == this) item.Close();
}

14voto

DHN Points 2572

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

Maintenant vous pouvez lier DialogResult à une VM et définir sa valeur d'une propriété. Le site Window se fermera, lorsque la valeur sera 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} />

Voici un résumé de ce qui est exécuté 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 l'espace de noms xmlns:hlp="clr-namespace:AC.Frontend.Helper" d'abord et ensuite la reliure hlp:AttachedProperties.DialogResult="{Binding DialogResult}" .

El AttachedProperty ressemble à ça. Ce n'est pas le même que celui que j'ai posté hier, mais, à mon avis, cela ne devrait pas avoir d'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
}

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