0

I am learning to use DelgateCommand from Prism....

In my UI, I have my UserName textbox and PasswordBox:

<TextBox Name="_UserNameTextBox" Text="{Binding UserName, Mode=TwoWay}" />

<PasswordBox Name="_PasswordBox"></PasswordBox>

And my Login Button:

<Button Name="button1" Command="{Binding LoginCommand, Mode=TwoWay}" CommandTarget="{Binding ElementName=_UserNameTextBox, Path=Text}">Login</Button>

Then my ViewModel I have:

    string _UserName = string.Empty;
    public string UserName
    {
        get
        {
            return _UserName;
        }
        set
        {
            if (value != _UserName)
            {
                _UserName = value;
                RaisePropertyChanged("UserName");
            }
        }

    }

    //For reference the password
    PasswordBox _PasswordBox { get; set; }


    public DelegateCommand<string> LoginCommand { get; set; }

    public LoginViewModel(PasswordBox passwordBox)
    {
        _PasswordBox = passwordBox;

        LoginCommand = new DelegateCommand<string>(
            (
                //Execute
                (str) =>
                {
                    Login(_PasswordBox.Password);
                }
            ),
                //CanExecute Delgate
                (usr) =>
                {
                    if (string.IsNullOrEmpty(usr) || string.IsNullOrEmpty(_PasswordBox.Password))
                        return false;
                    return true;
                }
            );
    }

I can see my UserName is binding properly and I did pass my PasswordBox as referece in ViewModel constructor. When I execute the application the Button is disabled, so I know is binded to the command.

But I never see the CanExecute delgate that I wrote is being check after I type things in UserName and PasswordBox.... And is never enabled...

So what did I done wrong?

EDIT:

=====

So end result is...this?

string _UserName = string.Empty;
        public string UserName
        {
            get
            {
                return _UserName;
            }
            set
            {
                if (value != _UserName)
                {
                    _UserName = value;
                    RaisePropertyChanged("UserName");
                    LoginCommand.RaiseCanExecuteChanged();
                }
            }

        }

        //For reference the password
        PasswordBox _PasswordBox { get; set; }


        public DelegateCommand<string> LoginCommand { get; set; }

        public LoginViewModel(PasswordBox passwordBox)
        {
            _PasswordBox = passwordBox;
            _PasswordBox.PasswordChanged += delegate(object sender, System.Windows.RoutedEventArgs e)
            {
                LoginCommand.RaiseCanExecuteChanged();
            };
            LoginCommand = new DelegateCommand<string>(
                (
                    (str) =>
                    {
                        Login(_PasswordBox.Password);
                    }
                ),
                    (usr) =>
                    {
                        if (string.IsNullOrEmpty(usr) || string.IsNullOrEmpty(_PasswordBox.Password))                        
                            return false;
                        return true;
                    }
                );
        }
King Chan
  • 4,212
  • 10
  • 46
  • 78
  • Where is the CanExecute delegate.. you have to set button.Enabled = true; where are you doing that..?? – MethodMan Jan 19 '12 at 19:14
  • hmm? The 2nd Parameter in DelgateCommand is Func I am passing a lambada expression to return true only if UserName is not null or empty, Password is not null or empty. Or I done wrong? – King Chan Jan 19 '12 at 19:17
  • You are not supposed to have a reference to a PasswordBox there, as Jon said, make the password a property as well. – H.B. Jan 19 '12 at 19:35
  • @H.B. I tried to make it as property, but there is problem with Binding PasswordBox... "'Binding' cannot be set on the 'Password' property of type 'PasswordBox'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject." – King Chan Jan 19 '12 at 19:47
  • @KingChan: Well, good luck working around that, i am pretty sure some questions have be asked about this before. Also, ask Jon, *he* suggested it after all... – H.B. Jan 19 '12 at 19:48
  • @H.B. I remember I searched about this solution a while ago, like this http://blog.functionalfun.net/2008/06/wpf-passwordbox-and-data-binding.html But I don't want to write a bunch of code just to bind a password. – King Chan Jan 19 '12 at 19:51

2 Answers2

2

Generally speaking, you have to call RaiseCanExecuteChanged whenever the effecting value returned by CanExecute changes. In this specific case you would need to call it whenever the value of the user or password fields changes. But that is exceedingly difficult, because your ViewModel implementation is totally wrong.

Here's what you should do instead:

  1. Expose a Username and a Password property inside your ViewModel. You will need to implement the getters and setters explicitly (i.e. it cannot be an automatic property).
  2. From within your view, bind the contents of the username and password input fields to these properties.
  3. Inside the property setters, call LoginCommand.RaiseCanExecuteChanged.

Here's what will happen when you do this (let's pick the password box for an example):

  1. The user types a character inside the password box.
  2. WPF sets the value of LoginViewModel.Password because of the two-way binding.
  3. The password setter calls RaiseCanExecuteChanged, which raises the CanExecuteChanged event on your command.
  4. The submit button (which has subscribed to that event when you bound it to the command) gets notified.
  5. The button calls CanExecute to see if executing the command is now allowed.
  6. Your delegate runs and returns true, so the button activates itself.
Jon
  • 428,835
  • 81
  • 738
  • 806
  • +1 After I replaced my CommandTarget with CommandParameter, add an EventHandler for PasswordChanged to RaiseCanExecuteChanged, raise RaiseCanExecuteChanged in UserName setter, it worked prefectly. :) And I didn't bind Password property wtih PasswordBox according to this post: Accoding this post, http://stackoverflow.com/questions/1483892/wpf-binding-to-the-passwordbox-in-mvvm-working-solution – King Chan Jan 19 '12 at 19:30
  • @KingChan: That post is IMHO misguided. Read what the high-rated comments under it say. If you believe that there are going to be hostile processes running on your system trying to steal your password you have bigger problems that just throwing in a `PasswordBox` and thinking that you somehow magically made your application secure. – Jon Jan 19 '12 at 19:32
  • o hmmm so I should write a Getter property for Password to get the password from PasswordBox instead? Because I can't bind to PasswordBox directly... – King Chan Jan 19 '12 at 19:36
1

You need to bind the Button.CommandParameter (which will be passed to the Execute and CanExecute), if that binding changes the CanExecute is reevaluted as far as i know.

(I think you are confusing the CommandParameter with the CommandTarget, the CommandTarget is not used inside the command, it is only used to raise a command on a certain element (which can be relevant in terms of command routing and such)

H.B.
  • 166,899
  • 29
  • 327
  • 400
  • 1
    @KingChan: **Do not do this.** It is taking you totally in the wrong direction. Try to understand what is happening here by reading my example. Even if this can be hacked into working, you are going to have the exact same problem with the password box! – Jon Jan 19 '12 at 19:28
  • @Jon: Actually it isn't really a problem, you could easily adapt this, with the two dependecies you just need a `MultiBinding`. – H.B. Jan 19 '12 at 19:36
  • @H.B.: Sure you *can* adapt it, you are using a Turing-complete language -- but if that's not bastard MVVM then I don't know what is. – Jon Jan 19 '12 at 19:45
  • @Jon: If you ask me this has nothing to do with MVVM,,, – H.B. Jan 19 '12 at 19:46
  • @H.B.: We can agree to disagree then. :) – Jon Jan 19 '12 at 20:02