WPF- ItemsControl 的 ListView 与 TextBoxes 性能问题
WPF- ListView of ItemsControl with TextBoxes performance issue
我正在努力寻找解决 WPF 性能问题的方法。
我有一个 C# 项目,它从串行端口接收一些 "messages" 并将它们显示在 GUI 上。此 GUI 允许编辑每条接收到的消息的字节,还有一个 "save" 按钮可将编辑后的消息保存到文件中。
对于 GUI,将 MVVM 与 WPF 结合使用,我为实现上述要求所做的工作如下:
- 包含 "SerialMessageViewModel".
的可观察集合的 "MainViewModel" class
- 每个 "SerialMessageViewModel" class 包含串行消息(字符串)的 "name" 及其数据字节。
View 使用 ListView 显示 "SerialMessageViewModel" 的集合,对于每个 SerialMessage,我使用 ItemsControl 来显示 MessageData 字节。因为我希望这些字节是可编辑的,所以我使用 TextBox 作为 ItemsControl 的 ItemTemplate。
这里的想法是让 ItemsControl 像 GUI 一样工作 "HexEditor"。
除了 ListView 的滚动速度非常慢之外,一切实际上都工作正常。
这使得应用程序使用起来非常令人沮丧..
我知道像这样将 ItemsControl 的 ListView 与 TextBoxes 一起使用确实会生成很多 TextBoxes,但我找不到另一种方法来制作类似的东西。
Visual Studio 分析工具显示滚动延迟是由 "layout" 时间引起的。
关于如何解决这个问题有什么建议吗?
非常感谢!
查看XAML
<Window x:Class="WpfListIssue.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:WpfListIssue"
mc:Ignorable="d"
Title="MainWindow" Height="800" Width="600">
<Window.Resources>
<ResourceDictionary>
<local:ByteToHexStringConverter x:Key="ByteToHexStringConverter" />
</ResourceDictionary>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ProgressBar Grid.Row="0" Height="20" IsIndeterminate="True" />
<ListView Grid.Row="1" Margin="10" ItemsSource="{Binding Messages}">
<ListView.View>
<GridView>
<GridViewColumn Header="Message Name" Width="120" DisplayMemberBinding="{Binding MessageName}" />
<GridViewColumn Header="Message Data" Width="400" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding MessageData}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Value, Converter={StaticResource ByteToHexStringConverter}}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
视图模型
public class MainViewModel : ViewModelBase
{
private ObservableCollection<SerialMessageViewModel> _messages;
public ObservableCollection<SerialMessageViewModel> Messages
{
get { return _messages; }
set
{
if (Equals(value, _messages)) return;
_messages = value;
RaisePropertyChanged();
}
}
public MainViewModel()
{
GetMessages();
}
private void GetMessages()
{
// Simulates the reception of messages from serial
Messages = new ObservableCollection<SerialMessageViewModel>();
Random rand = new Random();
for (int i = 0; i < 100; i++)
{
byte[] data = new byte[50];
rand.NextBytes(data);
SerialMessageViewModel message = new SerialMessageViewModel("Message n." + i, data);
Messages.Add(message);
}
}
}
public class SerialMessageViewModel : ViewModelBase
{
private string _messageName;
private ObservableCollection<NotifyByte> _messageData;
public string MessageName
{
get { return _messageName; }
set
{
if (Equals(value, _messageData)) return;
_messageName = value;
RaisePropertyChanged();
}
}
public ObservableCollection<NotifyByte> MessageData
{
get { return _messageData; }
set
{
if (Equals(value, _messageData)) return;
_messageData = value;
RaisePropertyChanged();
}
}
public SerialMessageViewModel(string messageName, byte[] data)
{
MessageName = messageName;
MessageData = new ObservableCollection<NotifyByte>();
foreach (byte b in data)
MessageData.Add(new NotifyByte(b));
}
}
public class NotifyByte : ViewModelBase
{
private byte _value;
public byte Value
{
get { return _value; }
set
{
if (Equals(value, _value)) return;
_value = value;
RaisePropertyChanged();
}
}
public NotifyByte(byte value)
{
_value = value;
}
}
ByteToHexStringConverter
public class ByteToHexStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
byte num = (byte)value;
return num.ToString("X2");
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
string input = (string)value;
if (!String.IsNullOrEmpty(input))
{
input = input.Replace(" ", "");
return System.Convert.ToByte(input, 16);
}
return 0x00;
}
}
这不使用您的示例代码,但总的来说,这里是您如何使用 DataGrid 来实现您正在寻找的内容,但要尝试使其更轻量级。请注意,我使用的是 TextBlock,因为我们不想允许从单元格进行编辑。您的自定义文本框 XAML 将进入 CellEditTemplate。
MainWindow.xaml
<DataGrid AutoGenerateColumns="False"
ItemsSource="{Binding DataSet}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding DisplayName}" />
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayForIsSomethingRelevant}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsSomethingRelevant}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
MainWindowVm.cs
public class MainWindowVm
{
public ObservableCollection<TestObject> DataSet { get; set; } = new ObservableCollection<TestObject>();
public MainWindowVm()
{
DataSet.Add(new TestObject {DisplayName = "Booyah", IsSomethingRelevant = true});
DataSet.Add(new TestObject {DisplayName = "Iggy Boop", IsSomethingRelevant = false});
}
}
TestObject.cs(bindablebase只是提供了notifypropertychanged的默认实现)
public class TestObject : BindableBase
{
private string _displayName;
public string DisplayName
{
get { return _displayName; }
set { SetProperty(ref _displayName, value); }
}
private bool _IsSomethingRelevant;
public bool IsSomethingRelevant
{
get { return _IsSomethingRelevant; }
set
{
SetProperty(ref _IsSomethingRelevant, value);
NotifyPropertyChanged(nameof(DisplayForIsSomethingRelevant));
}
}
public string DisplayForIsSomethingRelevant => IsSomethingRelevant
? "Totes Relevant"
: "Non-Relevono";
}
我正在努力寻找解决 WPF 性能问题的方法。 我有一个 C# 项目,它从串行端口接收一些 "messages" 并将它们显示在 GUI 上。此 GUI 允许编辑每条接收到的消息的字节,还有一个 "save" 按钮可将编辑后的消息保存到文件中。
对于 GUI,将 MVVM 与 WPF 结合使用,我为实现上述要求所做的工作如下:
- 包含 "SerialMessageViewModel". 的可观察集合的 "MainViewModel" class
- 每个 "SerialMessageViewModel" class 包含串行消息(字符串)的 "name" 及其数据字节。
View 使用 ListView 显示 "SerialMessageViewModel" 的集合,对于每个 SerialMessage,我使用 ItemsControl 来显示 MessageData 字节。因为我希望这些字节是可编辑的,所以我使用 TextBox 作为 ItemsControl 的 ItemTemplate。 这里的想法是让 ItemsControl 像 GUI 一样工作 "HexEditor"。
除了 ListView 的滚动速度非常慢之外,一切实际上都工作正常。
这使得应用程序使用起来非常令人沮丧..
我知道像这样将 ItemsControl 的 ListView 与 TextBoxes 一起使用确实会生成很多 TextBoxes,但我找不到另一种方法来制作类似的东西。
Visual Studio 分析工具显示滚动延迟是由 "layout" 时间引起的。
关于如何解决这个问题有什么建议吗? 非常感谢!
查看XAML
<Window x:Class="WpfListIssue.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:WpfListIssue"
mc:Ignorable="d"
Title="MainWindow" Height="800" Width="600">
<Window.Resources>
<ResourceDictionary>
<local:ByteToHexStringConverter x:Key="ByteToHexStringConverter" />
</ResourceDictionary>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ProgressBar Grid.Row="0" Height="20" IsIndeterminate="True" />
<ListView Grid.Row="1" Margin="10" ItemsSource="{Binding Messages}">
<ListView.View>
<GridView>
<GridViewColumn Header="Message Name" Width="120" DisplayMemberBinding="{Binding MessageName}" />
<GridViewColumn Header="Message Data" Width="400" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding MessageData}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Value, Converter={StaticResource ByteToHexStringConverter}}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
视图模型
public class MainViewModel : ViewModelBase
{
private ObservableCollection<SerialMessageViewModel> _messages;
public ObservableCollection<SerialMessageViewModel> Messages
{
get { return _messages; }
set
{
if (Equals(value, _messages)) return;
_messages = value;
RaisePropertyChanged();
}
}
public MainViewModel()
{
GetMessages();
}
private void GetMessages()
{
// Simulates the reception of messages from serial
Messages = new ObservableCollection<SerialMessageViewModel>();
Random rand = new Random();
for (int i = 0; i < 100; i++)
{
byte[] data = new byte[50];
rand.NextBytes(data);
SerialMessageViewModel message = new SerialMessageViewModel("Message n." + i, data);
Messages.Add(message);
}
}
}
public class SerialMessageViewModel : ViewModelBase
{
private string _messageName;
private ObservableCollection<NotifyByte> _messageData;
public string MessageName
{
get { return _messageName; }
set
{
if (Equals(value, _messageData)) return;
_messageName = value;
RaisePropertyChanged();
}
}
public ObservableCollection<NotifyByte> MessageData
{
get { return _messageData; }
set
{
if (Equals(value, _messageData)) return;
_messageData = value;
RaisePropertyChanged();
}
}
public SerialMessageViewModel(string messageName, byte[] data)
{
MessageName = messageName;
MessageData = new ObservableCollection<NotifyByte>();
foreach (byte b in data)
MessageData.Add(new NotifyByte(b));
}
}
public class NotifyByte : ViewModelBase
{
private byte _value;
public byte Value
{
get { return _value; }
set
{
if (Equals(value, _value)) return;
_value = value;
RaisePropertyChanged();
}
}
public NotifyByte(byte value)
{
_value = value;
}
}
ByteToHexStringConverter
public class ByteToHexStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
byte num = (byte)value;
return num.ToString("X2");
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
string input = (string)value;
if (!String.IsNullOrEmpty(input))
{
input = input.Replace(" ", "");
return System.Convert.ToByte(input, 16);
}
return 0x00;
}
}
这不使用您的示例代码,但总的来说,这里是您如何使用 DataGrid 来实现您正在寻找的内容,但要尝试使其更轻量级。请注意,我使用的是 TextBlock,因为我们不想允许从单元格进行编辑。您的自定义文本框 XAML 将进入 CellEditTemplate。
MainWindow.xaml
<DataGrid AutoGenerateColumns="False"
ItemsSource="{Binding DataSet}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding DisplayName}" />
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayForIsSomethingRelevant}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsSomethingRelevant}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
MainWindowVm.cs
public class MainWindowVm
{
public ObservableCollection<TestObject> DataSet { get; set; } = new ObservableCollection<TestObject>();
public MainWindowVm()
{
DataSet.Add(new TestObject {DisplayName = "Booyah", IsSomethingRelevant = true});
DataSet.Add(new TestObject {DisplayName = "Iggy Boop", IsSomethingRelevant = false});
}
}
TestObject.cs(bindablebase只是提供了notifypropertychanged的默认实现)
public class TestObject : BindableBase
{
private string _displayName;
public string DisplayName
{
get { return _displayName; }
set { SetProperty(ref _displayName, value); }
}
private bool _IsSomethingRelevant;
public bool IsSomethingRelevant
{
get { return _IsSomethingRelevant; }
set
{
SetProperty(ref _IsSomethingRelevant, value);
NotifyPropertyChanged(nameof(DisplayForIsSomethingRelevant));
}
}
public string DisplayForIsSomethingRelevant => IsSomethingRelevant
? "Totes Relevant"
: "Non-Relevono";
}