C# WPF create dpi aware child window

1

I want to create a dpi aware child window inside a WPF window. The child window will be used for directX rendering.

I created a minimal example with the window as follows:

<Window ...
        Loaded="OnLoaded"
        MouseMove="MainWindow_OnMouseMove">
    <DockPanel>
        <Menu DockPanel.Dock="Top">
            <MenuItem Header="TestItem"/>
        </Menu>
        <Border Background="Blue" x:Name="BorderHost"/>
    </DockPanel>
</Window>

The Border will be used to host my child window. The HwndHost for the border creates the directX swap chain and looks like this:

 public class ChildWindow : HwndHost
    {
        private readonly Border parent;
        private IntPtr hWnd = IntPtr.Zero;

        public SwapChain SwapChain { get; private set; }

        public ChildWindow(Border parent)
        {
            this.parent = parent;
            parent.SizeChanged += ParentOnSizeChanged;
        }

        private void ParentOnSizeChanged(object sender, SizeChangedEventArgs e)
        {
            // problem: width and height is not correctly scaled
            SwapChain?.Resize((int)(parent.ActualWidth), (int)(parent.ActualHeight));
        }

        protected override HandleRef BuildWindowCore(HandleRef hwndParent)
        {
            // create subwindow
            hWnd = CreateWindowEx(
                0, // dwstyle
                "static", // class name
                "", // window name
                WS_CHILD | WS_VISIBLE, // style
                0, // x
                0, // y
                (int)parent.ActualWidth, // renderWidth
                (int)parent.ActualHeight, // renderHeight
                hwndParent.Handle, // parent handle
                IntPtr.Zero, // menu
                IntPtr.Zero, // hInstance
                0 // param
            );

            // directx swap chain
            // problem: width and height is not correctly scaled
            SwapChain = new SwapChain(hWnd, (int)parent.ActualWidth, (int)parent.ActualHeight);

            return new HandleRef(this, hWnd);
        }

        protected override void DestroyWindowCore(HandleRef hwnd)
        {
            DestroyWindow(hWnd);
        }

        [DllImport("user32.dll", EntryPoint = "DestroyWindow", CharSet = CharSet.Unicode)]
        internal static extern bool DestroyWindow(IntPtr hwnd);

        [DllImport("user32.dll", EntryPoint = "CreateWindowEx", CharSet = CharSet.Unicode)]
        internal static extern IntPtr CreateWindowEx(
            int dwExStyle,
            string lpszClassName,
            string lpszWindowName,
            int style,
            int x, int y,
            int width, int height,
            IntPtr hwndParent,
            IntPtr hMenu,
            IntPtr hInst,
            [MarshalAs(UnmanagedType.AsAny)] object pvParam
        );

        internal const int
            WS_CHILD = 0x40000000,
            WS_VISIBLE = 0x10000000;
    }

The main window intializes the child window and clears its background to black or white after a mouse move:

public partial class MainWindow : Window
    {
        private ChildWindow child;
        private float childColor = 1.0f;

        public MainWindow()
        {
            InitializeComponent();
        }

        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            child = new ChildWindow(BorderHost);
            BorderHost.Child = child;
        }

        private void MainWindow_OnMouseMove(object sender, MouseEventArgs e)
        {
            if (child == null) return;
            if (child.SwapChain == null) return;

            // switch between black and white background
            childColor = 1.0f - childColor;

            // clear child background
            child.SwapChain.BeginFrame();
            Device.Get().ClearRenderTargetView(child.SwapChain.Rtv, new RawColor4(childColor, childColor, childColor, childColor));
            child.SwapChain.EndFrame();
        }
    }

The full source code can be found on https://github.com/kopaka1822/DpiAwareChildwindow.

Note:

  • I cannot create a directx device and swap chain at once because I need to work with an already existing directx device. Thus the deferred swap chain initialization.
  • I have two monitors with different DPI and I want my app to be properly displayed on both devices.

In order to add dpi awareness to my app, I added a manifest with:

  <application xmlns="urn:schemas-microsoft-com:asm.v3">
    <windowsSettings>
      <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True/PM</dpiAware>
      <gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">true</gdiScaling>
    </windowsSettings>
  </application>

I set gdiScaling to true, because I want all WPF components inside the window to scale as usual.

The window looks fine when the monitor DPI is 1.0: fine

However, the child window position and size is wrong on my monitor with DPI 2.0: not_fine

How do I properly scale and position my child window?


Edit: This is what I get when I don't use the gdiScaling scaling inside the manifest and move my window from the 2.0 dpi to my 1.0 dpi screen: still_broken The window looks fine on the 2.0 dpi screen but the title bar of my window is now over sized on my 1.0 dpi screen.

c#
wpf
winapi
directx
asked on Stack Overflow Jan 5, 2020 by Felix Brüll • edited Jan 11, 2020 by Felix Brüll

1 Answer

0

According to the document, I modified gdiScaling to the following format, and it work for me.

  <application xmlns="urn:schemas-microsoft-com:asm.v3">
    <windowsSettings>
      <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
      <!--<gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">true</gdiScaling>-->
    </windowsSettings>
  </application>
  <application>
    <windowsSettings xmlns="https://schemas.microsoft.com/SMI/2017/WindowsSettings">
      <gdiScaling>true</gdiScaling>
    </windowsSettings>
  </application>
answered on Stack Overflow Jan 10, 2020 by Drake Wu

User contributions licensed under CC BY-SA 3.0