MenuItem in Window, CommandBinding in UserControl - c#

I have a window:
<Window x:Class="SomeNamespace.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="350" Width="525">
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Copy"
CanExecute="CommandCanExecute" Executed="CommandExecuted"/>
</Window.CommandBindings>
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="File">
<MenuItem Command="ApplicationCommands.Copy"/>
</MenuItem>
</Menu>
</DockPanel>
</Window>
With some code behind:
void CommandCanExecute(object sender, CanExecuteRoutedEventArgs e) {
e.CanExecute = true;
}
void CommandExecuted(object sender, EventArgs e) {
MessageBox.Show("Done!");
}
And everything works the way I expect. I can use the MenuItem or the Ctrl+C input binding to run my command.
But now my class has gotten too big, and I decide to refactor. So I moved my code behind to a user control. Here's my new Window:
<Window x:Class="SomeNamespace.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:SomeNamespace"
Height="350" Width="525">
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="File">
<MenuItem Command="ApplicationCommands.Copy"/>
</MenuItem>
</Menu>
<my:UserControl1/>
</DockPanel>
</Window>
And my UserControl:
<UserControl x:Class="ImageDecompileSandbox.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<UserControl.CommandBindings>
<CommandBinding Command="ApplicationCommands.Copy"
CanExecute="CommandCanExecute" Executed="CommandExecuted"/>
</UserControl.CommandBindings>
</UserControl>
Basically, everything is the same, except the CommandBinding was moved from the window to UserControl and the two command methods were pushed down to the user control.
Question: Why does the above not work? Why is my User Control's command not picked up by the window? How do I get the MenuItem / KeyBindings from the window to work with the command execution in the User Control?

Thanks to dkozl, I was able to find a way to make this work.
The trick was indeed adding the CommandBinding back to the Window. Instead of declaring them all in the Window, which I can't do as the window doesn't know about the methods being used for Executed and CanExecute, I just added all the bindings from the control to the window:
CommandBindings.AddRange(_userControl1.CommandBindings);
I find this one-line hack to be exactly what I need, as it lets me keep the command controls and keybindings in the window while moving the command implementation to the control.
Thanks for the help dkozl!

Related

How to expand sub menus from code behind

This is XAML codes.
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Menu x:Name="Menu1">
<MenuItem x:Name="MenuItem1" Height="40" Header="File">
<MenuItem x:Name="MenuItem11" Header="Help"/>
<MenuItem x:Name="MenuItem12" Header="About"/>
<MenuItem x:Name="MenuItem13" Header="Exit"/>
</MenuItem>
</Menu>
</Grid>
</Window>
How to expand sub menus from code behind?
Question picture: https://prnt.sc/img9p6
You will need a MenuViewModel class that has a ObservalbleCollection of SubMenuViewModel(s).
This class will need a property for the IsOpen for The Menu. https://msdn.microsoft.com/en-us/library/system.windows.controls.menuitem.issubmenuopen(v=vs.110).aspx
You should not create the MenuItems in WPF. But bind the Itemssource to thies collection.
You then bind the IsOpen from the MenuItem to the MenuItems in WPF (ItemsTemplate)
WPF - How can I create menu and submenus using binding

Can't fire commands in window with frame

