Binding to ancestors from within a ResourceDictionary - wpf

How can I bind to a UserControl's property from within its ResourceDictionary? I want an object I declare in my resources to have the same DataContext as the UserControl it is contained in:
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Some.Namespace"
DataContext="{Binding Path=ViewModel, RelativeSource={RelativeSource Self}}">
<UserControl.Resources>
<local:SomeClass
x:Key="SomeClass"
DataContext="{Binding Path=DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" />
</UserControl.Resources>
</UserControl>
At runtime I get the error:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='UserControl', AncestorLevel='1''. BindingExpression:Path=DataContext; DataItem=null; target element is 'SomeClass' (Name=''); target property is 'DataContext' (type 'Object')

My workaround was to set the DataContext of the resource in the code-behind.
.xaml
<local:SomeType x:Key="SomeKey" SomeProperty="{Binding ... }" />
.xaml.cs
public SomeControl()
{
InitializeComponent();
((SomeType)this.Resources["SomeKey"]).DataContext = this;
}

When using FindAncestor, the target element needs to be a descendent (either logical or visual) of the source. Your object does not appear in either the visual or logical tree, it's simply in the resources. So you can't use RelativeSource with FindAncestor for your object.
You can use ElementName in your Binding though. Something like this should work:
<UserControl x:Name="userControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Some.Namespace"
DataContext="{Binding Path=ViewModel, RelativeSource={RelativeSource Self}}">
<UserControl.Resources>
<local:SomeClass
x:Key="SomeClass"
DataContext="{Binding Path=DataContext, ElementName=userControl}" />
</UserControl.Resources>
</UserControl>

Set x:Shared="False", this will clone resource on each use and make it a child of your element, enabling usage of bindings.
<local:SomeClass
x:Key="SomeClass"
x:Shared="False"
DataContext="{Binding Path=DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" />

I think what you're looking for is just {Binding} which binds to the inherited DataContext. Here's an example, though a bit strange shows how you can grab a color through binding to the DataContext:
<Window x:Class="AncestorBinding.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<SolidColorBrush x:Key="MyBrush" Color="Blue" />
</Window.Resources>
<StackPanel>
<Button DataContext="{Binding Source={StaticResource MyBrush}}" Content="My Button">
<Button.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="{Binding}" />
</Style>
</Button.Resources>
</Button>
</StackPanel>
</Window>

What I would do is to create an attached behavior (ContextualizeResourceBehavior) on the user control, and specify the resource key on that attached behavior. The behavior would lookup the resource (not sure you would be able to do it on attach, if not you would need to hook up Loaded event) and transfer the data context.

when you add your resource to the visual tree it should inherit the data context. but... have a look at element spy it might just do what you need.

Related

Bind Converter to the parent resource in xaml

