ContextMenuOpening when opening a ContextMenu from a ListViewItem - c#

I have a ListView with some data and an ItemContainerStyle to bind the ContextMenu to every item (so it only appears when clicking on an item in the ListView)
I've got the ContextMenu defined in ListView.Resources. How do I properly fire the ContextMenuOpening event and then access the ContextMenu in the ContextMenuEventArgs in said event?
ListView.Resources>
<ContextMenu x:Key="ItemContextMenu" FontFamily="Verdana" FontSize="14">
//Rest of the ContextMenu
<MenuItem Header="Custom Actions">
</MenuItem>
</ContextMenu>
</ListView.Resources>
Right now, I tried to have an Event Setter in the style,
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ContextMenu" Value="{StaticResource ItemContextMenu}" />
<EventSetter Event="ContextMenuOpening" Handler="ContextMenu_ContextMenuOpening" />
</ListView.ItemContainerStyle>
but this triggers a NullPointerException here in the C#-Code (which is basically copied from https://learn.microsoft.com/en-us/dotnet/framework/wpf/advanced/how-to-handle-the-contextmenuopening-event)
private void ContextMenu_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
FrameworkElement fe = e.Source as FrameworkElement;
ContextMenu cm = fe.ContextMenu;
List<MenuItem> customActions = new List<MenuItem>();
customActions.Add(new MenuItem {Header = "First Thing"});
customActions.Add(new MenuItem {Header = "Second Thing"});
foreach (MenuItem mi in cm.Items) // here is the NullPointerException
{
if((String)mi.Header == "Custom Actions")
{
foreach(MenuItem cmi in customActions)
{
mi.Items.Add(cmi);
}
}
}
}
How can I access the ContextMenu and modify it before opening?

Related

Displaying a Tooltip for ComboBoxItem - works after moving the mouse, but not on first try

I'm using WPF ComboBox with only two items in the Combobox. I display the Tooltip for each ComboboxItem by comparing the item.Content to a property that one class includes like this :
private void OnMouseHover(object sender, MouseEventArgs e)
{
var lineSelected = (modelGPZ.GetLineWyList().FirstOrDefault(x => x.isSelected == true));
ComboBoxItem item = sender as ComboBoxItem;
if ((double)item.Content == lineSelected.LiniaWyComboBox[0])
{
item.ToolTip = "This is the first Item";
}
if((double)item.Content == lineSelected.LiniaWyComboBox[1])
{
item.ToolTip = "This is the second Item";
}
}
The problem is that when I open the Combobox for the first time I don't get the Tooltip... Strange because the method is called when I debug it. What is more weird, when I hover my mouse over one item, and then another and at last to the first one.. my tooltip shows up..
I tried changing the event to MouseEnter which does not even call the method and what I thought so other related events for this such kind of action.
MSDN ComboBox Class
XAML for calling the mentiod method:
<ComboBox.ItemContainerStyle >
<Style TargetType="{x:Type ComboBoxItem}">
<EventSetter Event="MouseMove" Handler="OnMouseHover"/>
</Style>
</ComboBox.ItemContainerStyle>
For MouseMove and MouseLeave work as mentioned. For GotFocus the problem is isEditable=True - focus stayes on the TextBlock unfortunatelly.
ComboBox in MainWindows.xaml:
<DataGridTemplateColumn Header="PRĄD POJEMNOŚCIOWY [A]" HeaderStyle="{StaticResource PRAD_POJEMNOSCIOWY}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox x:Name="PradPojemnosciowyComboBox"
ItemsSource="{Binding LiniaWyComboBox, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding SelectedItemComboBox, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsEditable="True"
IsReadOnly="False"
Text="{Binding Prad_pojemnosciowy, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsTextSearchEnabled="False"
IsSynchronizedWithCurrentItem="True"
PreviewKeyDown="PradPojemnosciowyComboBox_OnPreviewKeyDown">
<ComboBox.Style>
<Style TargetType="{x:Type ComboBox}">
<Style.Triggers>
<Trigger Property="SelectedValue" Value="{x:Null}">
<Setter Property="SelectedItem" Value="{Binding SelectedItemComboBox, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Trigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
<ComboBox.ItemContainerStyle >
<Style TargetType="{x:Type ComboBoxItem}">
<EventSetter Event="GotFocus" Handler="PradPojemnosciowyComboBox_GotFocus"/>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
The behavior is logically imho. First you have no ToolTip for ComboBoxItem, on first time MouseMove ToolTip being set, but it's already too late to trigger it ToolTip(it seems ToolTip triggered earlier than MouseMove for ComboBoxItem being fired). So you can see the ToolTip only second time you enter the item.
Attach all to the GotFocus and it will work as expected:
<ComboBox.ItemContainerStyle >
<Style TargetType="{x:Type ComboBoxItem}">
<EventSetter Event="GotFocus" Handler="ComboBoxItem_GotFocus"/>
</Style>
</ComboBox.ItemContainerStyle>
private void ComboBoxItem_GotFocus(object sender, RoutedEventArgs e)
{
var lineSelected = (modelGPZ.GetLineWyList().FirstOrDefault(x => x.isSelected == true));
ComboBoxItem item = sender as ComboBoxItem;
if ((double)item.Content == lineSelected.LiniaWyComboBox[0])
{
item.ToolTip = "This is the first Item";
}
if ((double)item.Content == lineSelected.LiniaWyComboBox[1])
{
item.ToolTip = "This is the second Item";
}
}
Better way is to create in ViewModel a property ItemToolTip in the object, which is DataContext for the ComboBoxItem and bind it:
<ComboBox.ItemContainerStyle >
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="ToolTip">
<Setter.Value>
<ToolTip Content="{Binding ItemToolTip}"/>
</Setter.Value>
</Setter>
</Style>
</ComboBox.ItemContainerStyle>
Example how to prepare data in ViewModel:
public List<object> CbxItemsSource { get; set; } = InitCbxSource();
private static List<object> InitCbxSource()
{
var dblLst = new List<double>() { 1, 2, 3 };
return dblLst.Select(dbl => (object)new { ItemValue = dbl, ItemToolTip = "e.g. item index " + dblLst.IndexOf(dbl)}).ToList();
}
I think it's better option to use ToolTipOpening from ComboBox class than OnMouseHover.
Checkout my solution:
<ComboBox>
<ComboBoxItem ToolTip="" ToolTipOpening="ComboBox_OnToolTipOpening">13.42</ComboBoxItem>
<ComboBoxItem ToolTip="" ToolTipOpening="ComboBox_OnToolTipOpening">15.82</ComboBoxItem>
</ComboBox>
And now code-behind for ToolTip manipulation:
private void ComboBox_OnToolTipOpening(object sender, ToolTipEventArgs e)
{
ComboBoxItem item = sender as ComboBoxItem;
if (item.Content.Equals("13.42"))
{
item.ToolTip = "ToolTipItem1";
}
else if (item.Content.Equals("15.82"))
{
item.ToolTip = "ToolTipItem2";
}
}

