Search code examples
c#wpfmvvm

Close Window from ViewModel


Im creating a Login using a window control to allow a user to login into a WPF application that I am creating.

So far, I have created a method that checks whether the user has entered in the correct credentials for the username and password in a textbox on the login screen, binding two properties.

I have achieved this by creating a bool method, like so;

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

I also have a command that I bind to my button within the xaml like so;

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

When I enter in the username and password it executes the appropriated code, whether it being right, or wrong. But how can I close this window from the ViewModel when both username and password are correct?

I have previously tried using a dialog modal but it didn't quite work out. Furthermore, within my app.xaml, I have done something like the following, which loads the login page first, then once true, loads the actual application.

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: How can I close the Login Window control from the ViewModel?


Solution

  • You can pass the window to your ViewModel using the CommandParameter. See my Example below.

    I've implemented an CloseWindow Method which takes a Windows as parameter and closes it. The window is passed to the ViewModel via CommandParameter. Note that you need to define an x:Name for the window which should be close. In my XAML Window i call this method via Command and pass the window itself as a parameter to the ViewModel using 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();
        }
    }
    

    View

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

    Note that i'm using the MVVM light framework, but the principal applies to every wpf application.

    This solution violates of the MVVM pattern, because the view-model shouldn't know anything about the UI Implementation. If you want to strictly follow the MVVM programming paradigm you have to abstract the type of the view with an interface.

    MVVM conform solution (Former EDIT2)

    the user Crono mentions a valid point in the comment section:

    Passing the Window object to the view model breaks the MVVM pattern IMHO, because it forces your vm to know what it's being viewed in.

    You can fix this by introducing an interface containing a close method.

    Interface:

    public interface ICloseable
    {
        void Close();
    }
    

    Your refactored ViewModel will look like this:

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

    You have to reference and implement the ICloseable interface in your view

    View (Code behind)

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

    Answer to the original question: (former EDIT1)

    Your Login Button (Added CommandParameter):

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

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