How to use a ContextMenu UserControl in WPF? - c#

I have a user control like this:
<UserControl x:Class="MyApp.UserControls.MyContextMenu"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
ContextMenuOpening="OnContextMenuOpening"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.ContextMenu>
<ContextMenu>
...
</ContextMenu>
</UserControl.ContextMenu>
</UserControl>
My question is: how do I use that context menu for something like a data grid:
<DataGrid ContextMenu="{usercontrols:MyContextMenu}"
Unfortunately that does not work because the specified value is incorrect and expected a ContextMenu.
Note: I need to reuse my context menu in several places, so I have put it in its own file. Also, I need to be able to listen to OnContextMenuOpening events, because the menu upon opening needs to do some work regarding the menu and the event is not fired for the context menu sadly: http://connect.microsoft.com/VisualStudio/feedback/details/353112/contextmenu-opening-event-doesnt-fire-properly
"ContextMenu itself is a FrameworkElement derived class, but this
event will not be raised from the context menu being opened as a
source. The event is raised from the element that "owns" the context
menu as a property and is only raised when a user attempts to open a
context menu in the UI."
This event problem is the reason I have put the menu for a user control -- so that the user control can get the event and do the work.
Update: I tried to have it as a root element and extend the context menu:
And code-behind:
But I'm getting: ContextMenu cannot have a logical or visual parent.

Regardless of how you call your UserControl, it is not a ContextMenu. You would have to derive from ContextMenu instead of UserControl:
<ContextMenu x:Class="MyApp.MyContextMenu"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<MenuItem Header="Item 1"/>
<MenuItem Header="Item 2"/>
...
</ContextMenu>
and
public partial class MyContextMenu : ContextMenu
{
public MyContextMenu()
{
InitializeComponent();
}
}
But why would you do that at all?

Try to defineit like:
<DataGrid.Resources>
<ContextMenu x:Key="DgContextMenu">
...
</ContextMenu>
</DataGrid.Resources>
and after use it like
<DataGrid ContextMenu="{StaticResource DgContextMenu}
Should work.

Related

Type reference cannot find type named Commands

I want to have custom Commands to respond to in my application.
So I was following the instructions on this answer, and created a static class for my commands:
namespace MyNamespace {
public static class Commands {
public static readonly RoutedUICommand Create = new RoutedUICommand(
"Create Thing", nameof(Create),
typeof(MyControl)
);
}
}
I then tried using it on my UserControl:
<UserControl x:Class="MyNamespace.MyControl"
...boilerplate...
xmlns:local="clr-namespace:MyNamespace">
<UserControl.CommandBindings>
<CommandBinding Command="local:Commands.Create"
CanExecute="CanCreateThing"
Executed="CreateThing"/>
</UserControl.CommandBindings>
...the control's contents...
</UserControl>
The method CanCreateThing always sets CanExecute to true. CreateThing currently does nothing.
I get this error on the usage of MyControl in the window XAML:
Type reference cannot find type named '{clr-namespace:MyNamespace;assembly=MyAssembly}Commands'.
And this one in the Command="..." attribute in the binding.
Invalid value for property 'Command': 'Microsoft.VisualStudio.DesignTools.Xaml.LanguageService.Semantics.XmlValue'
UPDATE
Mathew got rid of the errors, however, the menu items with those commands are still grayed out. Relevant code:
<TreeView ...>
<ContextMenu>
<MenuItem Command="{x:Static local:Commands.Create}"/>
</ContextMenu>
...
</TreeView>
MyControl.xaml.cs
//...
private void CanCreateThing(object sender, CanExecuteRoutedEventArgs e) {
e.CanExecute = true;
}
//...
With Mathew's help, a fix was found:
First, I had to replace all instances of local:Commands.Create with {x:Static local:Commands.Create}. However, the menu items would still be gray.
So in each menu item, I added a CommandTarget referencing the ancestor ContextMenu:
<MenuItem Command="{x:Static local:Commands.CreateTrigger}"
CommandTarget="{Binding PlacementTarget, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"/>
And that made the menu items clickable.

Command Binding with WPF

