RenderTargetBitmap + 资源的 VisualBrush = 不完整的图像

9
我发现了“Visual to RenderTargetBitmap”问题的一个新变化!
我正在为设计师渲染WPF预览。这意味着我需要将WPF可视化对象渲染成位图,而不必显示该可视化对象。我有一个不错的小方法来完成它,看看下面的代码:
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;
}

除了一个小问题,一切正常...如果我的FrameworkElement有一个VisualBrush,那么这个画刷不会出现在最终渲染的位图中。类似于这样:

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

除了VisualBrush之外的所有内容都会呈现在位图上,但是该VisualBrush却无法显示。已经尝试了明显的谷歌解决方案并且失败了,甚至那些明确提到RTB中缺少VisualBrushes的解决方案也无效。

我有一个狡猾的怀疑,可能是因为它是一种资源,即懒惰资源没有被内联。所以一个可能的解决方法是,在渲染之前以某种方式强制解析所有静态资源引用。但我绝对不知道该如何做到这一点。

有人有解决方法吗?

2个回答

16

你有两个问题:

  1. 你没有在你的可视元素上设置PresentationSource,所以Loaded事件不会被触发。
  2. 你没有刷新Dispatcher队列。没有刷新Dispatcher队列,使用Dispatcher回调的任何功能都无法工作。

导致你问题的直接原因是未能刷新Dispatcher队列,因为VisualBrush使用它,但你很快可能会遇到PresentationSource问题,因此我建议你修复这两个问题。

以下是我的解决方法:

// 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;
}

提醒一下,无论何种情况下,静态资源查找都不会延迟:它会在加载XAML时立即处理并立即替换为从ResourceDictionary中检索到的值。唯一可能与StaticResource有关的方式是,如果它选择了错误的资源,因为两个资源具有相同的键名。我只是想解释一下这一点——它与您实际遇到的问题无关。


好的,我确实阅读并尝试刷新调度程序,但仅靠这个并没有帮助。我将尝试添加演示源,这是我之前没有做过的。谢谢。 - user1228
如果容器是根节点,为什么要返回 null,而不是返回根节点本身?VisualTreeHelper.GetParent(container)==null ? container : null 是什么意思? - user1228
1
我的代码旨在适用于独立的视觉元素和已经由窗口呈现的视觉元素。因此,在我的情况下,如果该视觉元素有一个父级,则它已经具有PresentationSource。只允许一个。通过将RootVisual传递为null,我能够使用单个代码路径处理这两种情况,并且成本非常低。最通用的解决方案是(PresentationSource.FromVisual(container)!=null ? null : FindVisualTreeRoot(container)),并使用VisualTreeHelper.GetParent查找根的单独FindVisualTreeRoot方法,但我的代码对大多数目的已经足够。 - Ray Burns
哇塞,这个答案能不能加10分啊?这个加上创建一个虚假的Dispatcher,就是获取任意WPF控件动态渲染到IIS服务器PNG流的缺失部分。 - Glenn Slayden
@Ray 我正在尝试在IIS 7/.NET 4.0中进行3D渲染。这种方法对于2D来说对我很有效,但是如果我尝试发送一个Viewport3D,则Viewport3D不会被渲染。有什么想法吗? - rsbarro
显示剩余2条评论

0

要将其内联,您可以这样做:

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

如果那样不起作用,我的猜测是你正在使用的`Visual`实例必须有一些特定的问题(这将需要进一步的代码来更好地诊断)。

1
我不想向人们解释:“嘿,我知道缩略图不完整。你为什么不放弃使用画笔资源呢?” - user1228

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接