Binding to two different DataContexts in a ContextMenu - wpf

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.

Related

Binding a button nested in DataTemplate

I need to bind a button's command inside a datatemplate like below:
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button Content="-" Cursor="Hand" Width="50"
Background="Red" x:Name="removeButton"
Command="{Binding Remove}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
Unfortunately it does not work. How can I bind a command in a button insade a datatemplate?
I found that thread in the forum:
Bindings in nested WPF DataTemplates
but the method given by person, who answered this question, does not work as well. I think, that something has changed in WPF since this time, I would you grateful for your help.
If Remove is defined in the view model of the parent ListView, you could bind to it using a RelativeSource:
Command="{Binding DataContext.Remove,
RelativeSource={RelativeSource AncestorType=ListView}}"
You could also set the AncestorType to Window or UserControl depending on where the command property is defined and where the DataTemplate is applied.

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.

Caliburn Micro with Treeview Context Menu

I have my hierarchical treeview binding wonderfully to my ViewModel using Caliburn Micro. (The ViewModel has an Items property that returns an ObservableCollection - the treeview is named to this Items property - nothing wrong with the binding).
However the issue comes up with the context menu. The menu fires a method on an instance of the object that the treenode represents. What I rather want to achieve, is to have the menu fire a method on my root ViewModel, passing to it as a parameter the instance of the object represented by the clicked treenode.
Here is my XAML:
<HierarchicalDataTemplate DataType="{x:Type m:TaskGrouping}"
ItemsSource="{Binding Children}">
<Label Content="{Binding Name}"
FontWeight="Bold">
<Label.ContextMenu>
<ContextMenu>
<MenuItem Header="Add New SubFolder"
cal:Message.Attach="AddNewSubfolder" />
<MenuItem Header="Remove this folder"
cal:Message.Attach="RemoveFolder" />
</ContextMenu>
</Label.ContextMenu>
</Label>
</HierarchicalDataTemplate>
What changes do I need to make to my XAML in order to achieve what I want?
ContextMenus are located in a separate visual tree from everything else - it can be a pain to get the bindings right (I often have 10-15 minutes of fighting the bindings on them to get them right!)
You've got your Message.Attach attached property set, all you need to do is ensure that the action target is pointing to the VM rather than the data item. You can use Action.TargetWithoutContext to specify the target for actions (CM will otherwise use DataContext)
You will also need to get a binding path which points to the other visual tree - try using RelativeSource bindings - the ContextMenu also has a property called PlacementTarget which should point to the element that the ContextMenu is attached to
So possibly:
cal:Action.TargetWithoutContext="{Binding DataContext, RelativeSource={RelativeSource AncestorType=Label}}"
or
cal:Action.TargetWithoutContext="{Binding PlacementTarget.DataContext}"
You might have to experiment as I often get this almost right first time!
EDIT by OP(Shawn):
This is what worked for me eventually:
<Label Content="{Binding Name}"
Tag="{Binding DataContext, ElementName=LayoutRoot}">
<Label.ContextMenu>
<ContextMenu
cal:Action.TargetWithoutContext="{Binding PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Run Task Now" cal:Message.Attach="SomeRootViewModelMethod($dataContext)" />

UI object, on samelevel of XAML Tree, as CommandParameter

I have an XAML tree as follows:
<Window>
<Grid>
<DockPanel>
<DataGrid>
<DataGrid.Resources>
<CheckBox Command="{Binding Command}" CommandParameter="??" />
</DataGrid.Resources>
</DataGrid>
<StackPanel>
<ChartLegend>
</ChartLegend>
<DataChart>
</DataChart>
</stackPanel>
</DockPanel>
</Grid>
</Window>
I want to have DataChart object as CommandParameter on ViewModel from a Command on DataGrid.
My Findings:
I'm getting DockPanel object as CommandParameter, then I have to apply method FindName("") to get the DataChart. And do further modifications.
But I want the DataChart object directly, to avoid TypeCasting or searching down the Tree.
You can keep datachart as a named resource in your DockPanel resources and use static resource binding to command parameter. Then use ContentControl to host it.
like this...
<DockPanel>
<DockPanel.Resources>
<DataChart x:Key="MyDataChart">
</DataChart>
</DockPanel.Resources>
<DataGrid>
<DataGrid.Resources>
<CheckBox
Command="{Binding Command}"
CommandParameter="{StaticResource MyDataChart}" />
</DataGrid.Resources>
</DataGrid>
<StackPanel>
<ChartLegend>
</ChartLegend>
<ContentControl Content="{StaticResource MyDataChart}"/>
</stackPanel>
</DockPanel>
Hoping that you wont use same MyDataChart to host to another area (as that would result in "visual tree parent disconnect" error).
Although I must ask you this... why is there a lonely CheckBox in your DataGrid resources?
Also your's and mine solution breaks MVVM because we are supplying a UI control (Chart control) to a View Model.

WPF Treeview contextmenu IsChecked binding MVVM

I've got a TreeView to which I associate a ContextMenu. That contextmenu has an item whose IsChecked property I want to bind to my ViewModel. Since I am using a tree each treeitem is bound to a subproperty of my ViewModel.
In the VS2010 output window I am seeing this databinding error:
BindingExpression path error: 'IsAutoStart' property not found on 'object' ''HostMgmtViewModel' (HashCode=12565727)'. BindingExpression:Path=IsAutoStart; DataItem='HostMgmtViewModel'
This clearly shows it is trying to bind to my ViewModel and not to the treeitem's associated data. How do I bind to the correct object? Remember my contextmenu is associated with the whole TreeView not to the specific treeitem.
---------- Edit
As xandy pointed out below the resolution to my problem was to bind the IsChecked like this:
{Binding Path=PlacementTarget.SelectedItem.IsDisabledStart, Mode=OneWay, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}
<TreeView Name="tview" Grid.Row="0" Tag="{Binding RelativeSource={RelativeSource Self}, Path=SelectedItem}">
<TreeView.ContextMenu>
<ContextMenu>
<MenuItem Name="miC" Header="{Binding Path=Tag.Key}" DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"></MenuItem>
</ContextMenu>
</TreeView.ContextMenu>
</TreeView>
This is the working code snippet I have. Courtesy of [this].1 All you need is to change the binding path in the tag. I am currently binding the Treeview to a dictionary, so it is the Key property of it. It should not have any problem in binding to any object collections. One interesting finding is context menu is not in part of element tree and this cause the problem. I could bind the text box with no problem:
<TextBlock Grid.Row="1" DataContext="{Binding ElementName=tview, Path=SelectedItem}">
<TextBlock.Text>
<Binding Path="Key" />
</TextBlock.Text>
</TextBlock>
But it is not functioning if for menuitem if I put the same thing.

Resources