I have a usercontrol1.xaml where I have a resource defined:
<UserControl x:Class="FrameworkDemo.usercontrol1View">
<UserControl.Resources>
<local:DemoManger x:Key="demoManager"/>
<local:DemoManagerConverterx x:Key="demoManagerConverter" Manager="{StaticResource strategyManager}"/>
</UserControl.Resources>
<telerik:RadTileView MinimizedItemsPosition="Top">
<telerik:RadTileViewItem>
<local:UserControl2View/>
</telerik:RadTileViewItem>
<telerik:RadTileViewItem>
........
</telerik:RadTileViewItem>
</telerik:RadTileView>
</UserControl>
Then in the user control view 2, I want to have this situation:
ss
<UserControl x:Class="FrameworkDemo.usercontrol2View">
<DockPanel>
<ComboBox MinWidth="270" Margin="0,0,5,5"
ItemsSource="{Binding Path=Demos, RelativeSource={RelativeSource AncestorType={x:Type local:DemoManager}}}"
SelectedValue="{Binding Path=CurrentStrategy, Converter={ ????}, Mode=TwoWay}"
IsEnabled="{Binding CanRefreshExecutionList, ElementName=Instance}"
DropDownOpened="StrategyComboBox_DropDownOpened">
</DockPanel>
</UserControl>
I was able to link to the parent control for the ItemSource, but for the converter how can I do it?. I can not event move the resource definition from control1 to control2. Inside of the RadTileViewItem is not possible to add another resource. Exactly in usercontrol1View I have a tabcontrol inside the RadTileViewItem and inside the tabiteam i have inlcluded the UserControl2View.
How can I link to the parent resource for the covnerter?
How can I link to the parent resource for the covnerter?
You can't. If you need to use the same converter in both UserControls, you have defined the resource in the wrong place actually.
You could either move it to your App.xaml file:
<Application x:Class="WpfApp1.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp1"
StartupUri="MainWindow.xaml">
<Application.Resources>
...
<local:DemoManagerConverterx x:Key="demoManagerConverter" Manager="{StaticResource strategyManager}"/>
</Application.Resources>
</Application>
Then you will be able to reference it across your entire application. The other option would be to define another resource of the same type in UserControl2:
<DockPanel>
<DockPanel.Resources>
<local:DemoManagerConverterx x:Key="demoManagerConverter" Manager="{StaticResource strategyManager}"/>
</DockPanel.Resources>
<ComboBox MinWidth="270" Margin="0,0,5,5"
ItemsSource="{Binding Path=Demos, RelativeSource={RelativeSource AncestorType={x:Type local:DemoManager}}}"
SelectedValue="{Binding Path=CurrentStrategy, Converter={StaticResource demoManagerConverter}, Mode=TwoWay}"
IsEnabled="{Binding CanRefreshExecutionList, ElementName=Instance}"
DropDownOpened="StrategyComboBox_DropDownOpened" />
</DockPanel>
But you can't reference a resource that is defined in a parent element using bindings.
I suppose you wanted bind converter object to the binding's Converter property.
You can't bind to the binding's Converter property since it is not a ´DependencyProperty´. You can acces a resource object and bind it e.g. to the ´Tag´, but it doesn't solve your problem:
<ComboBox MinWidth="270" Margin="0,0,5,5"
ItemsSource="{Binding Path=Demos, RelativeSource={RelativeSource AncestorType={x:Type local:DemoManager}}}"
SelectedValue="{Binding Path=CurrentStrategy, Converter={ ????}, Mode=TwoWay}"
Tag="{Binding Path='Resources[demoManagerConverter]', RelativeSource={RelativeSource AncestorType={x:Type localFrameworkDemo:usercontrol1View}}}"
IsEnabled="{Binding CanRefreshExecutionList, ElementName=Instance}"
DropDownOpened="StrategyComboBox_DropDownOpened">
If object is nested, you could just set(not bind) the Converter to the resource object:
Converter = {StaticResource demoManagerConverter}

How to assign DataContext from parent View's ViewModel (Property)

