Binding a TreeView with ContextMenu in Xaml - wpf

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.

Related

MenuItem within MenuItem not using all available space

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.

ContextMenu.ItemSource binding issue

I have a static resource defined as follows:
<ContextMenu x:Key="TestContextMenu" DataContext="{Binding Path=PlacementTarget, RelativeSource={x:Static RelativeSource.Self}}">
<MenuItem Command="ApplicationCommands.Cut"/>
<!--<ContextMenu.ItemsSource>
<CompositeCollection>
<MenuItem Command="ApplicationCommands.Cut"/>
</CompositeCollection>
</ContextMenu.ItemsSource>-->
</ContextMenu>
My application works fine like this. However, I want to be able to add extra items to the context menu. So instead of adding menu items I want to use a CompositeCollection and then I run into binding issues. I minimized the problem and ended up with this. When I comment the MenuItem and uncomment the ContextMenu.ItemSource I get this error:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.ItemsControl', AncestorLevel='1''. BindingExpression:Path=HorizontalContentAlignment; DataItem=null; target element is 'MenuItem' (Name=''); target property is 'HorizontalContentAlignment' (type 'HorizontalAlignment')
If the datacontext is correct in the first situation why is it not correct anymore in the second situation?
Edit: I don't want to add items to the contextmenu dynamically. I want to use this contextmenu as kind of a 'base context menu' providing cut/copy and paste from a composite collection resource. In some places I want more than just these three and there I could use a custom context menu that use that same collection combined with those extra items. Just to clarify this is the xaml I have in mind, but I cut the problem down to the simpler piece above.
<CompositeCollection x:Key="TreeViewItemContextMenuItems">
<MenuItem Command="ApplicationCommands.Cut" CommandTarget="{Binding}"/>
<MenuItem Command="ApplicationCommands.Copy" CommandParameter="{Binding}"/>
<MenuItem Command="ApplicationCommands.Paste" CommandParameter="{Binding}"/>
<Separator/>
<MenuItem Command="ApplicationCommands.Delete" CommandParameter="{Binding}"/>
</CompositeCollection>
<ContextMenu x:Key="TreeViewItemContextMenu" DataContext="{Binding Path=PlacementTarget, RelativeSource={x:Static RelativeSource.Self}}">
<ContextMenu.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{StaticResource TreeViewItemContextMenuItems}" />
</CompositeCollection>
</ContextMenu.ItemsSource>
</ContextMenu>
If you want to dynamically populate ContextMenu the better solution is do to as follows:
<ContextMenu
x:Key="TestContextMenu"
ItemsSource="{Binding MenuItems}">
</ContextMenu>
I removed DataContext="{Binding Path=PlacementTarget...}}" and added this ItemsSource="{Binding MenuItems}". MenuItems is a property of an object used as a data context. Here is an example of usage:
<Window>
...
<ItemsControl ContextMenu="{StaticResource ResourceKey=TestContextMenu}">
...
</ItemsControl>
</Window
In this case the context menu will inherit a data context from ItemsControl which in turn will inherit a data context from Window.
If you don't want to remove DataContext="{Binding Path=PlacementTarget...}}" then use the following code:
<ContextMenu
x:Key="TestContextMenu"
DataContext="{Binding Path=PlacementTarget, RelativeSource={x:Static RelativeSource.Self}}"
ItemsSource="{Binding DataContext.MenuItems}">
</ContextMenu>
EDIT:
You receive these errors because MenuItem controls by default try to bind some of their properties (HorizontalContentAlignment and VerticalContentAlignment) to ItemsControl using RelativeSource. The problem is that their are embeded inside CompositeCollection which doesn't support this kind of binding - see this article.
The problem will occure even if you override this binding in your XAML in this way:
<MenuItem Command="ApplicationCommands.Cut" CommandTarget="{Binding}"
HorizontalContentAlignment="Center" VerticalContentAlignment="Center"/>
Probably there is some workaround of this problem but personally I'll ignore these errors. They don't spoil anything in your application, don't they?

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.

Binding to two different DataContexts in a ContextMenu

I'm trying to bind to a property of a container from inside a DataTemplate. A simplified version of my markup looks like:
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type myCustomItem}">
<!--Visual stuff-->
<StackPanel>
<StackPanel.ContextMenu>
<ContextMenu>
<MenuItem Header="Add Item"
Command="{Binding myCustomItemsICommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type CustomContainerType}}, Path=ContainerProperty}"/>
</ContextMenu>
</StackPanel.ContextMenu>
</StackPanel>
</DataTemplate>
</Grid.Resources>
<CustomContainerType/>
</Grid>
My approach is based on this post but it doesn't seem to be working. The issue seems to arise from the placement of the ContextMenu within the visual tree. Basically I am trying to bind the Command to the DataContext of the DataTemplate but bind the CommandParameter to a DataContext outside the DataTemplate.
ContextMenus are not in the same visual tree as the rest of the controls, there are a few questions regarding how to do bindings accross that boundary but this might be somewhat difficult without specifying names.
ElementName fails as well because of the lacking tree connection, but you could use x:Reference in the Binding.Source instead.

