简体   繁体   English

WPI窗口中托管的本机HWND控件的DPI缩放问题

[英]DPI scaling problem with hosted native HWND control inside WPF window

I have a high dpi setting on my monitor as the monitor is a fairly small 3840 x 2160 monitor. 我的显示器上的dpi设置很高,因为该显示器是相当小的3840 x 2160显示器。

This is causing issues with one of the applications I am writing as I am hosting a control in my main application. 当我在主应用程序中托管控件时,这会导致我正在编写的一个应用程序出现问题。 I developed it based off a nice example in the WPF-Samples . 我根据WPF-Samples中的一个很好的示例开发了它。

On my screen, when the example is run in it's default state, the output looks like 在我的屏幕上,当示例以默认状态运行时,输出看起来像

在此处输入图片说明

I was able to account for the list control being the incorrect size by using 我能够通过使用来解释列表控件的大小不正确

//MainWindow
PresentationSource source = PresentationSource.FromVisual(this); //Account for any scaling on the screen7
_listControl = new ControlHost(ControlHostElement.ActualWidth, ControlHostElement.ActualHeight, source.CompositionTarget.TransformToDevice.M11, source.CompositionTarget.TransformToDevice.M22);

...

//ControlHost
public ControlHost(double height, double width, double dpXScale, double dpYScale)
{
    _hostHeight = (int) (height * dpXScale);
    _hostWidth = (int) (width * dpYScale);
}

在此处输入图片说明

However, even when it is the correct size, the hosted UI is not scaled as the rest of the program is. 但是,即使大小正确,托管UI也不会像程序的其余部分那样缩放。

How could the program scale the hosted UI based on the DPI of the user's screen? 程序如何根据用户屏幕的DPI缩放托管的UI?


There are three main files that make up the this example. 此示例包含三个主要文件。

ControlHost.cs ControlHost.cs

// // Copyright (c) Microsoft. All rights reserved.
// // Licensed under the MIT license. See LICENSE file in the project root for full license information.

#region Using directives

using System;
using System.Runtime.InteropServices;
using System.Windows.Interop;

#endregion

namespace WPFHostingWin32Control
{
    public class ControlHost : HwndHost
    {
        internal const int
            WsChild = 0x40000000,
            WsVisible = 0x10000000,
            LbsNotify = 0x00000001,
            HostId = 0x00000002,
            ListboxId = 0x00000001,
            WsVscroll = 0x00200000,
            WsBorder = 0x00800000;

        private readonly int _hostHeight;
        private readonly int _hostWidth;
        private IntPtr _hwndHost;

        public ControlHost(double height, double width, double dpXScale, double dpYScale)
        {
            _hostHeight = (int) (height * dpXScale);
            _hostWidth = (int) (width * dpYScale);
        }

        public IntPtr HwndListBox { get; private set; }

        protected override HandleRef BuildWindowCore(HandleRef hwndParent)
        {
            HwndListBox = IntPtr.Zero;
            _hwndHost = IntPtr.Zero;

            _hwndHost = CreateWindowEx(0, "static", "",
                WsChild | WsVisible,
                0, 0,
                _hostHeight, _hostWidth,
                hwndParent.Handle,
                (IntPtr) HostId,
                IntPtr.Zero,
                0);

            HwndListBox = CreateWindowEx(0, "listbox", "",
                WsChild | WsVisible | LbsNotify
                | WsVscroll | WsBorder,
                0, 0,
                _hostHeight, _hostWidth,
                _hwndHost,
                (IntPtr) ListboxId,
                IntPtr.Zero,
                0);

            return new HandleRef(this, _hwndHost);
        }

        protected override IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
        {
            handled = false;
            return IntPtr.Zero;
        }

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

        //PInvoke declarations
        [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);

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

MainWindow.xaml MainWindow.xaml

<Window x:Class="WPFHostingWin32Control.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WPFHostingWin32Control"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525" Loaded="On_UIReady">

  <DockPanel Background="LightGreen">
    <Border Name="ControlHostElement"
    Width="200"
    Height="200"
    HorizontalAlignment="Right"
    VerticalAlignment="Top"
    BorderBrush="LightGray"
    BorderThickness="3"
    DockPanel.Dock="Right"/>
    <StackPanel>
      <Label HorizontalAlignment="Center"
        Margin="0,10,0,0"
        FontSize="14"
        FontWeight="Bold">Control the Control</Label>
      <TextBlock Margin="10,10,10,10" >Selected Text: <TextBlock  Name="selectedText"/></TextBlock>
      <TextBlock Margin="10,10,10,10" >Number of Items: <TextBlock  Name="numItems"/></TextBlock>

      <Line X1="0" X2="200"
        Stroke="LightYellow"
        StrokeThickness="2"
        HorizontalAlignment="Center"
        Margin="0,20,0,0"/>

      <Label HorizontalAlignment="Center"
        Margin="10,10,10,10">Append an Item to the List</Label>
      <StackPanel Orientation="Horizontal">
        <Label HorizontalAlignment="Left"
          Margin="10,10,10,10">Item Text</Label>
        <TextBox HorizontalAlignment="Left"
          Name="txtAppend"
          Width="200"
          Margin="10,10,10,10" />
      </StackPanel>

      <Button HorizontalAlignment="Left"
        Click="AppendText"
        Width="75"
        Margin="10,10,10,10">Append</Button>

