MenuItem within MenuItem not using all available space - wpf

I have a menuitem that contains a datatemplate of a menuitem. Problem is the menuitem within that is not taking up all the available space on the right. Is there any way I can fix this?
<MenuItem Header="Test" ItemsSource="{Binding DataContext.Test, Source={x:Reference TestControl}}"
Command="{Binding DataContext.Test_Click, Source={x:Reference TestControl}}">
<MenuItem.ItemTemplate>
<DataTemplate>
<MenuItem Header="{Binding TestName}"
Command="{Binding DataContext.Test_Click, Source={x:Reference TestControl}}"
CommandParameter="{Binding TestId}">
<MenuItem.Icon>
<Ellipse Fill="{Binding TestId, Converter={StaticResource TestConverter}}"/>
</MenuItem.Icon>
</MenuItem>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>

What you are observing is the built-in column spacing of the default MenuItem's ControlTemplate.
Highlighting the culprit below:
These column definitions are used to show any keyboard shortcuts that you may have for the menu item, as well as some hardcoded column padding of 13 (why? I have no idea).
So to answer your question, if you want to take up the available space on the right, you will need to override the MenuItem's Template with a ControlTemplate of your own that does not include these last two column definitions.

Related

TextBlock inside MenuItem does not became gray if MenuItem is Disabled

If I use this code:
<MenuItem x:Name="MenuSave" Header="Save" IsEnabled="False"/>
when MenuSave is disabled (in real code by a RoutedUICommand programmatically), the Header is disabled and text is gray.
But if I need more text like here:
<MenuItem x:Name="MenuSaveAs" IsEnabled="False">
<MenuItem.Header >
<StackPanel Orientation="Horizontal">
<TextBlock Text="Save as"/>
<TextBlock> ...</TextBlock>
</StackPanel>
</MenuItem.Header>
</MenuItem>
In this case, the Header is disabled but text is not gray.
How can I obtain text gray with composite text?
This is just simplified code to explain the problem, the real code is combination of translated terms.
If you add the TextBlock thorough a HeaderTemplate, the color will be applied for the disabled state. By the way, you can use multiple Runs instead, so the same TextBlock is populated. If you bind a data item as Header, you can bind its properties in the template to the Runs.
<MenuItem x:Name="MenuSaveAs" IsEnabled="False">
<MenuItem.HeaderTemplate>
<DataTemplate>
<TextBlock>
<Run Text="Save as"/>
<Run> ...</Run>
</TextBlock>
</DataTemplate>
</MenuItem.HeaderTemplate>
</MenuItem>
Alternatively, if you need to format a string with a bound property, use HeaderStringFormat.
<MenuItem x:Name="MenuSaveAs"
IsEnabled="False"
Header="{Binding NameOfTheSavedItem}"
HeaderStringFormat="Save as {0}...">
If you really insist on setting the header directly, a workaround would be to bind the Foreground of TextBlock explicitly to the TextElement.Foreground of the ContentPresenter in the MenuItem control template. You can bind it on each TextBlock or add an implicit style that applies to all TextBlocks in scope automatically. Please note the word all.
<Menu>
<Menu.Resources>
<Style TargetType="TextBlock" BasedOn="{StaticResource {x:Type TextBlock}}">
<Setter Property="Foreground" Value="{Binding (TextElement.Foreground), RelativeSource={RelativeSource AncestorType=ContentPresenter}}"/>
</Style>
</Menu.Resources>
<MenuItem x:Name="MenuSaveAs"
IsEnabled="True">
<MenuItem.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Padding="0" Text="Save as"/>
<TextBlock Padding="0" Text="..."/>
</StackPanel>
</MenuItem.Header>
</MenuItem>
</Menu>
Please be aware that I suggest to use a single TextBlock with Runs for sentences or paragraphs in general, because panels with multiple TextBlocks result in incorrect spacing and alignment that do not match the typesetting that TextBlock and other document related types provide. It usually looks odd and disjointed and does not take into account the characteristics of a font.
I found an other easy way using <Label Padding="0"..>, instead af <TextBlock ..>:
<MenuItem x:Name="MenuSaveAs" IsEnabled="False">
<MenuItem.Header >
<StackPanel Orientation="Horizontal">
<Label Padding="0" Content="Save as"/>
<Label Padding="0" Content="..."/>
</StackPanel>
</MenuItem.Header>

