简体   繁体   中英

Binding Dependency Property to Current DataContext Property

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:

  • I have a custom user control with a custom dependency property.
  • The user controls can be nested inside of my other user controls.
  • Each of my user controls has a data context that is specified by a locator (I am following the MVVM pattern)
  • I want to bind the custom dependency property to a value in the parent view model.

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')

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.

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>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM