WPF绑定未更新,可能是因为值转换器或依赖项属性

时间:2018-05-02 03:40:05

标签: c# wpf data-binding

我已经将代码缩减到尽可能小的测试用例,但它仍然相当大;我希望它非常简单。

Foo有一个Bar和Baz类型的ObservableCollection。 Baz在Foo中保留了对Bar对象的引用的ObservableCollection。

主窗口有一个包含Foo中所有Baz对象的ListBox,它通过一个转换器使它们成为一个纯字符串。 SelectedItem被设置为窗口的DependencyProperty以便于参考。稍后在窗口中,列出了Foo中所有Bar对象的列表,可以通过此DependencyProperty(SelectedBaz)添加/删除。出于调试目的,添加了另一个ListBox,它显示了SelectedBaz的Bar对象。

正在发生的事情是SelectedBaz被更新,Foo持有的ObservableCollection中的Baz被更新,Fz的Baz集合的CollectionChanged事件被触发,但带有转换器的ListBox永远不会更新。

我试过在没有任何运气的情况下洒一些'Mode = TwoWay'(因为它们没有效果而被移除)。我已经尝试过使用SelectedValue和SelectedItem(似乎SelectedItem是从我的研究中做到这一点的正确方法,所以我就这样做了)。我尝试在添加/删除按钮单击中手动触发Baz ListBox中绑定目标的更新,但这没有效果。

然后我感到沮丧并试图破解它并使用带有SelectedIndex,MultiBinding,MultiValueConverter等的整数等等,我发现我遇到了同样的问题;更新源但不是Baz ListBox绑定中的目标。

所以,我们来了。

Foo.cs

using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Windows.Data;

namespace WpfApp1
{
    public class Foo : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public Foo()
        {
            bars.CollectionChanged += Bars_CollectionChanged;
            bazes.CollectionChanged += Bazes_CollectionChanged;

            bars.Add(new Bar("Bar 1"));
            bars.Add(new Bar("Bar 2"));
            bars.Add(new Bar("Bar 3"));

            bazes.Add(new Baz("Baz 1")
            {
                Bars = { bars[0] }
            });

            bazes.Add(new Baz("Baz 2")
            {
                Bars = { bars[1] }
            });

            bazes.Add(new Baz("Baz 3")
            {
                Bars = { bars[0], bars[1], bars[2] }
            });
        }

        public ObservableCollection<Bar> Bars
        {
            get
            {
                return bars;
            }
        }

        public ObservableCollection<Baz> Bazes
        {
            get
            {
                return bazes;
            }
        }

        private void Bars_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            NotifyPropertyChanged("Bars");
        }

        private void Bazes_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            NotifyPropertyChanged("Bazes");
        }

        private void NotifyPropertyChanged([CallerMemberName] string caller = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(caller));
        }

        private ObservableCollection<Bar> bars = new ObservableCollection<Bar>();

        private ObservableCollection<Baz> bazes = new ObservableCollection<Baz>();
    }

    public class Bar : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public Bar(string name)
        {
            this.name = name;
        }

        public string Name
        {
            get
            {
                return name;
            }

            set
            {
                if (name != value)
                {
                    name = value;
                    NotifyPropertyChanged();
                }
            }
        }

        private void NotifyPropertyChanged([CallerMemberName] string caller = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(caller));
        }

        private string name = "";
    }

    public class Baz : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public Baz(string name)
        {
            this.name = name;
            bars.CollectionChanged += Bars_CollectionChanged;
        }

        public ObservableCollection<Bar> Bars
        {
            get
            {
                return bars;
            }
        }

        public string Name
        {
            get
            {
                return name;
            }

            set
            {
                if (name != value)
                {
                    name = value;
                    NotifyPropertyChanged();
                }
            }
        }

        private void Bars_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            NotifyPropertyChanged("Bars");
        }

        private void NotifyPropertyChanged([CallerMemberName] string caller = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(caller));
        }

        private ObservableCollection<Bar> bars = new ObservableCollection<Bar>();

        private string name = "";
    }

    public class BazToString : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            Baz b = value as Baz;
            string s = "Baz is " + b.Name + " ";

            foreach (Bar bar in b.Bars)
            {
                s += "with a Bar " + bar.Name + " ";
            }

            return s;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

