In a windows service I'm trying to render a WPF Control into a png and the problem is one of the controls involved (namely an Oxyplot PlotView) checks if the control is actually visible and if not does not draw anything. The only way I've found to make IsVisible return true was to place the control inside a window and call Show on the window, with all the consequences of a window actually popping up, which I don't want.
I tried to put the control in a Page but did not help.
Here the basic code I'm using:
var plotView = new PlotView() { Height = height, Width = width, XPS = true, Model = plot };
var view = new ContentControl() { Content = plotView, Width= width, Height = height };
//force bindings
Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.ApplicationIdle, new DispatcherOperationCallback(_ => { return null; }), null);
view.Measure(new Size(width, height));
view.Arrange(new Rect(0, 0, width, height));
view.UpdateLayout();
var encoder = new PngBitmapEncoder();
var render = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
render.Render(view);
encoder.Frames.Add(BitmapFrame.Create(render));
using (var s = File.Open(filename, FileMode.Create))
{
encoder.Save(s);
}
This is running in an STA thread. Rendering other widgets, which are not a PlotView works, I can see in the debugger how the PlotView does not paint it's visuals because it checks whether it's visible.
I can suggest two solutions. The point is that
UIElement.IsVisibleis only set totrue(by WPF) when the element becomes a child of the element tree and is potentially visible.Please note that the goal is to force
UIElement.IsVisibleto returntrue.The first solution is to "fake" that the unrendered element (in your case the
PlotView) is a child of the logical tree. It does this by temporary declaring the unrendered element as the new visual root.The disadvantage is that if the operation takes too long, the
Framework.Unloadedevent will be raised for the complete element tree of the original visual root (which usually isWindow). Then after the visual root is restored theFrameworkElement.Loadedevent is raised. In addition we trigger a complete layout pass. This also means if the iterruption is long enough it will be visible to the user.However, in this scenario the execution time is just a few microseconds, which is why those performance considerations won't apply.
The second solution uses a "virtual" rendering host to make the unrendered element virtually visible. Such a host should be a container that doesn't force it's available space on its children when the available space is zero.
Configuring the host container to have a zero size is mandatory in order to avoid any impact on the layout. This is because we have to insert the host into the visual tree.
The
Canvaspanel is perfect because it has an initial size zero but doesn't restrict the size of its children. While theCanvasis invisible and doesn't affect the layout, the child elements can still occupy the space they desire. Other containers when set to a zero size, for example theBorder, will measure their children using this zero size, which results in the children not considered visible by the layout engine.The disadvantage is that we must modify the element tree to add an additional dummy element container.
Solution 1
MainWindow.xaml.cs
Solution 2
MainWindow.xaml
MainWindow.xaml.cs