为什么ListView中的DataTemplate不会自动更新其绑定?

问题描述 投票:0回答:2

我有以下类层次结构:

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

我有这个XAML窗口:

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

使用这些处理程序:

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

而这个转换器(作为任意转换器的替代品):

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

在我们运行此示例时出现的窗口中,我可以右键单击并选择“Add mid”,以便将Mid的实例添加到Top数据上下文中,并相应地更新列表视图,显示数字0(根据转换逻辑)

但是,当我单击“将bot添加到选定的中间”时,Bot的实例将添加到选定的Mid(我可以使用断点验证这一点),但列表视图不会相应更新(我希望0更改为1,但是没有为Mid的特定实例再次调用转换器。

为什么此更改不会触发GUI的更新?

我知道我可以解决一些问题(比如将数据上下文设置为null并返回,或者可能通过使用依赖项属性调用显式更新),但我有两个原因可以避免这种情况:

  • 我的实际代码比这个MCVE更复杂,看起来非常难看。
  • 我已经把所有必需的ObservableCollections和INotifyPropertyChanged接口都放到我的所有(实际)类中,这样我就不需要执行手动更新 - 所以我觉得在这种情况下应该发生自动更新,除非我'我错过了什么。
c# wpf data-binding
2个回答
1
投票

为什么此更改不会触发GUI的更新?

因为绑定的源属性(BotList)未更新。仅在更新数据绑定属性时才调用转换器。

您可以使用@Sinatr建议的MultiBinding,也可以

  • 直接绑定到集合的Count属性: <TextBlock Text="{Binding BotList.Count}" />
  • INotifyPropertyChanged类中实现Mid接口,并在添加项目时为PropertyChanged属性引发BotList事件。处理CollectionChanged

您也可以将转换逻辑移动到视图模型,绑定到此属性的属性,并在需要刷新绑定时为其提升PropertyChanged


1
投票

您可以使用多重绑定:

<TextBlock>
    <TextBlock.Text>
        <MultiBinding Converter="{StaticResource TriggersToString}">
            <Binding Path="BotList" />
            <Binding Path="BotList.Count" />
        </MultiBinding>
    </TextBlock.Text>
</TextBlock>

和多值转换器:

public class TriggersToString : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) =>
        (values[0] as IList<Bot>)?.Count.ToString(); // first binding

    ...
}

这样,无论何时更新任何一个绑定,都会调用转换器。

© www.soinside.com 2019 - 2024. All rights reserved.