views:

142

answers:

3

How do you make a Button call ICommand.CanExecute when the command parameter is changed?

This is my current XAML.

<Button Content="Delete" Command="{Binding DeleteItemCommand}" CommandParameter="{Binding SelectedItem, ElementName=DaGrid}" />

EDIT It appears this is only an issue in WPF.

+1  A: 

Strange. Normally OnCommandParameterChanged calls UpdateCanExecute (both internal methods). Does the Binding to CommandParameter work as expected?

Ozan
I just checked with Reflector : there's no `OnCommandParameterChanged` method, at least not in WPF 4... Actually, there isn't even a `PropertyChangedCallback` on the `CommandParameter` dependency property, so nothing special happens when the parameter value changes.
Thomas Levesque
I don't see `OnCommandParameterChanged` on the Button class.
Jonathan Allen
Ugh, that's some really bad news Thomas. It is hard to belive they missed that use case.
Jonathan Allen
@Thomas: we are talking about the Silverlight framework here. The DP is registered with OnCommandParameterPropertyChanged callback, which calls OnCommandParameterChanged
Ozan
What is a DP? ...
Jonathan Allen
@Jonathan: DependencyProperty, CommandParameter is one. In Silverlight, a change of CommandParameter leads directly to ICommand.CanExecute, make sure the Binding works.
Ozan
@Ozan, *you* are talking about the Silverlight framework ;). The title says "WPF/Silverlight", not just Silverlight
Thomas Levesque
+1  A: 

You need to call CommandManager.InvalidateRequerySuggested to re-evaluate CanExecute. Note that it will re-evaluate it for all commands, not just the one your want...

Thomas Levesque
CommandManager is not available in Silverlight
Ozan
well, your title said "WPF/Silverlight"... so you have an answer to half of your question ;)
Thomas Levesque
That doesn't seem to do anything. I even tried having my command explicitly register for the CommmandManager.RequerySuggested event.
Jonathan Allen
In the end I made my own eventing scheme loosely based on the CommandManager, but limited to just the commands I'm interested in. I won't bother posting it because it isn't a general solution.
Jonathan Allen
+3  A: 

I'm not sure what you're doing wrong, but here is an example of a Button being controlled both by a BindingParameter and a CanExecute Flag. Perhaps your binding parameter isn't a DependencyProperty, and therefore, when it changes the Button isn't being notified.

<UserControl x:Class="SilverlightICommandTest.MainPage"
         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:ct="clr-namespace:SilverlightICommandTest"
         mc:Ignorable="d"
         d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
    <ct:TestModel x:Key="Model" />
</UserControl.Resources>

<StackPanel x:Name="LayoutRoot" Orientation="Vertical" Background="White" DataContext="{StaticResource Model}">
    <CheckBox Content="Enable" IsChecked="{Binding TestCmd.CanDoCommand, Mode=TwoWay}" />
    <Grid HorizontalAlignment="Stretch">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <TextBlock Text="{Binding ElementName=testSlider, Path=Value}" Width="40" Grid.Column="0" />
        <Slider Name="testSlider" Minimum="0" Maximum="100" SmallChange="1" Grid.Column="1" />
    </Grid>
    <Button Command="{Binding TestCmd}" CommandParameter="{Binding ElementName=testSlider, Path=Value}" Content="Do Something" />
</StackPanel>
</UserControl>

And the code file:

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace SilverlightICommandTest
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
        }
    }

    public class TestModel : DependencyObject
    {
        TestCommand _testCmd = new TestCommand();

        public TestCommand TestCmd { get { return _testCmd; } }

        public TestModel()
        {
        }
    }

    public class TestCommand : DependencyObject, ICommand
    {
        public static readonly DependencyProperty CanDoCommandProperty = DependencyProperty.Register("CanDoCommand", typeof(Boolean), typeof(TestCommand), new PropertyMetadata(false, new PropertyChangedCallback(CanDoCommandChanged)));

        public Boolean CanDoCommand
        {
            get { return (Boolean)GetValue(CanDoCommandProperty); }
            set { SetValue(CanDoCommandProperty, value); }
        }

        public event EventHandler CanExecuteChanged;

        public TestCommand()
        {
        }

        public Boolean CanExecute(Object parameter)
        {
            return this.CanDoCommand && (((Int32)(Double)parameter) % 2 == 0);
        }

        public void Execute(Object parameter)
        {
            MessageBox.Show("Oh Hai!");
        }

        private void OnCanDoCommandChanged(DependencyPropertyChangedEventArgs args)
        {
            if (this.CanExecuteChanged != null)
            {
                this.CanExecuteChanged(this, new EventArgs());
            }
        }

        private static void CanDoCommandChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            ((TestCommand)sender).OnCanDoCommandChanged(args);
        }
    }
}

In the future I recommend doing a little more research on the pattern first (http://www.silverlightshow.net/items/Model-View-ViewModel-in-Silverlight.aspx), and if you still can't figure it out, post more of your source code.

Paul Wh
And FYI, to be a little more broad on the "What is a DependencyProperty" question. A DP is the WPF/Silverlight pattern for receiving notification when a property changes. Instead of using a private local member to store your value you use the DependencyObject.SetValue/GetValue methods, which gives the framework a chance to relay notifications of the change. WPF/Silverlight Data Binding depends on this to update the UI value when the source value changes. See http://msdn.microsoft.com/en-us/library/ms752914.aspx for more details.
Paul Wh