Copy UI element with adorner

淺唱寂寞╮ 提交于 2019-12-12 11:00:48

问题


I am working on taking screenshot of UI Element (WPF) in various size and i'm able to achieve this using "RenderTargetBitmap. But the UIElement which has an Adorner part is not coming while take copy. What should i do to achieve this. Any reference or code snippet?


回答1:


As far as I know, elements don't have direct references to their adorners. Adorners do reference their element through AdornedElement though, so you could search for adorners assigned to your element like this:

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

Here GetVisualChildren is an extension method which is defined as:

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

The size of the adorner seems to include the size of the adorned element (although I'm not sure if this is always the case), so if there is only one adorner, that is your screenshot size. If there is more than one adorner, you would need to find the maximum for each bound (left, top, right, bottom) to calculate the screenshot region.

You will need to capture the AdornerDecorator which contains both the elements being adorned and the AdornerLayer (layer in the above code). That would be the visual parent of the layer:

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

Once you have the container, you can render it with RenderTargetBitmap and crop it to the screenshot region.

For the screenshot region, you need the element bounds relative to the container. First, get the non-relative bounds:

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

Then get those bounds relative to the container:

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

As I mentioned above, you will need to do this for the element as well as each of its adorners and combine the maximum bounds into one final Rect which is just large enough to contain all of them.

Finally, use CroppedBitmap to get a cropped version of the RenderTargetBitmap:

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

CroppedBitmap and RenderTargetBitmap both inherit from BitmapSource, so you should be able to save it the same way.




回答2:


You can use the native WPF Printing name space to print to an XPS file and this will include the adorner in the result (I tested it successfully)...

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

If you did not want to use the PrintDialog (which actually opens a dialog box). You can use the XpsDocumentWriter class to programmatically control the process. The enabling snippet for this is...

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

...which was extracted from here: Print FixedDocument programmatically And there are more articles about fine-tuning the process if that is part of your requirements. NOTE that the XPS file is actually a 'zip' file masquerading as an 'xps' file, so you can unzip it by changing the extension to see if the contents are of any use.

Secondarily, I tested saving a window with an adorner on a TextBox with this code...

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

...with good results. I.e., the adorner appeared in the saved bitmap with its red border. This might differ from your code because I use a Png encoder (but saved to a 'jpg' file).

Although I have tested both approaches successfully, you'll need to check them on your hardware.

And lastly, as a last-ditch resort, you can deactivate WPF's hardware rendering mode and set it to software rendering...

RenderOptions.ProcessRenderMode = RenderMode.SoftwareOnly;

...for which there's a nice SO thread here: Software rendering mode - WPF




回答3:


In my case all I needed was call the AdornerLayer class like so:

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

This works if you want to include ALL adorners in your canvas.



来源:https://stackoverflow.com/questions/17969651/copy-ui-element-with-adorner

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!