我正在使用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
,它可以显示一个名为StringList
的ObservableCollection<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
所期望的某种事件或属性。
很抱歉这篇文章太长了,我可能不知道如何搜索正确的关键字。谢谢你的时间。
1条答案
按热度按时间pb3skfrl1#
当我进一步研究这个问题时,Stack Overflow实际上给出了一个非常有用和被低估的答案。
ListView in WinUI maintains ListBox text after reset
事实证明,这种行为是故意的,不知何故。它被称为虚拟化和容器回收。禁用此功能的方法是将
ListView.ItemsPanel
属性更改为包含StackPanel
的ItemsPanelTemplate
,它不实现虚拟化。我把这个留着,以防其他人也有类似的问题。不过,我确实想知道,如果没有容器回收,我是否可以保持虚拟化的性能优势。文档在这方面不是很清楚。
相关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
再次感谢您的时间,任何人阅读这一点。