Getting selected menuitem when it is dynamically created - c#

I've got a ContextMenu that looks like this:
<ContextMenu StaysOpen="False" Width="150" x:Name="HistoryContextMenu">
<MenuItem Command="{Binding HistorySetCurrent}" Header="Set _Current (C)"/>
<MenuItem Command="{Binding HistorySetReference}" Header="Set _Reference (R)" />
<MenuItem Header="Add to Group" ItemsSource="{Binding ElementName=Groups, Path=ChartGroups}" DisplayMemberPath="Name" HorizontalAlignment="Left">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewMouseUp">
<cmd:EventToCommand Command="{Binding HistoryGroupContextClickCommand}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=MenuItem}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</MenuItem>
</ContextMenu>
I'm using MVVM so I'm binding to a method to handle the PreviewMouseUp event (I have tried other events too). I get a list in my submenu as wanted, but when I click on one of them, the eventargs I get contain all the menu items in the list, not just the one I selected, with no incdication of the one chosen.
How do I find the selected menuitem? I've tried doing this with code behind, but the same problem occurs with that method too.

You should use an ItemContainerStyle to manage the Commands on the generated MenuItems.
Have a look at Kent's answer to this question which shows you how to setup the style: Using a DataTemplate for a MenuItem causes extra space on the left side to appear?
You would need to add the binding for the Command and the individual item as the CommandParameter in order to have context for the Command to run against.

Related

WPF shared menu between main top menu and right click context menu

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>

Is there a way to pass treeViewItem name as command parameter?

I am creating a simple treeview with binding to a collection. What I want is to display full info about the collection element in a listview when treeview element is selected. Similar to windows file explorer. The problem is I cannot bind selected treeviewitem to a Command.
Tried binding command to SelectedItemChanged event with a parameter of treeviewitem name - didn`t work.
Tried it with a simple event - gets cumbersome and breaks MVVM pattern. There must be an elegant way to solve this since what I am trying to do is really widespread in different apps.
<TreeView Grid.Row="1" Background="AliceBlue" ItemsSource="{Binding TopPanelNodes}" Name="TopTreeView" SourceUpdated="TopTreeView_SourceUpdated" >
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Nodes}">
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal" IsEnabled="False">
<TextBlock Text="{Binding Name}" >
</TextBlock>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectedItemChanged">
<i:InvokeCommandAction Command="{Binding UpdateInspectorCommand }" CommandParameter=??? />
</i:EventTrigger>
</i:Interaction.Triggers>
</TreeView>
I would like to pass treeviewitem name as command parameter instead of ??? in my code so selected treeview item will be identified by name so I can get respective info and bind it to listview.
Solved it using RelativeSource.
CommandParameter="{Binding Path=SelectedItem, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TreeView}}}"
Works fine.

WPF Datagrid ContextMenu SelectedItem

I have the following problem.
I have a DataGrid with a ContextMenu. But when the Command is triggered the SelectedItem is always null. Any recommendations?
Thats my ContextMenu:
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Command="{Binding OpenFileCommand}"
CommandParameter="{Binding Path=SelectedItem,
RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"
Header="open file" />
i also tried:
CommandParameter="{Binding ElementName=nameOfGrid, Path=SelectedItem}"
Thanks for your help. I fixed it this way: CommandParameter="{Binding PlacementTarget.SelectedItem,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ContextMenu}}}"
I think your answer is here: CommandParameters in ContextMenu in WPF
Another better option imho is to keep "SelectedItem" binded in your ViewModel.
Then you don't need a command parameter anymore, and you can juste use the SelectedItem binded from your ViewModel.
The problem is that the ContextMenu is in a different VisualTree.
You could use the Tag for binding the Command. See the following link

Getting ContextMenu target from Item in ItemsControl

I have an ItemsControl bound to a collection on my ViewModel. This ItemsControl presents several "Messages". I need a ContextMenu that, when clicked, provides an option to Copy that particular message to the clipboard.
The Command should activate on the ViewModel and the CommandParameter I want to pass is the Message itself.
The problem I'm having is getting the actual message that the ContextMenu has been opened OVER.
I've tried mutliple different approaches, but I cannot seem to figure out a way to pass the message as the Command's parameter.
Should I look for a different approach to accomplish this? Is the issue using an ItemsControl with an ItemsPresenter?
<ScrollViewer CanContentScroll="False"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Disabled"
Grid.RowSpan="1">
<ItemsControl ItemsSource="{Binding MyActiveConversation.OrderedMessages, UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding MyActiveConversation.IsOptOut, Converter={StaticResource BoolToEnabledInverter}}"
ItemTemplateSelector="{StaticResource tSelector}"
VirtualizingPanel.IsVirtualizing="False"
SourceUpdated="SourceUpdatedHandler" MinWidth="450">
<ItemsControl.ContextMenu>
<ContextMenu>
<MenuItem Header="Copy" Template="{DynamicResource MenuItemControlTemplate}"
CommandParameter="{Binding}" Command="{Binding Path=Data.CopyTextMessageCommand, Source={StaticResource ContextProxy}}">
</MenuItem>
</ContextMenu>
</ItemsControl.ContextMenu>
<ItemsControl.Template>
<ControlTemplate>
<Grid>
<ItemsPresenter VirtualizingPanel.IsVirtualizing="False"/>
</Grid>
</ControlTemplate>
</ItemsControl.Template>
</ItemsControl>
CommandParameter="{Binding}" works good for me, same for CommandParameter="{Binding .}"

Disabled menu element in WPF looks enabled

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?

Categories