How to Render a Wpf Usercontrol to a Bitmap Without Creating a Window

How to render a WPF UserControl to a bitmap without creating a window

Ended up using an HwndHost with no actual window.

void cwind()
{
Application myapp = new Application();
mrenderer = new WPFRenderer();
mrenderer.Width = 256;
mrenderer.Height = 256;

HwndSourceParameters myparms = new HwndSourceParameters();
HwndSource msrc = new HwndSource(myparms);
myparms.HwndSourceHook = new HwndSourceHook(ApplicationMessageFilter);

msrc.RootVisual = mrenderer;
myapp.Run();
}
static IntPtr ApplicationMessageFilter(
IntPtr hwnd, int message, IntPtr wParam, IntPtr lParam, ref bool handled)
{
return IntPtr.Zero;
}

WPF rendering control to Bitmap without Window

Oké I am not sure why. As said before I am not very familiar with WPF's internal rendering mechanism and what triggers it, but the solution is to wrap the Chart in a Viewbox. The following code does the trick:

        var viewbox = new Viewbox();
viewbox.Child = chart;
viewbox.Measure(chart.RenderSize);
viewbox.Arrange(new Rect(new Point(0, 0), chart.RenderSize));
chart.Update(true, true); //force chart redraw
viewbox.UpdateLayout();

// RenderTargetBitmap rtb = new RenderTargetBitmap((int)chart.DesiredSize.Width, (int)chart.DesiredSize.Height, 96, 96, PixelFormats.Pbgra32);
var rtb = new RenderTargetBitmap(500, 500, 96, 96, PixelFormats.Default);
rtb.Render(chart);

var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(rtb));

using (var stream = File.OpenWrite(@"D:\bitmap.png")) {
encoder.Save(stream);
}

render WPF user control in memory, not on screen

In your case the solution would be something like this:

 public static System.Windows.Controls.Image fromControlToImage(System.Windows.FrameworkElement control)
{
Size size = new Size(100, 100); // Or what ever size you want...
control.Measure(size);
control.Arrange(new Rect(size));
control.UpdateLayout();
...
}

Rending WPF in Memory is quite often asked here at SO:

Drawing Control to Memory

As the control has no parent container, you need to call Measure and Arrange in order to do a proper layout.

WPF Get Size of UIElement in Memory

You need to force a render of the item, or wait for the item to be rendered. You can then use the ActualHeight and ActualWidth properties.

An additional for your purpose:

Force Rendering of a WPF Control in Memory

Propably you can use a ViewBox to render in memory

Generating a PNG from WPF Control in a Windows Service

I'm not sure if you are allowed to create new Windows from a Service. but i can solve your timer problem.

If you want to create one of these timers, the typically need be executed in a STAT-Thread.Why is STAThread required?

Thread timer = new Thread(() =>
{
while (true)
{
//do stuff...

Thread.Sleep(5000); //wait 5 seconds
}
});
timer.SetApartmentState(ApartmentState.STA); //creates a new Thread for UIs
timer.IsBackground = true; //if you main Thread exits this will also shutdown.
timer.Start();

If you don't want to use Lambda

private void MyTimerMethode()
{
while (true)
{
// do stuff...

Thread.Sleep(5000); //wait 5 seconds
}
}

Thread timer = new Thread(MyTimerMethode);
timer.SetApartmentState(ApartmentState.STA); //creates a new Thread for UIs
timer.IsBackground = true;
timer.Start();

If you want to kill it you can use

timer.Abort();

But i recommend using a field like bool isAlive = true; and using it as while loop condition. now if you want to end it you can end it without killing it and throwing any exceptions.

This is what i found after a quick google search for rendering a controls as an image. How to render a WPF UserControl to a bitmap without creating a window

Edit:
I've tried using the code from 'How to render a WPF UserControl to a bitmap without creating a window' and it is working even with a Service.

Render image from UserControl that has LiveChart

For me this works fine for only the chart as well as for the whole user control:
(Note: be sure your usercontrol has a visible background color, if the background is transparent it will be rendered transparent which most image viewer show as black)

EDIT: ok, I guess my solution won't work if the chart isn't visible. But if you can't get it to work otherwise, why not show the usercontrol in a new window, save it and close that window again?

    public static BitmapSource RenderChartAsImage(FrameworkElement chart)
{
RenderTargetBitmap image = new RenderTargetBitmap((int)chart.ActualWidth, (int)chart.ActualHeight,
96, 96, PixelFormats.Pbgra32);
image.Render(chart);
return image;
}

public static void SaveChartImage(FrameworkElement chart)
{
System.Windows.Media.Imaging.BitmapSource bitmap = RenderChartAsImage(chart);

Microsoft.Win32.SaveFileDialog saveDialog = new Microsoft.Win32.SaveFileDialog();
saveDialog.FileName += "mychart";
saveDialog.DefaultExt = ".png";
saveDialog.Filter = "Image (*.png)|*.png";

if (saveDialog.ShowDialog() == true)
{
using (FileStream stream = File.Create(saveDialog.FileName))
{
System.Windows.Media.Imaging.PngBitmapEncoder encoder = new System.Windows.Media.Imaging.PngBitmapEncoder();
encoder.Frames.Add(System.Windows.Media.Imaging.BitmapFrame.Create(bitmap));
encoder.Save(stream);
}
}
}

How to create an image of a WPF UserControl at runtime

You can render a WPF control to a bitmap using RenderTargetBitmap, then this image can be copied to the clipboard, saved to a file, or used as part of your GUI

Check out Get a bitmap image from a Control view

Beware with this that you can hit problems when parts of the control you are trying to render are not visible (within a scroll viewer perhaps)

WPF Copy image of control to bitmap

Here is what I ended up with...

        tempWidth = myControl.ActualWidth;
tempHeight = myControl.ActualHeight;

myControl.Width = double.NaN;
myControl.Height = double.NaN;

myControl.UpdateLayout();

RenderTargetBitmap rtb = new RenderTargetBitmap((int)myControl.ActualWidth, (int)myControl.ActualHeight, 96, 96, PixelFormats.Pbgra32);

rtb.Render(myControl);

PngBitmapEncoder pbe = new PngBitmapEncoder();
pbe.Frames.Add(BitmapFrame.Create(rtb));
MemoryStream stream = new MemoryStream();
pbe.Save(stream);
image = (System.Drawing.Bitmap)System.Drawing.Image.FromStream(stream);

CEGrid.Width = tempWidth;
CEGrid.Height = tempHeight;


Related Topics



Leave a reply



Submit