WPFNoob WPFNoob - 3 months ago 22
C# Question

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


So, following onto my question - How can I close the Login
Window control
from the ViewModel?

Thanks in advance.

Answer

You can simply 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 close 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}"

It is a clean and easy solution which is in compliance with the MVVM programming paradigm.

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

XAML

<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 is the same for every wpf application.

EDIT:

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

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 IClosable
{
    void Close();
}

Your refactored ViewModel will look like this:

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

You need reference/implement the IClosable interface your view as well View (Code behind)

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