EDIT:
Here is a small sample Solution (VS 2013) showing my exact problem: nakkvarr.net/TestApp.zip
I have two views:
MainView
EmployeeView
I want my EmployeeView to reference the MainViewModel property "employees".
I tried this:
<DockPanel DataContext="{Binding DataContext.MainViewModel,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type EmployeeLisViewModel}}}">
But all I get is the Error: EmployeeListViewModel ist not supported in WPF ?!
(EmployeeListViewModel is also the name of the property of my MainViewModel)
The thing is:
My MainView has some menu items. One is for sorting the employee list, which is inside my UserControl. My UserControl ViewModel contains the Commands for sorting.
EDIT:
I changed it to vm:EmployeeListViewModel ... now the error is gone...but now I don't seem to get access to the ViewModel :/
<ia:InvokeCommandAction Command="{Binding LoadEmployeesCommand}"
Does not trigger, no emplyees get loaded. When I use the ViewModel directly, it works just fine.
MainWindow.xaml
<Window x:Class="de.XXX.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:de.XXX.Views"
xmlns:vm="clr-namespace:de.XXX.ViewModel"
Icon="/Images/App.ico"
Style="{StaticResource MainWindowStyle}"
Title="MainWindow">
<DockPanel DataContext="{Binding MainViewModel, Source={StaticResource Locator}}">
<Menu DockPanel.Dock="Top">
...
</Menu>
<controls:EmployeeListView DockPanel.Dock="Bottom" DataContext="{Binding EmployeeListViewModel}" />
</DockPanel>
</Window>
EmployeeListView.xaml //not model, copy & paste error x_X
<UserControl x:Class="de.XXX.Views.EmployeeListView"
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:vm="clr-namespace:de.XXX.ViewModel"
xmlns:ia="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
mc:Ignorable="d"
d:DesignHeight="350" d:DesignWidth="350">
<DockPanel DataContext="{Binding MainViewModel,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type vm:EmployeeListViewModel}}}">
<ListBox DockPanel.Dock="Bottom" x:Name="EmployeeList" ItemsSource="{Binding EmployeeList}" ScrollViewer.CanContentScroll="False" />
<ia:Interaction.Triggers>
<ia:EventTrigger EventName="Loaded">
<ia:InvokeCommandAction Command="{Binding LoadEmployeesCommand}" />
</ia:EventTrigger>
</ia:Interaction.Triggers>
</DockPanel>
</UserControl>
If you modify your SubControl to below, this should work.
<Grid DataContext="{Binding Path=DataContext.SubVM, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}">
I have a hint on what you are trying to achieve and I guess you have missed some details on Binding especially on other underlying properties needed to be set. This part is working based on the given code. But then, there are numerous ways in making it better. Good luck.
I think there might be a combination of misunderstandings here! Your UserControl has
x:Class="de.XXX.Views.EmployeeListView"
defined as the class name in xaml. However, you're also saying that the file is named EmployeeListViewModel.xaml? A .xaml file should never be named a viewmodel. I believe you should have MainWindow.xaml, EmployeeListView.xaml, MainWindowViewModel.cs, and EmployeeListViewModel.cs, is this the case?
Also, in your MainWindow.xaml, you're already setting the datacontext of your EmployeeListView usercontrol:
<controls:EmployeeListView DockPanel.Dock="Bottom" DataContext="{Binding EmployeeListViewModel}" />
This implies to me that your class structure is that MainViewModel contains an instance of the EmployeeListViewModel... is this true?
If all the above is true, then this part of your UserControl xaml does not make sense:
<DockPanel DataContext="{Binding MainViewModel,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type vm:EmployeeListViewModel}}}">
Whether EmployeeListViewModel is actually your view and not your viewmodel, the property "MainViewModel" does not exist on EmployeeListViewModel, so the binding will never work.
If you're trying to have a reference to your Window's DataContext, MainViewModel, within your UserControl, a possible way to do this is:
MainWindow.xaml:
<controls:EmployeeListView DockPanel.Dock="Bottom" DataContext="{Binding}" />
This should set the DataContext of your EmployeeListView to MainViewModel. Then, in your EmployeeListView, you can do this:
<UserControl x:Class="de.XXX.Views.EmployeeListView"
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:vm="clr-namespace:de.XXX.ViewModel"
xmlns:ia="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
mc:Ignorable="d"
d:DesignHeight="350" d:DesignWidth="350">
<DockPanel
<ListBox DockPanel.Dock="Bottom" x:Name="EmployeeList" ItemsSource="{Binding EmployeeList}" ScrollViewer.CanContentScroll="False" />
<ia:Interaction.Triggers>
<ia:EventTrigger EventName="Loaded">
<ia:InvokeCommandAction Command="{Binding EmployeeListViewModel.LoadEmployeesCommand}" />
</ia:EventTrigger>
</ia:Interaction.Triggers>
</DockPanel>
</UserControl>
There's a lot of assumptions about your views and viewmodels I made for this answer. I hope this at least helps. If it doesn't make any sense, please post your viewmodels as well :)

Resolving resources from one UserControl to a parent UserControl

