5

我正在拍摄各种尺寸的 UI 元素 (WPF) 的屏幕截图,我可以使用“RenderTargetBitmap”来实现这一点。但是在复制时,UIElement其中的Adorner一部分没有出现。我应该怎么做才能实现这一点。任何参考或代码片段?

4

3 回答 3

5

据我所知,元素并没有直接引用他们的装饰者。装饰器确实通过AdornedElement引用他们的元素,因此您可以搜索分配给您的元素的装饰器,如下所示:

var layer = AdornerLayer.GetAdornerLayer(element);
var adorners = layer.GetVisualChildren().Cast<Adorner>().Where(a => a.AdornedElement == element);

GetVisualChildren是一个扩展方法,定义为:

public static IEnumerable<DependencyObject> GetVisualChildren(this DependencyObject current) {
    return Enumerable.Range(0, VisualTreeHelper.GetChildrenCount(current)).Select(i => VisualTreeHelper.GetChild(current, i));
}

装饰器的大小似乎包括被装饰元素的大小(虽然我不确定是否总是如此),所以如果只有一个装饰器,那就是你的屏幕截图大小。如果有多个装饰器,则需要找到每个边界(左、上、右、下)的最大值来计算屏幕截图区域。

您将需要捕获AdornerDecorator其中包含被装饰的元素和AdornerLayerlayer在上面的代码中)。那将是图层的视觉父级:

var container = VisualTreeHelper.GetParent(layer) as Visual;

拥有容器后,您可以将其渲染RenderTargetBitmap并裁剪到屏幕截图区域。

对于屏幕截图区域,您需要相对于容器的元素边界。首先,得到非相对边界:

var elementBounds = element.RenderTransform.TransformBounds(new Rect(element.RenderSize));

然后获取相对于容器的边界:

var relativeElementBounds = element.TransformToAncestor(container).TransformBounds(elementBounds);

正如我上面提到的,您需要对元素以及它的每个装饰器执行此操作,并将最大边界组合到一个最终的 Rect 中,该 Rect 足够大以包含所有它们。

最后,使用CroppedBitmap获得裁剪后的版本RenderTargetBitmap

var croppedBitmap = new CroppedBitmap(renderTargetBitmap, new Int32Rect(left, top, width, height));

CroppedBitmap并且RenderTargetBitmap都继承自BitmapSource,因此您应该能够以相同的方式保存它。

于 2013-07-31T12:27:42.017 回答
4

您可以使用本机 WPF 打印名称空间打印到 XPS 文件,这将在结果中包含装饰器(我测试成功)...

using System.Windows.Controls;
private void ExecutePrintCommand(object obj)
{
    PrintDialog printDialog = new PrintDialog();
    if (printDialog.ShowDialog() == true)
    {
        printDialog.PrintVisual(_mainWindow, "Main Window with Adorner");
    }
}

如果您不想使用 PrintDialog(实际上会打开一个对话框)。您可以使用 XpsDocumentWriter 类以编程方式控制该过程。启用此功能的代码段是...

     XpsDocumentWriter xpsdw = PrintQueue.CreateXpsDocumentWriter(q);
     xpsdw.Write(viewer.Document);

...从此处提取: 以编程方式打印 FixedDocument 如果这是您的要求的一部分,还有更多关于微调过程的文章。请注意,XPS 文件实际上是一个伪装成“xps”文件的“zip”文件,因此您可以通过更改扩展名来解压缩它以查看内容是否有用。

其次,我测试了使用此代码在 TextBox 上保存带有装饰器的窗口...

    private void SaveWithAdorner()
    {
        RenderTargetBitmap rtb = RenderVisaulToBitmap(_mainWindow, 500, 300);
        MemoryStream file = new MemoryStream();
        BitmapEncoder encoder = new PngBitmapEncoder();
        encoder.Frames.Add(BitmapFrame.Create(rtb));
        encoder.Save(file);
        using (FileStream fstream = File.OpenWrite("Myimage.jpg"))
        {
            file.WriteTo(fstream);
            fstream.Flush();
            fstream.Close();
        }
    }

...效果很好。即,装饰器以红色边框出现在保存的位图中。这可能与您的代码不同,因为我使用的是 Png 编码器(但保存到“jpg”文件中)。

虽然我已经成功地测试了这两种方法,但您需要在您的硬件上检查它们。

最后,作为最后的手段,您可以停用 WPF 的硬件渲染模式并将其设置为软件渲染......

RenderOptions.ProcessRenderMode = RenderMode.SoftwareOnly;

...这里有一个很好的 SO 线程: 软件渲染模式 - WPF

于 2013-08-18T22:57:33.793 回答
0

就我而言,我需要的只是调用AdornerLayer类,如下所示:

    public void GetScreenshotWithAdorner(Canvas canvas, string filename)
    {
      AdornerLayer adornerlayer = AdornerLayer.GetAdornerLayer(canvas);

      RenderTargetBitmap rtb = new RenderTargetBitmap(
        (int)canvas.ActualWidth,
        (int)canvas.ActualHeight,
        96, //dip X
        96, //dpi Y
        PixelFormats.Pbgra32);
      rtb.Render(canvas); //renders the canvas screen first...
      rtb.Render(adornerlayer); //... then it renders the adorner layer

      SaveRTBAsPNG(rtb, filename);
    }

    private void SaveRTBAsPNG(RenderTargetBitmap bmp, string filename)
    {
      PngBitmapEncoder pngImage = new PngBitmapEncoder();

      pngImage.Frames.Add(BitmapFrame.Create(bmp));
      using (var filestream = System.IO.File.Create(filename))
      {
        pngImage.Save(filestream);
      }
    }

如果您想在画布中包含所有装饰器,则此方法有效。

于 2017-07-06T13:34:03.247 回答