How to expand sub menus from code behind - c#

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

Related

WPF sub menuitem do not show up in the XAML Editor

I created a Menu and inserted a MenuItem. For this MenuItem, I created 3 sub MenuItems.
When I select a sub MenuItem, it does not show up in the XAML editor.
<Window x:Class="CharakterTool3.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:CharakterTool3"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel>
<Menu>
<MenuItem Header="File">
<MenuItem Header="Open"/>
<MenuItem Header="Close"/>
<MenuItem Header="Exit"/>
</MenuItem>
</Menu>
</StackPanel>
</Window>
This is what I expected:
In my Editor, it do not show up:
During the runtime, it is selectable:
I installed Microsoft Visual Studio Community 2019 Version 16.9.3
with Microsoft .NET Framework Version 4.8.04084.
What is the source of the problem?
This question already has answers here:
How do I open a WPF menu at design-time?
You can set IsSubmenuOpen="True", but if you click somewhere else, menu will close. Set it again to open.

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

Shortcuts not working in UserControl

I have a UserControl, which has CommandBindings:
<UserControl x:Class="DbCreator.UserControls.Menu"
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:DbCreator"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.CommandBindings>
<CommandBinding Command="SelectAll" Executed="SelectAllCommand_Executed"/>
<CommandBinding Command="local:MyCommands.ToggleNavigator" Executed="ToggleNavigatorCommand_Executed"/>
<CommandBinding Command="local:MyCommands.ToggleGrid" Executed="ToggleGridCommand_Executed"/>
<CommandBinding Command="local:MyCommands.SelectNone" Executed="SelectNoneCommand_Executed"/>
</UserControl.CommandBindings>
<Menu Background="White">
<MenuItem Header="_Файл"/>
<MenuItem Header="_Правка">
<MenuItem Header="Выделить всё" Command="SelectAll"/>
<MenuItem Header="Убрать выделение" Command="local:MyCommands.SelectNone"/>
</MenuItem>
<MenuItem Header="_Вид">
<MenuItem x:Name="ToggleNavigator" Header="Скрыть навигатор" Command="local:MyCommands.ToggleNavigator"/>
<MenuItem x:Name="ToggleGrid" Header="Скрыть сетку" Command="local:MyCommands.ToggleGrid"/>
</MenuItem>
<MenuItem Header="_Диаграмма"/>
<MenuItem Header="_Справка"/>
</Menu>
When I click MenuItem, my Executed method is fired, but shortcuts not working. When this UserControl content was a part of MainWindow code, all shortcuts worked perfect. But I want to define menu in a separate file.
So the problem was that Menu was unfocused and didn't recieve input. Solution is to add InputBindings to MainWindow:
<Window.InputBindings>
<KeyBinding Command="SelectAll" CommandTarget="{Binding ElementName=Menu}" Key="A" Modifiers="Control"/>
<KeyBinding Command="local:MyCommands.SelectNone" CommandTarget="{Binding ElementName=Menu}" Key="A" Modifiers="Control+Shift"/>
<KeyBinding Command="local:MyCommands.ToggleNavigator" CommandTarget="{Binding ElementName=Menu}" Key="N" Modifiers="Alt"/>
<KeyBinding Command="local:MyCommands.ToggleGrid" CommandTarget="{Binding ElementName=Menu}" Key="G" Modifiers="Alt"/>
</Window.InputBindings>

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.

MenuItem in Window, CommandBinding in UserControl

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!

Categories

Resources