I have a WPF window with a grid containing 4 rectangles. One of these has a <frame> for showing pages, which is used in my application. I want to add commands to these buttons on my windows as well as to the pages.
Things I use: MVVM, Window as Mainwindow and Pages as contentpublisher in a frame.
For example, I want to login applicationwide with a button and command. While doing this on the page in my frame there are no errors, but I can't do the same in the window.
I was wondering if the windows lose focus so it can't fire that event while navigating to a page in the frame. So I tried to get the window with following command binding:
<Button Content="" FontFamily="Segoe UI Symbol"
Command="{Binding CommandWhichDoesNotFire, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type vw:MainViewModel}}}"
Width="32">
In my ViewModel "MainViewModel" I have a public ICommand Property and I initialize it at the constructor:
public ICommand CommandWhichDoesNotFire;
public MainViewModel()
{
MessageBox.Show("VM is real");
CommandWhichDoesNotFire= new TestCommand();
}
The DataContext of my MainView is set in the behind-code BEFORE InitilizeComponents();
Clicking on the button does not start ANY call of my command. It simply does not fire at all. What am I missing guys?
You should have :
public ICommand CommandWhichDoesNotFire{get;set;}
public MainViewModel()
{
MessageBox.Show("VM is real");
CommandWhichDoesNotFire= new TestCommand(MyCommand);
}
private void MyCommand(object obj){
//Whatever you want to do
}
I think I have found a solution to your problem.
The Frame for some reason doesn't inherit the DataContext of it's parent, even if you set the DataContext explicitly like so:
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}"
it still doesn't work. What it does it only sets the DataContext of the frame but not child elements:
And here is the DataContext of a child element:
Now this made me think that whatever we have always loved about WPF and it's controls was the ability to inherit the DataContext from it's parent, with exception of ContextMenu and now the Frame. Here is the approach I took when I had a frist look at oyur problem:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainVM/>
</Window.DataContext>
<Window.Resources>
<!--<local:MainVM x:Key="mainVM"/>-->
<local:LoginPage x:Key="login" />
<!--DataContext="{StaticResource mainVM}"-->
<ControlTemplate x:Key="ctrlTmpl">
<local:LoginPage/>
</ControlTemplate>
</Window.Resources>
<Grid>
<!--<Button x:Name="button" Content="Do something" Click="btnDoSomething" HorizontalAlignment="Left" Margin="221,60,0,0" VerticalAlignment="Top" Width="75"/>-->
<!--<Control Template="{StaticResource ctrlTmpl}"/> This works-->
<Frame Content="{StaticResource login}" DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}" />
</Grid>
Then I thought you can do this another way:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<!--<Window.DataContext>
<local:MainVM/>
</Window.DataContext>-->
<Window.Resources>
<local:MainVM x:Key="mainVM"/>
<local:LoginPage x:Key="login" DataContext="{StaticResource mainVM}"/>
<!---->
<ControlTemplate x:Key="ctrlTmpl">
<local:LoginPage/>
</ControlTemplate>
</Window.Resources>
<Grid>
<!--<Button x:Name="button" Content="Do something" Click="btnDoSomething" HorizontalAlignment="Left" Margin="221,60,0,0" VerticalAlignment="Top" Width="75"/>-->
<!--<Control Template="{StaticResource ctrlTmpl}"/> This works-->
<Frame Content="{StaticResource login}"/>
</Grid>
Notice how I included the VM in the resources and then used that instance to be the Controls DataContext. At this point when I click the button in my LoginPage.xaml which by the way is a UserControl it triggers the Command located in my MainVM. At this point you would have to assign the Window DataContext in the code behind like so:
public MainWindow()
{
InitializeComponent();
var vm = this.TryFindResource("mainVM");
if(vm != null)
{
this.DataContext = vm;
}
}
Now at this point you can use some sort of triggers to navigate through pages and use different Pages or UserControls. HTH
P.S. When I get a chance I will update some information about Context Menu and Frame from MSDN. Happy Coding

Why DataBinding is not propagating to UserControl

