Search code examples
c#wpfxamlmvvmicommand

ICommand doesn't work


I am starting with MVVM pattern and I have a problem with my button's command. I have a window which contains a TextBox for login and a custom PasswordBox with Password DependencyProperty for password (I know that any password should not be kept in memory, however it is only for fun, so do not be frustrated :) ) . There is also a button and I want it to be enabled if and only if the login and password are both not empty. However my command does not work properly. Here is the code:

LoginWindow xaml:

<Window x:Class="WpfMVVMApplication1.LoginWindow"
    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:WpfMVVMApplication1"
    xmlns:vm="clr-namespace:WpfMVVMApplication1.ViewModel"
    mc:Ignorable="d"
    Title="Login" WindowStartupLocation="CenterScreen" ResizeMode="NoResize"
    Width="250" Height="150">
<Window.DataContext>
    <vm:LoginViewModel />
</Window.DataContext>
<Window.Resources>
    <!-- here some styles for controls -->
</Window.Resources>
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="1.5*"/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
        <RowDefinition Height="*"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <TextBlock Text="Login:"/>
    <TextBlock Text="Password:" Grid.Row="1"/>
    <TextBox Text="{Binding Login, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
    <local:MyPasswordBox Grid.Row="1" PasswordText="{Binding Password, Mode=TwoWay}"/>
    <Button Command="{Binding SaveUserCommand}"/>
</Grid>

My LoginViewModel class

public class LoginViewModel : INotifyPropertyChanged
{
    public LoginViewModel()
    {
        SaveUserCommand = new Command(x => SaveUser(), x => { return !string.IsNullOrEmpty(Login) && !string.IsNullOrEmpty(Password); });
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public ICommand SaveUserCommand { get; private set; }

    private string login;
    private string password;
    public string Login
    {
        get
        {
            return login;
        }
        set
        {
            login = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Login"));
        }
    }

    public string Password
    {
        get
        {
            return password;
        }
        set
        {
            password = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Password"));
        }
    }

    public void SaveUser() { }
}

Also MyPasswordBox class may be useful:

<UserControl x:Class="WpfMVVMApplication1.MyPasswordBox"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:WpfMVVMApplication1"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">
<Grid>
    <PasswordBox x:Name="password" PasswordChanged="password_PasswordChanged"/>
</Grid>

public partial class MyPasswordBox : UserControl
{
    public MyPasswordBox()
    {
        InitializeComponent();
        PasswordText = "";
    }

    public static readonly DependencyProperty PasswordTextProperty =
        DependencyProperty.Register("PasswordText", typeof(string), typeof(MyPasswordBox), new FrameworkPropertyMetadata(""));

    public string PasswordText
    {
        get { return (string)GetValue(PasswordTextProperty); }
        set { SetValue(PasswordTextProperty, value); }
    }

    private void password_PasswordChanged(object sender, RoutedEventArgs e)
    {
        PasswordText = password.Password;
    }
}

I wrote some unit tests for that which checks the result of SaveUserCommand CanExecute method and they all passed. Furthermore, I run the debug in Visual Studio adding breakpoints in setters of Login and Password properties and they both are set properly.

I have run out of ideas what I could make wrong. Can anybody help me?

I feel that I don't notify the command about the changes properly, however I do not know how to do that


Solution

  • In order for WPF to pick up a change to whether a command can be executed, the command's CanExecuteChanged event needs to be fired.

    You will need to fire this event when the login or the password changes. You haven't shown the source of your class Command, but it will need to have a method that fires its CanExecuteChanged event. Then, just call this method from the Login and Password property setters.