I am creating a toolbar usercontrol which includes some dynamically added menuitems. The menuitems are part of observable collections of controls in the data context of the usercontrol, and are bound to the items source of their "top level" menu items as shown in the code below.
<UserControl.Resources>
<ResourceDictionary>
<MenuItem x:Key="fileKey" x:Shared="False" Header="_File" ItemsSource="{Binding FileMenuItems}" />
<MenuItem x:Key="viewKey" x:Shared="False" Header="_View" ItemsSource="{Binding ViewMenuItems}" />
...
Some of these top level menuitems are duplicated in multiple parts of the view in a primary dropdown menu as well as a main text menu bar.
<Menu Name="dropDownMenu">
<MenuItem>
<StaticResource ResourceKey="fileKey" />
<StaticResource ResourceKey="viewKey" />
</MenuItem>
</Menu>
...
<StackPanel x:Name="TextMenuPanel" Orientation="Horizontal">
<Menu Name="File">
<StaticResource ResourceKey="fileKey" />
</Menu>
</StackPanel>
The issue I am having is that if I open, say "File", from one menu, then the other menu, then attempt to open the first menu again it shows an empty box with no headers etc. of the original size of the menu, while the previously opened menu continues to display correctly if reopened.
How can I correctly show the bound menuitems in more than one place without breaking the view?
Related
I'm trying to make a shared menu between something on my top menu bar of my app, and the right click context menu of something in my interface in WPF. I've googled aroudn but I can't figure out hwo to share ONLY the menuitems list.
Here is a picture of the UI to help describe it:
The way this works is when an item in the list (as shown in the background) is selected, this menu becomes available to use. I would like to make it so that when you right click an item in the list, it also shows the same menu. I would like to avoid duplicating code, so I defined a resource for MenuItem in my window resources:
<MenuItem x:Key="modUtilsMenu">
<MenuItem Header="{Binding SelectedMod.ModName}" IsEnabled="False" FontWeight="Bold" />
<MenuItem Header="{DynamicResource string_Checkforupdates}" Command="{Binding SelectedModCheckForUpdatesCommand}" ToolTip="{DynamicResource string_tooltip_checksForUpdatesToThisMod}" >
<MenuItem.Icon>
<fa:ImageAwesome Style="{StaticResource EnableDisableImageStyle}" Icon="Cloud" Foreground="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Height="16" Width="16"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="{DynamicResource string_RestoremodfromME3Tweaks}" Command="{Binding RestoreModFromME3TweaksCommand}" ToolTip="{DynamicResource string_tooltip_forcesUpdateCheck}" >
<MenuItem.Icon>
<fa:ImageAwesome Style="{StaticResource EnableDisableImageStyle}" Icon="CloudDownload" Foreground="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Height="16" Width="16" RenderOptions.BitmapScalingMode="HighQuality"/>
</MenuItem.Icon>
</MenuItem>
...
I then add it to the interface as a sub element of the Mod Utils menuitem:
<MenuItem Header="{DynamicResource string_ModUtils}" Padding="4" IsEnabled="{Binding SelectedMod, Converter={StaticResource NullEnabledConverter}}">
<StaticResource ResourceKey="modUtilsMenu"/>
</MenuItem>
Obviously this doesn't work as it has a second MenuItem defined in the resource.
However, I am not sure how I can store a "list" of menu items to add as children of another object, as the root container element of MenuItem and ContextMenu are not the same. These are all command based menu items. I will have the same issue with a context menu too - how do I only share the contents and not the container? Do I have to do data binding?
I have looked at How do I share a menu definition between a context menu and a regular menu in WPF, but that seems to be just for single menu items. I suppose I could do it for every one of them, but I'm looking to see if there's a way to do this where I only have to update it in one place instead of three to make it work.
Menu and ContextMenu are both of type ItemsControl. You can treat them like this e.g. bind to a collection of item models and specify a DataTemplate.
The following example creates a collection of MenuItem as XAML resource.
To allow multiple instances of the collection it is important to to set the x:Shared attribute to False. Otherwise the menu will be rendered only in one location of the visual tree, no matter the number of references:
<Window>
<Window.Resources>
<x:Array x:Key="SharedMenuItems"
Type="MenuItem"
x:Shared="False">
<MenuItem Header="File">
<MenuItem Header="Save" />
</MenuItem>
<MenuItem Header="Settings" />
</x:Array>
</Window.Resources>
<StackPanel x:Name="RootPanel" viewModels:Item.IsMarkedAsRead="True">
<Menu ItemsSource="{StaticResource SharedMenuItems}" />
<Grid>
<Grid.ContextMenu>
<ContextMenu ItemsSource="{StaticResource SharedMenuItems}" />
</Grid.ContextMenu>
</Grid>
</StackPanel>
</Window>
I wrote an example application that highlights the issue, which might have to do with focus-scope because it's whenever a ToolBar is involved and a UserControl is unloaded when switching tabs.
The main window contains a Menu with a File->New command and a TabControl that displays a collection of "Box" instances:
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="_File">
<MenuItem Command="New"/>
</MenuItem>
</Menu>
<TabControl ItemsSource="{Binding Boxes}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" Width="40"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<local:BoxView/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</DockPanel>
Each "Box" instance has a Name property displayed in the tab header, and the rest of it is displayed in a "BoxView" UserControl. The "BoxView" has a ToolBar, the first button of which becomes the focus hog in the mentioned use case. The "BoxView" also displays a collection of "Compartments" that are inside each "Box" instance:
<StackPanel>
<ToolBarTray IsLocked="True">
<ToolBar>
<Button Content="Focus hog"/>
</ToolBar>
</ToolBarTray>
<ItemsControl ItemsSource="{Binding Compartments}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:CompartmentView/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
Each "Compartment" instance has a TotalValue property simply displayed in a TextBox in a "CompartmentView" UserControl:
<StackPanel>
<TextBox Text="{Binding TotalValue}"/>
</StackPanel>
And the problem is...
If I click on the TextBox of any one of the CompartmentView's, and then click a different tab of the main window, the first button of the ToolBar of the BoxView is focused (the focus hog one), disabling commands working in the main window.
It can be observed commands are disabled when opening the File menu. But after closing the menu and reopening, commands are enabled again.
How do I prevent the ToolBar in the nested UserControl from being focused when switching tabs?
Or perhaps I'm going at this wrong... how do I prevent focus from being set when switching tabs?
Update
The following link is a video on youtube where I explain the problem with the application.
https://youtu.be/0T5LK3CYxgw
Solution update
Thanks to themightylc for the answer, the issue was clearly a keyboard focus problem when the focused visual control (any selected "CompartmentView" or "BoxView" control) was unloaded while changing the selected tab in the TabControl. The solution was to focus on the TabControl itself in the main window whenever the tab selection changed:
private void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
(sender as TabControl).Focus();
}
Thanks also to Christoph Nahr's response for the solution on this msdn thread: https://social.msdn.microsoft.com/Forums/vstudio/en-US/f5de6ffc-fa03-4f08-87e9-77bbad752033/a-focusscope-nightmare-bug-commands-are-disabled?forum=wpf
<Button Content="Focus hog" Focusable="False" />
This solves the problem you're having. Focusable is a DependencyProperty If you still want to access the ToolBar via Keyboard-Focus, you can bind it or set it from code-behind when the Tab-Switch is complete.
You can of course set it via Style so you don't have to set it on every element of the ToolBar.
I'm trying to populate a menu item with a collection of children items. That was easy, however I need to add an extra that's always present that performs an "add" operation. I used a composite collection to add this on to the existing collection of items that need to be displayed.
Here's the code:
<MenuItem Header="Time Ranges" x:Name="TimeRangesMenuItem"
Background="{StaticResource TitleBarButtonBackgroundBrush}"
Margin="2">
<MenuItem.Resources>
<CollectionViewSource Source="{Binding ElementName=TimeRangesMenuItem, Path=DataContext.TimeSpans}" x:Key="TimeSpanMenuItems" />
</MenuItem.Resources>
<MenuItem.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding Source={StaticResource TimeSpanMenuItems}}" />
<Separator />
<MenuItem Header="Add New" cal:Message.Attach="NewTimeSpan()" />
</CompositeCollection>
</MenuItem.ItemsSource>
<MenuItem.ItemTemplate>
<ItemContainerTemplate>
<MenuItem Header="{Binding Name}" cal:Message.Attach="ConfigureTimeSpan()" />
</ItemContainerTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
It works, almost. The DataContext.TimeSpans ViewModel's I'm trying to display are "nested" inside two MenuItems, rather than just populating one, so I get this strange behavior (this only has a single TimeSpanViewModel "Time Span":
Mouse outside the "inner" MenuItem. Note, the inner one is functional if you press it:
Normal MenuItem:
Anyone got any ideas on how to fix this?
What is an item ItemContainerTemplate...
I think you want to manipulate the MenuItem.ItemContainerStyle instead, otherwise you create two MenuItems as observed.
The MenuItem.ItemTemplate already defines what is inside the given item container (varies by control, here MenuItem, in a ComboBox it's a ComboBoxItem, etc.). As there does not appear to be an ItemContainerTemplate property on the MenuItem you might only be able to use it that way implementing a selector that returns your template and setting it as ItemContainerTemplateSelector.
My problem is twofold, but i guess that they are related, and if I manage to fix one of them, I will solve both.
First of, lets see the xaml code for a ContextMenu that is linked to a Caliburn.Micro view model:
<ContextMenu>
<MenuItem Header="Configure modem" ItemsSource="{Binding Modems}">
<MenuItem.ItemTemplate>
<DataTemplate>
<MenuItem>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<ca:ActionMessage MethodName="SelectModem">
<ca:Parameter Value="{Binding Path=DataContext, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}" />
</ca:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
<MenuItem.Header>
<DockPanel>
<Image DockPanel.Dock="Left" Source="{Binding CarrierProfile.CarrierProfileIcon}" Width="40" Height="40"/>
<TextBlock Text="{Binding MenuText}" VerticalAlignment="Center" Margin="10 0"/>
</DockPanel>
</MenuItem.Header>
</MenuItem>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
</ContextMenu>
So basically this is just a DataTemplate where I set the Header to a DockPanel containing an image and a TextBlock.
One MenuItem looks like this:
Here you can see the main problem. You can see that there are "two selections". One outer selection, and one inner. If I click the inner selection, everything is fine, and my SelectModem method is called from my view model. However, if you click the outer selection the context menu goes away so that user thinks he has made a selection, but actually no method is called on the view model.
My second problem is that if I disable the MenuItem by adding IsEnabled="False" in the code above, the menu item looks disabled (text is grayed out), I cannot make the inner selection, but on hover is still shows the outer selection, and when clicked the menu goes away (but nothing is triggered in my view model)
So the question is: How can I get rid of the the outer selection?
I've just been cleaning up my program and I was checking how the tabs worked when I came across this very strange behaviour. My Main menu items were included in the tab order. Thinking that was strange I set their IsTabStop property to false, and ran it again.
They were no longer in my tab order but when I pressed ALT to activate the menu I could no longer use the arrow keys to navigate between the items.
Is there a way around this and make the menu act like a normal old school menu?
Here's how to exclude your menus from the tab order using KeyboardNavigation.TabNavigation:
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="_File">
<MenuItem Header="Open"/>
<MenuItem Header="E_xit"/>
</MenuItem>
</Menu>
<StackPanel KeyboardNavigation.TabNavigation="Cycle">
<TextBox Text=""/>
<TextBox Text=""/>
</StackPanel>
</DockPanel>