Delay's Blog is the blog of David Anson, a Microsoft developer who works with the Silverlight, WPF, Windows Phone, and web platforms.
http://dlaa.me/
@DavidAns
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. :(
MenuItem
ICommand
IsEnabled
Fortunately, there are some easy workarounds!
Workarounds
System.Windows.Controls.Input.Toolkit
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>
Template
// "Bouncing" the Template after toggling works around the issue menuItemBounce.IsEnabled = !menuItemBounce.IsEnabled; var template = menuItemBounce.Template; menuItemBounce.Template = null; menuItemBounce.Template = template;
MenuItemIsEnabledWorkaround.IsActive
MenuItemIsEnabledWorkaround
IsActive
True
Command
CanExecuteChanged
CanExecute
<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;
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.
ContextMenu
[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!