Search code examples
wpftooltipinfragisticsrendertargetbitmapxamdatachart

WPF Including Tooltip in Render with RenderTargetBitmap


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

Solution

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