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