![](/img/trans.png)
[英]Binding Dictionary Value to Dependency Property of a UserControl WPF
[英]WPF binding not updating, possibly because of value converter or dependency property
我已將代碼縮減為盡可能少的測試用例,但仍然很大。 我希望它非常簡單。
Foo具有Bar和Baz類型的ObservableCollection。 Baz在Foo中保留對Bar對象的引用的ObservableCollection。
主窗口有一個Foo中所有Baz對象的ListBox,這些對象通過轉換器使其成為純字符串。 SelectedItem設置為窗口的DependencyProperty,以方便參考。 在窗口的后面,列出了Foo中所有Bar對象的列表,可以通過此DependencyProperty(SelectedBaz)添加/刪除。 出於調試目的,添加了另一個ListBox,其中顯示了SelectedBaz的Bar對象。
發生的情況是,SelectedBaz被更新,Foo持有的ObservableCollection中的Baz被更新,Foo的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());
}
}
您沒有在Binding
提供Path
,因此您要綁定到整個對象,並且WPF不會跟蹤沒有路徑的綁定的屬性更改通知:
<Label Content="{Binding Converter={StaticResource BazToString}}" />
有兩種方法可以解決此問題(我建議采用第一種方法,因為它更干凈,而且對於WPF而言是典型的):
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");
}
更改Label
Binding
:
<Label Content="{Binding Path=This, Converter={StaticResource BazToString}}" />
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.