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();
}
}
我找了又找,没找到和我有同样问题的。我正在尝试呈现 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();
}
}