WPF 在使用 RenderTargetBitmap 进行渲染时包含工具提示

WPF Including Tooltip in Render with RenderTargetBitmap

我找了又找,没找到和我有同样问题的。我正在尝试呈现 WPF 应用程序的一些高 resolution/dpi 屏幕截图。唯一的问题是我需要在渲染中包含来自图表工具提示的信息,除此之外我可以很好地保存屏幕截图。

我目前正在使用 Infragistics XamDataChart 并且我在代码中生成工具提示而不是 xaml。

有人知道如何在可视化树中获取工具提示以便呈现吗?或者能够渲染整个 window 以及其中的所有内容,包括工具提示覆盖?

渲染代码:

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

我在后面的 MainWindow 代码中调用了这个。

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

有点晚了,但也许有人可以使用我的解决方案。

我的截图class基于以下解决方案:

  • 截图的制作基于QMINO技术博客postHigh Res Screenshots
  • 为了获取当前打开的工具提示,我在此处使用代码片段 post:Find all opened Popups in WPF application
  • 我花了一段时间才找到如何将屏幕截图的 UIElement 与 PopUps 合并的解决方案,但 Clemens gave the final clue Overlay Images with PngBitmapEncoder in WPF
  • 的评论

我使用元组 return 多个参数 C# 7.0 Tuples

这是我的 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();
      }

 }