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>
But from what I've read controls don't share the same data context with their parent window unless you set it so.
- Wrong. It's exactly the opposite, except in the case ofItemsControls
(such asListBox
), where each UI item is associated to a data Item from the ItemsSource collection. – Federico Berasategui