WPF input Interop over DirectX Airspace

WPF input Interop over DirectX Airspace

  • Comments 10

I've seen that the Airspace regions constraints have been a concern for some folks interested in using DirectX with their WPF applications. Based on this, here is a follow-up to an earlier sample I posted, this time using Layered windows to intercept input, allowing for the provision of rich WPF context menus, tooltips, and traditional mouse actions over the airspace of an interop region. It is pretty much the same as before, with one major change - the introduction of an "AirspaceOverlay" decorator.

 This decorator wraps around your interop content, and accepts an "OverlayChild" which is presented in a layered window over the content in the AirspaceOverlay control. In the example below, I used a canvas with a tooltip and some code-behind to also demonstrate a context menu. Here's a code snippet of the usage:

<MDXControl:AirspaceOverlay>

    <MDXControl:AirspaceOverlay.OverlayChild>

        <Canvas ToolTip = "A tooltip over a DirectX surface" Background="#01000000" Name="Overlay" />

    </MDXControl:AirspaceOverlay.OverlayChild>

    <!--Your Non-WPF Airspace Interop content goes here-->  

</MDXControl:AirspaceOverlay>

Known considerations:

  • Embedding of AirspaceOverlay in a Viewbox - due to region sizing issues, overlay placeent does not work correctly.
  • Hit testing within the transparent window will not occur on fully transparent regions, but will continue to propagate down to the interop surface. On the flip side, partially opaque regions will intercept input, preventing it from being recieved on the interop layer. This input handling behavior can be tweaked from the Win32 API's.
  • Focus will by default switch between the parent and layered child window. I return focus on mouse behavior to the parent in this implementation.
  • This sample is targeted to rectangular regions. By applying a custom shape consistent with the region below, irregular airspace regions can be overlaid with little additional effort.

I hope this helps some food for thought for folks looking at similar interop scenarios. Ok, have fun with the code, and feel free to send in questions.

Attachment: WPF-DX Interop - Part 2 .zip
Leave a Comment
  • Please add 5 and 4 and type the answer here:
  • Post
  • On my x64 system, it leaks memory like a sieve. Appears to be down in the unmanaged WPF code - managed memory is steady at 2mb. Haven't tried it on a 32bit system. Any ideas?

  • Hmm. Could you email(from the email link on side) me with details/repro on the 64 bit issue you are investigating?

    Thanks!

  • I was able to repro the issue Jamie described. The app was erroneoously creating a shader object each render cycle, without explicitly disposing previous due to a caching logic bug.

    BTW: In theory, the unmanaged shader resources should eventually get released when the managed objects are disposed via GC, but there is also an explicit api for release of the unmanaged resources.

    In ShaderRenderer.cs, Line 34 replace:

    reloadTexture = false;

    with:

    reloadShader = false;

  • Hi.

    Can i use this form to put WPF Button, WPF Windows or some WPF controls over directx surface... ¿?.

    Thanks you.

  • I really appreciate for your post, it's exactly what I'm looking for, however, why does .xaml won't display correctly in "Design" view in VS?

    It says below...

    A window parent could not be found for MS.Internal.Designer.DesignerPane+ContentRoot

      at WpfNuggets.ManagedDirectX.AirspaceOverlay.GetParentWindow(DependencyObject o) in D:\wpf\WPF DX Interop\ManagedDirectXContainer\AirspaceOverlay.cs:line 109

    Any help would be appreciated.

    Thank you very much.

  • Hi, do you have tested this solution even with an OpenGl rendered control?

  • Good solution, do you have found a solution to pass the mouse events to the Winform Control hosted in the WindowsFormsHost?

  • I have tested it, it works good.  One only issue is when max the window, this overlay window won't resize, I have do some refactor for it.

    Using following way to get the rectangle of parent window, and use it to resize the overlay window.

           private System.Drawing.Rectangle getWindowRectangle(Window parent)

           {

               System.Drawing.Rectangle windowRectangle;

               if (parent.WindowState == System.Windows.WindowState.Maximized)

               {

                   /* Here is the magic:

                    * Use Winforms code to find the Available space on the

                    * screen that contained the window

                    * just before it was maximized

                    * (Left, Top have their values from Normal WindowState)

                    */

                   var screen = Screen.FromHandle(new WindowInteropHelper(parent).Handle);

                   windowRectangle = screen.WorkingArea;

               }

               else

               {

                   windowRectangle = new System.Drawing.Rectangle(

                       (int)parent.Left, (int)parent.Top,

                       (int)parent.ActualWidth, (int)parent.ActualHeight);

               }

               return windowRectangle;

           }

  • Hi Guys,

    Soundwave asked a while ago if this approach is working for OpenGL. I use this approach for OpenGL and it works fine for us. Only thing we had to do is fixing the Maximize error by checking the window state as shown below:

              if(parentWindow.WindowState == WindowState.Normal)

               {

                  transparentInputWindow.Left = parentWindow.Left + r.Left + windowLeftMargin;

                  transparentInputWindow.Top = parentWindow.Top + r.Top + windowTopMargin;

               }

               else if (parentWindow.WindowState == WindowState.Maximized)

               {

                  transparentInputWindow.Left = r.Left + windowLeftMargin;

                  transparentInputWindow.Top = r.Top + windowTopMargin;

               }

               transparentInputWindow.Width = r.Width;

               transparentInputWindow.Height = r.Height;

  • This one works better for controls that don't consume the whole window.

       // Adapted from blogs.msdn.com/.../managed-directx-interop-with-wpf-part-2.aspx & www.4mghc.com/.../in-wpf-how-can-you-draw-a-line-over-a-windowsformshost

           protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)

           {

               base.OnRenderSizeChanged(sizeInfo);

               UpdateOverlaySize();

           }

           protected override void OnRender(DrawingContext drawingContext)

           {

               base.OnRender(drawingContext);

               if (_transparentInputWindow.Visibility != Visibility.Visible)

               {

                   UpdateOverlaySize();

                   _transparentInputWindow.Show();

                   _parentWindow = GetParentWindow(this);

                   _transparentInputWindow.Owner = _parentWindow;

                   _parentWindow.LocationChanged += ParentWindow_LocationChanged;

                   _parentWindow.SizeChanged += ParentWindow_SizeChanged;

               }

           }

           private static Window GetParentWindow(DependencyObject o)

           {

               var parent = VisualTreeHelper.GetParent(o);

               if (parent != null)

                   return GetParentWindow(parent);

               var fe = o as FrameworkElement;

               if (fe is Window)

                   return fe as Window;

               if (fe != null && fe.Parent != null)

                   return GetParentWindow(fe.Parent);  

               throw new ApplicationException("A window parent could not be found for " + o);

           }

           private void ParentWindow_LocationChanged(object sender, EventArgs e)

           {

               UpdateOverlaySize();

           }

           private void ParentWindow_SizeChanged(object sender, SizeChangedEventArgs e)

           {

               UpdateOverlaySize();

           }

           private void UpdateOverlaySize()

           {

               var hostTopLeft = PointToScreen(new Point(0, 0));

               _transparentInputWindow.Left = hostTopLeft.X;

               _transparentInputWindow.Top = hostTopLeft.Y;

               _transparentInputWindow.Width = ActualWidth;

               _transparentInputWindow.Height = ActualHeight;

           }

       }

Page 1 of 1 (10 items)