WPF Chain Binding

本文关键字:Binding Chain WPF | 更新日期: 2023-09-27 18:02:03

我想做一个像这样的链绑定:我有一个usercontrol与dependencyproperty在一个窗口与类似的dependencyproperty。我想把usercontrol的dependencyproperty绑定到window的dependencyproperty。

我创建了一个示例项目来演示我的问题:

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();
    }
  }

主窗口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>

主窗口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();
    }
  }

事情是,当我设置标题为"XXX"(的窗口),我希望它也通知用户控制和更新其标题,但它没有。我想避免附加依赖属性,并尽可能避免代码落后。什么好主意吗?

谢谢你的努力

WPF Chain Binding

问题出在绑定上。默认情况下,绑定在控件的DataContext属性上查找属性。每个绑定都有一个源,在本例中是控件的DataContext属性。你的绑定会评估为datacontext。caption。您真正想要的是创建绑定的源,即具有Caption属性的窗口。因此,按照如下所示更改代码。我在我的机器上测试了一下,效果不错。记住要初始化窗口的Caption属性。

新:

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

新:

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

您可以按照…

创建用户控件
<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>

…然后用你的依赖属性设置它,像这样…

    #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

然后你可以像这样将它部署到WPF应用程序中…

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

和相应的视图模型(或代码隐藏,如果这是你的设计)看起来像这样…

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
}

这个例子只是每5秒更新一次标题的时间。

当然,这个答案在依赖属性上使用了回调,但这是一个环境实用性胜过声明性编程的问题。