I am newbie to WPF and curious to know how to nested bind the properties in WPF. I have made a sample app for better understanding. Please find below the following scenario's.
Working Code:
UserControl1 >> MainWindow
Created a UserControl and included it in the mainWindow.
UserControl1.xaml
<StackPanel>
<TextBox x:Name="text1" Text="{Binding MyTextUC1, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox />
</StackPanel>
UserControl1.xaml.cs
public UserControl1()
{
InitializeComponent();
this.DataContext = this;
}
public static readonly DependencyProperty MyTextUC1Property =
DependencyProperty.Register("MyTextUC1", typeof(string), typeof(UserControl1));
public string MyTextUC1
{
get { return (string)GetValue(MyTextUC1Property); }
set { SetValue(MyTextUC1Property, value); }
}
MainWindwo.xaml
<StackPanel>
<local:UserControl1 x:Name="text1"/>
<TextBlock Text="{Binding ElementName=text1, Path=MyTextUC1}" />
</StackPanel>
On running this, text changes in the TextBox of UserControl1 got reflected in TextBlock of MainWindow.
Not Working:
UserControl1 >> UserControl2 >> MainWindow
Created a UserControl(usercontrol1) and included it in another UserControl(usercontrol2) and then included the UserControl2 in mainWindow.
UserControl1.xaml & UserControl1.xaml.cs
//Same as Working Code
UserControl2.xaml
<Grid>
<local:UserControl1 x:Name="text2" MyTextUC1="{Binding MyTextUC2,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
UserControl2.xaml.cs
public UserControl2()
{
InitializeComponent();
this.DataContext = this;
}
public static readonly DependencyProperty MyTextUC2Property =
DependencyProperty.Register("MyTextUC2", typeof(string), typeof(UserControl2));
public string MyTextUC2
{
get { return (string)GetValue(MyTextUC2Property); }
set { SetValue(MyTextUC2Property, value); }
}
MainWindwo.xaml
<StackPanel>
<local:UserControl2 x:Name="text1"/> //UserControl2 contains UserControl1
<TextBlock Text="{Binding ElementName=text1, Path=MyTextUC2}" />
</StackPanel>
On Running this, text changes in the TextBox of UserControl2 NOT REFLECTED in TextBlock of MainWindow.
I'm pretty sure that this is a basic thing in WPF and I may be implementing this in wrong way. Can someone suggest me where I am wrong? Thanks in advance.
If you check the Debug Output you will see the following error:
System.Windows.Data Error: 40 : BindingExpression path error: 'MyTextUC2' property not found on 'object' ''UserControl1' (Name='text2')'. BindingExpression:Path=MyTextUC2; DataItem='UserControl1' (Name='text2'); target element is 'UserControl1' (Name='text2'); target property is 'MyTextUC1' (type 'String')
The problem is with this binding:
<local:UserControl1 x:Name="text2" MyTextUC1="{Binding MyTextUC2, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
It is looking for the MyTextUC2 property in UserControl1. It is because you set the DataContext in UserControl1's constructor to this (UserControl1). So every binding on UserControl1 will be evaluated against UserControl1's DataContext (which is itself).
If you change it to this:
<local:UserControl1 x:Name="text2"
MyTextUC1="{Binding MyTextUC2, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource AncestorType={x:Type local:UserControl2}}}" />
it should start working.
Related
I have the following classes:
MainWindow.xaml
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<local:MyControl Margin="8" MyProperty="{Binding MyPropertyParent}" />
</Grid>
MainWindowViewModel.cs
public class MainWindowViewModel : ObservableObject
{
public string MyPropertyParent
{
get
{
return _myProperty;
}
set
{
_myProperty = value;
this.OnPropertyChanged();
}
}
private string _myProperty;
public MainWindowViewModel()
{
MyPropertyParent = "IT WORKS!!!";
}
}
MyControl.xaml
<UserControl.DataContext>
<local:MyControlViewModel/>
</UserControl.DataContext>
<Border CornerRadius="4" Background="White" BorderThickness="1" BorderBrush="#FFB0AEAE">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Border Grid.Row="0" Background="#FFC6C6FB">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Margin="4" FontSize="16" VerticalAlignment="Center" HorizontalAlignment="Left"
Text="{Binding MyPropertyHack}" />
</Grid>
</Border>
</Grid>
</Border>
MyControl.xaml.cs
public partial class MyControl : UserControl
{
public string MyProperty
{
get { return (string)GetValue(MyPropertyProperty); }
set { SetValue(MyPropertyProperty, value); }
}
public static readonly DependencyProperty MyPropertyProperty = DependencyProperty.Register(
nameof(MyProperty),
typeof(string),
typeof(MyControl),
new PropertyMetadata(null, MyPropertyPropertyChanged));
private static void MyPropertyPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
((MyControl)o)._viewModel.MyPropertyHack = (string)e.NewValue;
}
public MyControlViewModel _viewModel;
public MyControl()
{
InitializeComponent();
if (System.ComponentModel.DesignerProperties.GetIsInDesignMode(this))
return;
_viewModel = App.Provider.GetService<MyControlViewModel>();
this.DataContext = _viewModel;
}
}
MyControlViewModel.cs
public class MyControlViewModel : ObservableObject
{
private string _myPropertyHack;
public string MyPropertyHack
{
get
{
return _myPropertyHack;
}
set
{
if (_myPropertyHack != value)
{
_myPropertyHack = value;
this.OnPropertyChanged();
}
}
}
public MyControlViewModel()
{
}
}
I'm trying to get it so MainWindow.xaml can pass a property set in its view model through to the DependencyProperty of MyControl. However, when I run it the TextBlock's Text field is empty and so "IT WORKS!!!" is not being shown on the screen and my breakpoint in MyPropertyPropertyChanged is not being hit.
What am I doing wrong?
You set the DataContext of your MyControl explicitly, which means the following binding of MyProperty will bind to the property MyPropertyParent on an instance of MyControlViewModel, which does not have this property. You can also see this as a binding error in the debug window.
<local:MyControl Margin="8" MyProperty="{Binding MyPropertyParent}" />
If you refernce the parent data context (an instance of MainWindowViewModel) through an ElementName or RelativeSource binding or using Parent, it will work, e.g. here I assume a parent as Window. Replace it with a parent in your control, to check it yourself.
<local:MyControl Margin="8" MyProperty="{Binding DataContext.MyPropertyParent, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
Another note, you set the data context twice, one time in XAML and then in the constructor of the control, which means one will definitely be overridden.
<UserControl.DataContext>
<local:MyControlViewModel/>
</UserControl.DataContext>
public MyControl()
{
InitializeComponent();
if (System.ComponentModel.DesignerProperties.GetIsInDesignMode(this))
return;
_viewModel = App.Provider.GetService<MyControlViewModel>();
this.DataContext = _viewModel;
}
In general, you should not set the DataContext manually on a UserControl, but inherit it as usual. As you can see this just introduces errors and breaks data binding. Apart from that, you already define a dependency property for the binding, so it is just redundant.
The expression
MyProperty="{Binding MyPropertyParent}"
is supposed to create a Binding to the MyPropertyParent property of the object in the current DataContext.
While you seem to assume that this is the DataContext object set in the MainWindow, it is actually the DataContext object set in the UserControl's constructor.
You should have noticed a data binding error message in the Output Window in Visual Studio, which tells you that a property named MyPropertyParent could not be found on an object of type MyControlViewModel.
In general, a control should never explicity set its own DataContext, since DataContext-based Bindings like the above won't work as expected.
The view model object assigned to the UserControl's DataContext is unknown to the rest of your application. The application will not be able to communicate with this view model.
It is not clear to me why you need a private VM, but I will assume that you simply did not show some part of the task.
DataContext is a property for use by bindings as the default source.
This property has no other function anymore.
And this property is not the only place where the ViewModel can be set.
For a private ViewModel, it, in principle, is not suitable, as already noted in another answer.
Since it sets the source for the bindings of the properties of the control at the place of its application, which is quite unexpected for any programmer.
One of the acceptable options: set an instance in the DataContext of the main element in UserControl.
<Border x:Name="PART_MainBorder"
CornerRadius="4" Background="White"
BorderThickness="1" BorderBrush="#FFB0AEAE">
<d:Border.DataContext>
<local:MyControlViewModel/>
</d:Border.DataContext>
public MyControl()
{
InitializeComponent();
if (System.ComponentModel.DesignerProperties.GetIsInDesignMode(this))
return;
_viewModel = App.Provider.GetService<MyControlViewModel>();
PART_MainBorder.DataContext = _viewModel;
}
I created a small File Browser Control:
<UserControl x:Class="Test.UserControls.FileBrowserControl"
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="44" d:DesignWidth="461" Name="Control">
<Grid Margin="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox Margin="3" Text="{Binding SelectedFile}" IsReadOnly="True" TextWrapping="Wrap" />
<Button HorizontalAlignment="Right" Margin="3" Width="100" Content="Browse" Grid.Column="1" Command="{Binding BrowseCommand}" />
</Grid>
</UserControl>
With the following code behind:
public partial class FileBrowserControl : UserControl
{
public ICommand BrowseCommand { get; set; }
//The dependency property
public static DependencyProperty SelectedFileProperty = DependencyProperty.Register("SelectedFile",
typeof(string),typeof(FileBrowserControl), new PropertyMetadata(String.Empty));
public string SelectedFile { get{ return (string)GetValue(SelectedFileProperty);} set{ SetValue(SelectedFileProperty, value);}}
//For my first test, this is a static string
public string Filter { get; set; }
public FileBrowserControl()
{
InitializeComponent();
BrowseCommand = new RelayCommand(Browse);
Control.DataContext = this;
}
private void Browse()
{
SaveFileDialog dialog = new SaveFileDialog();
if (Filter != null)
{
dialog.Filter = Filter;
}
if (dialog.ShowDialog() == true)
{
SelectedFile = dialog.FileName;
}
}
}
And I use it like this:
<userControls:FileBrowserControl SelectedFile="{Binding SelectedFile}" Filter="XSLT File (*.xsl)|*.xsl|All Files (*.*)|*.*"/>
(SelectedFile is Property of the ViewModel of the usercontrol using this control)
Currently the issue is that when I click on Browse, the textbox in the usercontrol is correctly updating, but the SelectedFile property of the viewmodel parent control is not set(no call to the set property).
If I set the Mode of the binding to TwoWay, I got this exception:
An unhandled exception of type 'System.StackOverflowException' occurred in Unknown Module.
So what did I do wrong?
The problem is that you set your UserControl's DataContext to itself in its constructor:
DataContext = this;
You should not do that, because it breaks any DataContext based Bindings, i.e. to a view model instance that is provided by property value inheritance of the DataContext property
Instead you would change the binding in the UserControl's XAML like this:
<TextBox Text="{Binding SelectedFile,
RelativeSource={RelativeSource AncestorType=UserControl}}" />
Now, when you use your UserControl and write a binding like
<userControls:FileBrowserControl SelectedFile="{Binding SelectedFile}" />
the SelectedFile property gets bound to a SelectedFile property in your view model, which should be in the DataContext inherited from a parent control.
Do not ever set DataContext of UserControl inside usercontrol:
THIS IS WRONG:
this.DataContext = someDataContext;
because if somebody will use your usercontrol, its common practice to set its datacontext and it is in conflict with what you have set previously
<my:SomeUserControls DataContext="{Binding SomeDataContext}" />
Which one will be used? Well, it depends...
The same applies to Name property. you should not set name to UserControl like this:
<UserControl x:Class="WpfApplication1.SomeUserControl" Name="MyUserControl1" />
because it is in conflict with
<my:SomeUserControls Name="SomeOtherName" />
SOLUTION:
In your control, just use RelativeSource Mode=FindAncestor:
<TextBox Text="{Binding SelectedFile, RelativeSource={RelativeSource AncestorType="userControls:FileBrowserControl"}" />
To your question on how are all those third party controls done: They use TemplateBinding. But TemplateBinding can be used only in ControlTemplate. http://www.codeproject.com/Tips/599954/WPF-TemplateBinding-with-ControlTemplate
In usercontrol the xaml represents Content of UserControl, not ControlTemplate/
Using this:
<userControls:FileBrowserControl SelectedFile="{Binding SelectedFile}" ...
The FileBrowserControl's DataContext has already been set to itself, therefore you are effectively asking to bind to the SelectedFile where the DataContext is the FileBrowserControl, not the parent ViewModel.
Give your View a name and use an ElementName binding instead.
SelectedFile="{Binding DataContext.SelectedFile, ElementName=element}"
I created a small File Browser Control:
<UserControl x:Class="Test.UserControls.FileBrowserControl"
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="44" d:DesignWidth="461" Name="Control">
<Grid Margin="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox Margin="3" Text="{Binding SelectedFile}" IsReadOnly="True" TextWrapping="Wrap" />
<Button HorizontalAlignment="Right" Margin="3" Width="100" Content="Browse" Grid.Column="1" Command="{Binding BrowseCommand}" />
</Grid>
</UserControl>
With the following code behind:
public partial class FileBrowserControl : UserControl
{
public ICommand BrowseCommand { get; set; }
//The dependency property
public static DependencyProperty SelectedFileProperty = DependencyProperty.Register("SelectedFile",
typeof(string),typeof(FileBrowserControl), new PropertyMetadata(String.Empty));
public string SelectedFile { get{ return (string)GetValue(SelectedFileProperty);} set{ SetValue(SelectedFileProperty, value);}}
//For my first test, this is a static string
public string Filter { get; set; }
public FileBrowserControl()
{
InitializeComponent();
BrowseCommand = new RelayCommand(Browse);
Control.DataContext = this;
}
private void Browse()
{
SaveFileDialog dialog = new SaveFileDialog();
if (Filter != null)
{
dialog.Filter = Filter;
}
if (dialog.ShowDialog() == true)
{
SelectedFile = dialog.FileName;
}
}
}
And I use it like this:
<userControls:FileBrowserControl SelectedFile="{Binding SelectedFile}" Filter="XSLT File (*.xsl)|*.xsl|All Files (*.*)|*.*"/>
(SelectedFile is Property of the ViewModel of the usercontrol using this control)
Currently the issue is that when I click on Browse, the textbox in the usercontrol is correctly updating, but the SelectedFile property of the viewmodel parent control is not set(no call to the set property).
If I set the Mode of the binding to TwoWay, I got this exception:
An unhandled exception of type 'System.StackOverflowException' occurred in Unknown Module.
So what did I do wrong?
The problem is that you set your UserControl's DataContext to itself in its constructor:
DataContext = this;
You should not do that, because it breaks any DataContext based Bindings, i.e. to a view model instance that is provided by property value inheritance of the DataContext property
Instead you would change the binding in the UserControl's XAML like this:
<TextBox Text="{Binding SelectedFile,
RelativeSource={RelativeSource AncestorType=UserControl}}" />
Now, when you use your UserControl and write a binding like
<userControls:FileBrowserControl SelectedFile="{Binding SelectedFile}" />
the SelectedFile property gets bound to a SelectedFile property in your view model, which should be in the DataContext inherited from a parent control.
Do not ever set DataContext of UserControl inside usercontrol:
THIS IS WRONG:
this.DataContext = someDataContext;
because if somebody will use your usercontrol, its common practice to set its datacontext and it is in conflict with what you have set previously
<my:SomeUserControls DataContext="{Binding SomeDataContext}" />
Which one will be used? Well, it depends...
The same applies to Name property. you should not set name to UserControl like this:
<UserControl x:Class="WpfApplication1.SomeUserControl" Name="MyUserControl1" />
because it is in conflict with
<my:SomeUserControls Name="SomeOtherName" />
SOLUTION:
In your control, just use RelativeSource Mode=FindAncestor:
<TextBox Text="{Binding SelectedFile, RelativeSource={RelativeSource AncestorType="userControls:FileBrowserControl"}" />
To your question on how are all those third party controls done: They use TemplateBinding. But TemplateBinding can be used only in ControlTemplate. http://www.codeproject.com/Tips/599954/WPF-TemplateBinding-with-ControlTemplate
In usercontrol the xaml represents Content of UserControl, not ControlTemplate/
Using this:
<userControls:FileBrowserControl SelectedFile="{Binding SelectedFile}" ...
The FileBrowserControl's DataContext has already been set to itself, therefore you are effectively asking to bind to the SelectedFile where the DataContext is the FileBrowserControl, not the parent ViewModel.
Give your View a name and use an ElementName binding instead.
SelectedFile="{Binding DataContext.SelectedFile, ElementName=element}"
I would like to binding a property defined in code-behind, and another one defined in a class in the same template with datatype.
Here's an example:
My class:
public class MyClass
{
public string name { get; set; }
public MyClass(string name)
{
this.name=name;
}
}
Code behind:
public string name2;
public MyView()
{
this.InitializeComponent();
name2 = "Tim";
}
<DataTemplate x:Key="MasterListViewItemTemplate" x:DataType="model:MyClass">
<StackPanel>
<TextBlock Text="{x:Bind name}"/>
<TextBlock Text="{x:Bind name2}"/>
</StackPanel>
</DataTemplate>
In this case obviously the first TextBlock has no problems.
I wish the second TextBlock refers to the code-behind and not in MyClass.
How can I do ?
You should set the second TextBlock's datacontext to be the current window.
I think this can be achieved by using binding expression like this
<TextBlock DataContext="{Binding ElementName=MyView, Path=.}" Text="{x:Bind name2}" />
where MyView in the binding expression is the x:Name property of the MyView window.
EDIT(WPF): This binding should work even for ResourceDictionary entries
<TextBlock DataContext="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=.}" Text="{Binding name2}" />
And something important, i see that your example, name2 is just defined in the constructor of the window. The right way to do it should look like this.
public string name2 { get; set; }
public MyView()
{
this.InitializeComponent();
this.name2 = "Tim";
}
I hope this helps.
Try the following:
In constructor of your CodeBehind:
public MyView()
{
this.InitializeComponent();
this.DataContext = this; //set the datacontext here
name2 = "Tim";
}
In your XAML:
<TextBlock Text="{Binding DataContext.name2, ElementName=MyView}"/>
First your binding always looks at the DataContext of itself first and if none is specified is traverses up the tree to owner by owner until a DataContext is assigned. It then looks there for the property to bind to. Since you have put the same property name in both textblocks and haven't specified any other means of binding they are both looking for the property in the same DataContext. In other words they are both looking at your MyClass object.
To make this work you will need to tell the Binding where to look for the property by specifying the binding more discretely.
<TextBlock Text="{Binding Name2, Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}" />
This is assuming your DataTemplate is used on an object that is in a MainWindow type. Play with it to get yours.
Also, you will need to change the property in the code behind to be a DependencyProperty (since it's a UIElement this is easiest.)
public string Name2
{
get { return (string)GetValue(Name2Property); }
set { SetValue(Name2Property, value); }
}
public static readonly DependencyProperty Name2Property =
DependencyProperty.Register(nameof(Name2), typeof(string), typeof(MainWindow));
If you do this your DataTemplate will bind to that value.
This is just to answer the question and help you understand it some but not how I would personally design a DataTemplate.
I have a ViewModel
public class ViewModel:ViewModelObject
{
public ViewModel()
{
ProjectionDataElementList = new ObservableCollection<ProjectionDataElement>();
}
public ObservableCollection<ProjectionDataElement> ProjectionDataElementList { get; set; }
private ProjectionDataElement _currentSelectedProjectionDataElement;
public ProjectionDataElement CurrentSelectedProjectionDataElement
{
get
{
return _currentSelectedProjectionDataElement;
}
set
{
_currentSelectedProjectionDataElement = value;
OnPropertyChanged("CurrentSelectedProjectionDataElement");
}
}
}
A control called ProjectorDisplayControl
<UserControl x:Class="Fast_Project.ProjectorDisplayControl"
..................>
<Viewbox>
<StackPanel>
<TextBlock Text="{Binding Path=TextBody}"/>
</StackPanel>
</Viewbox>
public partial class ProjectorDisplayControl : UserControl
{
public ProjectorDisplayControl()
{
InitializeComponent();
}
public static readonly DependencyProperty ProjectedDataProperty = DependencyProperty.Register("ProjectedData", typeof(ProjectionDataElement), typeof(ProjectorDisplayControl),
new PropertyMetadata(new PropertyChangedCallback((objectInstance, arguments) =>
{
ProjectorDisplayControl projectorDisplayControl = (ProjectorDisplayControl)objectInstance;
projectorDisplayControl._projectedData = (ProjectionDataElement)arguments.NewValue;
})));
public ProjectionDataElement ProjectedData
{
get
{
return (ProjectionDataElement)GetValue(ProjectedDataProperty);
}
set
{
SetValue(ProjectedDataProperty, value);
}
}
private ProjectionDataElement _projectedData
{
get
{
return this.DataContext as ProjectionDataElement;
}
set
{
this.DataContext = value;
}
}
}
That control is used in two places.
First place is in a ListBox where it works great:
<ListBox Grid.Column="0" ItemsSource="{Binding Path=ProjectionDataElementList}" Name="projectedDataListBox"
SelectedItem="{Binding Path=CurrentSelectedProjectionDataElement, UpdateSourceTrigger=PropertyChanged}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Border BorderThickness="1" BorderBrush="Black">
<controls:ProjectorDisplayControl x:Name="_projectorDisplay" ProjectedData="{Binding}"/>
</Border>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Second place is on top of a form where I set the form's DataContext with a ViewModel object. To make the ProjectorDisplayControl consume the CurrentSelectedProjectionDataElement in the ViewModel I would expect to have to do this:
<Window x:Class="Fast_Project.DisplayWindow"
................>
<Viewbox>
<StackPanel>
<controls:ProjectorDisplayControl x:Name="_projectorDisplay" ProjectedData="{Binding CurrentSelectedProjectionDataElement}"/>
</StackPanel>
</Viewbox>
That code gives me two binding errors:
System.Windows.Data Error: 40 :
BindingExpression path error:
'TextBody' property not found on 'object' ''ViewModel' (HashCode=2512406)'.
BindingExpression:Path=TextBody;
DataItem='ViewModel' (HashCode=2512406);
target element is 'TextBlock' (Name='');
target property is 'Text' (type 'String')
System.Windows.Data Error: 40 :
BindingExpression path error:
'CurrentSelectedProjectionDataElement' property not found on 'object' ''ProjectionDataElement' (HashCode=37561097)'.
BindingExpression:Path=CurrentSelectedProjectionDataElement;
DataItem='ProjectionDataElement' (HashCode=37561097);
target element is 'ProjectorDisplayControl' (Name='_projectorDisplay');
target property is 'ProjectedData' (type 'ProjectionDataElement')
When I watch the setter of the private property _projectedData on ProjectorDisplayControl which sets the DataContext I first see it get set with a valid value and then set to null.
In the DisplayWindow that holds the single ProjectorDisplayControl I can remove the binding to the CurrentSelectedProjectionDataElement and then I only get the first binding error message:
<Viewbox>
<StackPanel>
<controls:ProjectorDisplayControl x:Name="_projectorDisplay" />
</StackPanel>
</Viewbox>
The first binding error makes me feel like the ProjectorDisplayControl's DataContext is getting set with a ViewModel object when the DisplayWindow's DataContext is getting set. But from what I've read controls don't share the same data context with their parent window unless you set it so.
I've treating the binding path for the ProjectorDisplayControl.ProjectedData in DisplayWindow like it was a ProjectionDataElement object as the second error message states.
<Viewbox>
<StackPanel>
<controls:ProjectorDisplayControl x:Name="_projectorDisplay" ProjectedData="{Binding }"/>
</StackPanel>
</Viewbox>
Then is tells me:
Cannot create default converter to perform 'one-way' conversions between types 'Fast_Project.ViewModel' and 'Fast_Project.ProjectionDataElement'
Like it really was the ViewModel object like I thought it was in the first place...
Anyways I suspect that the root of my problem lies in the how I see the ProjectorDisplayControl having a DataContext set to the ViewModel object. Anyone see where I messed up?
Thank you all for your help!
The solution was:
ProjectorDisplayControl
<StackPanel>
<TextBlock Text="{Binding Path=ProjectedData.TextBody, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type controls:ProjectorDisplayControl}}}"/>
</StackPanel>
DisplayWindow
<StackPanel>
<controls:ProjectorDisplayControl x:Name="_projectorDisplay" ProjectedData="{Binding Path=ViewModel.CurrentSelectedProjectionDataElement,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type controls:DisplayWindow}}}"/>
</StackPanel>
Child control inherits the Dependency property value (or DataContext in this case) from their Parents.
When you are using the UserControl inside the itemTempalte, then the DataContext of each item is already ProjectionDataElement and hence the DataContext of your control is set to ProjectionDataElement.
When you are using the control inside parent it will inherit its DataContext.
The problem is that you are setting your ProjectedData property on control and not using it inside it. If you want that each of your control should be bind to the value set on ProjectedData then you should update the bindings like:
<UserControl x:Class="Fast_Project.ProjectorDisplayControl"
..................>
<Viewbox>
<StackPanel>
<TextBlock Text="{Binding Path=ProjectedData.TextBody, RelativeSource={RelativeSource Self}"/>
</StackPanel>
For you second error, you must be setting the DataContext of that window to ProjectionDataElement somewhere that is why is searching it inside it.