Button in DataGrid RowDetails requires two cliks, workaround breaks double click

I have a DataGrid with a button defined in the RowDetailsTemplate. The problem is that when clicking the button, the first click is consumed by the DataGrid to select the row, so you need to click the button twice.
I've tried the workaround offered here: wpf RowDetailsTemplate focus:
XAML:
<DataGrid ItemsSource="{Binding SomeCollection}">
<DataGrid.Resources>
<Style TargetType="DataGridRow">
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="SelectRowDetails"/>
<Setter Property="DetailsVisibility" Value="{Binding HasCanadet, Converter={StaticResource BoolToVis}}"/>Mode=OneWay}"/>
</Style>
</DataGrid.Resources>
...
<DataGrid.RowDetailsTemplate>
<DataTemplate>
...
<Button Command="{Binding SomeCommand}" ... \>
...
</DataTemplate>
</DataGrid.RowDetailsTemplate>
...
<DataGrid>
Code Behind:
private void SelectRowDetails(object sender, MouseButtonEventArgs e)
{
var row = sender as DataGridRow;
if (row == null)
{
return;
}
row.Focusable = true;
row.Focus();
var focusDirection = FocusNavigationDirection.Next;
var request = new TraversalRequest(focusDirection);
var elementWithFocus = Keyboard.FocusedElement as UIElement;
if (elementWithFocus != null)
{
elementWithFocus.MoveFocus(request);
}
}
This works well, but I now need to detect a double click event on the rows of the DataGrid. I do this by adding InputBindings to the DataGrid and passing the SelectedItem as a command parameter:
...
<DataGrid.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick" Command="{Binding AnotherCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=SelectedItem}" />
</DataGrid.InputBindings>
...
The problem is that double click is only detected on the RowDetails (which is good) but are not detected when double clicking the rows themselves (which is not).
Any ideas?
Thanks.
Check if the source of the event is of type DataGridDetailsPresenter
private void SelectRowDetails(object sender, MouseButtonEventArgs e)
{
if(e.Source is DataGridDetailsPresenter) // Like this
{
var row = sender as DataGridRow;
if (row == null)
{
return;
}
row.Focusable = true;
row.Focus();
var elementWithFocus = Keyboard.FocusedElement as UIElement;
if (elementWithFocus != null)
{
elementWithFocus.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
}
}

Dynamically add radiobuttons in submenu in C# WPF

I would like to implement a dynamical menu in my application, written in C# and using WPF.
MenuSubSerialPortSelect.Items.Clear();
foreach (string element in GlobalVars.ActualSerialPorts)
{
MenuItem item = new MenuItem();
item.Header = element;
MenuSubSerialPortSelect.Items.Add(item);
}
The sub-menu adding is functional, but how can I add some radio buttons in this style?
I've read some websites that described that there must be used another "template", but I can't find a matched solution for me.
The attribute item.IsCheckable = true; is not a solution for me -- the entries must be blocked against each other.
It would be great if somebody could give me a tip on how to do that.
Try this
<Window x:Class="Stackoverflow.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Stackoverflow"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<RadioButton x:Key="RadioButton" HorizontalAlignment="Center"
GroupName="MyGroup" IsHitTestVisible="False"/>
<Style TargetType="MenuItem">
<Setter Property="Icon" Value="{StaticResource RadioButton}"/>
<EventSetter Event="Click" Handler="MenuItem_Click" />
</Style>
</Window.Resources>
<Grid Background="Red">
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="Print"/>
<MenuItem Header="Save"/>
<MenuItem Header="Open"/>
<MenuItem Header="Delete"/>
<MenuItem Header="Update"/>
</ContextMenu>
</Grid.ContextMenu>
</Grid>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
private void MenuItem_Click(object sender, System.Windows.RoutedEventArgs e)
{
MenuItem menuItem = sender as MenuItem;
if (menuItem != null)
{
RadioButton rb = menuItem.Icon as RadioButton;
if (rb != null)
{
rb.IsChecked = true;
}
}
}
}
Here i just have static menuitems.

