像十六进制查看器一样使用 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 在加载大量数据时非常慢。即使是最快的方法——批量加载ListViewItems 的整个集合——也很慢。使用 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 或更大的文件,那么您需要更有创意地一次加载数据块,并且可能会即时创建和缓存 ListViewItems 而不是比在初始加载阶段。查看ListView.VirtualMode 文档。示例代码显示了完整的功能......虽然它们的缓存策略有点初级。

【讨论】:

以上是关于像十六进制查看器一样使用 ListView的主要内容,如果未能解决你的问题,请参考以下文章

Android:如何像在联系人应用程序中一样使用字母索引器和快速滚动填充 ListView。

Mac 有啥好的十六进制编辑器/查看器? [关闭]

如何在 Autodesk forge 查看器 setTheming 颜色方法中使用十六进制颜色代码?

哪个控件可以构建十六进制编辑器?

HTML连载16-颜色控制属性2&标签选择器

Python语言及其运用_第七章_像高手一样玩转数据