This is a very difficult question because there are so many kinds of dynamism in WPF. I'll start with a simple example to help you understand some basic concepts that you need, and then begin to explain the different ways to dynamically update and / or replace ContextMenu and how DynamicResource fits into the image.
Original example: dynamically updating ContextMenu using StaticResource
Let's say you have the following:
<Window> <Window.Resources> <ContextMenu x:Key="Vegetables"> <MenuItem Header="Broccoli" /> <MenuItem Header="Cucumber" /> <MenuItem Header="Cauliflower" /> </ContextMenu> </Window.Resources> <Grid> <Ellipse ContextMenu="{StaticResource Vegetables}" /> <TextBox ContextMenu="{StaticResource Vegetables}" ... /> ... </Grid> </Window>
** Pay attention to using StaticResource .
This XAML will be:
- Create a ContextMenu object with three MenuItems and add it to Window.Resources
- Build an Ellipse object with reference to ContextMenu
- Build a TextBox with reference to ContextMenu
Since Ellipse and TextBox have references to the same ContextMenu, updating ContextMenu changes the available parameters for each. For example, the following button will add "Carrot" to the menu when the button is pressed.
public void Button_Click(object sender, EventArgs e) { var menu = (ContextMenu)Resources["Vegetables"]; menu.Items.Add(new MenuItem { Header = "Carrots" }); }
In this sense, each ContextMenu is dynamic: its elements can be changed at any time, and the changes will take effect immediately. This is true even when ContextMenu is actually open on the screen.
Dynamic ContextMenu updated with data binding
Another way in which one ContextMenu object is dynamic is that it responds to data binding. Instead of setting individual menu items, you can bind to a collection, for example:
<Window.Resources> <ContextMenu x:Key="Vegetables" ItemsSource="{Binding VegetableList}" /> </Window.Resources>
This assumes that the VegetableList is declared as an ObservableCollection or some other type that implements the INotifyCollectionChanged interface. Any changes made to the collection will instantly update ContextMenu, even if it is open. For instance:
public void Button_Click(object sender, EventArgs e) { VegetableList.Add("Carrots"); }
Please note that this kind of updating the collection does not need to be done in the code: you can also bind the list of vegetables to ListView, DataGrid, etc., so that end users can make changes. These changes will also be displayed in your ContextMenu.
Switch ContextMenus with code
You can also replace the ContextMenu element with a completely different ContextMenu. For instance:
<Window> <Window.Resources> <ContextMenu x:Key="Vegetables"> <MenuItem Header="Broccoli" /> <MenuItem Header="Cucumber" /> </ContextMenu> <ContextMenu x:Key="Fruits"> <MenuItem Header="Apple" /> <MenuItem Header="Banana" /> </ContextMenu> </Window.Resources> <Grid> <Ellipse x:Name="Oval" ContextMenu="{StaticResource Vegetables}" /> ... </Grid> </Window>
The menu can be replaced in the code as follows:
public void Button_Click(object sender, EventArgs e) { Oval.ContextMenu = (ContextMenu)Resources.Find("Fruits"); }
Note that instead of modifying the existing ContextMenu, we are moving to a completely different ContextMenu. In this situation, both ContextMenus are created immediately when the window is first built, but the Fruits menu is not used until it is switched.
If you want to avoid creating the Fruits menu until you need it, you can create it in the Button_Click handler instead of doing it in XAML:
public void Button_Click(object sender, EventArgs e) { Oval.ContextMenu = new ContextMenu { ItemsSource = new[] { "Apples", "Bananas" } }; }
In this example, every time you click the button, a new ContextMenu will be created and assigned to the oval. Any ContextMenu defined in Window.Resources still exists, but is not used (unless another control uses it).
Switch ContextMenus with DynamicResource
Using DynamicResource allows you to switch between ContextMenus without explicitly assigning its code. For instance:
<Window> <Window.Resources> <ContextMenu x:Key="Vegetables"> <MenuItem Header="Broccoli" /> <MenuItem Header="Cucumber" /> </ContextMenu> </Window.Resources> <Grid> <Ellipse ContextMenu="{DynamicResource Vegetables}" /> ... </Grid> </Window>
Since this XAML uses DynamicResource instead of StaticResource, modifying the dictionary will update the ContextMenu Ellipse property. For instance:
public void Button_Click(object sender, EventArgs e) { Resources["Vegetables"] = new ContextMenu { ItemsSource = new[] {"Zucchini", "Tomatoes"} }; }
The key idea here is that DynamicResource vs StaticResource only manages when searching for a dictionary. If StaticResource is used in the above example, assigning Resources["Vegetables"] will not update the Ellipse ContextMenu property.
On the other hand, if you update ContextMenu itself (by changing the Items collection or data binding), it does not matter whether you use DynamicResource or StaticResource: in each case, any changes you make to ContextMenu will be immediately visible.
Updating individual ContextMenu ITEMS using data binding
The best way to update ContextMenu based on the properties of the right-clicked element is to use data binding:
<ContextMenu x:Key="SelfUpdatingMenu"> <MenuItem Header="Delete" IsEnabled="{Binding IsDeletable}" /> ... </ContextMenu>
This will cause the Delete menu item to be automatically grayed out if the item does not have the IsDeletable flag set. In this case, the code (or even preferably) is not needed.
If you want to hide an element instead of just deleting it, set the Visibility parameter instead of IsEnabled:
<MenuItem Header="Delete" Visibility="{Binding IsDeletable, Converter={x:Static BooleanToVisibilityConverter}}" />
If you want to add / remove elements from ContextMenu based on your data, you can link them using CompositeCollection. The syntax is a bit more complicated, but it's still pretty simple:
<ContextMenu x:Key="MenuWithEmbeddedList"> <ContextMenu.ItemsSource> <CompositeCollection> <MenuItem Header="This item is always present" /> <MenuItem Header="So is this one" /> <Separator /> <CollectionContainer Collection="{Binding MyChoicesList}" /> <Separator /> <MenuItem Header="Fixed item at bottom of menu" /> </CompositeCollection> </ContextMenu.ItemsSource> </ContextMenu>
Assuming that “MyChoicesList” is an ObservableCollection (or any other class that implements INotifyCollectionChanged), the items added / deleted / updated in this collection will be immediately visible in ContextMenu.
Updating individual ContextMenu components without data binding
Whenever possible , you should control your ContextMenu elements using data binding . They work very well, are almost flawless and greatly simplify your code. Only if data binding cannot be used to work, does it make sense to use the code to update your menu items. In this case, you can create your ContextMenu by handling the ContextMenu.Opened event and making updates to this event. For instance:
<ContextMenu x:Key="Vegetables" Opened="Vegetables_Opened"> <MenuItem Header="Broccoli" /> <MenuItem Header="Green Peppers" /> </ContextMenu>
With this code:
public void Vegetables_Opened(object sender, RoutedEventArgs e) { var menu = (ContextMenu)sender; var data = (MyDataClass)menu.DataContext var oldCarrots = ( from item in menu.Items where (string)item.Header=="Carrots" select item ).FirstOrDefault(); if(oldCarrots!=null) menu.Items.Remove(oldCarrots); if(ComplexCalculationOnDataItem(data) && UnrelatedCondition()) menu.Items.Add(new MenuItem { Header = "Carrots" }); }
Alternatively, this code can simply change menu.ItemsSource if you used data binding.
Toggle ContextMenus with triggers
Another method commonly used to update ContextMenus is to use a trigger or DataTrigger to switch between the default context menu and a custom context menu depending on the trigger condition. This can handle situations where you want to use data binding, but you need to replace the menu as a whole, rather than updating parts of it.
Here is an illustration of how it looks:
<ControlTemplate ...> <ControlTemplate.Resources> <ContextMenu x:Key="NormalMenu"> ... </ContextMenu> <ContextMenu x:Key="AlternateMenu"> ... </ContextMenu> </ControlTemplate.Resources> ... <ListBox x:Name="MyList" ContextMenu="{StaticResource NormalMenu}"> ... <ControlTemplate.Triggers> <Trigger Property="IsSpecialSomethingOrOther" Value="True"> <Setter TargetName="MyList" Property="ContextMenu" Value="{StaticResource AlternateMenu}" /> </Trigger> </ControlTemplate.Triggers> </ControlTemplate>
In this case, you can still use data binding to control individual elements in both NormalMenu and AlternateMenu.
Releasing ContextMenu resources when closing a menu
If the resources used in ContextMenu are expensive in memory, you might want to free them. If you use data binding, this can happen automatically, because the DataContext is deleted when the menu is closed. If you are using the code, you may have to catch the Closed event in ContextMenu to release everything you created in response to the Opened event.
XAML deferred ContextMenu construct
If you have a very complex ContextMenu that you want to code in XAML but don’t want to boot, unless you need it, two main methods are available:
Put it in a separate ResourceDictionary. If necessary, load this ResourceDictionary and add it to the MergedDictionaries. While you are using DynamicResource, the combined value will be added.
Place it in a ControlTemplate or DataTemplate. The menu will not actually be created until the template is used.
However, none of these methods alone will cause the download to happen when the context menu is opened - only when creating an instance of the contained template or when combining the dictionary. To do this, you must use ContextMenu with an empty ItemsSource, and then assign the ItemSource in the Opened event. However, the ItemsSource value can be loaded from the ResourceDictionary in a separate file:
<ResourceDictionary ...> <x:Array x:Key="ComplexContextMenuContents"> <MenuItem Header="Broccoli" /> <MenuItem Header="Green Beans" /> ... complex content here ... </x:Array> </ResourceDictionary>
with this code in the Opened event:
var dict = (ResourceDictionary)Application.LoadComponent(...); menu.ItemsSource = dict["ComplexMenuContents"];
and this code in a private event:
menu.ItemsSource = null;
In fact, if you only have one x: Array, you can also skip ResourceDictionary. If your external XAML element is an x: Array, the Opened event code is simple:
menu.ItemsSource = Application.LoadComponent(....)
Critical Concepts Summary
DynamicResource is used only to switch values based on which resource dictionaries are loaded and what they contain: when updating the contents of dictionaries, DynamicResource automatically updates properties. StaticResource reads them only when XAML is loaded.
Regardless of whether DynamicResource or StaticResource is used, ContextMenu is created when the resource dictionary is loaded , and not when the menu is opened.
ContextMenus is very dynamic in that you can manipulate them using data binding or code, and the changes take effect immediately.
In most cases, you need to update ContextMenu using data bindings rather than code.
The menu can be completely replaced using code, triggers, or DynamicResource.
If the contents should be loaded into RAM only when the menu is opened, they can be loaded from a separate file in the opened event and cleared in the closed event.