The blog of dlaa.me

The one that got away [Simple workarounds for a visual problem when toggling a ContextMenu MenuItem's IsEnabled property directly]

A few days ago, Martin Naughton and Tiago Halm de Carvalho e Branco independently contacted me to report a problem they were having with the new ContextMenu control in the April '10 release of the Silverlight Toolkit. In both cases, they were toggling the IsEnabled property of a MenuItem directly and reported that the control's visuals weren't updating correctly. I was a little surprised at first because I knew I'd tested dynamic changes to the enabled state and I'd seen them work properly. But once I created a test project to investigate the report, I saw how the problem scenario was different.

The approach I focused my testing on (and which works correctly by all accounts) is the ICommand (Command/CommandParameter) scenario where the enabled state of the MenuItem is controlled by the CanExecute method of the ICommand implementation. In this scenario, the MenuItem changes its own IsEnabled state and updates its visuals explicitly, so everything is always in sync. But the code from the bug reports wasn't using ICommand; it was manipulating the IsEnabled property directly. The bug is that MenuItem doesn't find out about those changes - the indirect reason being that it doesn't own the IsEnabled property (which it inherits from Control). Because MenuItem doesn't know about the change, it doesn't know to update its visual state. :(

MenuItemIsEnabledWorkaround demonstration

Fortunately, there are some easy workarounds!

 

Workarounds

  • Do nothing. I've already checked in a fix for this bug and it will be part of the next Silverlight Toolkit release. If the scenario doesn't matter to you before then, you don't need to worry about it. Otherwise, maybe you can...
  • Patch the code, recompile the System.Windows.Controls.Input.Toolkit assembly, and use that in your project. I don't expect most people will want to take this approach, but if it suits you, then it's the next best thing to having a new Toolkit build. Here's the unified diff for the change to MenuItem.cs:
    @@ -143,6 +143,7 @@
             public MenuItem()
             {
                 DefaultStyleKey = typeof(MenuItem);
    +            IsEnabledChanged += new DependencyPropertyChangedEventHandler(HandleIsEnabledChanged);
                 UpdateIsEnabled();
             }
     
    @@ -301,6 +302,16 @@
             }
     
             /// <summary>
    +        /// Called when the IsEnabled property changes.
    +        /// </summary>
    +        /// <param name="sender">Source of the event.</param>
    +        /// <param name="e">Event arguments.</param>
    +        private void HandleIsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
    +        {
    +            ChangeVisualState(true);
    +        }
    +
    +        /// <summary>
             /// Changes to the correct visual state(s) for the control.
             /// </summary>
             /// <param name="useTransitions">True to use transitions; otherwise false.</param>
    But if you're not sure how or where to apply that change (or what to do afterward), this is probably not the best option for you. Instead, you might...
  • Switch from manually toggling the IsEnabled property to doing so indirectly via an ICommand implementation. In general, ICommand-based approaches are more consistent with the MVVM pattern and can be more architecturally pure. If you're not already familiar with the technique, it could be worthwhile to read about it: here's a overview of commanding in WPF. That said, it's not always convenient to make changes like this, and sometimes directly toggling IsEnabled really is the best approach. If so, then another option is to...
  • "Bounce" the Template property to null and back after changing the IsEnabled property. The bug is mainly cosmetic: the internal state is correct, but the visuals aren't. Therefore, any change to MenuItem that prompts it to update its visual state will correct the problem. While giving the MenuItem focus would work, too, a less intrusive way is to change the value of the control's Template property. But because we don't really want to change the Template, it's necessary to restore the original value. The following code demonstrates this technique:
    // "Bouncing" the Template after toggling works around the issue
    menuItemBounce.IsEnabled = !menuItemBounce.IsEnabled;
    var template = menuItemBounce.Template;
    menuItemBounce.Template = null;
    menuItemBounce.Template = template;
    Because a well-behaved control updates its visual states after getting a new Template, and because MenuItem is well-behaved (usually!), this "bounce" is enough to solve the problem. But maybe you're setting the IsEnabled property with a Binding or don't want to incur the cost of swapping out visuals like this. No problem, you can always...
  • Set the MenuItemIsEnabledWorkaround.IsActive attached property (from the code in my sample project) for a seamless workaround. Based on the observation that direct manipulation of the IsEnabled property is rarely associated with the use of an ICommand implementation and the fact that the ICommand scenario works properly today, I created a self-contained workaround that's easy to use. The MenuItemIsEnabledWorkaround class exposes an attached DependencyProperty and implements the ICommand interface. When IsActive is set to True on a MenuItem, an instance of the MenuItemIsEnabledWorkaround class is created and assigned to the MenuItem's Command property. This instance is also hooked up to the MenuItem's IsEnabledChanged event - when that event fires, the MenuItemIsEnabledWorkaround's CanExecuteChanged event is also fired and its CanExecute method reports the new value of the IsEnabled property. That may sound complicated, but it's simple in practice:
    <toolkit:MenuItem
        x:Name="menuItemWorkaround"
        Header="MenuItem with workaround active"
        delay:MenuItemIsEnabledWorkaround.IsActive="True"/>
    // Activating the workaround in XAML requires no code changes
    menuItemWorkaround.IsEnabled = !menuItemWorkaround.IsEnabled;
    By changing the IsEnabled scenario into an ICommand scenario, MenuItemIsEnabledWorkaround sidesteps the bug and saves the day!

 

Examples

I've created a sample application to demonstrate the use of the last two workarounds in practice. It contains a simple ContextMenu with three MenuItems and toggles their IsEnabled state every second (whether the menu is open or not). You'll see either of the last two workarounds is enough to keep the corresponding MenuItem's visual state up to date.

[Click here to download the MenuItemIsEnabledWorkaround sample application and source code.]

 

It's never fun when a bug sneaks by you. :( But it is nice when there are a variety of good options that don't involve jumping through hoops to implement. If you've run into this bug, I apologize for the trouble - and I hope these options help get you going again!