Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Copy UI element with adorner

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?

like image 223
Mohanavel Avatar asked Jul 31 '13 11:07

Mohanavel


2 Answers

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.

like image 180
nmclean Avatar answered Oct 06 '22 22:10

nmclean


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

like image 45
Gayot Fow Avatar answered Oct 06 '22 22:10

Gayot Fow