MainWindow.xaml

<Window x:Class="WpfApp1.MainWindow"
        x:Name="Main"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <local:Foo />
    </Window.DataContext>
    <Window.Resources>
        <local:BazToString x:Key="BazToString" />
    </Window.Resources>
    <Grid>
        <ListBox Width="300" Height="150" Margin="10,10,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" ItemsSource="{Binding Bazes}" SelectedItem="{Binding ElementName=Main, Path=SelectedBaz}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Label Content="{Binding Converter={StaticResource BazToString}}" />
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <ListBox x:Name="ListBoxBarSelector" Width="300" Height="150" Margin="10,170,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" ItemsSource="{Binding Bars}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Label Content="{Binding Name}" />
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <ListBox Width="300" Height="150" Margin="320,170,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" ItemsSource="{Binding ElementName=Main, Path=SelectedBaz.Bars}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Label Content="{Binding Name}" />
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <Button Width="100" Height="30" Margin="10,330,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Click="ButtonAddBar_Click" Content="Add Bar" />
        <Button Width="100" Height="30" Margin="120,330,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Click="ButtonDelBar_Click" Content="Delete Bar" />
    </Grid>
</Window>

MainWindow.xaml.cs

using System.Windows;

namespace WpfApp1
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        public Baz SelectedBaz
        {
            get
            {
                return (Baz)GetValue(SelectedBazProperty);
            }

            set
            {
                SetValue(SelectedBazProperty, value);
            }
        }

        private void ButtonAddBar_Click(object sender, RoutedEventArgs e)
        {
            Bar bar = ListBoxBarSelector.SelectedItem as Bar;

            if (bar != null && SelectedBaz != null && !SelectedBaz.Bars.Contains(bar))
            {
                SelectedBaz.Bars.Add(bar);
            }
        }

        private void ButtonDelBar_Click(object sender, RoutedEventArgs e)
        {
            Bar bar = ListBoxBarSelector.SelectedItem as Bar;

            if (bar != null && SelectedBaz != null && SelectedBaz.Bars.Contains(bar))
            {
                SelectedBaz.Bars.Remove(bar);
            }
        }

        private static readonly DependencyProperty SelectedBazProperty =
            DependencyProperty.Register(
                "SelectedBaz",
                typeof(Baz),
                typeof(MainWindow),
                new PropertyMetadata());
    }
}

1 个答案:

答案 0 :(得分:0)

您未在SELECT * FROM ( SELECT ROW_NUMBER() OVER (PARTITION BY field_2 ORDER BY (field_2 ),random()) AS r, t.* FROM mytable t) x WHERE x.r <= 2 中提供Path,因此您绑定了整个对象,WPF不会跟踪没有路径的绑定的属性更改通知:

Binding

有两种方法可以解决这个问题(我推荐第一种方法,因为WPF更干净,更典型):

  1. 您可以将<Label Content="{Binding Converter={StaticResource BazToString}}" /> IMultiValueConverter用于属性MultiBindingName。例如:
  2. 将转换器更改为Bars(我还建议在更改为多值转换器后为转换器提供更合适的名称):

    IMultiValueConverter

    使用适当的属性绑定更改public class BazToString : IMultiValueConverter { public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { var bazName = (string)values[0]; var bars = (IEnumerable<Bar>)values[1]; string s = "Baz is " + bazName + " "; foreach (Bar bar in bars) { s += "with a Bar " + bar.Name + " "; } return s; } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) { return null; } } Label的绑定:

    MultiBinding
    1. 更脏的解决方案是向<Label> <Label.Content> <MultiBinding Converter="{StaticResource BazToString}"> <Binding Path="Name" /> <Binding Path="Bars" /> </MultiBinding> </Label.Content> </Label> 类添加一个属性,以返回对象本身并绑定到它。您还需要为此属性举起Baz个事件。例如:
    2. 将属性添加到类PropertyChanged

      Baz

      添加public Baz This { get { return this; } } 事件:

      PropertyChanged

      更改private void Bars_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { NotifyPropertyChanged("Bars"); NotifyPropertyChanged("This"); } 的<{1}}:

      Binding