XAML 在WinUI 3中更新ListView的ListView

slmsl1lt  于 11个月前  发布在  其他
关注(0)|答案(1)|浏览(95)

我正在使用WinUI 3为朋友和我自己创建一个小工具。这主要是一个学习活动,作为一个新手,我觉得我学到了很多东西,玩得很开心。然而,我遇到了一些令人困惑的行为,我不确定我是否完全理解。
我正在处理具有以下结构的数据:

- "Root":
     - Item 1:
          - Sub-Item
          - Sub-Item
          - ...
     - Item 2:
          - Sub-Item
          - Sub-Item
          - ...
     - ...

字符串
我希望能够查看,编辑,添加和删除一个简单的UI结构中的项目。
为了简单起见,让“根”对象是ObservableCollection<ListItem>,其中ListItem定义如下:

public class ListItem : BindableBase
{
    private ObservableCollection<OString> _items;
    private string _header;

    public string Header { get { return _header; } set { SetProperty(ref _header, value); } }
    public ObservableCollection<OString> Items { get { return _items; } set { SetProperty(ref _items, value); } }

    public ListItem()
    {
        Header = "Header";
        Items = new ObservableCollection<OString>();
    }
}

public class OString : BindableBase
{
    private string _value;
    public string Value { get { return _value; } set { SetProperty(ref _value, value); } }
    
    public OString()
    {
        Value = "Default String";
    }
}


BindableBase是一个实现INotifyPropertyChanged的类,OString只是string的 Package 器,它扩展了BindableBase。我使用的BindableBase与Microsoft的sample applications相同。
我已经创建了一个UserControl,它可以显示一个名为StringListObservableCollection<OString>。下面是该控件的XAML。

<UserControl ... > 
<!--xmlns:custom points to where OString is defined, omitted for brevity-->
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid Grid.Row="0" >
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="Auto" />
            </Grid.ColumnDefinitions>
            <TextBlock Text="{x:Bind Header}" VerticalAlignment="Center" HorizontalAlignment="Left"/>
            <Button Name="AddItem" Click="AddItem_Click" Grid.Column="1" Content="+"/>
        </Grid>
        <ListView Name="StringList" ItemsSource="{x:Bind ItemsSource}" Grid.Row="1" SelectionMode="None">
            <ListView.ItemTemplate>
                <DataTemplate x:DataType="custom:OString" >
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="*" />
                            <ColumnDefinition Width="Auto" />
                        </Grid.ColumnDefinitions>
                        <TextBox Text="{x:Bind Value, Mode=TwoWay}" Grid.Column="0"/>
                        <Button Name="RemoveItem" Tag="{x:Bind}" Click="RemoveItem_Click" Grid.Column="1">-</Button>
                    </Grid>
                </DataTemplate>
            </ListView.ItemTemplate> 
        </ListView>
    </Grid>
</UserControl>


下面是StringList.xaml.cs的C#代码。

public sealed partial class StringList : UserControl
{
    public DependencyProperty HeaderProperty = DependencyProperty.Register(nameof(Header), typeof(string), typeof(DMActionList), new(""));
    public DependencyProperty ItemsSourceProperty = DependencyProperty.Register(nameof(ItemsSource), typeof(ObservableCollection<OString>), typeof(DMActionList), new(new ObservableCollection<OString>()));

    public string Header
    {
        get => (string)GetValue(HeaderProperty);
        set => SetValue(HeaderProperty, value);
    }
    public ObservableCollection<OString> ItemsSource
    {
        get => (ObservableCollection<OString>)GetValue(ItemsSourceProperty);
        set => SetValue(ItemsSourceProperty, value);
    }
    public StringList()
    {
        this.InitializeComponent();
    }

    private void AddItem_Click(object sender, RoutedEventArgs e)
    {
        ItemsSource?.Add(new());
    }

    private void RemoveItem_Click(object sender, RoutedEventArgs e)
    {
        if ((sender as Button).Tag != null && (sender as Button).Tag is OString)
        {
            ItemsSource?.Remove((sender as Button).Tag as OString);
        }
    }
}


StringList工作正常。当我正确使用控件和绑定时,我可以显示ObservableCollection<OString>,编辑项目,添加新项目和删除项目。我还可以在代码中编辑项目,UI正确更新。这会照顾数据结构中的每个项目,但我需要多个StringList来完全实现我正在处理的数据。
所以,我创建了一个(名字不太好)ListOfStringListTest控件。下面是这个控件的XAML:

<UserControl ... >
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid Grid.Row="0" >
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="Auto" />
            </Grid.ColumnDefinitions>
            <TextBlock Text="{x:Bind Header}" VerticalAlignment="Center" HorizontalAlignment="Left"/>
            <Button Name="AddItem" Click="AddItem_Click" Grid.Column="1" Content="+"/>
        </Grid>
        <ListView Name="ListList" ItemsSource="{x:Bind ItemsSource}" Grid.Row="1" SelectionMode="None">
            <ListView.ItemTemplate>
                <DataTemplate x:DataType="local:ListItem" >
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="*" />
                        </Grid.RowDefinitions>
                        <Grid Grid.Row="0">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*" />
                                <ColumnDefinition Width="Auto" />
                            </Grid.ColumnDefinitions> 
                            <TextBox Text="{x:Bind Header, Mode=TwoWay}" Grid.Column="0"/>
                            <Button Name="RemoveItem" Tag="{x:Bind}" Click="RemoveItem_Click" Grid.Column="1">-</Button>
                        </Grid>
                        <local:StringList Header="{x:Bind Header}" ItemsSource="{x:Bind Items}" Grid.Row="1"/>
                    </Grid>
                </DataTemplate>
            </ListView.ItemTemplate> 
        </ListView>
    </Grid>
