[C#WPF创建dpi的子窗口

问题描述 投票:1回答:1

我想在WPF窗口中创建一个dpi感知子窗口。子窗口将用于DirectX渲染。

我用如下窗口创建了一个最小示例:

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

边框将用于托管我的子窗口。边框的HwndHost创建了directX交换链,如下所示:

 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;
    }

主窗口会初始化子窗口,并在鼠标移动后将其背景清除为黑色或白色:

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();
        }
    }

完整的源代码可以在https://github.com/kopaka1822/DpiAwareChildwindow上找到。

注意:

  • 我无法一次创建DirectX设备和交换链,因为我需要使用现有的DirectX设备。因此,延迟交换链初始化。
  • 我有两个具有不同DPI的监视器,并且我希望我的应用能够在两个设备上正确显示。

为了向我的应用程序添加dpi意识,我添加了以下清单:

  <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>

我将gdiScaling设置为true,因为我希望窗口内的所有WPF组件都能像往常一样缩放。

当监视器DPI为1.0时,该窗口看起来不错:fine

但是,在使用DPI 2.0的显示器上,子窗口的位置和大小是错误的:not_fine

如何正确缩放和放置子窗口?


编辑:这是我不使用清单中的gdiScaling缩放比例并将窗口从2.0 dpi移至1.0 dpi屏幕时得到的结果:still_broken该窗口在2.0 dpi屏幕上看起来不错,但是我的窗口标题栏现在在1.0 dpi屏幕上尺寸过大。

c# wpf winapi directx
1个回答
0
投票

根据document,我将gdiScaling修改为以下格式,并且对我有用。

  <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>
© www.soinside.com 2019 - 2024. All rights reserved.