像十六进制查看器一样使用 ListView
Posted
技术标签:
【中文标题】像十六进制查看器一样使用 ListView【英文标题】:Use ListView like a hex viewer 【发布时间】:2021-12-27 03:05:01 【问题描述】:我正在开发一个具有用作十六进制查看器的列表视图的应用程序,但存在重要的性能问题。我不是一个很有经验的程序员,所以我不知道如何优化代码。
这个想法是在将项目添加到列表视图时避免应用程序冻结。我在考虑或多或少地以 100 个项目为一组添加项目,但我不知道如何处理上下滚动,我不确定这是否能解决这些性能问题。
控件将始终具有相同的高度,即 795 像素。这是我正在使用的代码:
private void Button_SearchFile_Click(object sender, EventArgs e)
//Open files explorer.
OpenFileDialog_SearchFile.Filter = "SFX Files (*.sfx)|*.sfx";
DialogResult openEuroSoundFileDlg = OpenFileDialog_SearchFile.ShowDialog();
if (openEuroSoundFileDlg == DialogResult.OK)
//Get the selected file
string filePath = OpenFileDialog_SearchFile.FileName;
Textbox_FilePath.Text = filePath;
//Clear list.
ListView_HexEditor.Items.Clear();
//Start Reading.
using (BinaryReader BReader = new BinaryReader(File.OpenRead(filePath)))
//Each line will contain 16 bits.
byte[] bytesRow = new byte[16];
while (BReader.BaseStream.Position != BReader.BaseStream.Length)
//Get current offset.
long offset = BReader.BaseStream.Position;
//Read 16 bytes.
byte[] readedBytes = BReader.ReadBytes(16);
//Sometimes the last read could not contain 16 bits, with this we ensure to have a 16 bits array.
Buffer.BlockCopy(readedBytes, 0, bytesRow, 0, readedBytes.Length);
//Add item to the list.
ListView_HexEditor.Items.Add(new ListViewItem(new[]
//Print offset
offset.ToString("X8"),
//Merge bits
((bytesRow[0] << 8) | bytesRow[1]).ToString("X4"),
((bytesRow[2] << 8) | bytesRow[3]).ToString("X4"),
((bytesRow[4] << 8) | bytesRow[5]).ToString("X4"),
((bytesRow[6] << 8) | bytesRow[7]).ToString("X4"),
((bytesRow[8] << 8) | bytesRow[9]).ToString("X4"),
((bytesRow[10] << 8) | bytesRow[11]).ToString("X4"),
((bytesRow[12] << 8) | bytesRow[13]).ToString("X4"),
((bytesRow[14] << 8) | bytesRow[15]).ToString("X4"),
//Get hex ASCII representation
GetHexStringFormat(bytesRow)
));
private string GetHexStringFormat(byte[] inputBytes)
//Get char in ascii encoding
char[] arrayChars = Encoding.ASCII.GetChars(inputBytes);
for (int i = 0; i < inputBytes.Length; i++)
//Replace no printable chars with a dot.
if (char.IsControl(arrayChars[i]) || (arrayChars[i] == '?' && inputBytes[i] != 63))
arrayChars[i] = '.';
return new string(arrayChars);
这是程序的图片:
【问题讨论】:
你的问题有点不清楚。您究竟在寻找什么? 您好!,简而言之,我们的想法是加载一个二进制文件,并在此列表视图中以十六进制格式显示它,而不会冻结应用程序,尽可能缩短时间。 是的,我知道你正在这样做,但是你的问题是什么? 如何避免将项目添加到列表视图时应用程序冻结 ***.com/questions/2519864/… 【参考方案1】:这是一个常见问题,主 UI 线程上的任何长时间运行的操作都会冻结应用程序。正常的解决方案是在后台线程上运行操作或作为异步方法......或者更常见的是两者的混合。
这里的主要问题是ListView
在加载大量数据时非常慢。即使是最快的方法——批量加载ListViewItem
s 的整个集合——也很慢。使用 340KB 文件进行了快速测试
~0.18s 加载项目,然后 ~2.3s 将项目添加到控件。而且由于最后一部分必须发生在 UI 线程上,因此有大约 2.3 秒的死区时间。
ListView
处理大型列表的最流畅的解决方案是使用虚拟列表模式。在这种模式下,ListView
在列表滚动时请求可见项目,而不是维护自己的项目列表。
要实现虚拟ListView
,您需要为RetrieveVirtualItem
事件提供处理程序,将VirtualListSize
属性设置为列表的长度,然后将VirtualMode
设置为true。 ListView
将在需要显示项目时调用您的 RetrieveVirtualItem
处理程序。
这是我的 PoC 代码:
// Loaded data rows.
ListViewItem[]? _rows = null;
// Load data and setup ListView for virtual list.
// Uses Task.Run() to (hopefully) get off the UI thread.
private Task LoadData(string filename)
=> Task.Run(() =>
// Clear current virtual list
// NB: Invoke() makes this run on the main UI thread
Invoke((Action)(() =>
listView1.BeginUpdate();
listView1.VirtualMode = false;
listView1.VirtualListSize = 0;
listView1.EndUpdate();
));
// Read data into '_rows' field.
using (var stream = File.OpenRead(filename))
var buffer = new byte[16];
var rows = new List<ListViewItem>();
int rc;
while ((rc = stream.Read(buffer, 0, buffer.Length)) > 0)
var items = new[]
(stream.Position - rc).ToString("X8"),
string.Join(" ", buffer.Take(rc).Select(b => $"b:X2")),
string.Join("", buffer.Take(rc).Select(b => (char)b).Select(b => char.IsControl(b) ? '.' : b)),
;
rows.Add(new ListViewItem(items));
_rows = rows.ToArray();
// Enable virtual list mode
Invoke((Action)(() =>
listView1.BeginUpdate();
listView1.VirtualListSize = _rows?.Length ?? 0;
listView1.VirtualMode = _rows?.Length > 0;
listView1.EndUpdate();
));
);
// Fetch rows from loaded data.
private void ListView1_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
if (_rows is not null && e.ItemIndex >= 0 && e.ItemIndex < _rows.Length)
e.Item = _rows[e.ItemIndex];
(插入关于缺乏错误处理等的常见免责声明。)
希望这是不言自明的。我在内容生成中采用了一些 LINQ 快捷方式,并减少了列数。更多列意味着滚动期间的刷新时间更慢,无论您是使用虚拟列表还是让控件处理项目集合。
对于非常大的文件,这种方法仍然是个问题。如果您经常加载大小为 10 MB 或更大的文件,那么您需要更有创意地一次加载数据块,并且可能会即时创建和缓存 ListViewItem
s 而不是比在初始加载阶段。查看ListView.VirtualMode
文档。示例代码显示了完整的功能......虽然它们的缓存策略有点初级。
【讨论】:
以上是关于像十六进制查看器一样使用 ListView的主要内容,如果未能解决你的问题,请参考以下文章
Android:如何像在联系人应用程序中一样使用字母索引器和快速滚动填充 ListView。