I've got a user control UserControl1, which defines a style in its resources. That user control contains an instance of UserControl2, which references that style:
<UserControl x:Class="UserControl1">
<UserControl.Resources>
<Style x:Key="MyStyle" />
</UserControl.Resources>
<Grid>
<UserControl2 />
</Grid>
</UserControl>
<UserControl x:Class="UserControl2">
<Grid Style="{StaticResource MyStyle}">
</Grid>
</UserControl>
However, UserControl2 cannot find that style resource, even though it is in the logical tree (within the resources of UserControl1). How can I get UserControl2 to find the resources in UserControl1?
You can do it but, but I would suggest using a ResourceDictionary instead.
Anyway, if you want to do it this way you can use FindAncestor to find the parent and access the Resource you want out of the Parent ResourceDictionary
<UserControl x:Class="UserControl1">
<UserControl.Resources>
<Style x:Key="MyStyle" />
</UserControl.Resources>
<Grid>
<UserControl2 />
</Grid>
</UserControl>
<UserControl x:Class="UserControl2">
<Grid Style="{Binding Resources[MyStyle], RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:UserControl1}}}">
</Grid>
</UserControl>
Beacause Resource is a dictionary you can access using the key just like in code behind
I had the same issue and was able to solve it by referencing the resource through DynamicResource instead of StaticResource:
<UserControl x:Class="UserControl2">
<Grid Style="{DynamicResource MyStyle}">
</Grid>
</UserControl>
The compiler does still give a warning that the resource cannot be resolved.

tricky command source issue

I'm trying to use the RadDiagram commands from a separate window. My bindings don't work because "Cannot find source for binding with reference 'ElementName=Diagram'". My (simplified) XAML looks like this:
<UserControl x:Class="Client.Wpf.EditableLayoutControl"
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:telerik="http://schemas.telerik.com/2008/xaml/presentation"
xmlns:s="clr-namespace:Client.Wpf" xmlns:Converters="clr-namespace:Client.Wpf.Converters"
mc:Ignorable="d" DataContext="{Binding RelativeSource={RelativeSource Self}}"
d:DesignHeight="300" d:DesignWidth="800" Background="Transparent" Name="Root">
<UserControl.Resources>
<Converters:MultiBoolToVisibilityConverter x:Key="multiBoolToVis"/>
<Converters:ObjectToBooleanConverter x:Key="objToBool"/>
<telerik:InvertedBooleanConverter x:Key="invBool"/>
</UserControl.Resources>
<telerik:RadDocking HasDocumentHost="False" BorderThickness="0" Name="DockingStation">
<telerik:RadSplitContainer>
<telerik:RadPaneGroup>
<telerik:RadPane CanDockInDocumentHost="False" Title="Controls" >
<telerik:RadPane.TitleTemplate>
<DataTemplate>
<Grid Margin="0 5">
<ContentPresenter Content="{Binding}" VerticalAlignment="Center"/>
<telerik:RadButton Command="telerik:DiagramCommands.Undo" CommandTarget="{Binding Path=Diagram, Source={RelativeSource FindAncestor, AncestorType={x:Type s:EditableLayoutControl}}}" />
</Grid>
</DataTemplate>
</telerik:RadPane.TitleTemplate>
<telerik:RadTabControl ...></telerik:RadTabControl>
</telerik:RadPane>
</telerik:RadPaneGroup>
</telerik:RadSplitContainer>
<telerik:RadSplitContainer>
<telerik:RadPaneGroup>
<telerik:RadPane PaneHeaderVisibility="Collapsed" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch">
<Grid>
<telerik:RadDiagram Name="Diagram">...
</telerik:RadDiagram>
</Grid>
</telerik:RadPane>
</telerik:RadPaneGroup>
</telerik:RadSplitContainer>
</telerik:RadDocking>
</UserControl>
The problematic line of code is this beaut from inside the DataTemplate:
CommandTarget="{Binding Path=Diagram, Source={RelativeSource FindAncestor, AncestorType={x:Type s:EditableLayoutControl}}}"
That line of code doesn't work because Diagram is not a property, and bindings require properties. Using ElementName instead of Path there also doesn't work as ElementName and Source are exclusive. I also tried this:
CommandTarget="{Binding ElementName=Diagram}"
That line of code doesn't work. It gives this error:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=Diagram'. BindingExpression:(no path); DataItem=null; target element is 'RadButton' (Name=''); target property is 'CommandTarget' (type 'IInputElement')
I also tried CommandTarget="{x:Reference Diagram}", but that feature is apparently not yet implemented in .NET 4.0. How is it done?
In this particular situation I had to set the Loaded event on the undo button. Then in my event handler (code-behind) I did this: ((RadButton)e.Source).CommandTarget = Diagram;.

Binding one dependency property to another

