Tuesday, May 25, 2010

Silverlight 4 command behavior – do you expect disabled button to be clickable?

Practical API Design: Confessions of a Java Framework Architect made me pay more attention every time I use/create some API. And I should say that Silverlight API design gives enough reasons to stop over and think about.

This time it was a commanding aspect/API. Lets say you have a very simple form with a button you want to fire a command from. From one side you see the API of IsEnabled, from another Command with its CanExecute:

image

Question is: Do you expect IsEnabled=False to silently do nothing when Command.CanExecute returns true and button to stay enabled? I didn’t truly expect it, but at least per design I can say I might not be in majority.

I’d rather expect IsEnabled to throw in case when I’m trying to set it to value contradictory to control’s command status.

image As a result you can raise a command from a “disabled” button without any problem.

Following code snippet was used to test the API behavior:

namespace TestCommandVsEnabled
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
            this.DataContext = new SampleViewModel();
        }
    }
    public class SampleViewModel
    {
        private bool isButtonEnabled;
        public bool IsButtonEnabled
            { get { return isButtonEnabled; } set { isButtonEnabled = value; } }
        private ICommand sampleCommand = new SampleCommand();
        public ICommand SampleCommand
            { get { return sampleCommand; } set { sampleCommand = value; } }
    }
    public class SampleCommand : ICommand
    {
 
        public bool CanExecute(object parameter)
        {
            return true;
        }
 
        public event EventHandler CanExecuteChanged;
 
        public void Execute(object parameter)
        {
            MessageBox.Show("command executed");
        }
    }
}
with xaml:
    <Grid x:Name="LayoutRoot" Background="White">
        <Button IsEnabled="{Binding IsEnabled, Mode=TwoWay}" Command="{Binding SampleCommand}">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="?IsEnabled:" FontSize="16"/>
                <TextBlock Text="{Binding IsButtonEnabled}" FontSize="16" />
            </StackPanel>
        </Button>
    </Grid>

Observation in reflector showed that this design is actually “copied” from Prism, although in Prism toggling command.CanExecute would only have once off effect on enabling/disabling the control with subsequent IsEnabled working as expected then. So for example with Prism, changing the order of bindings to:

<Button Command="{Binding SampleCommand}" IsEnabled="{Binding IsEnabled, Mode=TwoWay}">

would lead to have the button disabled. Not anymore with more aggressive/(lax??) SL4 approach though. In my opinion, example of a bad pattern taken to its extreme.

No comments: