【问题标题】:Rendering into a custom DrawingContext渲染到自定义的 DrawingContext
【发布时间】:2013-08-12 17:32:16
【问题描述】:

我想劫持通常的 WPF 渲染来将控件拆分为基元,进行布局管理,为我应用绑定等。

据我了解,WPF 中的整个渲染归结为在布局管理器计算的位置处渲染图元(文本、图像、线条、曲线),其值由依赖属性系统定义。如果我可以提供自己的原始渲染逻辑,我将能够渲染例如到自定义文档类型,传输原语以通过网络进行真实渲染等。

我的计划如下:

  1. 实现自定义DrawingContextDrawingContext 是一个抽象类,它定义了一堆方法,如 DrawEllipseDrawTextDrawImage 等。我需要为这个功能提供自己的实现。
  2. 创建一个 WPF UserControl 并强制它渲染到给定的DrawingContext

但是我遇到了以下问题:

  1. DrawingContext 包含抽象的内部方法 void PushGuidelineY1(double coordinate)void PushGuidelineY2(double leadingCoordinate, double offsetToDrivenCoordinate),我无法轻易覆盖它们。 (也许有一些技巧可以克服这个问题?)
  2. 似乎没有方法可以在DrawingContext 上呈现整个 视觉效果?为什么?

我可以做类似的事情

void RenderRecursively(UIElement e, DrawingContext ctx)
{
    e.OnRender(ctx);
    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(e); i++)
        RenderRecursively((UIElement)VisualTreeHelper.GetChild(e, i), ctx);
}

——但我想知道是否有直接的方法来渲染UIElement。 (当然,这个问题是小问题,但看到没有基础设施让我怀疑这是否是正确的方法。)

那么,DrawingContext 不是用来继承的吗?提供自定义DrawingContext 的整个想法是朝着正确方向迈出的一步,还是我需要重新考虑策略?是在 WPF 支持的自定义上下文上绘图,还是我需要寻找不同的拦截点?

【问题讨论】:

  • 来自关于 DrawingContext 的评论:You never directly instantiate a DrawingContext; you can, however, acquire a drawing context from certain methods, such as DrawingGroup.Open and DrawingVisual.RenderOpen. 对我来说,这意味着无法在某处真正提供自定义 DrawingContext。
  • @Clemens:是的,我看到了这句话,但我理解为“您通常不应该自己创建它,我们会在内部为您完成;而要绘制到 DrawingVisual,只需让 DrawingVisual 正确初始化它”。无论如何,有趣的是是否有有效的绘图截取点。
  • 绘制到 DrawingVisual 的唯一方法是绘制到 DrawingVisual.RenderOpen 提供的 DrawingContext。根本无法将您的自定义 DrawingContext 与 Visual 关联。这个想法毫无意义。
  • 我认为您作为示例提供的实现是正确的路径。这是比您要求的更好的拦截点,因为您有能力检查您获得的孩子的类型并对其做出反应。如果您有一个事件只接收线、弧等原语。您的下一步可能是检查哪个父级是该原语...所以..是的,在我看来,您走在正确的道路上。跨度>
  • DrawingContext 及其所有派生类都没有公共构造函数,所以不,你不能从它派生。 WPF 不像 EMF 或 WMF,或者打印机驱动程序,或者说 Postscript。没有“原始”的概念。底层实现(媒体接口层“MIL”)完全不受管理且大部分未记录。

标签: c# wpf drawingcontext


【解决方案1】:

您可能需要从相反的方向解决这个问题。您可以要求 WPF 为您提供 Drawing,而不是提供您自己的 DrawingContext。因此,它更像是一种“拉动”方法,而不是您想要的“推动”方法,但它应该可以到达同一个地方:如果您有一个 Drawing,那是外观的完整表示可视化树的一部分,这是一种数据结构,您可以遍历并发现从调用自定义DrawingContext 时会发现的所有内容。

我相信这与塞巴斯蒂安提到的 XPS 文档导出内部使用的基本方法相同。但是自己直接使用它是比通过 XPS API 使用它更直接的方法

本质上是相当简单的:VisualTreeHelper.GetDrawing。这将返回 DrawingGroup。 (Drawing 是一个抽象基类。)该文档页面向您展示了如何遍历返回的树。不幸的是,这并不能完成全部工作:它只是为您碰巧调用它的任何节点提供视觉效果,如果该节点有子节点,则不会包含它们。

因此,不幸的是,您仍然必须编写一些递归可视化树的东西,就像您已经计划的那样。您还需要处理附加到视觉对象的任何不透明蒙版、非基于蒙版的不透明度、剪辑区域、效果和转换,以获得正确的结果;您也必须做所有这些事情才能使您提出的方法正常工作,所以这里没有什么真正改变。 (正如 Sebastian 所建议的那样,使用 XPS API 的一个潜在优势是它可以为您完成所有这些工作。但是,以您想要的形式从 XPS 文档中提取信息是您的问题,而这最终可能会丢失您的信息可能想要保留。)