Can't find context menu in ContextMenuOpening

I am trying to replace a header in the context menu based on the data context of the selected row.
ContextMenuOpening fires, but then I am unable to find the context menu from there.
<UserControl ContextMenuOpening="AddItemHeader">
<UserControl.Resources>
<ContextMenu x:Key="RowMenu" DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="<to be set>"
Tag="delete menu item"
Click="MarkForDeletion">
</MenuItem>
[...]
</ContextMenu>
<Style x:Key="DefaultRowStyle" TargetType="{x:Type DataGridRow}">
<Setter Property="ContextMenu" Value="{StaticResource RowMenu}" />
</Style>
</UserControl.Resources>
.
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="ContextMenu" Value="{StaticResource RowMenu}" />
[...]
.
private void AddItemHeader(object sender, ContextMenuEventArgs e)
{
Console.WriteLine("ContextMenuOpening fired.");
FrameworkElement fe = e.Source as FrameworkElement;
ContextMenu menu = fe.ContextMenu;
if (menu == null)
{
Console.WriteLine("Menu not found!");
return;
}
MenuItem menuItem = null;
foreach (MenuItem mi in menu.Items) {
if ((string)mi.Tag == "to be set") {
menuItem = mi;
}
}
if (menuItem == null) {
return;
Console.WriteLine("Item not found!");
}
}
Which just outputs "Menu not found!"
You are setting the context menu to every row.
You can't find the context menu because the sender of the ContextMenuOpening is probably the grid and not the row.
I would assign the context menu to the datagrid and in the event handler of every menu item use the selecteditem of datagrid.
Try this:
var rowIndex = dataGrid.SelectedIndex;
var row = (DataGridRow)dataGrid.ItemContainerGenerator.ContainerFromIndex(selectedIndex);
ContextMenu cm = row.ContextMenu;

Get Text of a ContextMenu.ItemsSource generated MenuItem in WPF

I´ve got a Click-method of a MenuItem in a ContextMenu. In this method, I need the text of the item I´ve clicked.
Here´s the code:
private void menuItemKostenstellen_Click(object sender, RoutedEventArgs e) { }
I already tried with e.Source but that didn´t work.
How can I get this?
Use following:
<ContextMenu Name="conKostenstelle" >
<MenuItem Header="Kostenstellen" Name="menuItemKostenstellen">
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<EventSetter Event="Click" Handler="MenuItem_Click" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
</ContextMenu>
LinkedList<String> kliste = kosrep.GetKostenstellen();
menuItemKostenstellenunter.ItemsSource = kliste;
try this
private void menuItemKostenstellen_Click(object sender, RoutedEventArgs e)
{
MenuItem mi = sender as MenuItem;
string title = mi.Header.ToString();
}
Use ItemContainerStyle Property for Click Event on all MenuItems
<ContextMenu>
<ContextMenu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<EventSetter Event="Click" Handler="MenuItem_Click" />
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>

Categories