Reputation: 393
I keep trying to make this hurdle in WPF, and I think I've found a solution, albeit an ugly one.
The scenario is as follows:
Code...
Parent View
<UserControl DataContext="{Binding Source={StaticResource Locator}, Path=ParentControlLocator}">
<my:Child Demo="{Binding Path=DataContext.DemoTextAlpha, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl, AncestorLevel=1}}" />
</UserControl>
Parent Class View Model
public class ParentClassViewModel : BaseViewModel
{
private string _demoTextAlpha = "Some Alpha text";
public string DemoTextAlpha
{
get
{
return this._demoTextAlpha;
}
set
{
this._demoTextAlpha = value;
this.NotifyPropertyChange("DemoTextAlpha");
}
}
}
Child View
<UserControl DataContext="{Binding Source={StaticResource Locator}, Path=ChildControlLocator}">
<TextBlock Text="{Binding Path=SomeProperty}" />
</UserControl>
Child View Code Behind
public partial class Child : UserControl
{
public Child()
{
InitializeComponent();
}
public static readonly DependencyProperty DemoProperty =
DependencyProperty.Register("Demo",
typeof(string),
typeof(Child),
new FrameworkPropertyMetadata()
{
PropertyChangedCallback = OnDemoChanged,
BindsTwoWayByDefault = true
});
public string Demo
{
get { return this.GetValue(DemoProperty).ToString(); }
set { this.SetValue(DemoProperty, value); }
}
private static void OnDemoChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = (Child)d;
var viewModel = (ChildViewModel)control.DataContext;
viewModel.SomeProperty = (string)e.NewValue;
}
}
Child View Model
public class ChildViewModel : BaseViewModel
{
private string _someProperty;
public string SomeProperty
{
get
{
return _someProperty;
}
set
{
_someProperty = value;
this.NotifyPropertyChange("SomeProperty");
}
}
}
Ok, so this WORKS. What I'm trying to achieve is better/ more elegant code, particularly as it regards to this statement.
<my:Child Demo="{Binding Path=DataContext.DemoTextAlpha, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl, AncestorLevel=1}}" />
Even that I could live with, as far as elegance goes, but one thing that is bothering me right now is that when I type
Path=DataContext.DemoTextAlpha
The intellisense drops when I try to drill down inside the DataContext. So I have to be extra careful to type everything right.
So - is there any different way to make the properties of the DataContext appear in intellisense, or is there an alternative way to achieve the same thing that I'm doing now?
Thanks.
EDIT to Clarify
When I put something like this instead of specifying the relative source as in the above examples...
<my:Child Demo="{Binding DemoTextAlpha}"/>
I receive an error...
System.Windows.Data Error: 40 : BindingExpression path error: 'DemoTextAlpha' property not found on 'object' ''ChildViewModel' (HashCode=34126977)'. BindingExpression:Path=DemoTextAlpha; DataItem='ChildViewModel' (HashCode=34126977); target element is 'Child' (Name=''); target property is 'Demo' (type 'String')
Upvotes: 1
Views: 7641
Reputation: 44038
The DataContext
(along with a lot of other properties such as FontSize
) is "Inherited" along the visual tree. Therefore this:
<UserControl DataContext="{Binding Source={StaticResource Locator}, Path=ParentControlLocator}">
<my:Child Demo="{Binding Path=DataContext.DemoTextAlpha, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl, AncestorLevel=1}}" />
</UserControl>
Is exactly the same as this:
<UserControl DataContext="{Binding Source={StaticResource Locator}, Path=ParentControlLocator}">
<my:Child Demo="{Binding DemoTextAlpha}"/>
</UserControl>
With regards to the Intellisense support, I don't know what VS version you're using, but I'm using VS 2010 Pro with ReSharper 6.1 and it adds Intellisense support if you specify the d:DataContext
value:
<UserControl x:Class="...etc."
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TheViewModelNamespace"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" d:DataContext="{d:DesignInstance local:ViewModel}">
Edit:
Ok.. let's analize what you're doing here:
1 - Binding the UserControl to the ParentVM:
ParentVM -> UserControl
2 - Using RelativeSource To Grab some property from ParentVM and place it into a Custom DP you created in the Child control
ParentVM -> UserControl -> Child Control
3 - In the OnPropertyChanged of the custom DP, setting that same value to the ChildVM
ParentVM -> UserControl -> Child Control -> ChildVM
Do you realize you're using the View (User Control, Child Control) as an intermediate to share some properties between 2 View Models? Why don't you just
ParentVM -> ChildVM
Which would be easier, cleaner and really MVVM?
Either put a reference from the ParentVM directly to the ChildVM, or use something like a Messenger
pattern to have indirect communication between them.
Upvotes: 5
Reputation: 13194
DataContext is inherited:
<UserControl DataContext="{Binding Source={StaticResource Locator}, Path=ParentControlLocator}">
<my:Child Demo="{Binding DemoTextAlpha}" />
</UserControl>
If, ina different scenario, your child control has a different DataContext specified and you still need to bind to a property of your parent control's DataContext
, using ElementName
is probably nicer:
<UserControl x:Name="Parent" DataContext="{Binding Source={StaticResource Locator}, Path=ParentControlLocator}">
<my:Child Demo="{Binding Path=DataContext.DemoTextAlpha, ElementName=Parent}" />
</UserControl>
Upvotes: 2