if the title didn't scare you away, here goes...
<UserControl.Resources>
<!-- so the attached CustomObject can bind to the context -->
<my:BindingProxy x:Key="DataContextProxy" Data="{Binding}" />
<!-- for chaining IsNull to Visibility.Collapsed -->
<my:ConverterGroup x:Key="IsNullToVisibility">
<my:IsNullConverter />
<my:VisibilityValuesEqual />
</my:ConverterGroup>
<ContextMenu x:Key="ctxmnu">
<MenuItem Header="Copy" Click="ctxmnu_itmCopy_Click" />
<MenuItem Header="Add" Click="ctxmnu_itmAdd_Click"
IsEnabled="{Binding Source={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu},
Path=PlacementTarget,
Converter={StaticResource IsNotNullConverter}}"
Visibility="{Binding Source={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu},
Path=PlacementTarget.(myAP:APClass.Property),
Converter={StaticResource IsNullToVisibility}}"
/>
</ContextMenu>
</UserControl.Resources>
<Label Content="{Binding Path=Description}" ContextMenu="{StaticResource ctxmnu}">
<myAP:APClass.Property>
<myAP:CustomObject ID="{Binding Source={StaticResource DataContextProxy}, Path=ID}" />
</myAP:APClass.Property>
</Label>
</UserControl>
Basically, I've got a context menu, with two menu items... the first (copy) is always available... the second (Add) is only available if the context menu is applied from a UIElement with the attached property.
Most of this works... attached property is correctly binding via the proxy resource, menuitem click event is able to get the attached property value.
The only thing that DOESN'T work is the MenuItem.IsEnabled/Visibility binding (I was initially going to bind visibility, but lately I'm thinking IsEnabled is a better idea for UX).
The error is in the binding. Standard error 40 in the output window.
System.Windows.Data
Error: 40 :
BindingExpression path error:
'PlacementTarget' property not found on 'object' ''RelativeSource' (HashCode=22838427)'.
BindingExpression:Path=PlacementTarget.(0); DataItem='RelativeSource' (HashCode=22838427);
target element is 'MenuItem' (Name='');
target property is 'Visibility' (type 'Visibility')
Thing is, every article or example I've been able to find (regarding MenuItem binding to PlacementTarget) has the context menu directly on the UIElement, and since the binding occurs when the window/control is instantiated (before the context menu is shown), I'm thinking that the PlacementTarget is NULL and thus error.
Thx in advance!
The problem is you're using the Source property of the binding to assign a RelativeSource. It should be:
IsEnabled="{Binding RelativeSource={RelativeSource
^^^^^^^^^^^^^^
If you look at the error it actually explains it:
'PlacementTarget' property not found on 'object' ''RelativeSource'
The object should've been the context menu.
Related
I tried to bind a ToolTip text in a UserControl this way:
<Grid.ToolTip>
<TextBlock
Text="{
Binding Path=InfoTT,
RelativeSource={
RelativeSource Mode=FindAncestor,
AncestorType={x:Type UserControl}
}
}" />
</Grid.ToolTip>
And it doesn't work, the Tooltip was empty and in logs, I saw:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.UserControl', AncestorLevel='1''. BindingExpression:Path=InfoTT; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')*
But when I did:
<Grid
ToolTip="{
Binding Path=InfoTT,
RelativeSource={
RelativeSource Mode=FindAncestor,
AncestorType={x:Type UserControl}
}
}">
</Grid>
It worked. Can anyone explain why the first way doesn't work?
When Binding.RelativeSource doesn't resolve, you can always be sure that the Binding.Target is not part of the visual tree.
In your first example you are explicitly defining the tree structure of the ToolTip. You are explicitly creating the content e.g. by adding the TextBlock. The content of the ToolTip is not part of the visual tree and therefore the Binding.RelativeSource can't be resolved.
In your second example, you let the FrameworkElement implicitly create the ToolTip content.
Now FrameWorkElement will first resolve the Binding, which resolves, as the FrameworkElement is still part of the visual tree. The resolved value is taken, ToString invoked, a TextBlock created and the string value assigned to TextBlock.Text.
Solution
To solve the binding problem, when implementing the ToolTip explicitly, you can implement a Binding Proxy as suggested in a comment by #Mark Feldman which makes use of the StaticResource markup to provide a Binding.Source to elements that are not part of the visual tree.
It's basically a bindable ObjectDataProvider.
A similar solution to the binding proxy is to define the content as a resource of the Grid and then reference it via DynamicResource using a ContentPresnter:
<UserControl>
<Grid>
<Grid.Resources>
<!-- The proxy -->
<TextBlock x:Key="ToolTipText"
Text="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=InfoTT}" />
<Grid.ToolTip>
<ToolTip>
<ContentPresenter Content="{DynamicResource ToolTipText}" />
</ToolTip>
</Grid.ToolTip>
</Grid>
</UserControl>
But you could also make use of the fact that the DataContext is still inherited. Bindings to the DataContext will still resolve.
In your scenario, where you want to bind the content of the ToolTip to a property of the parent UserControl, you could bind this property to a property of the view model, which is the current DataContext of Grid (and therefore for its ToolTip). I only recommend this, when binding to business data and not layout data:
<UserControl InfoTT="{Binding ViewModelInfoTT}">
<UserControl.DataContext>
<ViewModel />
</UserControl.DataContext>
<Grid>
<Grid.ToolTip>
<ToolTip>
<TextBlock Text="{Binding ViewModelInfoTT}" />
</ToolTip>
</Grid.ToolTip>
</Grid>
</UserControl>
If you don't use view models and host the data directly in the control, you may like to set the DataContext to the control itself. This way you simplify all bindings and of course can now bind to the UserControl from within the ToolTip:
// Constructor
public MyUserControl()
{
InitializeComponent();
// Set the UserControl's DataContext to the control itself
this.DataContext = this;
}
<UserControl>
<Grid>
<Grid.ToolTip>
<ToolTip>
<TextBlock Text="{Binding InfoTT}" />
</ToolTip>
</Grid.ToolTip>
</Grid>
</UserControl>
Alternatively override the DataContext. Of course you'll lose access to the current context:
<UserControl>
<Grid DataContext="{Binding RelativeSource={RelativeSource AncestoType=UserControl}>
<Grid.ToolTip>
<ToolTip>
<TextBlock Text="{Binding InfoTT}" />
</ToolTip>
</Grid.ToolTip>
</Grid>
</UserControl>
I'm working on a WPF application and I'm using the MVVM pattern. I use MVVMLight to help me handle some Events. I need to forward the "Click" event so that I can pass the arguments as well so that I can know for sure which item that sent the event. If I use the "Command" I cant know for sure that it was the selected item that sent the event - as the item doesnt need to be selected to right click on it.
This is my code for displaying a list of "order lines". There are two types of order lines, and for one of the data types; "AccessoryOrderLine" - I want to add a context menu.
My problem is that I cannot access my Window's DataContext. I've named the root node in the Window "root", and I'm trying to access the root's DataContext, but this failes with the following error:
System.Windows.Data Error: 4 : Cannot find source for binding with
reference 'ElementName=root'.
BindingExpression:Path=DataContext.PackAccessory; DataItem=null;
target element is 'EventToCommand' (HashCode=5903270); target property
is 'Command' (type 'ICommand')
<ListBox HorizontalContentAlignment="Stretch" Margin="10,0,10,10" DockPanel.Dock="Bottom" Grid.Row="1" ItemsSource="{Binding OrderLines, NotifyOnSourceUpdated=True, NotifyOnTargetUpdated=True, UpdateSourceTrigger=PropertyChanged}">
<ListBox.Resources>
<DataTemplate DataType="{x:Type m:UnitOrderLine}">
<v:OrderLine />
</DataTemplate>
<DataTemplate DataType="{x:Type m:AccessoryOrderLine}">
<v:OrderLine>
<v:OrderLine.ContextMenu>
<ContextMenu>
<MenuItem Header="Pack 1" IsCheckable="False">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cmd:EventToCommand Command="{Binding ElementName=root, Path=DataContext.PackAccessory }" PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</MenuItem>
</ContextMenu>
</v:OrderLine.ContextMenu>
</v:OrderLine>
</DataTemplate>
</ListBox.Resources>
</ListBox>
I've also tried to use "TemplatedParent" and then I get access to my "OrderLine" DataContext, but I cant get one step further back to my "MainWindowModel".
http://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/
Found a solution to my problem :)
Found a solution. Updated my original post with the link to my solution.
Its not issue with the DataTemplate. Binding with ElemetName works in all cases except in the case of ContextMenu since it does not lies in the same visual tree as of your window. However, there is one hack where you can use the PlacementTarget property of your context menu.
For details refer to this link - http://social.msdn.microsoft.com/Forums/nl/wpf/thread/526ab350-8788-4bc6-a98a-1e4dee6ad33a
It contains exactly what you are trying to achieve here.
Seems like here are answers for your question:
ElementName Binding from MenuItem in
ContextMenu
WPF MenuItem.Command binding to
ElementName..
I have a TreeView with ContextMenu, and inside that menu I want to bind to a command on VIewModel
<TreeView x:Name="treeView"
ItemTemplate="{StaticResource ItemTemplate}"
ItemsSource="{Binding View}">
<TreeView.ContextMenu>
<ContextMenu>
<telerik:RadMenuItem Header="Remove" Command="{Binding RemoveCommand}" CommandParameter="{Binding ElementName=treeView, Path=SelectedItem, Mode=OneWay}" />
</ContextMenu>
</TreeView.ContextMenu>
</TreeView>
I receive an exception in Output window, like
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=treeView'. BindingExpression:Path=SelectedItem; DataItem=null; target element is 'RadMenuItem' (Name=''); target property is 'CommandParameter' (type 'Object')
I am actually using RadTreeView, but the same applies for TreeView. Why I cannot bind to TreeView's SelectedItem property? I have tried with RelativeSource AncestorType, the same problem.
The problem is that, like the Popup control, it's a different visual tree. The error is telling you that it is trying to find a property called 'CommandParameter' on a 'RadMenuItem', because this is the DataContext it has within the ContextMenu's visual tree.
This will help you: Placement Target
I ended up setting the CommandTarget property of the MenuItem to the ContextMenu's PlacementTarget property, but it doesn't look like that's the approach you're taking. Even still, the PlacementTarget is what you're looking for.
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.
I have a ListBox that I have added a ContextMenu to. I want one of the items in the ContextMenu to be bound to a command and I want the parameter passed to that command to be the currently selected item of the ListBox control. Here's my xaml:
<ListBox x:Name="selectedVascularBeds"
ItemsSource="{Binding Path=UserSelectedVascularBeds}"
dd:DragDrop.IsDropTarget="True"
dd:DragDrop.DropHandler="{Binding}"
DisplayMemberPath="VascularBedName">
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Remove" Command="{Binding Path=RemoveSelectedVascularBedCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType={x:Type ListBox}},
Path=SelectedItem}"/>
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
This ListBox is part of a user control that is bound to a view model object. My command method on the underlying object gets called but the parameter passed in is always null.
I have tested changing the binding of the CommandParameter to simply {Binding} which results in the user control's data context being passed into my method - so I know that the command is working and passes parameters correctly. I just can't seem to get the correct binding to access the ListBox's SelectedItem property.
Help?
the context menu is not a descendant of the list box. try an element name binding instead
<MenuItem Header="Remove" Command="{Binding Path=RemoveSelectedVascularBedCommand}" CommandParameter="{Binding ElementName=selectedVascularBeds, Path=SelectedItem}"/>
The ElementName binding also didn't work, the parameter was still null and I found an error in the console output:
System.Windows.Data Error: 4 : Cannot find source for binding with
reference 'ElementName=selectedVascularBeds'.
BindingExpression:Path=DataContext; DataItem=null; target element is
'MenuItem' (Name=''); target property is 'CommandParameter' (type
'Object')
Searching for that error lead me to this link though and it looks like the Context menu is different and I can't achieve what I want the way I'm going about it.
ElementName Binding from MenuItem in ContextMenu