MenuItem Icon not clickable? WPF

I have a menuitem that contains a sub menu of menuitems. For some reason, to set off the command i have to click the actual menuitem and not the menuitem icon as well. i.e. clicking the menuitem icon doesn't trigger the command. Here is my XAML. Is there any easy fix for this as i want to be able to click both the text and the icon for the command to run.
<MenuItem Header="Choose Shoe Colour"
ItemsSource="{Binding DataContext.ShoeModels, Source={x:Reference sItemControl}}"
Command="{Binding DataContext.ChooseShoe_Click, Source={x:Reference sItemControl}}">
<MenuItem.ItemTemplate>
<DataTemplate>
<MenuItem Header="{Binding ShoeColour}"
Command="{Binding DataContext.ChooseShoe_Click, Source={x:Reference sItemControl}}"
CommandParameter="{Binding ShoeId}">
<MenuItem.Icon>
<Ellipse Fill="{Binding ShoeId, Converter={StaticResource ShoeIdToColorConverter}}"/>
</MenuItem.Icon>
</MenuItem>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>

Binding To the Parent of A Context Menu Which is A Templated Grid

In a WPF .Net 4 application have a master detail situation where a datagrid has rows which can have the detail information as found in the RowDetailsTemplate which has an internal datagrid.
Within the RowDetailsTemplate is a grid to hold the sub details which has a context menu. The problem found is when binding the CommandTarget of one of the details' MenuItem, I am unable to target that details datagrid as generated by the template. The below binding ends up getting the Master datagrid and not the containing datagrid which is holding the details information/contextmenu.
<DataGrid x:Name="dgEditScript" ItemsSource="{Binding CurrentScript}">
<DataGrid.CommandBindings>
<CommandBinding Command="commands:ScriptingCommands.SetChecked"
Executed="CheckAllAfter" />
</DataGrid.CommandBindings>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<DataGrid ItemsSource="{Binding SubCommands}">
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="Check All From Selected"
Command="commands:ScriptingCommands.SetChecked"
CommandTarget="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}">
<MenuItem.Icon>
<Image Source="Images/checkboxes.png" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
The problem when using the above Realtive source binding, it gives me the dgEditScript grid (top level) and not the parent of the context menu, the unnamed holding DataGrid which the context menu was launched from.
How do I get the sub grid in the binding; to target the parent of the context menu?
If I'm understanding your question right, you have a collections of items, and you want the context menu to be attached to the selected item (when you're right clicking on it ...)
Here's some similar code I'm using:
<ListBox x:Name="name_here"
ItemsSource="{Binding source_collection_name}"
SelectedItem="{Binding property_name_on_VM, UpdateSourceTrigger=PropertyChanged}"
>
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Header ="Edit Item" Command="{Binding EditItem_Command}"
CommandParameter="{Binding property_name_on_VM}"
/>
<MenuItem Header ="Delete Item" Command="{Binding DeleteItem_Command}"
CommandParameter="{Binding property_name_on_VM}"
/>
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
This way, whenever you click on an item (or right click), it's selected, and then you just send that item as a command parameter, so you have the item you need.
Hope this helps.

WPF treeview contextmenu command parameter

