2013-07-31 138 views
5

我在各种尺寸的考虑UI元素(WPF)的屏幕截图工作,并复印UI元素我能够做到这一点使用“RenderTargetBitmap,但它有一个Adorner部分UIElement不来而采取的副本。我应该怎么做,以实现这一目标。任何参考或代码段?与装饰器

回答

5

据我所知,元素没有自己的装饰器直接引用。装饰器就通过AdornedElement引用它们的元素,因此,您可以搜索装饰器分配给你的元素是这样的:

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

这里图10是其被定义为扩展方法:

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

装饰器的大小似乎包括佐餐元件的尺寸(虽然我不知道这是否是总是如此),因此,如果有只有一个装饰者,那就是你的截图尺寸。如果有多个装饰器,则需要为每个装订(左,上,右,下)找到最大值来计算屏幕截图区域。

您需要捕捉其中包含两个被装饰的元素AdornerDecorator,并(在上面的代码layer)的AdornerLayer。这将是该层的视觉父:

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足够大以容纳所有这些Rect。

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

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

CroppedBitmapRenderTargetBitmap无论从BitmapSource继承,所以你应该能够将其保存以同样的方式。

+0

好吧,这会给装饰者,但我需要采取整个屏幕截图。在这种情况下,我必须拍摄两个不同控件的快照,并且需要将两个图像之一集成在另一个之上,并具有许多复杂性,比如找到过去的装饰图像的位置...... – Mohanavel

+0

@Mohanavel您需要捕获整个父元素然后从中选择控件占用的区域。事实证明,这比我想象的要复杂得多,所以我编辑了更多细节。 – nmclean

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); 

...这是从这里提取:Print FixedDocument programmatically而且还有更多的文章微调的过程中,如果这是你的要求的一部分。请注意,XPS文件实际上是伪装成'xps'文件的'zip'文件,因此您可以通过更改扩展名来查看内容是否有用,从而将其解压缩。

其次,我测试保存窗口上,此代码文本框装饰器...

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线程在这里:Software rendering mode - WPF

+2

+1显示如何实际渲染和保存位图。装饰者的问题在于他们被放置在前景层上,而不是被包含在他们所装饰的元素的可视化树中。这意味着它们会在您渲染整个窗口时出现,但不会在渲染单个装饰元素时出现 - 因此可以通过裁剪完整位图来捕获单个元素。 – nmclean

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

如果你想在你的画布中包含所有的装饰物,这是有效的。