繁体   English   中英

C# WPF 创建 dpi 感知子窗口

[英]C# WPF create dpi aware child window

我想在 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>

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

 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 时,窗口看起来不错: 美好的

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

如何正确缩放和定位我的子窗口?


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

根据文档,我将gdiScaling修改为以下格式,它对我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>

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM