web-dev-qa-db-fra.com

Fermer la fenêtre de ViewModel

Je crée une connexion en utilisant un window control pour permettre à un utilisateur de se connecter à une application WPF 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 les variables username et password dans une variable textbox sur l'écran de connexion, binding deux properties

J'ai atteint cet objectif en créant une méthode bool, comme ceci;

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 une command que je bind à mon bouton dans la xaml comme so;

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

Lorsque je saisis le nom d'utilisateur et le mot de passe, le code approprié est exécuté, qu'il soit correct ou incorrect. Mais comment puis-je fermer cette fenêtre de ViewModel lorsque le nom d'utilisateur et le mot de passe sont corrects?

J'ai déjà essayé d'utiliser un dialog modal mais cela n'a pas vraiment fonctionné. De plus, dans mon application.xaml, j’ai fait quelque chose comme ce qui suit, qui charge d’abord la page de connexion, puis une fois la valeur true chargée, 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 le Login Window control à partir du ViewModel?

Merci d'avance.

66
WPFNoob

Vous pouvez passer la fenêtre à votre ViewModel à l’aide de CommandParameter. Voir mon exemple ci-dessous.

J'ai implémenté une méthode CloseWindow qui prend un Windows en paramètre et le ferme. La fenêtre est transmise 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 passe la fenêtre elle-même en tant que paramètre au ViewModel à l'aide de 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();
    }
}

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

Cette solution viole le 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 la programmation MVVM, vous devez résumer le type de la vue avec une interface.

Solution conforme MVVM (Ancien EDIT2)

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

Le passage de l'objet Window au modèle de vue détruit le modèle MVVM IMHO, car il oblige votre vm à savoir en quoi il est visualisé.

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

Interface:

public interface IClosable
{
    void Close();
}

Votre ViewModel refacturé ressemblera à ceci:

ViewModel

public RelayCommand<IClosable> CloseWindowCommand { get; private set; }

public MainViewModel()
{
    this.CloseWindowCommand = new RelayCommand<IClosable>(this.CloseWindow);
}

private void CloseWindow(IClosable window)
{
    if (window != null)
    {
        window.Close();
    }
}

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

Voir (code derrière)

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

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

Votre bouton de connexion (CommandParameter 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();
     }
 }
117
Joel

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 dans ce genre de situation.

Si vous suivez la route Behavior, 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 CloseTrigger à une valeur booléenne qui serait définie lors de la fermeture de 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é que vous auriez définie lorsque vous souhaitiez fermer la fenêtre de la manière suivante:

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("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 ())

26
Steve Van Treeck

Je mets généralement un événement sur le modèle de vue lorsque je dois le faire, puis le raccorde à 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(); 
24
ChrisO

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

Vous pouvez maintenant lier DialogResult à un VM et définir sa valeur de propriété. La Window se 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 se passe 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 constater, je déclare l’espace de nom xmlns:hlp="clr-namespace:AC.Frontend.Helper" en premier, puis la liaison hlp:AttachedProperties.DialogResult="{Binding DialogResult}"

La AttachedProperty ressemble à ceci. Ce n'est pas le même que j'ai posté 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
}
12
DHN

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 une aide au 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 comme ça dans viewmodel

WindowManager.CloseWindow(ViewID);
8
RassK

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

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

Que diriez-vous de this ?

ViewModel:

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

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

La réponse que vous avez choisie ne me semble pas anormale. Je pensais simplement que ce serait un moyen plus simple de le faire!

3

Voici un exemple simple d'utilisation de 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();
    }
2
Hamish Gunn

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

Ensuite, définissez 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();
    }

Puis dans l'ensemble de fichiers XAML 

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

Mon moyen proposé est l'événement Declare dans ViewModel et utilise blend InvokeMethodAction comme ci-dessous.

Exemple de ViewModel

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 Une interface pouvant être fermée est comme ci-dessous mais vous n'avez pas besoin d'effectuer cette action. ICloseable vous aidera à créer un service de vue générique. Par conséquent, si vous construisez une vue et ViewModel par injection de dépendance, vous pourrez alors: 

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

Ci-dessous, 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>

1
RAJ

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

Application.Current.Windows[0].Close();
0
chandudab

Je sais que c'est un ancien post, probablement personne ne ferait défiler aussi loin, je sais que je ne l'ai pas fait. Alors, après des heures d’essai, j’ai trouvé ce blog et l’avoir tué. Le moyen le plus simple de le faire, l'a essayé et cela fonctionne à merveille.

Blog

0
Serlok

C'est simple . Vous pouvez créer votre propre classe ViewModel pour Login - LoginViewModel . Vous pouvez créer une vue var dialog = new UserView (); dans votre LoginViewModel. Et vous pouvez configurer Command LoginCommand en 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;
    }
}
0
misak