New to WPF...was reading this WPF Routed Command with Bindings per-Tab and am close to getting it working.
The MenuItem is disabled until my RuleTab (tabitem) is selected but rather than pop my Find Dialog it shows System.Windows.Input.CommandBinding on the menu. What am I doing wrong?
XAML:
<MenuItem Header="_Find..." IsEnabled="{Binding ElementName=RuleTab, Path=IsSelected}" >
<CommandBinding Command="Find" Executed="ExecuteFind" CanExecute="Find_CanExecute" ></CommandBinding>
</MenuItem>
Code-Behind:
private void ExecuteFind(object sender, ExecutedRoutedEventArgs e)
{
// Initiate FindDialog
FindDialog dlg = new FindDialog(this.RuleText);
// Configure the dialog box
dlg.Owner = this;
dlg.TextFound += new TextFoundEventHandler(dlg_TextFound);
// Open the dialog box modally
dlg.Show();
}
void dlg_TextFound(object sender, EventArgs e)
{
// Get the find dialog box that raised the event
FindDialog dlg = (FindDialog)sender;
// Get find results and select found text
this.RuleText.Select(dlg.Index, dlg.Length);
this.RuleText.Focus();
}
private void Find_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = RuleTab.IsSelected;
}
Any suggestions would be greatly appreciated!
Figured it out! Thanks to those responded. All I had to do was move my commandbinding to:
<Window.CommandBindings>
<CommandBinding Command="Find" Executed="ExecuteFind" CanExecute="Find_CanExecute" ></CommandBinding>
</Window.CommandBindings>
Then reference Command=Find in my MenuItem.
You'll find you need to add the CommandBinding to the TabItem (as per the linked sample). Then to bind your MenuItem you should use the Command property, possibly along with a CommandParameter and CommandTarget (pointing back at the TabItem I would expect).
For example, I have a MenuItem in a ContextMenu and I want the command to fire on the context (placement target) of the ContextMenu:
<MenuItem Header="View"
ToolTip="Open the Member Central view for this member"
Command="{x:Static local:Commands.CustomerViewed}"
CommandParameter="{Binding Path=PlacementTarget.DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"
CommandTarget="{Binding Path=PlacementTarget, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"
/>
Related
I know how to set default ApplicationCommands commands in WPF in order to enable simple cut, copy, and paste operations via ContextMenu. However I need to be able to do this in the code behind so that I can assign the commands dynamically as my TextBoxes are created.
How can I recreate this very simple WPF code in the code behind:
<TextBox x:Name="txtTagName" Style="{StaticResource TextBoxStyle}">
<TextBox.ContextMenu>
<ContextMenu Style="{StaticResource DefaultContextMenuStyle}">
<MenuItem x:Name="cmCut" Header="Cut" Command="ApplicationCommands.Cut" />
<MenuItem x:Name="cmCopy" Header="Copy" Command="ApplicationCommands.Copy" />
<MenuItem x:Name="cmPaste" Header="Paste" Command="ApplicationCommands.Paste" />
</ContextMenu>
</TextBox.ContextMenu>
</TextBox>
you could do something like:
this.cmCut.Command = ApplicationCommands.Cut;
How can I recreate this very simple WPF code in the code behind
Something like this, i.e. you programmatically create an instance of a TextBox and a ContextMenu and set the same properties that you set in your XAML markup:
TextBox textBox = new TextBox();
textBox.Style = FindResource("TextBoxStyle") as Style;
ContextMenu cm = new ContextMenu();
cm.Style = FindResource("DefaultContextMenuStyle") as Style;
cm.Items.Add(new MenuItem() { Header = "Cut", Command = ApplicationCommands.Cut });
cm.Items.Add(new MenuItem() { Header = "Copy", Command = ApplicationCommands.Copy });
cm.Items.Add(new MenuItem() { Header = "Paste", Command = ApplicationCommands.Paste });
textBox.ContextMenu = cm;
Good answer found here: How do I add a custom routed command in WPF?
I wanted to add custom inputs with my own commands for MenuItems and with appropriate texts for the commands displayed in the MenuItems. What solved my problem was to add both a command binding and an input binding section for the window there I could bind my command class and input to that command:
<Window x:Class="SomeNamespace.MainWindow"
<!--Other stuff here-->
xmlns:local="clr-namespace:SomeNamespace"
mc:Ignorable="d"
Title="MainWindow" Height="544" Width="800">
<Window.CommandBindings>
<CommandBinding Command="local:Commands.SomeCommand" Executed="CommandBinding_SomeCommand" />
<CommandBinding Command="local:Commands.SomeOtherCommand" Executed="CommandBinding_SomeOtherCommand" />
</Window.CommandBindings>
<Window.InputBindings>
<KeyBinding Command="local:Commands.SomeCommand" Key="S" Modifiers="Ctrl" />
<KeyBinding Command="local:Commands.SomeOtherCommand" Key="O" Modifiers="Ctrl" />
</Window.InputBindings>
And then I could use it in my MenuItems like this (note that "InputGestureText" adds the shortcut/input text to the MenuItem):
<MenuItem Name="MenuItemSomeCommand" Command="local:Commands.SomeCommand" InputGestureText="Ctrl+S" />
<MenuItem Name="MenuItemSomeOtherCommand" Command="local:Commands.SomeOtherCommand" InputGestureText="Ctrl+O" />
Code for the "Commands" class (in my case in Commands.cs):
using System.Windows.Input;
namespace SomeNamespace
{
public static class Commands
{
public static readonly RoutedUICommand BuildFiles =
new RoutedUICommand("Some Command", "SomeCommand", typeof(MainWindow));
public static readonly RoutedUICommand BuildFiles =
new RoutedUICommand("Some Other Command", "SomeOtherCommand", typeof(MainWindow));
}
}
And code for the Executed binding command in MainWindow.xaml.cs:
public void CommandBinding_SomeCommand(Object sender, ExecutedRoutedEventArgs e)
{
// Add code that should trigger when the "SomeCommand" MenuItem is pressed
}
public void CommandBinding_SomeOtherCommand(Object sender, ExecutedRoutedEventArgs e)
{
// Add code that should trigger when the "SomeOtherCommand" MenuItem is pressed
}
I'm writing a Windows desktop app using C# and WPF. I've a combobox that is used to either type in a file path or to select from previously used file paths. The files in the list can become invalid if the file is deleted or the user entered a valid file but not the desired file. They've asked for a way to remove bad entries from the combobox drop-down list. They want to right-click on an item and select Remove from the context menu.
<ComboBox x:Name="cbDocket" IsEditable="True">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Remove" Click="cbDocket_MenuItemRemove">/>
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBo>
That's the easy part. What I cannot figure out is how to determine which item they selected. Searching hasn't found any suggestions that work. Any help would be appreciated. Is there another way to do this that would be easier? Oh yes, I fairly new to both C# and WPF.
Thanks, Brian
Context menus in WPF are pretty badly broken by design, so about half of all normal cases, anything you do that actually works is a kludge. I've come up with a few; here's another that's not really all that kludgey.
XAML:
<ComboBox x:Name="cbDocket" IsEditable="True">
<ComboBox.Resources>
<ContextMenu x:Key="ItemMenu">
<MenuItem Header="Remove" Click="MenuItem_Click" />
</ContextMenu>
</ComboBox.Resources>
<!-- Some arbitrary random junk to display in the ComboBox -->
<TextBlock Text="Foo" />
<TextBlock Text="Bar" />
<!-- End of arbitrary random junk -->
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter
Property="ContextMenu"
Value="{StaticResource ItemMenu}"
/>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
Code behind:
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
// This needs some null checking and a try catch, but this is the guts of it.
// sender should be the MenuItem as well
var menuItem = e.OriginalSource as MenuItem;
// Since we used ItemContainerStyle to give each ComboBoxItem its own
// personal ContextMenu, each ContextMenu will have its PlacementTarget
// set to the ComboBoxItem that owns it.
var cbItem = (menuItem.Parent as ContextMenu).PlacementTarget as ComboBoxItem;
// ???
//var dataItem = cbItem.DataContext;
}
You didn't mention how you're populating the combo box, so I don't know whether you've got DataContext set on the items or what. But once you have the ComboBoxItem, you can get there.
Add an event to track the mouse movement while the dropdown is open and use that when the remove is clicked.
<ComboBox x:Name="cbDocket" HorizontalAlignment="Left" Margin="33,30,0,269" Width="124" IsEditable="True">
<ComboBox.ContextMenu>
<ContextMenu Name="Menu">
<MenuItem Header="Remove" Click="cbDocket_MenuItemRemove"/>
</ContextMenu>
</ComboBox.ContextMenu>
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<EventSetter Event="MouseMove" Handler="cbDocket_OnMouseMove" />
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
Code behind:
ComboBoxItem cbiSelected = null;
private void cbDocket_MenuItemRemove(object sender, RoutedEventArgs e)
{
cbDocket.Items.Remove(cbiSelected);
}
private void cbDocket_OnMouseMove(object sender, MouseEventArgs e)
{
ComboBoxItem cbiHover = sender as ComboBoxItem;
if (cbiHover.IsHighlighted)
{//Verify the item is highlighted
cbiSelected = cbiHover;
}
}
There's a couple ways of doing this. Since you are using the Click event handler, then the source of the event is always the item that was clicked. For example:
Xaml:
<ComboBox x:Name="cbDocket" IsEditable="True">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Remove" Click="cbDocket_MenuItemRemove">/>
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBo>
Xaml.cs:
private void cbDocket_MenuItemRemove(object source, EventArgs args)
{
// Source is your item:
cbDocekt.Items.Remove(cbDocket.SelectedItem);
}
Of course you can always obtain the selected item, or even change the selected item in the combo box using the SelectedItem property.
I'm assuming you are using code behind to do this.
With the following program code you can add a context menu to a combo box. In this context menu a single item or all items of the combo box can be removed.
private ComboBox FolderComboBox;
private ContextMenu FolderHistoryContextMenu;
...
private void CreateFolderHistoryContextMenu()
{
MenuItem RemoveFolderMenuItem, RemoveAllFoldersMenuItem;
FolderHistoryContextMenu = new ContextMenu();
RemoveFolderMenuItem = new MenuItem();
RemoveFolderMenuItem.Header = "Remove folder";
RemoveFolderMenuItem.Click += new RoutedEventHandler(RemoveFolderMenuItem_Click);
FolderHistoryContextMenu.Items.Add(RemoveFolderMenuItem);
RemoveAllFoldersMenuItem = new MenuItem();
RemoveAllFoldersMenuItem.Header = "Remove all folders";
RemoveAllFoldersMenuItem.Click += new RoutedEventHandler(RemoveAllFoldersMenuItem_Click);
FolderHistoryContextMenu.Items.Add(RemoveAllFoldersMenuItem);
// The context menu is assigned to each item in the list of the combo box and not to the combo box itself.
if (FolderComboBox.ItemContainerStyle == null)
{
FolderComboBox.ItemContainerStyle = new Style(typeof(ComboBoxItem));
}
FolderComboBox.ItemContainerStyle.Setters.Add(new Setter(ContextMenuProperty, FolderHistoryContextMenu));
}
private void RemoveFolderMenuItem_Click(object sender, RoutedEventArgs e)
{
ComboBoxItem ClickedComboBoxItem = FolderHistoryContextMenu.PlacementTarget as ComboBoxItem;
if (ClickedComboBoxItem != null)
{
object ComboBoxDataItem = ClickedComboBoxItem.DataContext;
FolderComboBox.Items.Remove(ComboBoxDataItem);
}
}
private void RemoveAllFoldersMenuItem_Click(object sender, RoutedEventArgs e)
{
FolderComboBox.Items.Clear();
}
I've been trying to create right click functionality to bring up a context menu in C# but it doesn't seem to be working. Any idea why?
private void dataGridView1_MouseClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
ContextMenu m = new ContextMenu();
m.MenuItems.Add(new MenuItem("Copy"));
int currentMouseOverRow = dataGridView1.HitTest(e.X, e.Y).RowIndex;
m.Show(dataGridView1, new Point(e.X, e.Y));
}
}
I'm not sure what you're using, WPF/WinForms/BlackMagic/Etc... but it seems that every right click you'll be creating a NEW context menu, which isn't attached to anything...
ContextMenu MyMenu = new ContextMenu();
MyMenu.MenuItems.Add("Copy");
You should have that attached to whatever control you're using (in your case, the grid I suppose, or the row):
SomeGrid.ContextMenu = MyMenu;
In WPF, using one on a ListBox would look like
<ListBox x:Name="NameYourList"
ItemsSource="{Binding SomeItem}"
SelectedItem="{Binding SomeProperty, UpdateSourceTrigger=PropertyChanged}"
>
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Header ="Copy Me" Command="{Binding Copy_Command}"
CommandParameter="{Binding SomeProperty}"
/>
</ContextMenu>
</ListBox.ContextMenu>
Here is the xaml for the contextMenu:
<Window.Resources>
<ContextMenu x:Key="IBContextMenu" x:Shared="true" Name="IBContextMenu1">
<MenuItem Header="Edit" Click="ibEdit_Click" AllowDrop="False" />
<MenuItem Header="Clear" Click="ibClear_Click"/>
</ContextMenu>
</Window.Resources>
Both the edit and clear items' methods need to know which label to act upon. How can I do this?
I think you are looking for PlacementTarget:
http://msdn.microsoft.com/en-us/library/system.windows.controls.contextmenu.placementtarget.aspx
If you switch over to a Command-pattern, you can actually get this via Binding and pass it along as the CommandParameter...
Here's an answer I came up with. I don't really like it because it's a bit hack-ish, but it works. The idea is that you make your labels listen to the MouseRightButtonUp event, which is fired when the user releases the right mouse button after clicking to open the context menu. In the event handler, you set a private Label variable to the label that the user just right-clicked. Then, in your MenuItem click handler, you can access that private Label variable. Note that all the labels you want to do this must use the same event handler for MouseRightButtonUp.
For example:
<Window.Resources>
<ContextMenu x:Key="MyMenu">
<MenuItem Header="Edit" Click="Edit_Click"/>
<MenuItem Header="Clear" Click="Clear_Click"/>
</ContextMenu>
</Window.Resources>
<StackPanel>
<Label ContextMenu="{StaticResource MyMenu}"
MouseRightButtonUp="Label_MouseRightButtonUp">Some text</Label>
<Label ContextMenu="{StaticResource MyMenu}"
MouseRightButtonUp="Label_MouseRightButtonUp">Some junk</Label>
<Label ContextMenu="{StaticResource MyMenu}"
MouseRightButtonUp="Label_MouseRightButtonUp">Some stuff</Label>
<Label ContextMenu="{StaticResource MyMenu}"
MouseRightButtonUp="Label_MouseRightButtonUp">Some 0000</Label>
</StackPanel>
Code behind:
private void Edit_Click(object sender, RoutedEventArgs e)
{
if (clickedLabel != null)
{
MessageBox.Show(clickedLabel.Content.ToString());
}
}
private void Clear_Click(object sender, RoutedEventArgs e)
{
if (clickedLabel != null)
{
MessageBox.Show(clickedLabel.Content.ToString());
}
}
private Label clickedLabel;
private void Label_MouseRightButtonUp(object sender, MouseButtonEventArgs e)
{
clickedLabel = (Label)sender;
}
Try to set a DataContext to the Labels, for example
And in the Click event just check the ((FrameworkElement)sender).DataContext for FIRST/SECOND etc. Let us know if that works.
In WPF application there is a Grid with a number of objects (they are derived from a custom control). I want to perform some actions on each of them using context menu:
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Name="EditStatusCm" Header="Change status" Click="EditStatusCm_Click"/>
</ContextMenu>
</Grid.ContextMenu>
But in the event handler I cannot get know which of the objects was right-clicked:
private void EditStatusCm_Click(object sender, RoutedEventArgs e)
{
MyCustControl SCurrent = new MyCustControl();
MenuItem menu = sender as MenuItem;
SCurrent = menu.DataContext as MyCustControl; // here I get a run-time error
SCurrent.Status = MyCustControl.Status.Sixth;
}
On that commented line Debugger says: Object reference not set to an instance of an object.
Please help, what is wrong in my code?
Edited (added):
I tried to do the same, using Command approach:
I declared a DataCommands Class with RoutedUICommand Requery and then used Window.CommandBindings
<Window.CommandBindings>
<CommandBinding Command="MyNamespace:DataCommands.Requery" Executed="RequeryCommand_Executed"></CommandBinding>
</Window.CommandBindings>
XAML of MenuItem now looks like:
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Name="EditStatusCm" Header="Change status" Command="MyNamespace:DataCommands.Requery"/>
</ContextMenu>
</Grid.ContextMenu>
And event handler looks like:
private void RequeryCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
IInputElement parent = (IInputElement)LogicalTreeHelper.GetParent((DependencyObject)sender);
MyCustControl SCurrent = new MyCustControl();
SCurrent = (MuCustControl)parent;
string str = SCurrent.Name.ToString();// here I get the same error
MessageBox.Show(str);
}
But debugger shows the same run-time error: Object reference not set to an instance of an object.
What is missing in my both approaches?
How I should reference right-clicked object in WPF Context Menu item click event handler?
note the CommandParameter
<Grid Background="Red" Height="100" Width="100">
<Grid.ContextMenu>
<ContextMenu>
<MenuItem
Header="Change status"
Click="EditStatusCm_Click"
CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=Parent}" />
</ContextMenu>
</Grid.ContextMenu>
</Grid>
and use it in the handler to figure out which Grid it is
private void EditStatusCm_Click(object sender, RoutedEventArgs e)
{
MenuItem mi = sender as MenuItem;
if (mi != null)
{
ContextMenu cm = mi.CommandParameter as ContextMenu;
if (cm != null)
{
Grid g = cm.PlacementTarget as Grid;
if (g != null)
{
Console.WriteLine(g.Background); // Will print red
}
}
}
}
Update:
If you want the menuitem handler to get to the Grid's children instead of the Grid itself, use this approach
<Grid Background="Red" Height="100" Width="100">
<Grid.Resources>
<ContextMenu x:Key="TextBlockContextMenu">
<MenuItem
Header="Change status"
Click="EditStatusCm_Click"
CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=Parent}" />
</ContextMenu>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="ContextMenu" Value="{StaticResource TextBlockContextMenu}" />
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="Row0" Grid.Row="0" />
<TextBlock Text="Row1" Grid.Row="1" />
</Grid>
Just replace the TextBlocks with whatever your custom object type is. Then in the event handler, replace Grid g = cm.PlacementTarget as Grid with TextBlock t = cm.PlacementTarget as TextBlock (or whatever your custom object type is).
By binding the Data Context like so in the xaml:
ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource= {RelativeSource Self}}">
You can then do this:
private void Context_MenuClick(object sender, RoutedEventArgs e)
{
var menuItem = e.Source as MenuItem;
MyDoStuffFunction(menuItem.DataContext);
}
The data context will be bound to the object that was clicked to open the ContextMenu.
I got it from a codeproject article at this link:
http://www.codeproject.com/Articles/162784/WPF-ContextMenu-Strikes-Again-DataContext-Not-Upda
menu = sender as MenuItem will be null if the sender is not a MenuItem or a derived class thereof. Subsequently trying to dereference menu will blow up.
It's likely that your sender is a Menu or ContextMenu or a ToolStripMenuItem or some other form of menu item, rather than specifically being a MenuItem object. Use a debugger breakpoint to stop the code here and examine the sender object to see exactly what class it is.
For RoutedEventArgs
RoutedEventArgs.source is the reference to the object that raised the event
RoutedEventArgs.originalSource is the reporting source as determined by pure hit testing, before any possible Source adjustment by a parent class.
So .Sender should be the answer. But this depends on how the menuitems are added and bound
See this answer collection and choose the method that will work for you situation!
Shouldn't you be checking RoutedEventArgs.Source instead of sender?
You had two different problems. Both problems resulted in the same exception, but were otherwise unrelated:
First problem
In your first approach your code was correct and ran well except for the problem here:
SCurrent.Status = MyCustControl.Status.Sixth;
The name "Status" is used both as a static member and as an instance member. I think you cut-and-pasted the code incorrectly into your question.
It may also be necessary to add the following after MenuItem menu = sender as MenuItem;, depending on your exact situation:
if(menu==null) return;
Second problem
In your second approach you used "sender" instead of "e.Source". The following code works as desired:
private void RequeryCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
IInputElement parent = (IInputElement)LogicalTreeHelper.GetParent((DependencyObject)e.Source);
// Changed "sender" to "e.Source" in the line above
MyCustControl SCurrent = new MyCustControl();
SCurrent = (MuCustControl)parent;
string str = SCurrent.Name.ToString();// Error gone
MessageBox.Show(str);
}
Final Note
Note: There is no reason at all to bind CommandParameter for this if you use the commanding approach. It is significantly slower and takes more code. e.Source will always be the source object so there is no need to use CommandParameter, so use that instead.
This works for me:-
XAML:-
<DataGrid.ContextMenu>
<ContextMenu x:Name="AddColumnsContextMenu" MenuItem.Click="AddColumnsContextMenu_Click">
</ContextMenu>
For adding menu items:-
foreach (String s in columnNames)
{
var item = new MenuItem { IsCheckable = true, IsChecked = true ,Header=s};
AddColumnsContextMenu.Items.Add(item);
}
And here comes the listener:-
private void AddColumnsContextMenu_Click(object sender, RoutedEventArgs e)
{
MenuItem mi = e.Source as MenuItem;
string title = mi.Header.ToString();
MessageBox.Show("Selected"+title);
}
Thanks...
In my case I was able to use:
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
MenuItem menuItem = e.Source as MenuItem;
ContextMenu parent = menuItem.Parent as ContextMenu;
ListBoxItem selectedItem = parent.PlacementTarget as ListBoxItem;
}