【发布时间】:2014-04-05 20:12:49
【问题描述】:
我有一些捕获的网络数据(二进制)的 WinForms 显示。我正在尝试将 WPF 作为学习练习,因为它应该更易于维护。
您可能已经看到,二进制数据最常见的显示格式是双列布局,左侧显示每个值的数值,右侧显示对应于可打印 ASCII 字符的八位位组字符串。
我还希望能够独立更改每个值的颜色以突出显示检测到的更改。
到目前为止,我已经尝试使用RichTextBox,其中FlowDocument 由Table 填充。有一个TableRowGroup 和两列,一列用于数字显示,一列用于字符串显示。
每次数据更改时,循环都会重新创建TableRowGroup。每 16 个字节的数据形成一个新的 TableRow,其中包含一个 Paragraph。 TableCell 中的值每个都作为Run 进行管理。与需要着色的值对应的Run 对象在放入Paragraph 之前进一步嵌套在Span 中。
这可行,但对于中等数据大小,它明显比 WinForms RichTextBox 慢。我想 WPF 并没有真正设计为在单个 FlowDocument 中拥有 ~4000 个 Run 实例。
另外,有人告诉我,强制创建这么多 TableCell 和 Run 对象并不是 WPF 的预期方法。但我也不太清楚如何将其映射到视图模型,如何触发新数据的属性更改以及更改的显示选项。
任何建议将不胜感激。非常希望支持复制到剪贴板。
internal void RenderMessageDiff(GameMessage oldMsg, GameMessage newMsg)
{
if (newMsg == null) return;
var tablerows = new TableRowGroup();
if (oldMsg == null) oldMsg = newMsg;
var aby = newMsg.payload;
for (int offset = 0; offset < aby.Length - 1; offset += 16)
{
var leftCol = new Paragraph();
var rightCol = new Paragraph();
int blocklen = Math.Min(16, aby.Length - offset);
switch (selectDisplayFormat.SelectedValue as string)
{
case "u1":
for (int i = 0; i < blocklen; ++i)
{
if (i > 0) leftCol.Inlines.Add(new Run(","));
var v = new Run(aby[offset + i].ToString());
if (aby[offset + i] == oldMsg.payload[offset + i])
leftCol.Inlines.Add(v);
else
leftCol.Inlines.Add(new Span(v) { Foreground = Brushes.Red });
}
break;
case "u2":
for (int i = 0; i + 1 < blocklen; i += 2)
{
if (i > 0) leftCol.Inlines.Add(new Run(","));
var v = new Run(newMsg.ConvertUInt16LE(offset + i).ToString());
if (newMsg.ConvertUInt16LE(offset + i) == oldMsg.ConvertUInt16LE(offset + i))
leftCol.Inlines.Add(v);
else
leftCol.Inlines.Add(new Span(v) { Foreground = Brushes.Red });
}
break;
case "u4":
for (int i = 0; i + 3 < blocklen; i += 4)
{
if (i > 0) leftCol.Inlines.Add(new Run(","));
var v = new Run(newMsg.ConvertUInt32LE(offset + i).ToString());
if (newMsg.ConvertUInt32LE(offset + i) == oldMsg.ConvertUInt32LE(offset + i))
leftCol.Inlines.Add(v);
else
leftCol.Inlines.Add(new Span(v) { Foreground = Brushes.Red });
}
break;
case "x1":
default:
for (int i = 0; i < blocklen; ++i)
{
if (i > 0) leftCol.Inlines.Add(new Run(" "));
var v = new Run(aby[offset + i].ToString("X2"));
if (aby[offset + i] == oldMsg.payload[offset + i])
leftCol.Inlines.Add(v);
else
leftCol.Inlines.Add(new Span(v) { Foreground = Brushes.Red });
}
break;
}
Span changeRun = null;
for (int i = 0; i < blocklen; ++i)
{
char c = (char)aby[offset + i];
if (c < 32 || c >= 127) c = '.';
var v = new Run(c.ToString());
if (aby[offset + i] == oldMsg.payload[offset + i])
{
rightCol.Inlines.Add(v);
changeRun = null;
}
else
{
if (changeRun == null)
rightCol.Inlines.Add(changeRun = new Span(v) { Foreground = Brushes.Red });
else
changeRun.Inlines.Add(v);
}
}
tablerows.Rows.Add(new TableRow()
{
Cells = { new TableCell(leftCol), new TableCell(rightCol) }
});
}
textMessageDiff.RowGroups.Clear();
textMessageDiff.RowGroups.Add(tablerows);
}
【问题讨论】:
-
这只是为了展示?是否有任何理由需要从 UI 编辑这些值?
-
@kmacdonald:没有编辑。但是其他重要的交互是有帮助的。复制到剪贴板。选择子集进行进一步处理。正在搜索。
-
@kmacdonald:由于着色不会影响定位,有没有办法我可以在每个段落中使用单个
Run并覆盖渲染?我应该改用Grid还是ListView? -
如果您真的想进入 WPF,我建议您看看如何真正利用 MVVM 方法。您的示例是使用带有 Datatemplate 的 ItemsControl 的主要候选者,该 Datatemplate 将直观地表示每一行。您的数据模板可以由单个文本块组成。通过简单地绑定到您的行集合,您可以获得一组统一的行来显示......这越来越啰嗦了。我会尽我所知写一些东西作为答案。
-
@kmacdonald:即使我的数据每行分配了多个值?我想我可以将它分解为映射到行的切片,如果这样可以使 MVVM 更好地工作。
标签: c# wpf performance grid-layout flowdocument