INotifyPropertyChanged用于依赖属性

时间:2013-08-11 12:02:56

标签: wpf data-binding dependency-properties inotifypropertychanged

我有一个INotifyPropertyChanged数据结构,它是用户控件的依赖项。其中一个数据结构的属性绑定到控件的一个元素。

MyData.cs(使用MVVM Light实现INotifyPropertyChanged):

public class MyData : ObservableObject
{
    private string _text;

    public string Text
    {
        get { return _text; }
        set { Set(() => Text, ref _text, value); }
    }
    public override string ToString()
    {
        return Text;
    }
}

TextControl.xaml:

<UserControl x:Class="UITester.TextControl"
         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" 
         Name="This"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <TextBox Text="{Binding ElementName=This, Path=Data.Text, Mode=TwoWay}"></TextBox>
    </Grid>
</UserControl>

TextControl.xaml.cs:

public partial class TextControl : UserControl
{
    public static MyData DefaultData = new MyData {Text = "Default"};

    public MyData Data
    {
        get { return (MyData)GetValue(DataProperty); }
        set { SetValue(DataProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Text.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty DataProperty =
        DependencyProperty.Register("Data", typeof(MyData), typeof(TextControl), new PropertyMetadata(DefaultData));

    public TextControl()
    {
        InitializeComponent();
    }
}

现在我想要的是依赖属性DataProperty每当其中一个内部属性发生更改时就会更新(即通知任何感兴趣的人已经更改)。

我用两种方式测试:

  • 通过将依赖项属性Data绑定到Label

  • 通过在MyControlText中创建依赖项属性MainWindow,并将控件的DataLabel绑定到它。

MainWindow.xaml:

<Window x:Class="UITester.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit" xmlns:uiTester="clr-namespace:UITester"
    Title="MainWindow" Height="350" Width="525"
    x:Name="me">
<StackPanel DataContext="{Binding ElementName=me}">

    <uiTester:TextControl x:Name="MyControl" Data="{Binding ElementName=me, Path=MyControlText}"></uiTester:TextControl>
    <Label Content="{Binding ElementName=MyControl, Path=Data}"></Label>
    <Label Content="{Binding ElementName=me, Path=MyControlText}"></Label>

    <Button Click="SetButtonClick">Set</Button>
    <Button Click="ResetButtonClick">Reset</Button>

</StackPanel>

</Window>

MainWindow.xaml.cs:

public partial class MainWindow : Window
{
    private readonly Dictionary<int, int> _selected = new Dictionary<int, int>();

    public MainWindow()
    {
        InitializeComponent();

        //list.ItemsSource = _selected.Values;
    }

    private void SetButtonClick(object sender, RoutedEventArgs e)
    {
        MyControlText = new MyData{Text = "new"};
    }

    private void ResetButtonClick(object sender, RoutedEventArgs e)
    {
        MyControlText = null;
    }



    public MyData MyControlText
    {
        get { return (MyData)GetValue(MyControlTextProperty); }
        set { SetValue(MyControlTextProperty, value); }
    }

    // Using a DependencyProperty as the backing store for MyControlText.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty MyControlTextProperty =
        DependencyProperty.Register("MyControlText", typeof(MyData), typeof(MainWindow), new PropertyMetadata(new MyData{ Text = ""}));
}

当我更改控件中的TextBox时,我希望依赖项属性Data更新,因此我希望我的Label都更新。

这不起作用,所以我尝试注册明确更改的内容:

public partial class TextControl : UserControl
{
    public static MyData DefaultData = new MyData {Text = "Default"};

    public MyData Data
    {
        get { return (MyData)GetValue(DataProperty); }
        set { SetValue(DataProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Text.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty DataProperty =
        DependencyProperty.Register("Data", typeof(MyData), typeof(TextControl), new PropertyMetadata(DefaultData) { PropertyChangedCallback = TextPropertyChanged });

    private static void TextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var c = (TextControl) d;
        if (e.NewValue != null)
        {
            ((MyData) e.NewValue).PropertyChanged += (s, ea) =>
                {
                    c.GetBindingExpression(DataProperty).UpdateSource();
                };
        }
    }

    public TextControl()
    {
        InitializeComponent();
    }
}

每当我设置一个新的依赖项属性时,我就会这样想,我将注册它的任何属性发生变化的事件。在处理程序中,我希望更新绑定到依赖项proeprty的所有元素。但这也不起作用......

我知道控件内部的绑定工作因为当我在UpdateSource放置一个断点时代码确实中断了,但我不明白如何更新整个依赖属性(以及标签必然会这样。)

更新: 我也尝试调用'd.InvalidateProperty(DataProperty);'而不是'c.GetBindingExpression(DataProperty).UpdateSource();'但它也不起作用......

1 个答案:

答案 0 :(得分:0)

使用MultiBinding可以让它部分工作。 这是一个更清晰的例子:

  • Data有两个string字段 - ABstring的{​​{1}}表示形式是两个Data的串联。
  • 控件string允许使用转换器设置NotifyEditorA数据。
  • B' an propagates the change to the underlyingDataConverter,知道如何在两个IMultiValueConverterstring之间进行转换

<强> Data.cs

Data

<强> NotifyEditor.xaml:

public class Data : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    string a, b;

    public string A
    {
        get { return a; }
        set
        {
            a = value;
            if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("A"));
        }
    }

    public string B
    {
        get { return b; }
        set
        {
            b = value;
            if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("B"));
        }
    }

    public override string ToString()
    {
        return string.Concat(A, B);
    }
}

<强> NotifyEditor.xaml.cs:

<UserControl x:Class="SqlConnection.NotifyEditor"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             Name="Me">
    <StackPanel>
        <TextBox Name="TextA"></TextBox>
        <TextBox Name="TextB"></TextBox>
    </StackPanel>
</UserControl>

<强> DataConverter.cs:

public partial class NotifyEditor : UserControl
{
    public Data MyData
    {
        get { return (Data)GetValue(MyDataProperty); }
        set { SetValue(MyDataProperty, value); }
    }

    // Using a DependencyProperty as the backing store for MyData.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty MyDataProperty =
        DependencyProperty.Register("MyData", typeof(Data), typeof(NotifyEditor), new PropertyMetadata(new Data()));

    public NotifyEditor()
    {
        InitializeComponent();

        var b = new MultiBinding
            {
                Converter = new DataConverter(),
                Mode = BindingMode.TwoWay,
                NotifyOnSourceUpdated = true,
                NotifyOnTargetUpdated = true

            };
        b.Bindings.Add(new Binding("Text") { Source = TextA, Mode = BindingMode.TwoWay});
        b.Bindings.Add(new Binding("Text") { Source = TextB, Mode = BindingMode.TwoWay });

        SetBinding(MyDataProperty, b);
    }
}

<强> MainWindow.xaml:

public class DataConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return new Data { A = values[0] as string, B = values[1] as string };
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
    {
        var d = value as Data;
        if (d != null)
            return new object[] { d.A, d.B };
        return null;
    }
}

<强> MainWindow.xaml.cs:

<Window x:Class="SqlConnection.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:SqlConnection"
        Name="me"
        Title="MainWindow" Height="350" Width="525">
    <StackPanel>
        <local:NotifyEditor x:Name="ne"></local:NotifyEditor>
        <TextBox Text="{Binding ElementName=ne, Path=MyData, Mode=OneWay}"></TextBox>
        <Button Click="SetClick">Set</Button>
    </StackPanel>
</Window>

在我的测试中,我创建了public partial class MainWindow : Window { public Data Data { get { return (Data)GetValue(DataProperty); } set { SetValue(DataProperty, value); } } // Using a DependencyProperty as the backing store for Data. This enables animation, styling, binding, etc... public static readonly DependencyProperty DataProperty = DependencyProperty.Register("Data", typeof(Data), typeof(MainWindow), new PropertyMetadata(new Data())); public MainWindow() { InitializeComponent(); } private void SetClick(object sender, RoutedEventArgs e) { ne.MyData = new Data { A = "new A", B = "new B" }; } } ,其中包含一个名为MainWindowNotifyEditor的{​​{1}}绑定到控件的Label。另外,我添加了一个按钮,为Content设置Data。这确实有效。当我更改Data底部NotifyEditor更新中的TextBox时,当我单击按钮时,所有字段也会更新(因此转换器的两个方向都有效)。< / p>

这只是部分解决方案的原因是我仍然无法将NotifyEditor s TextBox绑定到NotifyEditor的依赖项属性或视图模型的属性。如果我加上这个:

Data

到我的窗口(其中MainWindow <local:NotifyEditor MyData="{Binding ElementName=me, Path=Data, Mode=TwoWay}"></local:NotifyEditor> <Label Content="{Binding ElementName=me, Path=Data}"></Label> <Button Click="SetMyDataClick">Set My Data</Button> 的名称,而me是类型MainWindow的依赖项属性),它不起作用。我认为通过设置绑定Data,我在控件内定义的多重绑定被取消,然后数据的非传播方式应该传播。

如何保持我在控件中定义的多重绑定,但仍能从外部添加更多绑定?