I'm trying to get a "Copy" context menu to display when clicking on the advanced options icon of any property. What do I need to add to make it work?
<xctk:PropertyGrid
x:Name="PropertyGrid"
Grid.Column="1" Margin="8"
ShowSummary="False"
AutoGenerateProperties="True"
HideInheritedProperties="False"
SelectedObject="{Binding InspectedObject}"
SelectedObjectName="{Binding InspectedObject, Converter={StaticResource PropertyGridPropertyNameConverter}}"
SelectedObjectTypeName="{Binding InspectedObject, Converter={StaticResource PropertyGridPropertyTypeConverter}}"
SelectedObjectChanged="PropertyGrid_OnSelectedObjectChanged"
ShowAdvancedOptions="True"
>
<xctk:PropertyGrid.AdvancedOptionsMenu >
<ContextMenu>
<MenuItem Header="Copy" Click="MenuItem_OnClick"></MenuItem>
</ContextMenu>
</xctk:PropertyGrid.AdvancedOptionsMenu>
</xctk:PropertyGrid>
I'd like the "Copy" context item to display and take a click whether or not the property is read only.
In the MenuItem click event handler, you can access property data by property DataContext in sender object.
private void MenuItem_OnClick(object sender, RoutedEventArgs e)
{
MenuItem menuItem = sender as MenuItem;
if (menuItem != null && menuItem.DataContext is PropertyItem)
{
Clipboard.SetData(DataFormats.Text, ((PropertyItem)menuItem.DataContext).Value);
}
}
In the following link, you can find more information about this topic:
https://kmatyaszek.github.io/2018/08/22/extended-wpftoolkit-propertygrid-copybutton.html
I have noticed that this solution works when you clicked (left mouse button) at AdvancedOptionsMenu icon, there is a problem with the right mouse click on the property item. To solve this problem I forked the original repo and I have added copy value menu item to AdvancedOptionsMenu. Check it out: https://github.com/kmatyaszek/wpftoolkit
Related
I have a wrap panel that is populated with Image controls at run time. I want to use a context menu to remove images I want to delete.
<toolkit:WrapPanel x:Name="wrap_Panel">
<toolkit:ContextMenuService.ContextMenu>
<toolkit:ContextMenu IsZoomEnabled="false" x:Name="ContextMenu" >
<toolkit:MenuItem x:Name="Delete" Header="Delete" Click="DeleteImage"/>
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
</toolkit:WrapPanel>
This allows me to get the menu up but how do I actually delete the image I pressed?
private void DeleteImage( object sender, RoutedEventArgs e )
{
MenuItem menuItem = sender as MenuItem;
if ( menuItem != null )
{
Image imageItem = menuItem.DataContext as Image;
...
find the imageItem in your collection or list and delete it.
I have listbox with context menu, when i press "Application Key" its not showing up context menu, it has to work like windows.
<ListBox.ContextMenu>
<ContextMenu Name="ZoneIformationList" StaysOpen="true" Background="WhiteSmoke">
<ContextMenu.BitmapEffect>
<BitmapEffectGroup/>
</ContextMenu.BitmapEffect>
<MenuItem Header="Edit" Name="EditNutritionContextMenu" />
<MenuItem Header="Remove" Name="RemoveNutritionContextMenu" />
</ContextMenu>
</ListBox.ContextMenu>
You have a couple of choices on how to achieve your goal. Either you can create a Command that opens the ContextMenu, then add a KeyBinding with the command and a KeyGesture of ALT + CTRL to the InputBindings of your main window.
You can find out more about the KeyBinding class from the KeyBinding Class page and the KeyGesture class from the KeyGesture Class page at MSDN.
The other option is to add a handler for the PreviewMouseDown (or similar) event and monitor whether the ALT and CTRL keys are being pressed, then programmatically launch the ContextMenu control if they are:
public void ListBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
if ((e.KeyboardDevice.Modifiers & ModifierKeys.Alt) == ModifierKeys.Alt &&
(e.KeyboardDevice.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
{
// Open ContextMenu
}
}
Now all that is left is to open the ContextMenu from code:
if (element.ContextMenu != null )
{
element.ContextMenu.PlacementTarget = element;
element.ContextMenu.IsOpen = true;
}
If the ContextMenu is not placed correctly, you can set the placement using the ContextMenu.HorizontalOffset and ContextMenu.HorizontalOffset properties.
I have a ListView with ContextMenu on each ListViewItem that has Click event,
how can I detect in the event handler which Item was clicked in this ContextMenu?
I need the item ID.
<Style TargetType="{x:Type ListViewItem}">
.
.
.
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate TargetType="tv:TreeListViewItem">
<Grid>
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="Open in current tab" Click="MenuItemCurrentTab_Click"/>
<MenuItem Header="Open in new tab" Click="MenuItemNewTab_Click"/>
</ContextMenu>
</Grid.ContextMenu>
See this thread..
Following the same way as the answer from the link you would
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="Open in current tab"
Click="MenuItemCurrentTab_Click"
CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=Parent}"/>
...
private void MenuItemCurrentTab_Click(object sender, RoutedEventArgs e)
{
MenuItem menuItem = sender as MenuItem;
if (menuItem != null)
{
ContextMenu parentContextMenu = menuItem.CommandParameter as ContextMenu;
if (parentContextMenu != null)
{
ListViewItem listViewItem = parentContextMenu.PlacementTarget as ListViewItem;
}
}
}
UPDATE
Add this to get the parent ListViewItem from the Grid
public T GetVisualParent<T>(object childObject) where T : Visual
{
DependencyObject child = childObject as DependencyObject;
while ((child != null) && !(child is T))
{
child = VisualTreeHelper.GetParent(child);
}
return child as T;
}
private void MenuItemCurrentTab_Click(object sender, RoutedEventArgs e)
{
MenuItem menuItem = sender as MenuItem;
if (menuItem != null)
{
ContextMenu parentContextMenu = menuItem.CommandParameter as ContextMenu;
if (parentContextMenu != null)
{
Grid grid = parentContextMenu.PlacementTarget as Grid;
ListViewItem listViewItem = GetVisualParent<ListViewItem>(grid);
}
}
}
private void MenuItemCurrentTab_Click(object sender, RoutedEventArgs e)
{
MenuItem menuItem = (MenuItem)e.Source;
ContextMenu menu = (ContextMenu)menuItem.Parent;
ListViewItem item = (ListViewItem)menu.PlacementTarget;
// do something with item
}
But it's probably better idea to create single ContextMenu, give it proper name, and use it for all list view items.
A recurring problem, with many attempts to solve but all have their drawbacks. The accepted answer here, for instance, supposes that each ListViewItem has its own ContextMenu. This works but, especially with a larger number of list items, has a considerable cost in XAML complexity and can be slow. And really isn't necessary at all. If we only use a single ContextMenu on the ListView itself, some other solutions suggest to use
<MenuItem CommandParameter="{Binding PlacementTarget.SelectedItem, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
which seems to solve the problem at first sight (PlacementTarget points to the ListView, its SelectedItem points to the list item, so the menu item handler can use the CommandParameter to get the originating list item), but, unfortunately, fails if the ListView has multiple selection enabled (SelectedItem will point to one of the items selected but not necessarily the one currently clicked) or if we use ListView.PreviewMouseRightButtonDown to disable the selection on right-click (which is, arguably, the only logical thing to do with multiple selections).
There is, however, an approach that has all the benefits:
single ContextMenu on the ListView itself;
works with all selection schemes, single, multiple, disabled;
even with multiple selection, it will pass the currently hovered item to the handler.
Consider this ListView:
<ListView ContextMenuOpening="ListView_ContextMenuOpening">
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Menu1" Click="Menu1_Click" CommandParameter="{Binding Parent, RelativeSource={RelativeSource Self}}" />
</ContextMenu>
</ListView.ContextMenu>
</ListView>
The CommandParameter is used to pass the parent of the MenuItem, ie. the ContextMenu itself. But the main trick comes in the menu opening handler:
private void ListView_ContextMenuOpening(object sender, ContextMenuEventArgs e) {
var menu = (e.Source as FrameworkElement).ContextMenu;
menu.Tag = (FrameworkElement)e.OriginalSource;
}
Inside this handler, we still know the original source of the event, the root FrameworkElement of the list item DataTemplate. Let's store it in the Tag of the menu for later retrieval.
private void Menu1_Click(object sender, RoutedEventArgs e) {
if (sender is MenuItem menu)
if (menu.CommandParameter is ContextMenu context)
if (context.Tag is FrameworkElement item)
if (item.DataContext is DataType data) {
//process data
}
}
In the menu click handler, we can look up the original ContextMenu we stored in the command parameter, from that we can look up the root FrameworkElement of the list item that we stored just before, and finally get the object stored in the list item (of type DataType).
ListViewItem item = myListView.SelectedItem as ListViewItem;
Seems to work just fine as the item is selected when you right-click it.
So, lets say I have a ComboBox with a custom data template. One of the items in the data template is a button:
<ComboBox Width="150" ItemsSource="{Binding MyItems}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Button Content="ClickMe" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
The problem with this is that the button eats the click, and the item does not get selected if the button is selected. This means that the pull-down does not go away, and no item is selected.
I get WHY this is happening.
Is there a way to work around it? Possibly a way to process the button click (I am binding to a command) and tell it to continue up the chain so the combo box can also process the click?
Note: I am seeing my problem in Silverlight, but I am guessing that the exact same behavior can be seen with WPF.
OK, I got it figured out. It is a total hack, but it still lets me bind my command to the button and continue to have Combo-box behavior for selecting the item:
<ComboBox x:Name="MyCombo" Width="150" ItemsSource="{Binding MyItems}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Button Content="ClickMe" Click="Button_Click" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
And in the code behind:
private void Button_Click(object sender, RoutedEventArgs e)
{
MyCombo.SelectedItem = (sender as Button).DataContext;
MyCombo.IsDropDownOpen = false;
}
If I really wanted to, I could bind the SelectedItem and IsDropDownOpen to properties in my ViewModel but I decided against it to keep this behavior as a hack extension of the XAML, in an effort to keep my ViewModel clean.
Your best bet would probably be to set the SelectedItem in the button's command.
I found another possibility for the MVVM context. I used an derived class for ComboBox and if an item is adden which derives from ButtonBase I attach to the Click event to close the ComboBox.
This works for my project - but just, because the items itself are buttons, it would not work if they just contain buttons as a child element.
public class MyComboBox : ComboBox
{
public MyComboBox()
{
// use Loaded event to modify inital items.
Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
if (Items != null)
{
foreach (var item in Items)
{
var button = item as ButtonBase;
if (button != null)
{
ModifyButtonItem(button);
}
}
}
}
protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
base.OnItemsChanged(e);
// Check added items. If an item is a button, modify the button.
if (e.NewItems != null)
{
foreach (var item in e.NewItems)
{
var button = item as ButtonBase;
if (button != null)
{
ModifyButtonItem(button);
}
}
}
}
private void ModifyButtonItem(ButtonBase button)
{
button.Click += (sender, args) => { IsDropDownOpen = false; };
}
}
I don't know if there is a way to do what you want. If you were to put a Button in a ListBox, for example, the same behavior occurs - clicking the Button does not cause its item in the ListBox to be selected. In fact, this is the case for any control in an ItemsControl that supports selection.
You might be able to do something with the Click event and mark it as not handled so that it continues up the visual tree, but even then I'm not sure if that would work or not.
I have a menu in wpf that has an input box and a button on it. Once the user clicks the button I need to close the menu.
Is there a way to do this?
<Menu x:Name="MainMenu">
<MenuItem Header="Main">
<MenuItem Header="SubMenu" x:Name="SubMenu">
<StackPanel Orientation="Horizontal">
<TextBox Width="50" x:Name="TextBox" />
<Button Content="Click Me and Close" x:Name="Button" IsDefault="True"/>
</StackPanel>
</MenuItem>
</MenuItem>
Thanks,
Jon
Get hold of the MenuItem and do:
_menuItem.IsSubmenuOpen = false;
Easy way to get hold of it:
<Button x:Name="_button" Tag="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type MenuItem}, AncestorLevel=2}"/>
Code-behind:
_button.Click += delegate
{
(_button.Tag as MenuItem).IsSubmenuOpen = false;
};
I find that using IsSubmenuOpen doesn't properly eliminate focus from the Menu containing the MenuItem (especially if the Menu is in a ToolBar - the top-level MenuItem remains Selected even though the menu is "Closed"). I find sending a MouseUp event to the MenuItem works better (in the button's, or nested control's, Click event handler):
private void button_Click(object sender, RoutedEventArgs e) {
Button b = sender as Button;
if (b == null || !(b.Parent is MenuItem))
return;
MenuItem mi = b.Parent as MenuItem;
mi.RaiseEvent(
new MouseButtonEventArgs(
Mouse.PrimaryDevice, 0, MouseButton.Left
)
{RoutedEvent=Mouse.MouseUpEvent}
);
}
Steve thanks for your solution. That is actually right answer, and finally something that really works beside of tons of bad answers over the internet. I have a shorter (and more safe) solution based on your anwser. Because direct parent (e.Parent) of the button is not always MenuItem (from original answer that is StackPanel), your solution will not work. So just set the Name property of the MenuItem (Name="MyMenuItem") and hook this handler on the Button:
private void Button_Click(object sender, RoutedEventArgs e) {
MyMenuItem.RaiseEvent(new MouseButtonEventArgs(Mouse.PrimaryDevice, 0, MouseButton.Left) {
RoutedEvent = Mouse.MouseUpEvent
});
}