I'd like to hijack the usual WPF rendering to split the controls into primitives, do the layout management, apply the bindings etc. for me.
As far as I understand, the whole rendering in WPF boils down to rendering of primitives (text, image, line, curve) at the locations calculated by the layout manager with values defined by the dependency property system. If I could supply my own primitive-rendering logic, I would be able to render e.g. to a custom document type, transfer the primitives for real rendering over the network etc.
My plan is following:
- Implement a custom
DrawingContext
. TheDrawingContext
is an abstract class, which defines a bunch of methods likeDrawEllipse
,DrawText
,DrawImage
etc. — I'll need to supply my own implementation for this functionality. - Create a WPF
UserControl
and force it to render into a givenDrawingContext
.
However I've encountered the following problems:
DrawingContext
contains abstract internal methodsvoid PushGuidelineY1(double coordinate)
andvoid PushGuidelineY2(double leadingCoordinate, double offsetToDrivenCoordinate)
, which I cannot override easily. (Perhaps there is some trick to overcome this?)- There seems to be no method to render the whole visual on a
DrawingContext
? Why?
I can do something like
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);
}
— but I wonder if there is a direct way to render an UIElement
. (Of course, this problem is a minor one, but seeing no infrastructure for it makes me wonder if this is the proper way.)
So, is the DrawingContext
not intended for inheriting from? Is the whole idea of supplying a custom DrawingContext
a step in the right direction, or I need to rethink the strategy? Is drawing onto a custom context supported in WPF, or I need to look for a different interception point?
You may need to approach this problem from the opposite direction. Instead of aiming to provide your own
DrawingContext
, you can instead ask WPF to provide you with aDrawing
. So it's more of a 'pull' approach' than the 'push' approach you're aiming for, but it should make it possible to get to the same place: if you have aDrawing
that is a complete representation of the appearance of part of the visual tree, that's a data structure you can walk and discover everything that you would have discovered from calls to a customDrawingContext
.I believe this is the same basic approach that the XPS document export Sebastian mentions uses internally. But using it directly yourself is a more direct approach than using it through the XPS APIs
At the heart is something fairly simple:
VisualTreeHelper.GetDrawing
. This returns aDrawingGroup
. (Drawing
is an abstract base class.) That documentation page shows you how to walk through the tree that you get back. Unfortunately, this doesn't do the whole job: it just provides the visuals for whichever node you happen to call it in, and if that node has children, they won't be included.So you will, unfortunately, still have to write something that recurses the visual tree, much like you were already planning. And you will also need to handle any opacity masks, non-mask-based opacity, clip regions, effects, and transformations that are attached to the visual to get the correct results; you'd have had to do all that too to make your proposed approach work correctly, so nothing really changes here. (One potential advantage of using the XPS API as Sebastian suggests is that it does all this for you. However, it's then your problem to extract the information from the XPS document in the form you want, and that may end up losing information that you might want to preserve.)