Search code examples
c#wpfadorneruielement

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?


Solution

  • 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.