我已经将代码缩减到尽可能小的测试用例,但它仍然相当大;我希望它非常简单。
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());
}
}
答案 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更干净,更典型):
<Label Content="{Binding Converter={StaticResource BazToString}}" />
和IMultiValueConverter
用于属性MultiBinding
和Name
。例如:将转换器更改为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
<Label>
<Label.Content>
<MultiBinding Converter="{StaticResource BazToString}">
<Binding Path="Name" />
<Binding Path="Bars" />
</MultiBinding>
</Label.Content>
</Label>
类添加一个属性,以返回对象本身并绑定到它。您还需要为此属性举起Baz
个事件。例如:将属性添加到类PropertyChanged
:
Baz
添加public Baz This
{
get { return this; }
}
事件:
PropertyChanged
更改private void Bars_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
NotifyPropertyChanged("Bars");
NotifyPropertyChanged("This");
}
的<{1}}:
Binding