      <Line X1="0" X2="200"
        Stroke="LightYellow"
        StrokeThickness="2"
        HorizontalAlignment="Center"
        Margin="0,20,0,0"/>

      <Label HorizontalAlignment="Center"
        Margin="10,10,10,10">Delete the Selected Item</Label>

      <Button Click="DeleteText"
        Width="125"
        Margin="10,10,10,10"
        HorizontalAlignment="Left">Delete</Button>
    </StackPanel>
  </DockPanel>
</Window>

MainWindow.cs MainWindow.cs

// // Copyright (c) Microsoft. All rights reserved.
// // Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows;

namespace WPFHostingWin32Control
{
    /// <summary>
    ///     Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        internal const int
            LbnSelchange = 0x00000001,
            WmCommand = 0x00000111,
            LbGetcursel = 0x00000188,
            LbGettextlen = 0x0000018A,
            LbAddstring = 0x00000180,
            LbGettext = 0x00000189,
            LbDeletestring = 0x00000182,
            LbGetcount = 0x0000018B;

        private Application _app;
        private IntPtr _hwndListBox;
        private int _itemCount;
        private ControlHost _listControl;
        private Window _myWindow;
        private int _selectedItem;

        public MainWindow()
        {
            InitializeComponent();
        }

        private void On_UIReady(object sender, EventArgs e)
        {
            _app = Application.Current;
            _myWindow = _app.MainWindow;
            _myWindow.SizeToContent = SizeToContent.WidthAndHeight;

            PresentationSource source = PresentationSource.FromVisual(this); //Account for any scaling on the screen7

            _listControl = new ControlHost(ControlHostElement.ActualWidth, ControlHostElement.ActualHeight, source.CompositionTarget.TransformToDevice.M11, source.CompositionTarget.TransformToDevice.M22);
            ControlHostElement.Child = _listControl;
            _listControl.MessageHook += ControlMsgFilter;
            _hwndListBox = _listControl.HwndListBox;
            for (var i = 1; i <= 100; i++) //populate listbox
            {
                var itemText = "Item" + i;
                SendMessage(_hwndListBox, LbAddstring, IntPtr.Zero, itemText);
            }
            _itemCount = SendMessage(_hwndListBox, LbGetcount, IntPtr.Zero, IntPtr.Zero);
            numItems.Text = "" + _itemCount;
        }

        private void AppendText(object sender, EventArgs args)
        {
            if (txtAppend.Text != string.Empty)
            {
                SendMessage(_hwndListBox, LbAddstring, IntPtr.Zero, txtAppend.Text);
            }
            _itemCount = SendMessage(_hwndListBox, LbGetcount, IntPtr.Zero, IntPtr.Zero);
            numItems.Text = "" + _itemCount;
        }

        private void DeleteText(object sender, EventArgs args)
        {
            _selectedItem = SendMessage(_listControl.HwndListBox, LbGetcursel, IntPtr.Zero, IntPtr.Zero);
            if (_selectedItem != -1) //check for selected item
            {
                SendMessage(_hwndListBox, LbDeletestring, (IntPtr) _selectedItem, IntPtr.Zero);
            }
            _itemCount = SendMessage(_hwndListBox, LbGetcount, IntPtr.Zero, IntPtr.Zero);
            numItems.Text = "" + _itemCount;
        }

        private IntPtr ControlMsgFilter(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
        {
            int textLength;

            handled = false;
            if (msg == WmCommand)
            {
                switch ((uint) wParam.ToInt32() >> 16 & 0xFFFF) //extract the HIWORD
                {
                    case LbnSelchange: //Get the item text and display it
                        _selectedItem = SendMessage(_listControl.HwndListBox, LbGetcursel, IntPtr.Zero, IntPtr.Zero);
                        textLength = SendMessage(_listControl.HwndListBox, LbGettextlen, IntPtr.Zero, IntPtr.Zero);
                        var itemText = new StringBuilder();
                        SendMessage(_hwndListBox, LbGettext, _selectedItem, itemText);
                        selectedText.Text = itemText.ToString();
                        handled = true;
                        break;
                }
            }
            return IntPtr.Zero;
        }

        [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Unicode)]
        internal static extern int SendMessage(IntPtr hwnd,
            int msg,
            IntPtr wParam,
            IntPtr lParam);

        [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Unicode)]
        internal static extern int SendMessage(IntPtr hwnd,
            int msg,
            int wParam,
            [MarshalAs(UnmanagedType.LPWStr)] StringBuilder lParam);

        [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Unicode)]
        internal static extern IntPtr SendMessage(IntPtr hwnd,
            int msg,
            IntPtr wParam,
            string lParam);
    }
}

Winforms does not handle high-DPI settings very well by default. 默认情况下,Winforms不能很好地处理高DPI设置。 As stated in the comments, you can try some of the available manifest settings to get it to resize. 如评论中所述,您可以尝试一些可用的清单设置来调整其大小。 If that won't work for your situation then you will need to scale the control yourself. 如果这不适用于您的情况,那么您将需要自行扩展控件。 Most Winforms controls have a Scale method you could call when you adjust the height and width: 大多数Winforms控件都有一个Scale方法,您可以在调整高度和宽度时调用它:

_listControl.Scale(dpXScale, dpYScale);

of course, since your actual code uses a custom control your mileage may vary. 当然,由于您的实际代码使用自定义控件,因此您的里程可能会有所不同。

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

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