Question

I want to make a chain binding like this: I have a usercontrol with a dependencyproperty inside a window with similar dependencyproperty. I want to bind the dependencyproperty of the usercontrol to the dependencyproperty of the window.

I created a sample project to demonstrate my problem:

UserControl1 XAML:

<UserControl x:Class="WpfApplication1.UserControl1"
             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="300" d:DesignWidth="300" DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <Grid>
        <Label Content="{Binding Caption}"/>
    </Grid>
</UserControl>

UserControl1 C#:

public partial class UserControl1 : UserControl
  {
    public static readonly DependencyProperty CaptionProperty = DependencyProperty.Register("Caption", typeof(string), typeof(UserControl1));
    public string Caption
    {
      get { return (string)GetValue(CaptionProperty); }
      set { SetValue(CaptionProperty, value); }
    }

    public UserControl1()
    {
      InitializeComponent();
    }
  }

MainWindow XAML:

<Window xmlns:WpfApplication1="clr-namespace:WpfApplication1"  x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525" DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Label Content="{Binding Caption, Mode=OneWay}"/>
        <WpfApplication1:UserControl1 x:Name="uc" Caption="{Binding Caption, Mode=OneWay}"  Grid.Row="1"/>
    </Grid>
</Window>

MainWindow C#:

public partial class MainWindow : Window
  {
    public static readonly DependencyProperty CaptionProperty = DependencyProperty.Register("Caption", typeof(string), typeof(MainWindow));
    public string Caption
    {
      get { return (string)GetValue(CaptionProperty); }
      set { SetValue(CaptionProperty, value); }
    }

    public MainWindow()
    {
      InitializeComponent();
      (new Thread(() => { Thread.Sleep(2000); Dispatcher.Invoke(() => { uc.Caption = "YYY"; Caption = "XXX"; }); })).Start();
    }
  }

The thing is that when I set Caption to "XXX" (of the Window) I would expect it to also notify the usercontrol and update its Caption, but it doesn't. I would like to avoid Attached dependency properties and avoid code behind as much as possible. Any ideas?

Thanks for any efforts.

Was it helpful?

Solution 2

You can create a user control along the lines of...

<UserControl x:Class="ChainBinding.CaptionGuy"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             >
    <Grid>
            <Label Name="CaptionLabel"/>
    </Grid>
</UserControl>

...and instrument it with your dependency property like this...

    #region Caption (DependencyProperty)
    public string Caption
    {
        get { return (string)GetValue(CaptionProperty); }
        set { SetValue(CaptionProperty, value); }
    }
    public static readonly DependencyProperty CaptionProperty =
        DependencyProperty.Register("Caption", typeof(string), typeof(CaptionGuy),
          new PropertyMetadata{PropertyChangedCallback = CaptionChanged});
    private static void CaptionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        CaptionGuy cg = d as CaptionGuy;
        if (cg != null && e.NewValue!=null)
        {
            cg.CaptionLabel.Content = e.NewValue.ToString();
        }
    }
    #endregion

Then you can deploy it in a WPF application like this...

<Grid>
    <chainBinding:CaptionGuy Caption="{Binding VmCaption}"/>
</Grid>

And the corresponding View Model (or code-behind if that's your design) would look like this...

public class ViewModel : INotifyPropertyChanged
{
    public ViewModel()
    {
        Dispatcher dispatcher = Dispatcher.CurrentDispatcher;
        DispatcherTimer dt = new DispatcherTimer(new TimeSpan(0,0,0,5), DispatcherPriority.Normal,
            delegate
            {
                VmCaption = DateTime.Now.ToString("G");
            }, dispatcher);
        dt.Start();
    }
    private string _vmCaption;
    public string VmCaption
    {
        [DebuggerStepThrough]
        get { return _vmCaption; }
        [DebuggerStepThrough]
        set
        {
            if (value != _vmCaption)
            {
                _vmCaption = value;
                OnPropertyChanged("VmCaption");
            }
        }
    }
    #region INotifyPropertyChanged Implementation
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged(string name)
    {
        var handler = System.Threading.Interlocked.CompareExchange(ref PropertyChanged, null, null);
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(name));
        }
    }
    #endregion
}

This example just updates the caption with the time every 5 seconds.

Of course this answer uses a callback on the dependency property, but this is a matter where circumstantial practicality wins out over declarative programming.

OTHER TIPS

The problem is with your bindings. By default binding looks for a property on the DataContext property of the control. Every binding has a source and in your case its the DataContext property of the control. So your binding is evaluating to DataContext.Caption. What your really want is to make the source of the binding, the window that has the Caption property. So change the code as indicated below. I tested it on my machine and it works. Remember to initialize the Caption property of the window.

new:

<UserControl x:Class="WpfApplication1.UserControl1"
         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="300" d:DesignWidth="300">
<Grid>
    <Grid>
        <Label Content="{Binding Path=Caption, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=UserControl}}"/>
    </Grid>
</Grid>

new:

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:current="clr-namespace:WpfApplication1"
    Title="MainWindow" Height="350" Width="525" >
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition/>
    </Grid.RowDefinitions>
    <Label Content="{Binding Caption, Mode=OneWay, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=Window}}"/>
    <current:UserControl1 x:Name="uc" Caption="{Binding Path=Caption, Mode=OneWay, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=Window}}"  Grid.Row="1"/>
</Grid>

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top