XAML 在WinUI3中,当列表视图集合发生变化时,如何强制列表视图中的组合框更新?

uajslkp6  于 2023-08-01  发布在  其他
关注(0)|答案(2)|浏览(102)

我相信这是ListView工作方式的一个怪癖。我已经包括了我认为正在发生的问题描述,以及我的问题:我该怎么办?

问题描述

我有一个ListView,在ItemTemplate中指定了一个ComboBoxComboBox显示ListView中显示的项目的可编辑属性。
根据official documentation中的“提示”(在标题“项目选择”下),我正在处理Loaded事件以初始化ComboBox中的SelectedItem属性。(我这样做是因为数据是从异步源中提取的,可能无法立即使用-我无法单独使用XAML中的数据绑定)。
当页面第一次加载时,它可以完美地工作。然而,当我尝试在之后向集合中添加项时-我得到一些意外的行为...尽管使用相同的函数(下面示例中的SetItems())来执行两次更新。使用下面示例中的print语句,我已经确认当页面第一次加载时,输出是预期的:列表中的每个项目都有一个输出行,并且每个ComboBox中的SelectedValue(在下面的示例中显示每个项目的Status属性)是正确的。
但是,当我稍后更新列表时(在页面和ListView初始加载之后),通过调用相同的SetItem()函数(可能在向列表添加一个项之后),来自相同的print语句的输出不是预期的。只出现一个print语句,并从列表中打印一个看似随机的项,沿着不正确的Status。这是意外的,因为我正在清除ObservableCollection,并重新添加所有项目(包括新项目)。
在页面XAML中:

<ListView ItemsSource="{x:Bind MyCollection}">
    <ListView.ItemTemplate>
        <DataTemplate x:DataType="models:MyDataType">
            <!-- Some things omitted for brevity -->
            <ComboBox x:Name="MyComboBox"
                      Tag="{x:Bind ID}"
                      SelectedValuePath="Status"
                      Loaded="MyComboBox_Loaded"></ComboBox>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

字符串
集合、事件处理程序和集合变更代码背后的代码:

// The collection.
public ObservableCollection<MyDataType> MyCollection { get; set; }

// The ComboBox Loaded event handler.
private void MyComboBox_Loaded(object sender, RoutedEventArgs e)
{
    // Get the ID of the item this CombBox is for.
    ComboBox comboBox = sender as ComboBox;
    string id = comboBox.Tag as string;

    // Get a reference to the item in the collection.
    MyDataType item = this.MyCollection.Where(i => i.ID == id).FirstOrDefault();

    // Initialize the ComboBox based on the item's property.
    comboBox.SelectedValue = item.Status;

    // Wire up the Selection Changed event (doing this after initialization prevents the handler from running on initialization).
    comboBox.SelectionChanged += this.MyComboBox_SelectionChanged;

    // Print statement for debugging.
    System.Diagnostics.Debug.WriteLine("ComboBox for item " + item.ID + " loaded and set to " + item.Status);
}

// The function used to update the observable collection (and thus bound listview).
public void SetItems(List<MyDataType> items)
{
    // Clear the collection and add the given items.
    this.MyCollection.Clear();
    items.ForEach(i => this.MyCollection.Add(i));
}

我所想的

我相信这是ListView工作原理的一个怪癖。我记得在过去的某个地方阅读到过.NET的不同部分中的项目重复器的经验,每个项目的UI实际上可能不是每次都创建新的;出于效率原因,UI项可以被回收和重用。我猜这里发生的事情是相似的;并且“重用”的项不触发Loaded事件,因为它们从未被重新加载。

那么,我如何绕过它,并强制ComboBox在每次集合更改时更新,就像页面首次加载时一样?
更新

print语句的意外输出似乎不是随机的-它似乎始终是ListView中的最后一项。因此,每当调用SetItem()函数时(在页面加载的初始时间之后),只有ListView中最后一项的ComboBox才会触发其Loaded事件。其他物品上的ComboBox是乱序的,但是……几乎就像他们都被向上移动了一个(所以有ListView中的下一个项目的Status,而不是他们所在的项目)。

zf2sa74q

zf2sa74q1#

您不需要使用Loaded来实现此目的。
让我给你看一个简单的例子:

MainPage.xaml

<Page
    x:Class="ListViewWithComboBoxExample.MainPage"
    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:local="using:ListViewWithComboBoxExample"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:models="using:ListViewWithComboBoxExample.Models"
    x:Name="RootPage"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
    mc:Ignorable="d">

    <Grid RowDefinitions="Auto,*">
        <Button
            Grid.Row="0"
            Click="AddRandomItemsButton_Click"
            Content="Add random item" />
        <ListView
            Grid.Row="1"
            ItemsSource="{x:Bind MyCollection, Mode=OneWay}">
            <ListView.ItemTemplate>
                <DataTemplate x:DataType="models:MyDataType">
                    <!--
                        Using 'Binding' with 'ElementName' and 'Path' is the trick here
                        to access code-behind properties from the DataTemplate.
                    -->
                    <ComboBox
                        ItemsSource="{Binding ElementName=RootPage, Path=MyComboBoxOptions}"
                        SelectedValue="{x:Bind Status, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                        Tag="{x:Bind ID, Mode=OneWay}" />
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>
</Page>

字符串

MainPage.xaml.cs

using ListViewWithComboBoxExample.Models;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Collections.ObjectModel;

namespace ListViewWithComboBoxExample;

public sealed partial class MainPage : Page
{
    public MainPage()
    {
        this.InitializeComponent();
    }

    public ObservableCollection<string> MyComboBoxOptions { get; set; } = new()
    {
        "Active",
        "Inactive",
        "Unknown",
    };

    // ObservableCollections notify the UI when their contents change,
    // not when the collection itself changes.
    // You need to instantiate this here or in the constructor.
    // Otherwise, the binding will fail.
    public ObservableCollection<MyDataType> MyCollection { get; } = new();

    private void SetItem(MyDataType item)
    {
        MyCollection.Add(item);
    }

    private void AddRandomItemsButton_Click(object sender, RoutedEventArgs e)
    {
        MyCollection.Clear();

        Random random = new();
        int itemsCount = random.Next(100);

        for (int i = 0; i < itemsCount; i++)
        {
            MyDataType newItem = new()
            {
                ID = random.Next(100).ToString(),
                Status = MyComboBoxOptions[random.Next(MyComboBoxOptions.Count)],
            };

            SetItem(newItem);
        }
    }
}

gpfsuwkq

gpfsuwkq2#

听起来您需要使用SelectionChanged事件处理程序而不是加载。如果我没记错的话,你可以两个都用,但没有必要。您可以在页面的方法中使用一行代码(它具有“this.Initialize();“),它将在启动时加载您想要的任何内容。
我不是100%确定你的程序是做什么的。请尝试添加其他事件处理程序;有几个是可以适用。

相关问题