tags:

views:

1300

answers:

4

This is MVVM application. There is a window and related view model class.

There is TextBox, Button and ListBox on form. Button is bound to DelegateCommand that has CanExecute function. Idea is that user enters some data in text box, presses button and data is appended to list box.

I would like to enable command (and button) when user enters correct data in TextBox. Things work like this now:

  • CanExecute() method contains code that checks if data in property bound to text box is correct.
  • Text box is bound to property in view model
  • UpdateSourceTrigger is set to PropertyChanged and property in view model is updated after each key user presses.

Problem is that CanExecute() does not fire when user enters data in text box. It doesn't fire even when text box lose focus.

How could I make this work?

Edit:
Re Yanko's comment:
Delegate command is implemented in MVVM toolkit template and when you create new MVVM project, there is Delegate command in solution. As much as I saw in Prism videos this should be the same class (or at least very similar).

Here is XAML snippet:

    ...
    <UserControl.Resources>
      <views:CommandReference x:Key="AddObjectCommandReference" 
                              Command="{Binding AddObjectCommand}" />
   </UserControl.Resources>

   ...
   <TextBox Text="{Binding ObjectName, UpdateSourceTrigger=PropertyChanged}"> </TextBox>
   <Button Command="{StaticResource AddObjectCommandReference}">Add</Button>
   ...

View model:

   // Property bound to textbox
   public string ObjectName
    {
        get { return objectName; }
        set { 
            objectName = value;
            OnPropertyChanged("ObjectName");
        }
    }


    // Command bound to button
    public ICommand AddObjectCommand
    { 
        get 
        {
            if (addObjectCommand == null)
            {
                addObjectCommand = new DelegateCommand(AddObject, CanAddObject);
            }
            return addObjectCommand;
        } 
    }

    private void AddObject()
    {
        if (ObjectName == null || ObjectName.Length == 0)
            return;
        objectNames.AddSourceFile(ObjectName);
        OnPropertyChanged("ObjectNames"); // refresh listbox
    }

    private bool CanAddObject()
    {
        return ObjectName != null && ObjectName.Length > 0;
    }

As I wrote in the first part of question, following things work:

  • property setter for ObjectName is triggered on every keypress in textbox
  • if I put return true; in CanAddObject(), command is active (button to)

It looks to me that binding is correct.

Thing that I don't know is how to make CanExecute() fire in setter of ObjectName property from above code.

Re Ben's and Abe's answers:

  • CanExecuteChanged() is event handler and compiler complains:

    The event 'System.Windows.Input.ICommand.CanExecuteChanged' can only appear on the left hand side of += or -=

  • there are only two more members of ICommand: Execute() and CanExecute()

Do you have some example that shows how can I make command call CanExecute().

I found command manager helper class in DelegateCommand.cs and I'll look into it, maybe there is some mechanism that could help.

Anyway, idea that in order to activate command based on user input, one needs to "nudge" command object in property setter code looks clumsy. It will introduce dependencies and one of big points of MVVM is reducing them.

Edit 2:

I tried to activate CanExecute by calling addObjectCommand.RaiseCanExecuteChanged() to ObjectName property setter from above code. This does not help either. CanExecute() is fired few times when form is initialized, but after that it never gets executed again. This is the code:

   // Property bound to textbox
   public string ObjectName
    {
        get { return objectName; }
        set { 
            objectName = value;
            addObjectCommand.RaiseCanExecuteChanged();              
            OnPropertyChanged("ObjectName");
        }
    }

Edit 3: Solution

As Yanko Yankov and JerKimball wrote, problem is static resource. When I changed button binding like Yanko suggested:

<Button Command="{Binding AddObjectCommand}">Add</Button>

things started to work immediately. I don't even need RaiseCanExecuteChanged(). Now CanExecute fires automatically.

Why did I use static resource in first place?
Original code was from WPF MVVM toolkit manual. Example in that manual defines commands as static resource and then binds it to menu item. Difference is that instead of string property in my example, MVVM manual works with ObservableCollection.

Edit 4: Final explanation

I finally got it. All I needed to do was to read comment in CommandReference class. It says:

/// <summary>
/// This class facilitates associating a key binding in XAML markup to a command
/// defined in a View Model by exposing a Command dependency property.
/// The class derives from Freezable to work around a limitation in WPF when 
/// databinding from XAML.
/// </summary>

So, CommandReference is used for KeyBinding, it is not for binding in visual elements. In above code, command references defined in resources would work for KeyBinding, which I don't have on this user control.
Of course, sample code that came with WPF MVVM toolkit were correct, but I misread it and used CommandReference in visual elements binding.
This WPF MVVM really is tricky sometimes.

+1  A: 

Try raising CanExecuteChanged when your property changes. The command binding is really distinct from the property binding and buttons bound to commands are alerted to a change in status by the CanExecuteChanged event.

In your case, you could fire a check when you do the PropertyChanged on the bound property that would evaluate it and set the command's internal CanExecute flag and then raise CanExecuteChanged. More of a "push" into the ICommand object than a "pull".

Ben Von Handorf
Please, check question again, I added code and some comments regarding your answer
zendar
+2  A: 

Since you are using the DelegateCommand, you can call it's RaiseCanExecuteChanged method when your text property changes. I'm not sure what you are trying to accomplish with your CommandReference resource, but typically you just bind the commands directly to the button element's Command property:

<TextBox Text="{Binding ObjectName, UpdateSourceTrigger=ValueChanged}" />
<Button Command="{Binding AddObjectCommand}" Content="Add" />

This would be the relevant portion of your view model:

public string ObjectName
{
    get { return objectName; }
    set
    {
        if (value == objectName) return;
        value = objectName;
        AddObjectCommand.RaiseCanExecuteChanged();
        OnPropertyChanged("ObjectName");
    }
}
Abe Heidebrecht
Please, check question again, I added code and some comments regarding your answer
zendar
Abe! You crazy DelegateCommand firing fool! :)
JerKimball
+1  A: 

Echoing Abe here, but the "right" path to take here is using:

public void RaiseCanExecuteChanged();

exposed on DelegateCommand. As far as dependencies go, I don't think you're really doing anything "bad" by raising this when the property that the command depends on changes within the ViewModel. In that case, the coupling is more or less contained wholly within the ViewModel.

So, taking your above example, in your setter for "ObjectName", you would call RaiseCanExecuteChanged on the command "AddObjectCommand".

JerKimball
Jer, your comment on Yanko's answer was right. Please see Edit3 for explanation.
zendar
+3  A: 

Things look much clearer now with the edits, thanks! This might be a stupid question (I'm somewhat tired of a long day's work), but why don't you bind to the command directly, instead of through a static resource?

<Button Command="{Binding AddObjectCommand}">Add</Button>
Yanko Yankov
This is my first hunch, too - since a static resource is not going to be continually re-evaluated, even calling RaiseCanExecuteChanged is not going to do much.
JerKimball
Yeah, seems like he's binding the button to a static instance of the view model, which is a different instance from the one the text box is bound to, and most likely that former instance is lacking any connection to the outside world to begin with.
Yanko Yankov
You were right. Problem is with static resource. Please see Edit3 for explanation.
zendar