I have TreeView with HierarchicalDataTemplate. On TreeView I have ContextMenu
<TreeView Name="_packageTreeView" ItemsSource="{Binding PackageExtendedList}" Behaviors:TreeViewInPlaceEditBehavior.IsEditable="True">
<TreeView.ContextMenu>
<ContextMenu StaysOpen="true">
<MenuItem Header="Добавить пакет" Height="20" Command="{Binding AddPackageCommand}"
CommandParameter="{Binding ElementName=_packageTreeView, Path=SelectedItem}">
<MenuItem.Icon>
<Image Source="/Resources/ManualAdd.png" Width="15" Height="15"></Image>
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</TreeView.ContextMenu>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Childs}">bla bla bla</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
As you can see, I bind Command to menu item. AddPackageCommand defined in ViewModell class as usually. Invoke command works fine, but I always have null in CommandParameter. I found some questions similar to my, but I don't understand solutions. For example:
CommandParameters in ContextMenu in WPF
Anyway it doesn't work for me :( What am I doing wrong?
Updated
That seems to be working, but it's all the same, I don't understand why CommandParameter doesn't work with TreeView.Name.
CommandParameter="{Binding PlacementTarget, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"
for examplle, such a sample works fine
<i:EventTrigger EventName="SelectedItemChanged">
<i:InvokeCommandAction Command="{Binding PackageTreeItemChangeCommand}" CommandParameter="{Binding ElementName=_packageTreeView, Path=SelectedItem}"/>
</i:EventTrigger>
What's the hell...
And anyway, I have TreeView object in CommandParameter, not TreeViewItem. I can get SelectedItem from TreeView, but how can I send exactly TreeViewItem as CommandParameter?
to Sheridan
Question was WHY this doesn't work.
CommandParameter="{Binding ElementName=_packageTreeView, Path=SelectedItem}"
And this works
CommandParameter="{Binding PlacementTarget, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"
WHY sometimes I can use direct TreeView control name and sometimes I cannot.
As I understand, matter is different DataContext of TreeView control and ContextMenu because ContextMenu has its own VisualTree and it is not the part of TreeView ViaualTree.
Unfortunately, that approach doesn't work too, I have null again. I set TreeView.Tag, sure.
<ContextMenu DataContext="{Binding PlacementTarget.Tag, RelativeSource={
RelativeSource Self}}" StaysOpen="true">
<MenuItem Header="Добавить пакет" Height="20" Command="{Binding AddPackageCommand}"
CommandParameter="{Binding ElementName=_packageTreeView, Path=SelectedItem}">
<MenuItem.Icon>
<Image Source="/Resources/ManualAdd.png" Width="15" Height="15"></Image>
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
This is the easiest way, but if I have SelectedItem property in ViewModel it has no sense bind it to CommandParameter, because I already have it in ViewModel.
<MenuItem Header="Добавить пакет" Height="20" Command="{Binding AddPackageCommand}"
CommandParameter="{Binding SelectedItem}">
<MenuItem.Icon>
<Image Source="/Resources/ManualAdd.png" Width="15" Height="15"></Image>
</MenuItem.Icon>
</MenuItem>
You showed us that you already have an answer... why on earth did you post yet another question on this same subject instead of simply following the example in the answer? It doesn't work for you, because you didn't copy the answer properly.
In your example post answer, the Tag property is set to the TreeView control that the menu is applied on, but you haven't done this.
Your next problem is that you have ignored this Tag property again in the CommandParameter... somehow, you have changed this from the correct answer:
CommandParameter="{Binding PlacementTarget.Tag, RelativeSource={RelativeSource
FindAncestor, AncestorType={x:Type ContextMenu}}}
to this in your question:
CommandParameter="{Binding PlacementTarget, RelativeSource={RelativeSource
FindAncestor, AncestorType={x:Type ContextMenu}}}"
All you needed to do was copy and paste it. All the same, you might have even more luck doing this:
<TreeView Tag="{Binding DataContext, RelativeSource={RelativeSource Self}}"
Name="_packageTreeView" ItemsSource="{Binding PackageExtendedList}"
Behaviors:TreeViewInPlaceEditBehavior.IsEditable="True">
<TreeView.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.Tag, RelativeSource={
RelativeSource Self}}" StaysOpen="true">
<MenuItem Header="Добавить пакет" Height="20" Command="{Binding AddPackageCommand}"
CommandParameter="{Binding ElementName=_packageTreeView, Path=SelectedItem}">
<MenuItem.Icon>
<Image Source="/Resources/ManualAdd.png" Width="15" Height="15"></Image>
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</TreeView.ContextMenu>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Childs}">bla bla bla</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Look at the TreeView.Tag property... this is set to its own DataContext, which means that whatever is set as the DataContext of the TreeView is now available in the Tag property object.
Next, look at the ContextMenu.DataContext property... this is set to the Tag property of the PlacementTarget, which is the control that the ContextMenu is applied to, or in this case, the Treeview.
If you haven't worked it out yet, this means that the DataContext of the ContextMenu is now set to the same object as the DataContext of the TreeView. If this is not what you want because your Command is on a different object, then just change the Binding path in the Tag property to point to wherever the object that had the Command is.
The last thing that you can do to make this simpler is to add a property to your view model/code behind that binds to the TreeView.SelectedItem property:
<TreeView SelectedItem="{Binding SelectedItem}"... />
Then you can simply refer to this property for your CommandParameter:
<MenuItem Header="Добавить пакет" Height="20" Command="{Binding AddPackageCommand}"
CommandParameter="{Binding SelectedItem}">
<MenuItem.Icon>
<Image Source="/Resources/ManualAdd.png" Width="15" Height="15"></Image>
</MenuItem.Icon>
</MenuItem>
This last part of course assumes that you have set your view model/code behind as the Tag property of the TreeView. If you still don't understand this, take a look at the Context Menus in WPF page on WPF Tutorial.NET.
UPDATE >>>
I simply don't understand why you posted this question. First you said you couldn't do something, but then provided us with a link to a valid solution in another post. After trying to help you, you then say that it did work, but you don't know why... but then you correctly answered your own question again:
As I understand, matter is different DataContext of TreeView control and ContextMenu because ContextMenu has its own VisualTree and it is not the part of TreeView ViaualTree.
As you said, the ContextMenu has its own visual tree. This means that it is not aware of controls, named or otherwise, in another visual tree. However, if the ContextMenu.DataContext is provided with an object such as the containing view, then it can be aware of controls in another visual tree (more specifically, the visual tree of the controls in the view).
This whole issue seems to be down to a lack of knowledge on your part about Binding in general and Binding.Path syntax more specifically. Please take a look at the following articles on MSDN for more help on this topic:
Binding.Path Property
Property Path Syntax
RelativeSource MarkupExtension
So many people try to run with WPF before they can walk.

