WPF Ribbon: RibbonCommands Can Cause Memory Leaks
I stumbled over an issue when dealing with Microsoft’s WPF ribbon today. Apparently, the control that heavily relies on RibbonCommands rather than arbitrary ICommand instances may cause severe memory leaks. Have a look at this simple Window:
<Window ...> <Grid> <Grid.Resources> <r:RibbonCommand x:Key="MyCommand" Executed="OnRibbonClicked" LabelTitle="Click Me" /> </Grid.Resources> <!-- a ribbon that only displays a command in the quick access toolbar --> <r:Ribbon> <r:Ribbon.QuickAccessToolBar> <r:RibbonQuickAccessToolBar> <r:RibbonButton Command="{StaticResource MyCommand}" r:RibbonQuickAccessToolBar.Placement="InToolBar" /> </r:RibbonQuickAccessToolBar> </r:Ribbon.QuickAccessToolBar> </r:Ribbon> </Grid> </Window>
…and here’s the event listener that was declared for the RibbonCommand:
private void OnRibbonClicked(object sender, ExecutedRoutedEventArgs e) { MessageBox.Show("Ribbon command executing."); }
Once opened and closed, this window will not be garbage collected until the application shuts down. The reason is the OnRibbonClicked event handler that doesn’t get deregistered.
There are signs that Microsoft will dump RibbonCommand completely once the ribbon goes live. However: This probably won’t happen too fast, so you will have to solve the issue on your own. On the bright side: It forces a cleaner design on you.
I prefer custom commands anyway, so the natural way to go was to simply implement my own class that derives from RibbonCommand:
///<summary> /// Base class for custom <see cref="RibbonCommand"/> /// implementations. ///</summary> public abstract class RibbonCommandBase : RibbonCommand { /// <summary> /// Creates the command and registers weak event listeners. /// </summary> protected RibbonCommandBase() { Executed += OnExecute; CanExecute += OnCanExecute; } /// <summary> /// Determines whether the command can be executed or not. /// The default implementation always allows that. /// </summary> protected virtual void OnCanExecute(object sender, CanExecuteRoutedEventArgs e) { e.CanExecute = true; } /// <summary> /// Command implementation - executes command logic. /// </summary> protected abstract void OnExecute(object sender, ExecutedRoutedEventArgs e); }
The noteworthy thing here is that this abstract base class registers event listeners for its own events – one virtual, one abstract. Accordingly, a deriving command implementation only has to provide its own OnExecute and OnCanExecute (optional) methods to get going:
public class MessageCommand : RibbonCommandBase { protected override void OnExecute(object sender, ExecutedRoutedEventArgs e) { MessageBox.Show("Ribbon command executing."); } }
The last thing to correct is the command declaration in XAML – rather than declaring a RibbonCommand with a code-behind event listener, you declare your custom command (MessageCommand in the sample below). As you can see, there is no longer a listener for the Executed event – the command takes care of this on its own. Accordingly, this command can be declared in a resource dictionary without a code-behind file.
<!-- Custom command - can be declared in a resource dictionary --> <cmd:MessageCommand x:Key="MyCommand" LabelTitle="Click Me" SmallImageSource="About.png" />
Sample Project
The attached sample project shows the two variants and the missing garbage collection of the standard ribbon commands.
I wasn’t sure about licensing restrictions, therefore I did not include the WPF ribbon assembly in the downloadable sample – you’ll have to add the reference yourself before compiling. Sorry for the inconvenience :/
Download: ribbon-commands.zip
This helped me a lot, thank you!
Hi Philip, this is great.
Would one need to write a new class for each command in the ribbon?
Thanks
THANKS. This is a real lifesaver. I’ve been looking for “mmy” memory leak only to find it’s MS.
check out the article on the mvvm pattern here:
http://msdn.microsoft.com/en-us/magazine/dd419663.aspx
There is a RelayCommand object that is similiar to what he is doing here except you pass in the delegate or method for onexecute when you create the RelayCommand so you don’t have to create a class for each command.