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}"
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 use Prism as the MVVM container for WPF, and I want to define a series of commands in the parent component, and then let the child component execute different commands.
How can I write to be successful?
// parent control
<StackPanel>
<local:TestUserControl1 xxx="hello" yyy="TestJumpOtherTabCommand"></local:TestUserControl1>
<local:TestUserControl1 xxx="world" yyy="TestModifyModelDtoCommand"></local:TestUserControl1>
</StackPanel>
// vm
public DelegateCommand TestJumpOtherTabCommand { get; set; }
public DelegateCommand TestModifyModelDtoCommand { get; set; }
<UserControl x:Class="GearboxManagementSystem.Views.TestUserControl1">
<Grid>
<TextBlock Text="xxx"></TextBlock>
<Button Command="yyy"></Button>
</Grid>
</UserControl>
When designing custom user controls, you can define dependency properties. Go to the code-behind of the control and add the code below. I define a Text dependency property for the text that you want to bind in the TextBlock and a Command dependency property for the Button.
public partial class TestUserControl1 : UserControl
{
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register(nameof(Text), typeof(string), typeof(TestUserControl1));
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register(nameof(Command), typeof(ICommand), typeof(TestUserControl1));
public TestUserControl1()
{
InitializeComponent();
}
public string Text
{
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
public ICommand Command
{
get => (ICommand)GetValue(CommandProperty);
set => SetValue(CommandProperty, value);
}
}
In your user control XAML, please add columns or rows, because as it is the TextBlock and the Button will overlap in the same cell of the Grid (only one is visible, the other one is occluded). Furthermore, add bindings that use RelativeSource with an AncestorType of your custom control. This will bind the target property to the dependency property defined in your user control.
<UserControl x:Class="WpfApp1.TestUserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:YourApp">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Text, RelativeSource={RelativeSource AncestorType={x:Type local:TestUserControl1}}}"/>
<Button Grid.Column="1" Command="{Binding Command, RelativeSource={RelativeSource AncestorType={x:Type local:TestUserControl1}}}"/>
</Grid>
</UserControl>
Finally, set or bind the dependency properties of your user control in the parent control.
<StackPanel>
<local:TestUserControl1 Text="Hello" Command="{Binding TestJumpOtherTabCommand}"/>
<local:TestUserControl1 Text="World" Command="{Binding TestModifyModelDtoCommand}"/>
</StackPanel>
Here are valuable resources that you should read before continuing to implement your own control. They will provide the basics on what dependency properties are and how to define or use them.
Dependency properties overview
Custom Dependency Properties
How to: Implement a Dependency Property
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 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.
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.