简体   繁体   English

为什么ListView中的此DataTemplate无法自动更新其绑定?

[英]Why is this DataTemplate inside a ListView not updating its binding automatically?

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: 我有这个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>

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) 在我们运行此示例时出现的窗口中,我可以右键单击并选择“添加中间”,以便将Mid的实例添加到Top数据上下文中,并且列表视图将相应地更新,显示数字0(按照转换逻辑)

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 ). 但是,当我单击“将机器人添加到选定的中段”时, Bot实例添加到选定的Mid (我可以使用断点进行验证),但是列表视图不会相应更新(我希望0更改为1,但对于Mid特定实例,不会再次调用该转换器。

Why does this change not trigger an update of the GUI? 为什么此更改不会触发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: 我知道我可以通过一些技巧来解决此问题(例如,将数据上下文设置为null并返回,或者可以通过使用依赖项属性来调用显式更新),但是我有两个原因想要避免这种情况:

  • My actual code is more complex than this MCVE and it would look very ugly. 我的实际代码比此MCVE更复杂,并且看起来非常难看。
  • I've already sprinkled all my (actual) classes with all the required the 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. 我已经在所有(实际)类中添加了所有必需的ObservableCollectionINotifyPropertyChanged接口,正是这样,我才不需要执行手动更新-所以我觉得在这种情况下应该进行自动更新,除非我已经错过了一些东西。

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? 为什么此更改不会触发GUI的更新?

Because the source property of the binding ( BotList ) is not updated. 因为绑定的源属性( BotList )没有更新。 The converter is invoked only when the data bound property is updated. 仅在更新数据绑定属性时才调用转换器。

You could use a MultiBinding as suggested by @Sinatr or you could 您可以使用@Sinatr建议的MultiBinding ,也可以

  • bind directly to the Count property of the collection: 直接绑定到集合的Count属性:

     <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. Mid类中实现INotifyPropertyChanged接口,并在每次添加项目时引发BotList属性的PropertyChanged事件。 Handle CollectionChanged . 处理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. 您还可以将转换逻辑移动到视图模型,绑定到该模型的一个属性,并在每次刷新绑定时都为其引发PropertyChanged

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM