none
Hosting D3D content in WPF (.Net 3.0) Application

    Question

  • I know this question has been asked over and over but I can't seem to find a suitable answer. I am trying to display a Direct3D window within my WPF application. Some constraints are that I would like to be able to do this without requiring .NET 3.5 or 3.5SP1 if possible. I want to be able to target any Vista user whether the user has upgraded to .net 3.5 or not. Also, once the 3d Window is hosted I need to be able to modify settings such as Anti-aliasing level or mipmap detail level.

    I have a C++ DLL that implements this beautifully when hosted by an MFC application. I just pass in an HInstance to the application and the HWND to the DLL API and it can render D3D content in that window no problem. So I know that the D3D DLL works.

    However, when I tried to host it in the WPF application it doesn't work. I took the naive approach and just passed in the HWND of a WindowsFormsHost object.

    Here is my XAML for the Window:

    <Window x:Class="WpfPreviewerApp.Window1" 
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        Title="Window1" 
        Height="300" 
        Width="300">  
        <Grid x:Name="grid3dPreviewer">  
            <WindowsFormsHost x:Name="formsHost"></WindowsFormsHost> 
        </Grid> 
    </Window> 

    Here is the code behind for my Window:

        public partial class Window1 : Window  
        {  
            [DllImport("Gfx3dPreviewPOC.dll", SetLastError = true)]  
            public static extern IntPtr GpInit3dWindow(IntPtr hInstance, IntPtr handle);  
     
            [DllImport("Gfx3dPreviewPOC.dll", SetLastError = true)]  
            public static extern IntPtr GpStartRendering(IntPtr handle);  
     
            [DllImport("kernel32.dll", CharSetCharSet = CharSet.Auto)]  
            public static extern IntPtr GetModuleHandle(string lpModuleName);  
     
            public Window1()  
            {  
                InitializeComponent();  
                this.Loaded += new RoutedEventHandler(Window1_Loaded);  
            }  
     
            void Window1_Loaded(object sender, RoutedEventArgs e)  
            {  
                IntPtr hInstance = GetModuleHandle(null);  
                IntPtr handleWnd = GpInit3dWindow(hInstance, formsHost.Handle);  
                GpStartRendering(handleWnd);  
            }  
        } 


    Like I said, the Gfx3dPreviewPOC.dll works flawlessly when hosted in a Win32 MFC application. Here the GpInit3dWindow crashes because even though the HWND associated with the WindowsFormsHost object is valid when I look at it in the debugger, however the height and width of the window are 0 and the 3d graphics can't render in that window because the buffer for rendering the 3d is zero size by virtue (or vice) of the fact that the WindowsFormsHost window has basically no size.

    So what gives? Why does that WindowsFormsHost window have an invalid size? Even if I just set the Width and Height properties of the WindowsFormsHOst object to 100 each, it still fails in the same way.

    Also, I've heard there are problems trying to host DIrect3D content in .NET 3.0. Am I going to run into problems if I try to also make my Window transparent or make other screen elements partially transparent?

    Thanks in advance.
    Friday, August 29, 2008 11:30 PM

