0

我有一个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;
    }
}

文本控件.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>

文本控制.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每当其内部属性之一发生更改时,依赖属性得到更新(即通知任何感兴趣的人它已更改)。

我通过两种方式对此进行测试:

  • 通过将依赖属性绑定DataLabel

  • MyControlText并通过在其中创建依赖属性并将控件和 aMainWindow绑定到它。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,因此我希望我的两个Labels 都更新。

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

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

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

我知道控件内部的绑定是有效的,因为当我在UpdateSource代码处设置断点时确实会中断,但我不明白如何更新整个依赖属性(以及绑定到它的标签)。

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

4

1 回答 1

0

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

  • Data有两个string字段 -AB。的string表示是两个sData的串联。string
  • 该控件允许使用转换器NotifyEditor进行设置A和数据`。B' an propagates the change to the underlying
  • DataConverter是一个IMultiValueConverter知道如何在两个strings 和Data

数据.cs

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:

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

NotifyEditor.xaml.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);
    }
}

数据转换器.cs:

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:

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

MainWindow.xaml.cs:

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" };
    }
}

在我的测试中,我创建了MainWindow一个包含一个命名的NotifyEditor和一个绑定到控件的. 另外,我添加了一个按钮,用于设置. 这确实有效。当我更改底部更新中的任何一个时,当我单击按钮时,所有字段也会更新(因此转换器的两个方向都可以工作)。LabelContentDataDataNotifyEditorTextBoxNotifyEditorTextBox

这只是部分解决方案的原因是我仍然无法将NotifyEditors绑定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是类型的依赖属性Data)它不起作用。我认为通过设置绑定MyData="{Binding ElementName=me, Path=Data, Mode=TwoWay}",我在控件中定义的多重绑定被取消,然后没有数据以应有的方式传播。

如何将我定义的多重绑定保留在我的控件内,但仍然能够从外部添加更多绑定?

于 2013-08-12T04:29:44.993 回答