簡體   English   中英

WPI窗口中托管的本機HWND控件的DPI縮放問題

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

我的顯示器上的dpi設置很高,因為該顯示器是相當小的3840 x 2160顯示器。

當我在主應用程序中托管控件時,這會導致我正在編寫的一個應用程序出現問題。 我根據WPF-Samples中的一個很好的示例開發了它。

在我的屏幕上,當示例以默認狀態運行時,輸出看起來像

在此處輸入圖片說明

我能夠通過使用來解釋列表控件的大小不正確

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

在此處輸入圖片說明

但是,即使大小正確,托管UI也不會像程序的其余部分那樣縮放。

程序如何根據用戶屏幕的DPI縮放托管的UI?


此示例包含三個主要文件。

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

<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

// // 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不能很好地處理高DPI設置。 如評論中所述,您可以嘗試一些可用的清單設置來調整其大小。 如果這不適用於您的情況,那么您將需要自行擴展控件。 大多數Winforms控件都有一個Scale方法,您可以在調整高度和寬度時調用它:

_listControl.Scale(dpXScale, dpYScale);

當然,由於您的實際代碼使用自定義控件,因此您的里程可能會有所不同。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM