当WinUI 3中的listview集合发生变化时,如何强制listview中的组合框更新?

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

我相信这是

ListView
工作原理的一个怪癖。我已经包含了我认为正在发生的问题描述,以及我的问题:我如何解决它?

问题描述

我有一个

ListView
,在
ComboBox
内部指定了
ItemTemplate
ComboBox
显示
ListView
中显示的项目的可编辑属性。

根据官方文档中的“提示”(在“项目选择”标题下),我正在处理

Loaded
事件以初始化
SelectedItem
中的
ComboBox
属性。 (我这样做是因为数据是从异步源中提取的,并且可能无法立即可用 - 我无法单独在 XAML 中进行数据绑定工作)。

当页面首次加载时,效果完美。然而,当我随后尝试将项目添加到集合中时,我得到了一些意外的行为......尽管使用相同的函数(下面示例中的

SetItems()
)来进行两次更新。使用下面示例中的 print 语句,我已经确认,当页面首次加载时,输出符合预期:列表中的每个项目都有一个输出行,并且每个
SelectedValue
中的
ComboBox
(在下面的示例中显示每个项目的
Status
属性)是正确的。

当我稍后更新列表时(在页面和

ListView
初始加载之后),但是,通过调用相同的
SetItem()
函数(可能在将项目添加到列表之后),同一 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 在每次集合更改时更新,就像页面首次加载时的情况一样?

更新

打印语句的意外输出似乎不是随机的 - 它似乎始终是

ListView
中的最后一项。因此,每当调用
SetItem()
函数时(在页面加载的初始时间之后),只有
ComboBox
中最后一项的
ListView
才会触发其
Loaded
事件。其他项目上的
ComboBox
被打乱了,但是......几乎就像它们都向上移动了一个(因此
Status
中下一个项目的
ListView
而不是它们所在的项目)。

c# xaml listview combobox winui-3
2个回答
0
投票

听起来您需要使用 SelectionChanged 事件处理程序而不是加载。如果我没记错的话,你可以同时使用它们,但这不是必需的。您可以在页面的方法中使用一行代码(即具有“this.Initialize();”的代码),它将在启动时加载您想要的任何内容。

我不能 100% 确定你的程序是做什么的。尝试添加其他事件处理程序;有一些可以申请。


0
投票

您不需要为此使用

Loaded

让我向您展示一个简单的例子:

主页.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);
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.