WPF Including Tooltip in Render with RenderTargetBitmap

411 Views Asked by At

I've searched and searched but haven't been able to find anything with the same problem as me. I'm trying to render some high resolution/dpi screenshots of a WPF application. Only problem is that I need to include information from chart tooltips in the render, other than that I can save screenshots just fine.

I'm currently using Infragistics XamDataChart and I generate the tooltips in code rather xaml.

Anyone have a clue how to get the tooltip in the visual tree so it renders? Or to be able to render the whole window and everything inside of it including tooltip overlays?

Code for the render:

public static void RenderVisualToFile(this FrameworkElement visual)
    {
        var width = (int)visual.RenderSize.Width;
        var height = (int)visual.RenderSize.Height;

        RenderTargetBitmap renderTarget = new RenderTargetBitmap(width * 4, height * 4, 384, 384, PixelFormats.Pbgra32);
        renderTarget.Render(visual);

        // Encode and save to PNG file
        var enc = new PngBitmapEncoder();
        enc.Frames.Add(BitmapFrame.Create(renderTarget));

        if (Directory.Exists("Screenshots"))
        {
            using (var stm = File.Create(@"Screenshots\Render_" + DateTime.Now.ToString("yyMMMdd_HHmmss") + ".png"))
                enc.Save(stm);
        }
        else
        {
            Directory.CreateDirectory("Screenshots");
            using (var stm = File.Create(@"Screenshots\Render_" + DateTime.Now.ToString("yyMMMdd_HHmmss") + ".png"))
                enc.Save(stm);
        }
    }    

And I call this in the MainWindow code behind.

    if (e.Key == Key.PrintScreen)
    {
        this.RenderVisualToFile();
    }
1

There are 1 best solutions below

0
On BEST ANSWER

It is a bit late but maybe someone can use my solution.

My Screenshot class is based on the following solutions:

I use tuples to to return multiple parameters C# 7.0 Tuples.

So here is my class:

 using System.Collections.Generic;
 using System.IO;
 using System.Linq; 
 using System.Windows;
 using System.Windows.Controls.Primitives;
 using System.Windows.Interop;
 using System.Windows.Media;
 using System.Windows.Media.Imaging;

 public class Screenshot
 {
      //UIElement to create screenshot 
      private UIElement _element;
      //Bounds for the screenshot
      private Rect _screenshotBounds;
      //Path for Screenshot
      private string _path;


      private const int DPI = 384;
      private const double BASEDPI = 96;
      private const double DPISCALE = DPI / BASISDPI;

      public Screenshot(UIElement element, string path)
      {
          this._element = element; 
          this._screenshotBounds = this.createBounds(this._element);
          this._path = path;
      }

      //public interface to create the screenshot
      public void createScreenshot()
      {
         if (this._element == null)
         {
             return;
         }
         //Create a list of tuples with the elements to render in the screenshot
         List<(UIElement, Rect, Point)> listElements = new List<(UIElement, Rect, Point)>
         {
              //Fist element in the list should be the actual UIElement
              this.createElementBoundPosition(this._element); 
         };

         RenderTargetBitmap renderBitMap = this.createBitMap(this._screenshotBounds);

         //Get the opened Popups, create a list of tuples for the Popups and add them to the list of elements to render
         listElements.AddRange(this.createListPopUpBoundsPosition( this.getOpenPopups()));

         DrawingVisual drawingVisual = this.createDrawingVisual(listElements);
         renderBitMap.Render(drawingVisual);

         this.saveRTBAsPNG(renderBitMap);
      }

      //Create DrawingVisual based on List of Tuples
      private DrawingVisual createDrawingVisual(List<(UIElement, Rect, Point)> listElements)
      {
           DrawingVisual drawingVisual = new DrawingVisual();

           using (DrawingContext context = drawingVisual.RenderOpen())
           {
                foreach((UIElement element, Rect bounds, Point position) in listElements)
                {
                      VisualBrush visualBrush = new VisualBrush(element);
                      context.DrawRectangle(visualBrush, null, new Rect(position, bounds.Size));
                }
           }

           return drawingVisual;
      }

      //Save RenderTargetBitmap to file
      private void saveRTBAsPNG(RenderTargetBitmap bitmap)
      {
          PngBitmapEncoder pngBitmapEncoder = new PngBitmapEncoder()
          {
              Interlace = PngInterlaceOption.On
          }

          pngBitmapEncoder.Frames.Add(BitmapFrame.Create(bitmap));

          using (FileStream fileStream = File.Create(this._path))
          {
              pngBitmapEncoder.Save(fileStream);
          }
      }

      //Create Bounds for Element
      private Rect createBounds(UIElement element)
      {
          new Rect(new Size((int)element.RenderSize.Width, (int)element.RenderSize.Height)); 
      } 

      //Create a Tuple with the Element, its bounds and its position
      private (UIElement element, Rect bounds, Point position) createElementBoundPosition(UIElement element)
      {
          return (element, this.createBounds(element), element.PointToScreen(new Point(0,0)));
      } 

      //create the RenderTargetBitmap
      private RenderTargetBitmap createBitMap(Rect bounds)
      {
          (int width, int height) calculatedBounds = this.calculateBounds(bounds);
           return new RenderTargetBitmap(calculatedBounds.width, calculatedBounds.height, DPI, DPI, PixelFormats.Pbgra32);
      }

      //recalculate bounds according to the scale
      private (int width, int heigth) calculateBounds(Rect bounds)
      {
          int width = (int)(bounds.Width * DPISCALE);
          int height = (int)(bounds.Height * DPISCALE);
          return (width, height);
      }

      //Convert the list of Popups into a List of Tuples
      private List<(UIElement element, Rect bounds, Point position)> createListPopUpBoundsPosition(List<Popup> listPopup)
      {
           List<(UIElement, Rect, Point)> list = new List<(UIElement, Rect, Point)>();

           foreach (Popup p in listPopup)
           {
               //The Child-Element contains the UIElement to render
               UIElement uiElement = p.Child;
               list.Add(this.createElementBoundPosition(uiElement));
           }
           return list;
      }

      //get the open Popups
      private List<Popup> getOpenPopups()
      {
           return PresentationSource.CurrentSources.OfType<HwndSource>()
               .Select(h => h.RootVisual)
               .OfType<FrameworkElement>()
               .Select(f => f.Parent)
               .OfType<Popup>()
               .Where(p => p.IsOpen).ToList();
      }

 }