DataBinding to selected TreeViewItem only if child of other TreeViewItem

I have a custom class (NewBlockLabelInfo) with an observable collection of another custom class (DoorControllerLabelInfo) I've successfully databound the NewBlockLabelInfo class to the treeview, and everything displays fine.
I have a lot of textboxs that are data bound to certain properties, and updating these reflects in the treeview.
I'd like to databind one set of textboxs for the properties, to the selected item in the treeview IF the selected item is a child of the specified treeviewitem (Observable Collection, Door Controllers)
The Data Context is specified at the window level.
I've looked long and hard for a way to do this, let alone the best way.
Heres the WPF XAML for the TreeView
<TreeView Margin="12,150,582,16" Name="treeView1">
<TreeViewItem Header="{Binding Path=BlockName}" Style="{StaticResource BlockItem}" IsExpanded="True">
<TreeViewItem Style="{StaticResource PhoneNoItem}" Header="{Binding Path=TelephoneNumber}"/>
<TreeViewItem Style="{StaticResource DataNoItem}" Header="{Binding Path=DataNumber}"/>
<TreeViewItem Style="{StaticResource CompanyItem}" Header="{Binding Path=CompanyName}"/>
<TreeViewItem Style="{StaticResource ConnectedItem}" Header="{Binding Path=ConnectedDC}" />
<TreeViewItem IsExpanded="True" Header="Door Controllers" Foreground="#FF585858" ItemsSource="{Binding Path=DoorControllers, UpdateSourceTrigger=PropertyChanged}" Name="DCTreeViewItem" Selected="DCTreeViewItem_Selected">
<TreeViewItem.ItemTemplate>
<HierarchicalDataTemplate>
<TreeViewItem Header="{Binding Path=DCName}" Style="{StaticResource DCItem}" IsExpanded="True" Selected="DCTreeViewItem_Selected" >
<TreeViewItem Header="{Binding Path=Address}" Style="{StaticResource AddressItem}" />
<TreeViewItem Header="{Binding Path=Channel1}" Style="{StaticResource Door1Item}" />
<TreeViewItem Header="{Binding Path=Channel2}" Style="{StaticResource Door2Item}" />
</TreeViewItem>
</HierarchicalDataTemplate>
</TreeViewItem.ItemTemplate>
</TreeViewItem>
<TreeViewItem IsExpanded="True" Header="Flats" Foreground="#FF585858" ItemsSource="{Binding Path=FlatNames, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}">
<TreeViewItem.ItemTemplate>
<DataTemplate>
<TreeViewItem Header="{Binding}" Style="{StaticResource FlatsItem}" IsExpanded="True">
</TreeViewItem>
</DataTemplate>
</TreeViewItem.ItemTemplate>
</TreeViewItem>
</TreeViewItem>
</TreeView>
How can I bind a textbox to a selected item property (or to the databound class property) of a TreeViewItem only if it is a child of Door Controllers TreeViewItem
Thank you in advance
Oliver
You would likely be best served with defining a DataTemplate selector, and creating several datatemplates. The selector can evaluate all kinds of logical rules, and return the template that you want.
Here is a pretty good getting started tutorial on DataTemplateSelectors.
EDIT
After rereading your question here is what I have got.
Do your model classes have navigation properties to get to parent objects? If so, you can use triggers (or more preferable if you are using MVVM) properties on the ViewModel to enable/disable/alter visibility based on what the parent object is rather than the parent TreeViewItem. It is a bit more difficult to access the visual tree the way you are describing.
I solved this myself by adding an event handler on click for each of the child TreeViewItems I am concerned with. With this I could then get the DataContext from it, and set it to be the DataContext of the TextBoxs, and create my own bindings.
Since DataContext retrieval is done by reference, and not by value this works.
Here is the event handler for when one of the child nodes are clicked than I am concerned with: (Forgive the temporary naming)
private void DCTreeViewItem_Selected(object sender, RoutedEventArgs e)
{
TreeViewItem currentItem = (TreeViewItem)e.OriginalSource;
textBox5.DataContext = ((DoorControllerLabelInfo)currentItem.DataContext);
Binding b = new Binding("DCName");
b.Mode = BindingMode.TwoWay;
b.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
BindingOperations.SetBinding(textBox5, TextBox.TextProperty, b);
}
From this, I set the path, binding mode, and update source trigger in the XAML so that only the Data Context need to be updated.
Thanks
Oliver

Resources