How can I render a WPF UserControl to a bitmap without creating a window? I need to render a WPF UserControl and upload it to another program. The bitmaps will be rendered through a Windows Service, so creating a window is not an option (I know there's ways to 'virtually' create windows, but unfortunately anything that calls a command to create a window is NOT an option in my case). Is there a way to RENDER the UserControl without binding it to a Window?
            Asked
            
        
        
            Active
            
        
            Viewed 2.8k times
        
    28
            
            
        - 
                    Perhaps you could supply a little more information. If the User Control can't be rendered, why does it even exist? It is a visual element. – Steve Wellens Mar 04 '11 at 03:29
 - 
                    It needs to be rendered, but not directly to a display (so there can be no window). It's being rendered to an OpenGL cube. The cube rendering works, but currently I have to create a separate window to do the rendering in. It would be nice if I didn't need a separate window for the WPF rendering. – bbosak Mar 04 '11 at 03:34
 
4 Answers
70
            
            
        Have you tried spinning up an instance of the user control and doing something like this:
UserControl control = new UserControl1();
control.Measure(new Size(300, 300));
control.Arrange(new Rect(new Size(300,300)));
RenderTargetBitmap bmp = new RenderTargetBitmap(300, 300, 96, 96, PixelFormats.Pbgra32);
bmp.Render(control);
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bmp));
using (Stream stm = File.Create(@"c:\test.png"))
   encoder.Save(stm);
It looks like you need to Measure, Arrange. This worked for me.
        RQDQ
        
- 15,461
 - 2
 - 32
 - 59
 
- 
                    2Google search to export a user control to a JPG brought me here and this answer was perfect. Thanks very much. – Richard Powell Feb 27 '14 at 11:03
 - 
                    1This works really well however underlying styles are not properly applied to the user control. I'm not sure how to overcome that at the moment, otherwise a perfect solution! – JT_ Jan 13 '15 at 09:13
 - 
                    1To make this work, did you have to have the control previously displayed? I am creating a window and will get black output unless I previously Show() and Close() the window. – Steve Robbins Jun 10 '16 at 20:24
 - 
                    4If it helps someone, calling `control.UpdateLayout()` immediately after `Arrange()` did the trick for me. – dotNET Oct 20 '16 at 12:01
 - 
                    @SteveRobbins Interestingly enough, I got the same result you did when calling this from my main method on the control itself. But when I moved the code into the Control and just called the method on the specific element (i.e. specific Control within my control that I wanted) it behaved as described. This is in addition to having to call control.UpdateLayout() per (at)dotNET's suggestion so that it had my data from my DataContext instead of just a blank grid. – claudekennilol May 15 '18 at 19:02
 
8
            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;
    }
        bbosak
        
- 5,353
 - 7
 - 42
 - 60
 
- 
                    1I had to modify your solution a bit to make it work for me, but using a Win32 wrapper to force WPF to load in a hidden window is a great idea. – floele Nov 19 '12 at 08:54
 - 
                    @floele: maybe for some solutions, a `Win32` wrapper is a great idea. But in this case, I think @RQDQ has the proper solution. – IAbstract Aug 15 '15 at 21:15
 - 
                    2Please tell me What is WPFRenderer? (where is that assembly file and namespace) – Maria Jul 17 '16 at 12:50
 - 
                    Two comments: 1) WPFRenderer appears to be a custom UserControl. 2) This solution is the best because it supports Bindings. Simply creating, measuring, and arranging a UserControl (as @RQDQ) has suggested is a simpler, more elegant solution but does not support Bindings. So I guess it depends on your use case what you should do. – Tim Cooke Jan 21 '17 at 15:03
 - 
                    This is also the solution if you want to render animations \ storyboards in your xaml. – BryanJ Apr 11 '17 at 03:12
 
4
            
            
        Apparently, if you call control.UpdateLayout() after measuring and arranging, the user control doesn't need to be in any window.
        Jiří Skála
        
- 649
 - 9
 - 23
 
1
            
            
        Based on IDWMaster's solution I did it a bit differently using the System.Windows.Forms.UserControl. Otherwise the bindings were not up-to-date when the export to bitmap happened. This works for me (this is the WPF control to render):
System.Windows.Forms.UserControl controlContainer = new System.Windows.Forms.UserControl();
controlContainer.Width = width;
controlContainer.Height = height;
controlContainer.Load += delegate(object sender, EventArgs e)
{
    this.Dispatcher.BeginInvoke((Action)delegate
    {
        using (FileStream fs = new FileStream(path, FileMode.Create))
        {
            RenderTargetBitmap bmp = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
            bmp.Render(this);
            BitmapEncoder encoder = new PngBitmapEncoder();
            encoder.Frames.Add(BitmapFrame.Create(bmp));
            encoder.Save(fs);
            controlContainer.Dispose();
        }
    }, DispatcherPriority.Background);
};
controlContainer.Controls.Add(new ElementHost() { Child = this, Dock = System.Windows.Forms.DockStyle.Fill });
IntPtr handle = controlContainer.Handle;
        floele
        
- 3,668
 - 4
 - 35
 - 51