</UserControl>


此控件的C#以及ListItem类如下所示:

public sealed partial class ListOfStringListTest : UserControl
{
    public DependencyProperty ItemsSourceProperty = DependencyProperty.Register(nameof(ItemsSource), typeof(ObservableCollection<ListItem>), typeof(ListOfStringListTest), new(new ObservableCollection<ListItem>()));
    public DependencyProperty HeaderProperty = DependencyProperty.Register(nameof(Header), typeof(string), typeof(ListOfStringListTest), new("Header"));
        public ObservableCollection<ListItem> ItemsSource
    {
        get => (ObservableCollection<ListItem>)GetValue(ItemsSourceProperty);
        set => SetValue(ItemsSourceProperty, value);
    }
    public string Header
    {
        get => (string)GetValue(HeaderProperty);
        set => SetValue(HeaderProperty, value);
    }
           
    public ListOfStringListTest()
    {
        this.InitializeComponent();
    }

    private void RemoveItem_Click(object sender, RoutedEventArgs e)
    {
        ItemsSource.Remove((sender as Button).Tag as ListItem);
    }

    private void AddItem_Click(object sender, RoutedEventArgs e)
    {
        ItemsSource.Add(new());
    }
}

public class ListItem : BindableBase
{
    private ObservableCollection<OString> _items;
    private string _header;
    public string Header { get { return _header; } set { SetProperty(ref _header, value); } }
    public ObservableCollection<OString> Items { get { return _items; } set { SetProperty(ref _items, value); } }

    public ListItem()
    {
        Header = "Header";
        Items = new ObservableCollection<OString>();
    }
}


现在这个控件并没有像我预期的那样工作。我可以添加和删除项目,每个项目上的按钮似乎都在创建新的子项目。我在StringList中的AddItem_Click()上设置了一个断点,每次单击按钮,ItemsSource列表中的项目数量都会增加。然而,总体UI永远不会更新。我猜测这与ListView如何在DataTemplate中调整大小和放置其项有关,但我不确定情况是否如此或该如何处理。
奇怪的是,每当我把类似的“列表列表”控件放在按钮的Flyout中时,StringList控件的功能就像预期的那样。然而,当我删除包含StringList控件的项目,然后立即向列表中添加另一个新项目时,StringList控件显示相同,具有相同的旧数据和子项。此数据在代码中的绑定对象中不存在,我检查过了,它是空的。控制子项的添加/删除按钮也不起作用。如果我的解释不好,我很抱歉,我明白这是一个令人困惑的数据结构和一个非常奇怪的问题。对于新手来说,这看起来好像UI元素被“重用,“尽管我的添加按钮总是调用new().再次,在代码隐藏中,数据显示为空列表。然而,UI以某种方式与数据解同步或解除绑定。
我尝试过在不使用UserControl.的情况下重新创建一个类似的数据结构,它似乎可以工作,但访问特定的列表/项/子项数据感觉很混乱;添加和删除项并不简单,我无法弄清楚如何删除子项。
我也试过使用BindableList<T>(),但我也遇到了其他问题,我在调试这些问题时遇到了麻烦,因为崩溃只是在App.g.i.cs中放置了一个基本上没有帮助的异常消息,并让调试器在那里中断。
我开始怀疑UserControl没有传播ListView所期望的某种事件或属性。
很抱歉这篇文章太长了,我可能不知道如何搜索正确的关键字。谢谢你的时间。

pb3skfrl

pb3skfrl1#

当我进一步研究这个问题时,Stack Overflow实际上给出了一个非常有用和被低估的答案。
ListView in WinUI maintains ListBox text after reset
事实证明,这种行为是故意的,不知何故。它被称为虚拟化和容器回收。禁用此功能的方法是将ListView.ItemsPanel属性更改为包含StackPanelItemsPanelTemplate,它不实现虚拟化。
我把这个留着,以防其他人也有类似的问题。不过,我确实想知道,如果没有容器回收,我是否可以保持虚拟化的性能优势。文档在这方面不是很清楚。
相关MS文档:https://learn.microsoft.com/en-us/dotnet/desktop/wpf/advanced/optimizing-performance-controls?view=netframeworkdesktop-4.8https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.itemscontrol?view=windowsdesktop-8.0https://learn.microsoft.com/en-us/windows/uwp/debug-test-perf/optimize-gridview-and-listviewhttps://learn.microsoft.com/en-us/windows/uwp/debug-test-perf/listview-and-gridview-data-optimizationhttps://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.virtualizingstackpanel.isvirtualizing?view=netframework-4.0
再次感谢您的时间,任何人阅读这一点。

相关问题