Answers

  • I think I solved my problem... For my application I don't have the luxury of installing .NET 3.5 Framework. I also don't really have any airspace issues since I don't need to occupy the same space as other WPF elements with my D3D element or even show tooltips or menus from the D3D window.

    Instead of using WIndowsFormsHost which derives from HwndHost I made my own control that inherits from HwndHost. If you inherit from HwndHost you should override the BuildWindowCore function if you want it to actually do anything like create a window. Here is my BuildWindowCore which is lifted and modified from an SDK sample:

    protected override HandleRef BuildWindowCore(HandleRef hwndParent)  
    {  
       //hwndControl = IntPtr.Zero;  
       hwndHost = IntPtr.Zero;  
     
       hwndHost = CreateWindowEx(0, "static", "",  
                                    WS_CHILD | WS_VISIBLE,  
                                    0, 0,  
                                    hostHeight, hostWidth,  
                                    hwndParent.Handle,  
                                    (IntPtr)HOST_ID,  
                                    IntPtr.Zero,  
                                    0);  
     
       return new HandleRef(this, hwndHost);  

    I bind this HwndHost object to a XAML object. In my case I am using a Border to bind to. Here is the line from my XAML:

    <Border x:Name="controlHost" Grid.Row="0"></Border> 

    Now in the code behind for that Window I do the following in the Loaded event hander:

    void Window1_Loaded(object sender, RoutedEventArgs e)  
    {  
       ThreeDControl = new ControlHost(controlHost.ActualHeight, controlHost.ActualWidth);  
     
       controlHost.Child = ThreeDControl;  
     
       // TODO: If we need to handle messages meant for the control window attach  
       // this message hook.  
       //listControl.MessageHook += new HwndSourceHook(ControlMsgFilter);  

    I commented out the MessageHook handler but that is for handling messages sent to the ThreeDControl window whihc I don't bother handling at this point. Finally, I start rendering my 3D Scene on the HwndHost window from a button Click handler like this:

    private void Button_Click(object sender, RoutedEventArgs e)  
    {  
       IntPtr hInstance = GetModuleHandle(null);  
     
       ThreeDHwnd = GpInit3dWindow(hInstance, ThreeDControl.Handle);  
     
       GpStartRendering(ThreeDHwnd);  

    Of course for this you will need a few Win32 APIs to PInvoke. But it works. I do have problems if I resize my window. The application hangs on resize. I need to handle that case. I also can't call the GpIni3dWindow function from the Windows Loaded event handler. It doesn't render anything at that point. This is why I actually initialize the 3dWindow from a button handler instead of the Loaded handler which is where I'd like it to be.

    Anyway, there were several references to this in the .NET 3.0 SDK but none were specifically aimed at doing D3D windows. Maybe I can convince our program management that we should use .NET 3.5 so I can use the D3DImage class. We prefer not to burden our users with that install since it makes for a much larger download (my product targets Vista and later only so we don't need to install .Net 3.0).
    • Marked as answer by Marco Zhou Thursday, September 4, 2008 2:19 AM
    Wednesday, September 3, 2008 4:20 PM

All replies

  • -> So what gives? Why does that WindowsFormsHost window have an invalid size? Even if I just set the Width and Height properties of the WindowsFormsHOst object to 100 each, it still fails in the same way.

    Have you tried creating a Windows Forms Control something like the following:

    System.Windows.Forms.UserControl control = new System.Windows.Forms.UserControl();
    control.Width = 400;
    control.Height = 300;
    WindowsFormsHost host = new WindowsFormsHost();
    host.Child = control;

    And then you could pass the hwnd of the System.Windows.Forms.UserControl to your 3D APIs. The hwnd used to WindowsFormsHost is used to chain up the Windows Forms native hwnds, it's not intended to be used directly for other purpose.

    Or you could directly use HwndHost could gives you a hwnd you could directly manipulate with?

    -> Also, I've heard there are problems trying to host DIrect3D content in .NET 3.0. Am I going to run into problems if I try to also make my Window transparent or make other screen elements partially transparent?

    Yes, there an airspace issue associated with hosting D3D using hwnd as clearly documented in this article:
    http://msdn.microsoft.com/en-us/library/aa970688.aspx

    That said, the .NET Framework 3.5 SP1 introduced the D3DImage which could overcome those air space issues, and could have the hosted D3D content blended with the WPF content.

    Hope this helps
    • Edited by Marco Zhou Wednesday, September 3, 2008 6:44 AM add stuff
    Wednesday, September 3, 2008 6:43 AM
  • I think I solved my problem... For my application I don't have the luxury of installing .NET 3.5 Framework. I also don't really have any airspace issues since I don't need to occupy the same space as other WPF elements with my D3D element or even show tooltips or menus from the D3D window.

    Instead of using WIndowsFormsHost which derives from HwndHost I made my own control that inherits from HwndHost. If you inherit from HwndHost you should override the BuildWindowCore function if you want it to actually do anything like create a window. Here is my BuildWindowCore which is lifted and modified from an SDK sample:

    protected override HandleRef BuildWindowCore(HandleRef hwndParent)  
    {  
       //hwndControl = IntPtr.Zero;  
       hwndHost = IntPtr.Zero;  
     
       hwndHost = CreateWindowEx(0, "static", "",  
                                    WS_CHILD | WS_VISIBLE,  
                                    0, 0,  
                                    hostHeight, hostWidth,  
                                    hwndParent.Handle,  
                                    (IntPtr)HOST_ID,  
                                    IntPtr.Zero,  
                                    0);  
     
       return new HandleRef(this, hwndHost);  

    I bind this HwndHost object to a XAML object. In my case I am using a Border to bind to. Here is the line from my XAML:

    <Border x:Name="controlHost" Grid.Row="0"></Border> 

    Now in the code behind for that Window I do the following in the Loaded event hander:

    void Window1_Loaded(object sender, RoutedEventArgs e)  
    {  
       ThreeDControl = new ControlHost(controlHost.ActualHeight, controlHost.ActualWidth);  
     
       controlHost.Child = ThreeDControl;  
     
       // TODO: If we need to handle messages meant for the control window attach  
       // this message hook.  
       //listControl.MessageHook += new HwndSourceHook(ControlMsgFilter);  

    I commented out the MessageHook handler but that is for handling messages sent to the ThreeDControl window whihc I don't bother handling at this point. Finally, I start rendering my 3D Scene on the HwndHost window from a button Click handler like this:

    private void Button_Click(object sender, RoutedEventArgs e)  
    {  
       IntPtr hInstance = GetModuleHandle(null);  
     
       ThreeDHwnd = GpInit3dWindow(hInstance, ThreeDControl.Handle);  
     
       GpStartRendering(ThreeDHwnd);  

    Of course for this you will need a few Win32 APIs to PInvoke. But it works. I do have problems if I resize my window. The application hangs on resize. I need to handle that case. I also can't call the GpIni3dWindow function from the Windows Loaded event handler. It doesn't render anything at that point. This is why I actually initialize the 3dWindow from a button handler instead of the Loaded handler which is where I'd like it to be.

    Anyway, there were several references to this in the .NET 3.0 SDK but none were specifically aimed at doing D3D windows. Maybe I can convince our program management that we should use .NET 3.5 so I can use the D3DImage class. We prefer not to burden our users with that install since it makes for a much larger download (my product targets Vista and later only so we don't need to install .Net 3.0).
    • Marked as answer by Marco Zhou Thursday, September 4, 2008 2:19 AM
    Wednesday, September 3, 2008 4:20 PM
  • HI Marco,

    Can you give me some direction here? I have run into a very tough problem. I was able to get my D3D scene to render within a WPF application. I did this by writing my own class based on HwndHost (which WindowsFormsHost is also based). It works great in a test application that I wrote. Then when I integrated this into my main applicaiton it stopped rendering the 3D Scene. I stepped through the debugger and everything seemed to be setup correctly but no 3D rendering was visible.

    When I narrowed down what was different between my test app and my production application I found that my main Window has the AllowsTransparency=True and WindowStyle=None. The transparency thing causes DirectX content not to render on Vista and XP from what I have seen.

    Is there any way around this problem without having to resort to installing the .NET framwork 3.5? I did use the D3DImage you mentioned and this problem goes away. It appears to be able to handle DirectX content and transparent windows together. The problem is that we don't want to force a 197MB download on our customers since we are targeting Vista only for our product.

    Hopefully someone at Microsoft will have a solution since I can see this might require me to rethink whether or not we want to continue with WPF for this project.
    Wednesday, September 17, 2008 6:32 PM