Binding a TreeView with ContextMenu in Xaml

I'm pretty new to Xaml and need some advise.
A TreeView should be bound to a hierarchical object structure. The TreeView should have a context menu, which is specific for each object type.
I've tried the following:
<TreeView>
<TreeView.Resources>
<DataTemplate x:Key="RoomTemplate">
<TreeViewItem Header="{Binding Name}">
<TreeViewItem.ContextMenu>
<ContextMenu>
<MenuItem Header="Open" />
<MenuItem Header="Remove" />
</ContextMenu>
</TreeViewItem.ContextMenu>
</TreeViewItem>
</DataTemplate>
</TreeView.Resources>
<TreeViewItem Header="{Binding Name}" Name="tviRoot" IsExpanded="True" >
<TreeViewItem Header="Rooms"
ItemsSource="{Binding Rooms}"
ItemTemplate="{StaticResource RoomTemplate}">
<TreeViewItem.ContextMenu>
<ContextMenu>
<MenuItem Header="Add room"></MenuItem>
</ContextMenu>
</TreeViewItem.ContextMenu>
</TreeViewItem>
</TreeViewItem>
But with this markup the behavior is as intended, but the child items (the rooms) are indented too much.
Anyway all the bining samples I could find use TextBlock instead of TreeViewItem in the DataTemplate, but wonder how to integrate the ContextMenu there.
You would not normally create a DataTemplate containing a TreeViewItem, because the binding infrastructure will be creating the TreeViewItem for you -- all your DataTemplate needs to do is specify what should be displayed as the content of the TreeViewItem. That's why the samples you've found use TextBlocks instead of TreeViewItems in the DataTemplate.
I suspect the use of TreeViewItem rather than TextBlock causes the excessive indenting because you have a (manually created) TreeViewItem in your DataTemplate (which incurs one level of indent) inside another (automatic) TreeViewItem (which incurs another level of indent). Therefore, using a TextBlock instead of a TreeViewItem should cure this. Integrating the ContextMenu shouldn't be an issue because TextBlock has a ContextMenu property too.
So you should be able to just change your DataTemplate as follows:
<DataTemplate x:Key="RoomTemplate">
<TextBlock Text="{Binding Name}">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="Open" />
<MenuItem Header="Remove" />
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
Incidentally for TreeViews it is common to use a HierarchicalDataTemplate rather than a plain DataTemplate because this allows for multiple levels of items via the HierarchicalDataTemplate.ItemsSource property. This may not be required in your scenario though.

Resources