RenderTargetBitmap + Resource'd VisualBrush =

2020-02-09 04:32发布

问题:

I've found a new twist on the "Visual to RenderTargetBitmap" question!

I'm rendering previews of WPF stuff for a designer. That means I need to take a WPF visual and render it to a bitmap without that visual ever being displayed. Got a nice little method to do it like to see it here it goes

private static BitmapSource CreateBitmapSource(FrameworkElement visual)
{
    Border b = new Border { Width = visual.Width, Height = visual.Height };
    b.BorderBrush = Brushes.Black;
    b.BorderThickness = new Thickness(1);
    b.Background = Brushes.White;
    b.Child = visual;

    b.Measure(new Size(b.Width, b.Height));
    b.Arrange(new Rect(b.DesiredSize));

    RenderTargetBitmap rtb = new RenderTargetBitmap(
                                (int)b.ActualWidth,
                                (int)b.ActualHeight,
                                96,
                                96,
                                PixelFormats.Pbgra32);

    // intermediate step here to ensure any VisualBrushes are rendered properly
    DrawingVisual dv = new DrawingVisual();
    using (var dc = dv.RenderOpen())
    {
        var vb = new VisualBrush(b);
        dc.DrawRectangle(vb, null, new Rect(new Point(), b.DesiredSize));
    }
    rtb.Render(dv);
    return rtb;
}

Works fine, except for one leeetle thing... if my FrameworkElement has a VisualBrush, that brush doesn't end up in the final rendered bitmap. Something like this:

<UserControl.Resources>
    <VisualBrush
        x:Key="LOLgo">
        <VisualBrush.Visual>
            <!-- blah blah -->
<Grid 
    Background="{StaticResource LOLgo}">
<!-- yadda yadda -->

Everything else renders to the bitmap, but that VisualBrush just won't show. The obvious google solutions have been attempted and have failed. Even the ones that specifically mention VisualBrushes missing from RTB'd bitmaps.

I have a sneaky suspicion this might be caused by the fact that its a Resource, and that lazy resource isn't being inlined. So a possible fix would be to, somehow(???), force resolution of all static resource references before rendering. But I have absolutely no idea how to do that.

Anybody have a fix for this?

回答1:

You have two problems:

  1. You didn't set a PresentationSource on your visual so Loaded events won't fire.
  2. You didn't flush the Dispatcher queue. Without flushing the Dispatcher queue, any functionality that uses Dispatcher callbacks won't work.

The immediate cause of your problem is failure to flush the Dispatcher queue, since VisualBrush uses it, but you will probably run into the PresentationSource problem before long so I would fix both of these.

Here is how I do it:

// Create the container
var container = new Border
{
  Child = contentVisual,
  Background = Brushes.White,
  BorderBrush = Brushes.Black,
  BorderThickness = new Thickness(1),
};

// Measure and arrange the container
container.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
container.Arrange(new Rect(container.DesiredSize));

// Temporarily add a PresentationSource if none exists
using(var temporaryPresentationSource = new HwndSource(new HwndSourceParameters()) { RootVisual = (VisualTreeHelper.GetParent(container)==null ? container : null) })
{
  // Flush the dispatcher queue
  Dispatcher.Invoke(DispatcherPriority.SystemIdle, new Action(() => { }));

  // Render to bitmap
  var rtb = new RenderTargetBitmap((int)b.ActualWidth, (int)b.ActualHeight, 96, 96, PixelFormats.Pbgra32);
  rtb.Render(container);

  return rtb;
}

FYI, StaticResource lookup is never delayed under any circumstances: It is processed the moment the XAML is loaded and immediately replaced with the value retrieved from the ResourceDictionary. The only way StaticResource could possibly be related is if it picked up the wrong resource because two resources had the same key. I just thought I should explain this -- it has nothing to do with your actual problem.



回答2:

Well to inline it, you could just do something like this:

<Grid>
    <Grid.Background>
        <VisualBrush>
            <VisualBrush.Visual>
                <!-- blah blah -->
            </VisualBrush.Visual>
        </VisualBrush>
    </Grid.Background>
</Grid>

If that doesn't work, my guess would be that it must be something specific with the Visual instance you are using (and that will require further code to better diagnose).