This article shows how to display OpenGL content hosted in a Windows Presentation Foundation (WPF) based application. While working on a new project, I came across this issue and I want to share what I found. I am not an expert on WPF or OpenGL, though.
The controls in this example are implemented using managed C++, because it makes using native OpenGL and Win32 libraries easier. This is a nice example of the usefulness of managed C++, I think. Note that you can create a wrapper for an existing native C++ control the same way. Tha Tao-Framework based version makes life even easier, not requiring any managed C++.
The sample application will show a WPF program in C#, which displays an OpenGL window (or control). This is similar to the situation encountered in CAD / CAM applications. Also, the window should close when pressing the ESC key. In WinForms and Win32, this problem is fairly easy to solve. There are excellent articles and samples out there (Thanks to Jeff Molofee aka NeHe). However, due to the fact that the internal structure of WPF is very different from that of the Win32 API or WinForms, we have to change a few things.
Fortunately, there is a very nifty framework out there called "Tao" which can be found at The Tao Framework. The Tao Framework renders this article quite superfluous. What remains is a very simple sample application which can be downloaded at the top of the article along with parts of the source of Tao. Note that Tao is distributed under a different license (MIT License). See the file "Copying" for details.
Now, if you really want to know how to do it manually, here we go: In this article, I assume that you have a basic idea of how to create an OpenGL-window using Win32 API. You haven't heard of PIXELFORMATDESCRIPTOR
before? In this case, you might want to read NeHe's first tutorial. (See the Resources Section).
Also, some very basic WPF know-how will be useful (e.g. how to reference custom controls in another assembly from XAML). I suggest you read Sacha Barber's excellent introduction to WPF here on The Code Project. (Huge thanks to Sacha for his articles!)
Most notably, we will use WindowsFormsHost
and HwndHost
in this article.
In order to create an OpenGL-window, we need to have a dedicated HWND
. In Win32, we can simply create one of our own. WindowsForms
controls, on the other hand, each have their own HWND
anyway, so we can simply use that. In WPF, however, there is only one HWND
for the application (With some exceptions: menus, for example, have their own window). As we do not want to interfere with the rendering of WPF's controls, acquiring the application's HWND
is not a good idea (if possible at all). So how do we get a window for OpenGL to render to?
Microsoft provides us with two simple classes made for WPF / Win32 interoperation. As the name suggests, these can be found in the namespace namespace System.Windows.Interop
. These are the earlier mentioned WindowsFormsHost
and HwndHost
classes. We will host a WindowsForms
UserControl
in the WindowsFormsHost
, and a custom Win32 API window using the HwndHost
. Note that WindowsFormsHost
is actually derived from HwndHost
. Let's look at the simpler case of using a WindowsForms UserControl
in a WindowsFormsHost
first:
The WindowsFormsHost
is a control we can simply embed in the WPF Applications' XAML file like this...
<int:WindowsFormsHost Name="windowsFormsHost1">
<oglc:OpenGLUserControl Name="openGLControl1"/>
</int:WindowsFormsHost>
(Note that the namespace
s used in here must be declared first. See the sample code or refer to Sacha's article for more information on that.)
... where the OpenGLUserControl
itself is defined as (Managed C++):
public ref class OpenGLUserControl : public UserControl
{
};
or, in C#, as...
public class OpenGLUserControl : UserControl
{
};
... respectively. This is not OpenGL-specific yet and can be used to host any Windows Forms control conveniently!
For our OpenGL-enabled Forms control, we'll need the following declaration and member variables:
public ref class OpenGLUserControl : public UserControl
{
private:
HDC m_hDC;
HWND m_hWnd;
HGLRC m_hRC;
System::ComponentModel::Container^ components;
}
If you haven't used managed C++ before, just ignore the '^'-symbol.
For initialization, we register a delegate in the constructor:
this->Load += gcnew System::EventHandler(this,
&OpenGLUserControl::InitializeOpenGL);
In C#, this would look a little simpler:
this.Load += new System.EventHandler(InitializeOpenGL);
The initialization handler is as follows:
virtual void
InitializeOpenGL( Object^ sender, EventArgs^ e)
{
m_hWnd = (HWND) this->Handle.ToPointer();
}
We need to resize the OpenGL-viewport if the window size changes, so we need to register another delegate:
this->SizeChanged += gcnew EventHandler(this,
&OpenGLUserControl::ResizeOpenGL);
(I won't write down the C# version every time in order not to bloat the article).
This method does little more than setting the OpenGL viewport and updating the projection matrix. As a matter of fact, I have chosen to use an orthogonal projection for reasons that I will explain shortly.
For perspective projections, the projection matrix must be recalculated when the window size changes, for example, using gluPerspective()
or glFrustum()
, that's why I left the code in this method.
void ResizeOpenGL(Object^ sender, EventArgs^ e)
{
glViewport( 0, 0, Width, Height );
glOrtho(-1.0, 1.0, -1.0, 1.0, 1.0, 100.0);
}
Also, we have to override the OnPaintBackground()
method to avoid flicker:
virtual void OnPaintBackground( PaintEventArgs^ e ) override
{
}
The actual OpenGL drawing can then be performed in the OnPaint()
method:
virtual void OnPaint( System::Windows::Forms::PaintEventArgs^ e ) override
{
}
That's it, basically! We now have a Windows Forms control which will display an OpenGL-window. The sample code also renders one of the impressive triangles!
Now we can go one inheritance level higher and mess around with HwndHost
so we can use any Win32 control (or window). First, we can no longer insert the control in XAML. Instead, we create a placeholder in XAML, in this case, just a Border
-control:
<Window x:Class="WPFOpenGLApp.OpenGLHWndWindow"
Title="OpenGL Test Window" Height="300" Width="480"
Loaded="Window_Loaded">
<Grid>
<Border Name="hwndPlaceholder" />
</Grid>
</Window>
... and programmatically attach a child to it upon load:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
HwndHost host = new WPFOpenGLLib.OpenGLHwnd();
hwndPlaceholder.Child = host;
}
Et Voila!
Implementing the control itself also is somewhat different from the WindowsForms
-case:
- Using the
HwndHost
, we create the HWND
ourselves. To do that, we also need to register our Window
class.
- For drawing purposes, we only overwrite the
OnRender()
method. There is no Paint
/PaintBackground
distinction.
- We have to care about the system's DPI setting manually.
- Our
Window
now has its own WindowProc
!
In principle, you could take a complete Win32 application and put it into the HwndHost
. The procedure of creating a window is the same as under Win32, but it has to be performed in the overwritten BuildWindowCore()
method.
virtual HandleRef BuildWindowCore(HandleRef hwndParent) override
Yet, meaningful interaction of WPF and Win32 is fraught with its own perils. More about that later.
A little note at the side: To allow that, we check whether the WNDCLASS
has already been registered:
bool RegisterWindowClass()
{
WNDCLASS wndClass;
if(GetClassInfo(m_hInstance,
m_sWindowName, &wndClass))
{
return true;
}
}
This works exactly as it does in Win32. This, however, seems a little strange: The class HwndHost
supplies us with a managed method called WndProc()
. MSDN suggests to overwrite this, but I didn't manage to initialize the window this way.
When registering the Window
class, one can specify the WNDPROC
to be used. Leaving it empty resulted in strange access violations during initialization, while the following simple implementation proved to work out fine, thus rendering the overrideable WndProc()
method irrelevant:
LRESULT WINAPI
MyMsgProc(HWND _hWnd, UINT _msg,
WPARAM _wParam, LPARAM _lParam)
{
return DefWindowProc( _hWnd, _msg, _wParam, _lParam );
}
bool RegisterWindowClass()
{
WNDCLASS wndClass;
wndClass.lpfnWndProc = (WNDPROC)MyMsgProc;
}
At this point, however, the window does not have focus. Unfortunately, that will prevent not only our WNDPROC
to handle any key events, but it will also prevent HwndHost
from forwarding the keyboard information to WPF. Thus, we have to acquire focus manually by a little more sophisticated version of MyMsgProc
:
LRESULT WINAPI
MyMsgProc(HWND _hWnd, UINT _msg,
WPARAM _wParam, LPARAM _lParam)
{
switch(_msg)
{
case WM_IME_SETCONTEXT:
if(LOWORD(_wParam) > 0)
SetFocus(_hWnd);
return 0;
default:
return DefWindowProc( _hWnd, _msg, _wParam, _lParam );
}
}
Note that we have to check for LOWORD(_wParam) > 0
, otherwise the message stands for losing focus rather than gaining it.
Using the simple message handler presented above, most of the commands will be forwarded to the parent. We can thus easily catch key events in the WPF-based Window
class which owns the host.
However, this topic can be a lot more complicated, especially if we want to have two-way interaction between the Win32 control and WPF. This is outside this article's scope, however.
Since there are now more and more devices with a screen resolution of above 96 DPI, it becomes more important for applications to be DPI-Aware. To tell the truth, I never bothered about the DPI until I set it to 120 myself.
This is why I chose to use an orthogonal projection here: It enables us to check (by visual means) whether we correctly mapped the screen, or not.
In our case here, the problem becomes highly annoying: If you don't take the system's DPI setting into consideration, you will have a large border where you simply cannot draw to - your GL window is too small:
In order to avoid that, we get the system's DPI setting on initialization and multiply the new window size with it upon resize:
virtual HandleRef
BuildWindowCore(HandleRef hwndParent) override
{
m_hDC = GetDC(m_hWnd);
m_dScaleX = GetDeviceCaps(m_hDC, LOGPIXELSX) / 96.0;
m_dScaleY = GetDeviceCaps(m_hDC, LOGPIXELSY) / 96.0;
}
virtual void
OnRenderSizeChanged(SizeChangedInfo^ sizeInfo) override
{
int iHeight = (int)
(sizeInfo->NewSize.Height * m_dScaleY);
int iWidth = (int)
(sizeInfo->NewSize.Width * m_dScaleX);
glViewport( 0, 0, iWidth, iHeight);
}
Although the presented techniques are very similar at a first glance, they are targeted at different things: The HwndHost
is a class the actual control is derived from. On the other hand, the WindowsFormsHost
is a WPF-Control which we can place in an XAML file - the actual control in this case must be a UserControl
.
While the WindowsFormsHost
allows the use of an arbitrary WinForms user control with very little effort, usage of the HwndHost
can be quite tricky, especially when it comes to input handling. This is largely because it completely breaks the controls scheme of the GUI and, in the case of input events, even overrides the main (WPF) application. On the other hand, being able to combine Win32 and WPF with a few tricks is still marvellous!
One thing that bothers me is the exact behaviour of CS_OWNDC
. I have read some articles on the net about it, but in the end I did not find an explanation that satisfied me. Removing it from the code doesn't seem to change anything, but I wonder what happens when we perform more complex rendering operations?
Another issue is performance. I did not talk about it in the article at all for a reason. My system is barely capable of displaying a transparency-enabled Vista desktop at full resolution... In my case, performance is a catastrophe! However, I believe that is largely due to a fill rate bottleneck of my old GeForce MX 5200. Also, we can't make a performance measurement using a timer that invalidates the control from time to time, of course!
Thank you for reading!
This is my first article. Phew... quite a bit of work!
Any feedback is highly appreciated!
- 2009-03-05 v. 1.2: Added Tao Code
- 2008-02-20 v. 1.1: Fixed some spelling mistakes
- 2008-02-19 v. 1.0: Initial release