4

I have created a TextBox that implements ICommandSource, for the most part I followed Microsoft's example using a Slider. This TextBox will execute the bound command upon pressing the "Enter" key. This portion of the behavior is working correctly, the issue I am having is that IsEnabled can no longer be set in XAML? I am not sure why this is, you can still set IsEnabled on native Microsoft classes like Button.

public class CommandTextBox : TextBox, ICommandSource
{
    // The DependencyProperty for the Command.
    public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(CommandTextBox), new PropertyMetadata(OnCommandChanged));

    // The DependencyProperty for the CommandParameter.
    public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register("CommandParameter", typeof(object), typeof(CommandTextBox));

    // The DependencyProperty for the CommandTarget.
    public static readonly DependencyProperty CommandTargetProperty = DependencyProperty.Register("CommandTarget", typeof(IInputElement), typeof(CommandTextBox));

    public ICommand Command
    {
        get { return (ICommand)GetValue(CommandProperty); }
        set { SetValue(CommandProperty, value); }
    }

    public object CommandParameter
    {
        get { return GetValue(CommandParameterProperty); }
        set { SetValue(CommandParameterProperty, value); }
    }

    public bool CommandResetsText { get; set; }

    public IInputElement CommandTarget
    {
        get { return (IInputElement)GetValue(CommandTargetProperty); }
        set { SetValue(CommandTargetProperty, value); }
    }

    // Command dependency property change callback. 
    private static void OnCommandChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
    {
        CommandTextBox ctb = (CommandTextBox)dp;
        ctb.HookUpCommand((ICommand)e.OldValue, (ICommand)e.NewValue);
    }

    // Add a new command to the Command Property. 
    private void HookUpCommand(ICommand oldCommand, ICommand newCommand)
    {
        // If oldCommand is not null, then we need to remove the handlers. 
        if (oldCommand != null)
            RemoveCommand(oldCommand);

        AddCommand(newCommand);
    }

    // Remove an old command from the Command Property. 
    private void RemoveCommand(ICommand command)
    {
        EventHandler handler = CanExecuteChanged;
        command.CanExecuteChanged -= handler;
    }

    // Add the command. 
    private void AddCommand(ICommand command)
    {
        var handler = new EventHandler(CanExecuteChanged);
        canExecuteChangedHandler = handler;
        if (command != null)
            command.CanExecuteChanged += canExecuteChangedHandler;
    }

    private void CanExecuteChanged(object sender, EventArgs e)
    {
        if (Command != null)
        {
            RoutedCommand command = Command as RoutedCommand;

            // If a RoutedCommand. 
            if (command != null)
            {
                if (command.CanExecute(CommandParameter, CommandTarget))
                    this.IsEnabled = true;
                else
                    this.IsEnabled = false;
            }
            // If a not RoutedCommand. 
            else
            {
                if (Command.CanExecute(CommandParameter))
                    this.IsEnabled = true;
                else
                    this.IsEnabled = false;
            }
        }
    }

    // If Command is defined, pressing the enter key will invoke the command; 
    // Otherwise, the textbox will behave normally. 
    protected override void OnKeyDown(KeyEventArgs e)
    {
        base.OnKeyDown(e);

        if (e.Key == Key.Enter)
        {
            if (Command != null)
            {
                RoutedCommand command = Command as RoutedCommand;

                if (command != null)
                    command.Execute(CommandParameter, CommandTarget);
                else
                    Command.Execute(CommandParameter);

                if (CommandResetsText)
                    this.Text = String.Empty;
            }
        }
    }

    private EventHandler canExecuteChangedHandler;
}

XAML

<Button Command="{Binding GenericCommand}" IsEnabled="False" /> // This is disabled(desired).
<CommandTextBox Command="{Binding GenericCommand}" IsEnabled="False" /> // This is enabled?

Any feedback would be appreciated.

Derrick Moeller
  • 4,808
  • 2
  • 22
  • 48
  • "Can no longer be set"? What are your assumptions for that? You set `IsEnabled` in your code every time `CanExecuteChanged` is called, so any value you set in XAML will be overwritten almost immediately when you start your application.... – Patrick Dec 10 '14 at 21:16
  • @Patrick I have included some XAML to show the issue, I would like CommandTextBox's implementation of IsEnabled to mirror that of Button. – Derrick Moeller Dec 11 '14 at 12:59
  • @FrumRoll `IsEnabled` gets overriden in `CanExecuteChanged` handler. Probably your command calls CanExecuteChanged. You better to set a breakpoint on `CanExecuteChanged` and see throughout the stacktrace to find the cause. – Eldar Dordzhiev Dec 11 '14 at 13:08
  • @EldarDordzhiev You are correct, the CanExecuteChanged handler is overriding IsEnabled. My question might be better asked as, How is my implementation different from the implementation found in Button and what can be done to address this difference in behavior? – Derrick Moeller Dec 11 '14 at 19:34
  • @FrumRoll ok, I got it, check the answer below. – Eldar Dordzhiev Dec 11 '14 at 19:50

1 Answers1

4

First, you need to create a private property or a field that will be set if the command can execute:

private bool _canExecute;

Second, you need to override IsEnabledCore property:

protected override bool IsEnabledCore
{
    get
    {
        return ( base.IsEnabledCore && _canExecute );
    }
}

And finally, you just fixing CanExecuteChanged method by replacing IsEnabled with _canExecute and coercing IsEnabledCore after all:

private void CanExecuteChanged(object sender, EventArgs e)
{
    if (Command != null)
    {
        RoutedCommand command = Command as RoutedCommand;

        // If a RoutedCommand. 
        if (command != null)
        {
            _canExecute = command.CanExecute(CommandParameter, CommandTarget);
        }
        // If a not RoutedCommand. 
        else
        {
            _canExecute = Command.CanExecute(CommandParameter);
        }
    }

    CoerceValue(UIElement.IsEnabledProperty);
}
Eldar Dordzhiev
  • 5,105
  • 2
  • 22
  • 26