使用装饰器复制UI元素
本文关键字:复制 UI 元素 | 更新日期: 2023-09-27 18:25:34
我正在拍摄各种大小的UI元素(WPF)的屏幕截图,我可以使用"RenderTargetBitmap"来实现这一点。但在复制时,有Adorner
部分的UIElement
没有出现。我应该怎么做才能实现这一目标。有参考或代码片段吗?
据我所知,元素没有直接引用其装饰器。不过,装饰师确实通过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
,它包含被装饰的元素和AdornerLayer
(上面代码中的layer
)。这将是该层的视觉父层:
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
,因此您应该能够以相同的方式保存它。
您可以使用本机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
在我的情况下,我只需要调用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);
}
}
如果你想在画布上包括所有的装饰物,这是有效的。