【讨论】:

  • 在我的测试中这不起作用。我认为您需要使用我建议的方法,因为GetDrawing 对于大多数Controls 将产生null,因为在OnRender 期间绘制的视觉效果不会被该辅助方法枚举 - 尝试枚举Border 和你会得到null
  • 如果我在具有非空 BorderBrush 和非零 BorderThicknessBorder 上调用 GetDrawing,我会返回一个非空 DrawingGroup,其中包含单个GeometryDrawing,其大小与Border 相同,RectangleGeometry 与其Geometry,以及与ThicknessBorderBrush 设置匹配的Pen。您是否有机会在 a)实际上没有绘制任何东西的边框或 b)尚未实现其视觉效果的边框上进行测试?
  • 您可能就在这里 - 边界以前从未被可视化过。这可能是问题所在,虽然我还没有检查。对诬告深表歉意!
  • 确实,我现在正在使用这样的方法,但只检查UIElements 而不是Drawings。我的方法的缺点是文本渲染非常复杂,需要实现自定义TextSource,以便将TextBlocks 中的文本半自动拆分为行。用你的方法,我免费得到这个。
  • 您建议的方法有什么好处?如果您希望 WPF 完成所有布局工作,包括文本布局,可视层(即Drawing)将是正确的选择。为什么要进入一个让自己有更多工作要做的水平?
【解决方案2】:

我认为您的方法行不通,因为(正如其他人所提到的)您无法提供自己的 DrawingContext 实现。

我建议改为:为了“扁平化” WPF 呈现,让 WPF 将您的视觉效果导出到 XPS 文档。在这个过程中,所有的渲染基本上都被列举为简单的渲染基元,剩下的就是Canvass、基本形状、字形和其他绘图基元。

然后迭代文档页面中的视觉效果。据我所知,生成的视觉效果将仅由基元组成,因此无需调用OnRender。相反,这使您能够从外部自省视觉实例(使用instanceof-cascades 并读取/解释属性)。这仍然需要大量工作,因为您需要像 WPF 一样解释属性,但据我所知,这至少适用于许多主要用例。

【讨论】:

  • XPS 文档的方法似乎是一个不错的方法。 XPS 文档内容的快速检查表明原始字体没有被保留,而是嵌入到文档中——但这应该不是问题,因为人们可以使用嵌入的字体本身进行绘图。这种方法的一个可能问题是,使用 WPF 我可以控制复杂性,但对于 XPS,即使是简单的 WPF 源,我也可能需要解析所有 XPS 功能。
  • 非常感谢您的回答。虽然我没有选择你提议的方式,但这显然是一个非常好的主意,值得更多的支持。
【解决方案3】:

我尝试做类似的事情来为 winRT 创建一个 FlowDocumentViewer。但由于 WinRT 远不如 WPF 成熟,它也将太多的委托给本机层(通过渲染线程),我无处可去。但这是我学到的,我希望我能很好地解释它。

WPF 使用硬件加速的图形渲染。因此,简单来说,WPF LayoutEngine 构建逻辑可视树,然后将其转换为渲染指令,然后发送到图形硬件执行或渲染。

DrawingContext 是一个重要的类,它与底层图形系统交互以进行渲染、管理缩放、缓存等。 WPF 运行时带有默认实现,可以渲染所有视觉效果。 IMO,它被制成抽象类的原因是微软可以为 Silverlight 等提供不同的实现。但它意味着被我们覆盖。

如果您必须替换 WPF 渲染,那么最好的办法是创建一个 UserControl,覆盖 Arrange 和 Measure 调用,并使用 DrawingVisual.RenderOpen() 将每个元素渲染到 DrawingVisual,并从您的代码中排列它们等。管理 DataBinding 通知将是您必须自己做的另一件事。

看起来很有趣的项目。祝你好运!

【讨论】:

  • 其实内部有很多DrawingContexts,用于命中测试等简单的事情。我的最终目标是将 WPF 呈现为某种文档类型,因此自定义 UserControl 无济于事。还是谢谢你的建议!
【解决方案4】:

与其尝试编写自己的DrawingContext,不如创建一个派生自FrameworkElementUIElement 甚至Visual 的类,在其OnRender 方法中执行您的活动。您仍然必须使用Draw[Something] 的给定实现,但您将控制参数和操作顺序。您仍然可以从辅助来源解析原语和指令,并且您的一个 UIElement/FrameworkElement 可以在运行时编写指令。

【讨论】:

  • 这可行,但仅适用于自定义视觉效果。我的问题是抓住什么,例如TextBlock 的 OnRender 正在做。
  • 这是一个有趣的问题。如果您找到答案,请考虑分享您的经验。 :-)
猜你喜欢
  • 2017-06-09
  • 2022-01-10
  • 2013-07-27
  • 1970-01-01
  • 2012-08-25
  • 1970-01-01
  • 2016-11-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多