I have a custom Tab Control that I have created, but I am having an issue. I have an Editable TextBox as part of the custom TabControl View.
<Controls:EditableTextControl x:Name="PageTypeName"
Style="{StaticResource ResourceKey={x:Type Controls:EditableTextControl}}" Grid.Row="0" TabIndex="0"
Uid="0"
AutomationProperties.AutomationId="PageTypeNameTextBox"
AutomationProperties.Name="PageTypeName"
Visibility="{Binding ElementName=PageTabControl,Path=ShowPageType}">
<Controls:EditableTextControl.ContextMenu>
<ContextMenu x:Name="TabContextMenu">
<MenuItem Header="Rename Page Type" Command="{Binding Path=PlacementTarget.EnterEditMode, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
AutomationProperties.AutomationId="RenamePageTypeMenuItem"
AutomationProperties.Name="RenamePageType"/>
<MenuItem Header="Delete Page Type" Command="{Binding Path=PageTypeDeletedCommand}"
AutomationProperties.AutomationId="DeletePageTypeMenuItem"
AutomationProperties.Name="DeletePageType"/>
</ContextMenu>
</Controls:EditableTextControl.ContextMenu>
<Controls:EditableTextControl.Content>
<!--<Binding Path="CurrentPageTypeViewModel.Name" Mode="TwoWay"/>-->
<Binding ElementName="PageTabControl" Path="CurrentPageTypeName" Mode ="TwoWay"/>
</Controls:EditableTextControl.Content>
</Controls:EditableTextControl>
In the Content section I am binding to a Dependency Prop called CurrentPageTypeName. This Depedency prop is part of this custom Tab Control.
public static DependencyProperty CurrentPageTypeNameProperty = DependencyProperty.Register("CurrentPageTypeName", typeof(object), typeof(TabControlView));
public object CurrentPageTypeName
{
get { return GetValue(CurrentPageTypeNameProperty) as object; }
set { SetValue(CurrentPageTypeNameProperty, value); }
}
In another view, where I am using the custom TabControl I then bind my property, with the actual name value, to CurrentPageTypeName property as seen below:
<Views:TabControlView Grid.Row="0" Name="RunPageTabControl"
TabItemsSource="{Binding RunPageTypeViewModels}"
SelectedTab="{Binding Converter={StaticResource debugConverter}}"
CurrentPageTypeName="{Binding Path=RunPageName, Mode=TwoWay}"
TabContentTemplateSelector="{StaticResource tabItemTemplateSelector}"
SelectedIndex="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.SelectedTabIndex}"
ShowPageType="Hidden" >
<!--<Views:TabControlView.TabContentTemplate>
<DataTemplate DataType="{x:Type ViewModels:RunPageTypeViewModel}">
<RunViews:RunPageTypeView/>
</DataTemplate>
</Views:TabControlView.TabContentTemplate>-->
</Views:TabControlView>
My problem is that nothing seems to be happening. It is grabbing its Content from the Itemsource, and not from my chained Dependency props. Is what I am trying even possible? If so, what have I done wrong.
Thanks for looking.
Unless I'm missing something this is definitely possible. Here is a simplified working example.
User control with a dependency property named TestValue, containing a TextBox bound to this property:
<UserControl x:Class="TestApp.TestControl" 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" d:DesignHeight="300" d:DesignWidth="300"
x:Name="TestControlName">
<Grid>
<TextBox Text="{Binding ElementName=TestControlName, Path=TestValue, Mode=TwoWay}"/>
</Grid>
</UserControl>
A different view using this user control, binding the above mentioned dependency property to something:
<Window x:Class="TestApp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:TestApp="clr-namespace:TestApp" Title="MainWindow"
Height="350" Width="525">
<StackPanel>
<TestApp:TestControl TestValue="{Binding ElementName=SourceTextBox, Path=Text, Mode=TwoWay}" />
<TextBox Name="SourceTextBox" />
</StackPanel>
</Window>
It sounds that the issue is somewhere in the part of the code you have not posted (e.g. wrong name used in Content binding).
I think you already solved this yourself for the "SelectedIndex" property. Just do the same thing for the "CurrentPageType" property i.e. use RelativeSource

Resources