This morning I asked a question here and doing a simple working sample gave me a different behavior than expected.
Full working sample at GitHub. Main partial code below.
In this present case, the command is never propagated to any UserControl, either if the UserControl is use directly as a child of the Window. It also not work if the UserControl is used as a DataTemplate for a ListBox ItemTemplate.
I also include a hack button to fix the problem where the Command reach the UserControls. The hack come from StackOverflow.
But using the hack does not explain why UserControl does not receive the Command (without it) and using this hack also break the first rule of good coding: "Hi cohesion and Low coupling". The hack should be used in the the window code in order for it to manage the Command in the UserControl, my thought is that it should happen by default.
Why the command is not propagating by default to the UserControl and what should I do to propagate the command to the UserControl in a clean way?
Note: Using only one CommandBinding (removing one or the other) in the UserControl does not fix the problem .
Partial code:
<Window x:Class="CommandRoutingIntoItemTemplate.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:CommandRoutingIntoItemTemplate"
xmlns:system="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<local:UserControlTest></local:UserControlTest>
<ListBox Grid.Row="1">
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Aqua" BorderThickness="2">
<local:UserControlTest></local:UserControlTest>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.Items>
<system:String>1</system:String>
<system:String>2</system:String>
</ListBox.Items>
</ListBox>
<StackPanel Grid.Row="2" Orientation="Horizontal">
<Button Command="local:Commands.CommandTest">Put focus on TestBlock and click here to see if command occurs</Button>
<Button Click="AddHack">Hack</Button>
</StackPanel>
</Grid>
</Window>
UserControl:
<UserControl x:Class="CommandRoutingIntoItemTemplate.UserControlTest"
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:local="clr-namespace:CommandRoutingIntoItemTemplate"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.CommandBindings>
<CommandBinding Command="local:Commands.CommandTest" CanExecute="CommandTestCanExecuteUserControl" Executed="CommandTestExecuteUserControl"></CommandBinding>
</UserControl.CommandBindings>
<Grid>
<TextBox Text="UserControlTest">
<TextBox.CommandBindings>
<CommandBinding Command="local:Commands.CommandTest" CanExecute="CommandTestCanExecuteTextBox" Executed="CommandTestExecuteTextBox"></CommandBinding>
</TextBox.CommandBindings>
</TextBox>
</Grid>
</UserControl>
The reason that you are not getting commands invoked on your user control is that your buttons are not in separate focus scope. For WPF to pick up focused element for command target correctly, it needs to be in separate focus scope from command invoking control.
Framework will just traverse up visual tree from button looking for command bindings in their focus scope (in your case it won't find any). When framework does not find any command bindings in current focus scope, only then it looks into parent focus scope for focused element (In your case, buttons are in Window focus scope which has no parent scope so the search will end there).
Simply setting FocusManager.IsFocusScope="True" on your StackPanel will fix the issue.
You could also specify CommandTarget property on your buttons to point to your user control and not rely on focus.

WPF KeyBinding on Page in Frame (in TabControl)

I have a WPF Window containing a TabControl, which has Frames in it which display Pages. Like this:
<Window x:Class="MyNamespace.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow">
<DockPanel>
<TabControl x:Name="TabControl">
<TabItem Header="StartScreen" x:Name="StartScreenTab">
<Frame Source="StartScreenPage.xaml"/>
</TabItem>
<TabItem Header="OtherTab" x:Name="OtherTab">
<Frame Source="OtherPage.xaml"/>
</TabItem>
</TabControl>
</DockPanel>
</Window>
In one of the pages, I have a KeyBinding:
<Page x:Class="MyNamespace.View.OtherPage"
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"
Title="OtherPage">
<Page.InputBindings>
<KeyBinding Key="U" Modifiers="Control" Command="{Binding MyCommand}"/>
</Page.InputBindings>
<!-- Content ... -->
</Page>
MyCommand is a property of OtherPage.DataContext, it's only available from the page, not available from outside.
My problem is that the KeyBinding only works after I click on a control inside the Page. I want that KeyBinding to work whenever OtherPage is visible, equivalently when OtherTab is the active tab. How can I achieve this?
The only way that you can achieve that is to move your KeyBinding and the ICommand implementation to the MainWindow.xaml file:
<Window.InputBindings>
<KeyBinding Key="U" Modifiers="Control" Command="{Binding MyCommand}"/>
</Window.InputBindings>
If you can't move the actual implementation of the ICommand, then you must at least make it accessible from there.
I did something similar to what Sheridan recommended in his answer.
In MainWindow.xaml I declare a RoutedUICommand:
<Window x:Class="MyNamespace.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow">
<Window.Resources>
<ResourceDictionary>
<RoutedUICommand x:Key="OtherPageCommand"/>
</ResourceDictionary>
</Window.Resources>
<Window.CommandBindings>
<CommandBinding Command="{StaticResource OtherPageCommand}"
Executed="OtherPageCommandExecuted"/>
</Window.CommandBindings>
<Window.InputBindings>
<KeyBinding Key="U" Modifiers="Control" Command="{StaticResource OtherPageCommand}"/>
</Window.InputBindings>
<DockPanel>
<TabControl x:Name="TabControl">
<TabItem Header="StartScreen" x:Name="StartScreenTab">
<Frame Source="StartScreenPage.xaml"/>
</TabItem>
<TabItem Header="OtherTab" x:Name="OtherTab">
<Frame Source="OtherPage.xaml"/>
</TabItem>
</TabControl>
</DockPanel>
</Window>
In MainWindow.xaml.cs I have this method:
private void OtherPageCommandExecuted(object sender, ExecutedRoutedEventArgs e)
{
var page = ((Frame)((TabItem) TabControl.SelectedItem).Content).Content as OtherPage;
if (page != null)
{
var viewModel = page.DataContext as MyViewModel;
if (viewModel != null)
{
var openWindow = viewModel.MyCommand;
var parameter = null; // put your command parameter here
if (openWindow.CanExecute(parameter))
openWindow.Execute(parameter);
}
}
}
This way I propagate the keybinding to the page but the actual command remains in the viewmodel of the page.

WPF menu closes on mouseup when window open from excel add-in project

I have a problem when creating a menu in WPF. What happens is that it closes automatically when you stop pressing the mouse button. I want it to behave as regular menu's where you can click and the subitems will stay up but I can't find anyway to get this done.
The code looks like this:
<Window x:Class="ExcelAddIn.MyWindow"
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>
<DockPanel>
<Menu Width="Auto" IsMainMenu="True" >
<MenuItem Header="Item">
<MenuItem Header="SubItem" />
</MenuItem>
</Menu>
</DockPanel>
</Grid>
</Window>
I'm wondering if it has anything to do with logical focus maybe? I saw something about it might being a bug in .NET framwork? Any ideas?
Thanks in advance
I didn't think it made any difference at first but obviously it does. When running the code in a standalone WPF application it works, however when I try to open the WPF window from a Excel-addin project I get this problem..
Ok! I solved the problem. Turns out it was a focus problem after all.
When the excel addin executed the WPF window the excel window was still in focus. So on every mouseup the focus would jump back from WPF to excel.
All I had to do was change the execution from this:
MainWindow mainWindow = new MainWindow();
mainWindow.Activate();
mainWindow.Show();
to this:
MainWindow mainWindow = new MainWindow();
mainWindow.Activate();
mainWindow.ShowDialog();
Thanks for help anyway guys!
I adjusted your code a tiny bit (DockPanel.Dock="Top" and an extra grid to fill the rest of the dock panel). It works fine and the menu stays open. Does it work for you?:
<Window x:Class="WpfApplication2.Window3"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window3" Height="300" Width="300">
<Grid>
<DockPanel>
<Menu Width="Auto"
IsMainMenu="True" DockPanel.Dock="Top">
<MenuItem Header="Item">
<MenuItem Header="SubItem" />
</MenuItem>
</Menu>
<Grid />
</DockPanel>
</Grid>
</Window>

Categories

Resources