Im a complete Noob to this so im having a really hard time wrapping my head around how this works.
Basically I have a Main Page that im using, and within the XAML i have created a menu
What I have is a Document (DummyDoc) that contains a TextBox within it that i am trying to send the find command to.
Ive tried this every which way and googled it but i just cant seem to get it to work for me and could use some help with a push in the right direction
Main form
<Window>
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="clr-namespace:DMC_Robot_Editor"
xmlns:local="clr-namespace:DMC_Robot_Editor.GUI"
<Menu>
<MenuItem Header="_Edit">
<MenuItem Header="_Cut"/>
</MenuItem>
<MenuItem/>
<Grid>
<local:DummyDoc x:Name="_Editor"/>
</Grid>
</Window>
That is the main form that i am using. then i have my second document "DummyDoc"
<ad:DocumentContent x:Name="document" x:Class="DMC_Robot_Editor.Controls.DummyDoc"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ad="clr-namespace:AvalonDock;assembly=AvalonDock"
xmlns:local="clr-namespace:DMC_Robot_Editor.Controls"
xmlns:ed="schemas.microsoft.com/expression/2010/drawing"
Title="Window1" Height="300" Width="300"
IsVisibleChanged="Is_VisibleChanged" PropertyChanged="document_PropertyChanged">
<Grid>
<Menu >
<MenuItem Header="_File">
<MenuItem Header="was here"/>
</MenuItem>
</Menu>
<local:Editor x:Name="source" IsVisibleChanged="Is_VisibleChanged" TextChanged="TextChanged" UpdateFunctions="raiseupdated" />
<local:Editor x:Name="data" Visibility="Hidden" IsVisibleChanged="Is_VisibleChanged" TextChanged="TextChanged" UpdateFunctions="raiseupdated"/>
</Grid>
</ad:DocumentContent>
DummyDoc is a window that has an Inherited Editor in it.
<avalonedit:TextEditor
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:avalonedit="http://icsharpcode.net/sharpdevelop/avalonedit"
x:Class="DMC_Robot_Editor.Controls.Editor"
x:Name="editor"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="300"
TextChanged="Text_Changed"
IsVisibleChanged="raiseUpdate"
MouseMove="Mouse_Move"
MouseHover="Mouse_Hover"
MouseHoverStopped="Mouse_Hover_Stopped" KeyUp="editor_KeyUp">
</avalonedit:TextEditor>
My Ultimate Question is how do i use WPF Binding to make the "Cut" Action from the main form initiate the cut() method of the textbox?
I wrote textbox in it because in code behind, im doing the following
partial class DummyDoc:DocumentContent
{
public Editor TextBox{get;set;}
private void Is_VisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (sender is Editor)
this.TextBox = sender as Editor;
if ((VisibilityChanged != null) && (TextBox != null))
raiseupdated(TextBox, new FunctionEventArgs(this.TextBox.Text));
}
}
ElementName looks up elements by looking for an element which is using the string identifier you specify.
Did you put x:Name="local:TextBox" on your TextBox tag?
I think you've got your wires crossed by using "local:TextBox".
For starters...that is the syntax used to refer to an element type within a namespace .... it means "the type TextBox in the local namespace".....it's not valid (or rather doesn't mean the same) in the context you are using....you should just assign an "identifier" string.
So....
CommandTarget="{Binding ElementName=textboxFind}"
...
<TextBox x:Name="textboxFind" ..... />
would be more appropriate.
UPDATE (in light of question being clarified):
You should specify a "Command" in your menu item which will get raised when you choose that menu item.
Then if the TextEditor has the focus (...and thus is the command target...)....then it should see the Cut command.
I would expect the Avalon Editor to be able to handle the well know "ApplicationCommands" i.e. Cut, Copy, Paste, etc.
<MenuItem Header="_Cut" Command="ApplicationCommands.Cut">

How to handle TextBlock.KeyDown event, when TextBlock is a part of UserControl?

