I have the following class hierarchy:
namespace WpfBindingProblem
{
public class Top
{
public IList<Mid> MidList { get; } = new ObservableCollection<Mid>();
}
public class Mid
{
public IList<Bot> BotList { get; } = new ObservableCollection<Bot>();
}
public class Bot
{
}
}
And I have this XAML window:
<Window x:Class="WpfBindingProblem.MainWindow"
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:WpfBindingProblem"
mc:Ignorable="d"
Title="MainWindow" Height="217.267" Width="333.686">
<Window.DataContext>
<local:Top/>
</Window.DataContext>
<Window.Resources>
<local:TriggersToString x:Key="TriggersToString"/>
</Window.Resources>
<Grid>
<ListView Margin="10" ItemsSource="{Binding MidList}" x:Name="ThatList">
<ListView.Resources>
<DataTemplate DataType="{x:Type local:Mid}">
<TextBlock Text="{Binding BotList, Converter={StaticResource TriggersToString}}" />
</DataTemplate>
</ListView.Resources>
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Add mid" Click="AddMid"/>
<MenuItem Header="Add bot to selected mid" Click="AddBot" />
</ContextMenu>
</ListView.ContextMenu>
<ListView.View>
<GridView>
<GridViewColumn/>
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
With these handlers:
namespace WpfBindingProblem
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void AddMid(object sender, RoutedEventArgs e)
{
if(DataContext is Top p)
{
p.MidList.Add(new Mid());
}
}
private void AddBot(object sender, RoutedEventArgs e)
{
if(ThatList.SelectedItem is Mid c)
{
c.BotList.Add(new Bot());
}
}
}
}
And this converter (as a stand-in for any arbitrary converter):
namespace WpfBindingProblem
{
[ValueConversion(typeof(IList<Bot>), typeof(string))]
public class TriggersToString : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if(value is IList<Bot> list)
{
return list.Count.ToString();
}
throw new InvalidOperationException();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new InvalidOperationException();
}
}
}
In the window that appears when we run this example, I can right click and choose "Add mid" so an instance of Mid
is added to the Top
data context, and the list view is updated accordingly, showing the number 0 (as per the conversion logic)
However, when I click "Add bot to selected mid", an instance of Bot
is added to the selected Mid
(I can verify this using breakpoints), but the list view is not updated accordingly (I expected 0 to be changed to 1, but the converter is not called again for that particular instance of Mid
).
Why does this change not trigger an update of the GUI?
I know I can work around this with some hacks (like setting the data context to null
and back, or possibly by invoking explicit updates using dependency properties), but there are two reasons why I'd like to avoid that:
ObservableCollection
s and the INotifyPropertyChanged
interfaces, precisely so that I wouldn't need to perform manual updates — so I feel like automatic updates should happen in this case, unless I've missed something. You can use multi binding:
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource TriggersToString}">
<Binding Path="BotList" />
<Binding Path="BotList.Count" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
and multi value converter:
public class TriggersToString : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) =>
(values[0] as IList<Bot>)?.Count.ToString(); // first binding
...
}
This way the converter is called whenever either of bindings is updated.
Why does this change not trigger an update of the GUI?
Because the source property of the binding ( BotList
) is not updated. The converter is invoked only when the data bound property is updated.
You could use a MultiBinding
as suggested by @Sinatr or you could
bind directly to the Count
property of the collection:
<TextBlock Text="{Binding BotList.Count}" />
implement the INotifyPropertyChanged
interface in the Mid
class and raise the PropertyChanged
event for the BotList
property whenever an item is added to it. Handle CollectionChanged
.
You might also move your convert logic to the view model, bind to a property of this one and also raise the PropertyChanged
for it whenever you want the binding to be refreshed.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.