I have this simple UserControl:
<UserControl x:Class="WPFTreeViewEditing.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBlock Text="Hello, world!" KeyDown="TextBlock_KeyDown" />
</Grid>
</UserControl>
I want to handle TextBlock.KeyDown event. So, I've added an event handler to the code-behind:
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
private void TextBlock_KeyDown(object sender, KeyEventArgs e)
{
MessageBox.Show("Key up!");
}
}
but it doesn't fire. What's wrong?
UPDATE.
PreviewKeyDown doesn't fire too.
This UserControl is used in HierarchicalDataTemplate then:
<Window x:Class="WPFTreeViewEditing.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPFTreeViewEditing"
Title="MainWindow" Height="265" Width="419">
<Grid>
<TreeView ItemsSource="{Binding}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:ViewModel}" ItemsSource="{Binding Items}">
<local:UserControl1 />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</Grid>
</Window>
From the documentation for UIElement.KeyDown:
Occurs when a key is pressed while focus is on this element.
You're using TextBlock which doesn't have the focus, so your KeyDown event will be handled by another control.
You can switch to TextBox and appy some styles so it'll look and behave like TextBlock, but you'll be able to get the focus and handle the event.
You should use PreviewKeyDown event instead of KeyDown event.
Ok, even though this question was posted a long time ago I had the same problem and found a way to get KeyDown events working, though it might not be what you're looking for I'll post the code to help future people with the same problem.
First thing first a KeyDown event handler in an Xaml Object will only fire off if that object has focus. Therefore you need a CoreWindow event handler, it's kind off the same thing but it will always run no matter what object or thing has focus. The following will be the code.
//CLASS.xaml.h
ref class CLASS{
private:
Platform::Agile<Windows::UI::Core::CoreWindow> window;
public:
void KeyPressed(Windows::UI::Core::CoreWindow^ Window, Windows::UI::Core::KeyEventArgs^ Args);
//CLASS.xaml.cpp
CLASS::CLASS(){
InitializeComponent();
window = Window::Current->CoreWindow;
window->KeyDown += ref new TypedEventHandler
<Windows::UI::Core::CoreWindow^, Windows::UI::Core::KeyEventArgs^>(this, &CLASS::KeyPressed);
};
void CLASS::KeyPressed(Windows::UI::Core::CoreWindow^ Window, Windows::UI::Core::KeyEventArgs^ Args){
SimpleTextBox->Text = Args->VirtualKey.ToString();
};
Basically you want a value to hold your window and use that to create a new TypedEventHandler. For safety you'll generally want to do this in your class' constructor a function that's only called once the moment the class starts (I still prefer the constructor though).
You can use this method to create an event handler for any event. Just change the "KeyDown" for another attribute like KeyUp, PointerMoved, PointerPressed and change the "&CLASS::KeyPressed" to the name of the function you want to be fired the moment you get an event of a corresponding type.

View is not being resolved with Toolbar ItemsSource

It doesn't seem like the Caliburn Micro framework is retrieving my SinglePaintToolbarView when it is binded as a list of buttons in the toolbar of the ShellView. I would like the buttons to just display their text content when they are added to the toolbar. But, instead I'm getting this:
There doesn't appear to be any clickable buttons in the toolbar. I know my plugins are being loaded successfully, because I was able to bind one of the plugins in the list as a ContentControl and the view appeared. It just doesn't seem to work when I try to bind a list of the plugins in a toolbar.
Here is what I have:
ShellView.xaml
<UserControl x:Class="Starbolt.Views.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<ToolBarTray>
<ToolBar ItemsSource="{Binding Path=ToolbarPlugins}"/>
</ToolBarTray>
</Grid>
</UserControl>
ShellViewModel.cs
[Export(typeof(IShell))]
public class ShellViewModel : PropertyChangedBase, IShell
{
[ImportMany(typeof(IToolbarPlugin))]
private IEnumerable<IToolbarPlugin> _toolbarPlugins = null;
public IEnumerable<IToolbarPlugin> ToolbarPlugins { get { return _toolbarPlugins; } }
}
SinglePaintToolbarView.xaml
<UserControl x:Class="Starbolt.Plugin.SinglePaintTool.Views.SinglePaintToolView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="128" d:DesignWidth="32">
<Button Name="btnSinglePaintTool" Content="Single Paint Tool" Width="128" Height="32"/>
</UserControl>
SinglePaintToolViewModel.cs
[Export(typeof(IToolbarPlugin))]
public class SinglePaintToolViewModel : IToolbarPlugin
{
}
Basically, your design seems to be working. If you replace
<ToolBarTray>
<ToolBar x:Name="ToolbarPlugins"/>
</ToolBarTray>
(note that you do not need to bind the ItemsSource explicitly, you can just as well use the Caliburn Micro property name conventions) with the following:
<ListBox x:Name="ToolbarPlugins"/>
the SinglePaintToolView button is displayed as intended.
I suspect that the problem is with the ToolBar ControlTemplate, which most certainly restricts the toolbar items layout more than what for example a ListBox ControlTemplate does.
So my guess is that if you really want to use the ToolBar control to display your IToolbarPlugin views, you will probably have to design a dedicated ToolBar control template in your project.
Alternatively, you could implement a toolbar replacement using e.g. ListBox. This could be a start:
<ListBox x:Name="ToolbarPlugins">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>

ContextMenu shows less than 1 second

I would like to control when the contextmenu of my control to show or not.
here is my code:
void MyControl_MouseRightButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if ( some condition .....)
{
this.Focus();
contextmeun.PlacementTarget = this;
contextmeun.IsOpen = true;
}
}
However, it just show up less than 1 second then disappear immediately. Why is that?
Thank you for all your help!
Probably because you're focussing the control that the context menu belongs to, then showing the context menu, however when the parent control gets focus, the context menu closes.
Try setting the context menu in Xaml instead to get the correct behaviour
<MyControl>
<MyControl.ContextMenu>
<ContextMenu>
<!-- Define context menu here -->
</ContextMenu>
</MyControl.ContextMenu>
</MyControl>
This can be done in pure XAML form, all you need to do is bind your visibility of context menu with a bool property containing your condition like this -
<YourControl>
<YourControl.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</YourControl.Resources>
<YourControl.ContextMenu>
<ContextMenu Visibility="{Binding IsEnable,
Converter={StaticResource BooleanToVisibilityConverter}}">
<MenuItem Header="MenuItem1"/>
<MenuItem Header="MenuItem2"/>
<MenuItem Header="MenuItem3"/>
</ContextMenu>
</YourControl.ContextMenu>
</YourControl>
Here IsEnable is a plain CLR property, in its getter you can have the logic for your condition depending on which you need to toggle the visibility of your context menu..

Categories