3

我目前正在处理我们的 WPF 应用程序中的一些高 DPI 问题(.NET 4.6.1 - 系统 DPI 感知处于活动状态)。

通常,该应用程序会按照我们的预期进行 - 根据当前显示的 DPI 设置进行缩放,当将其从屏幕 A @ 100% 移动到屏幕 B @ 150% 时,它会正确地“在半点”更改其整体比例。

大多数未解决的问题是因为我们有一些基于像素/DIP 的计算没有考虑 DPI 设置。我通过计算正确的 DPI 值来解决这个问题:

var source = PresentationSource.FromVisual(this);
var dpiX = source?.CompositionTarget?.TransformToDevice.M11 ?? 1;
var dpiY = source?.CompositionTarget?.TransformToDevice.M22 ?? 1;

在那里我发现了第一件奇怪的事情(至少对我来说):

  1. 如果主显示器设置为例如 125%,我得到dpiX所有屏幕的 1.25,即使是 100% 的辅助屏幕,但所有像素值已经乘以 1.25(意味着 1600x1200 像素屏幕的工作尺寸为 2000x1500) .
  2. 如果主屏幕处于 100% 并且辅助屏幕处于例如 150% 则正好相反:我总是得到 1 dpiX,但所有值都已经正确并且不需要更正(=> 或乘法/潜水1 不会破坏它)。

但现在我的实际问题是:
我将一些弹出窗口放置在其放置目标的中心,并具有以下绑定:

<Popup.HorizontalOffset>
    <MultiBinding Converter="{lth:CenterConverter}">
        <Binding RelativeSource="{RelativeSource Self}" Path="PlacementTarget.ActualWidth" />
        <Binding RelativeSource="{RelativeSource Self}" Path="Child.ActualWidth" />
        <Binding RelativeSource="{RelativeSource Self}" Path="." />
    </MultiBinding>
</Popup.HorizontalOffset>

和转换器:

public class CenterConverter : MarkupExtension, IMultiValueConverter
{
    public override object ProvideValue(IServiceProvider serviceProvider) => this;

    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        if (values.Any(v => v == DependencyProperty.UnsetValue))
            return Double.NaN;

        double placementTargetWidth = (double)values[0];
        double elementWidth = (double)values[1];

        var offset = (placementTargetWidth - elementWidth) / 2;

        ////if (values.Length >= 3 && values[2] is Visual)
        ////{
        ////    var source = PresentationSource.FromVisual((Visual)values[2]);
        ////    var dpiX = source?.CompositionTarget?.TransformToDevice.M11 ?? 1;
        ////    offset *= -1; //dpiX;
        ////}

        return offset;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) { throw new NotSupportedException(); }
}

对于案例 2,如果没有注释掉的代码,一切都已经正常工作,但对于案例 1,我尝试将 DPI 值相乘,但最终正确的做法是将其乘以使其-1正常工作。

为什么会这样?
我怎样才能有效地检测到什么时候需要呢?dpiX > 1?

我也对缩放问题或整个中心放置的其他解决方案持开放态度。

PS:我正在运行安装了 .NET 4.7 的 Windows 10 1703(由于某些其他原因,应用程序仍以 4.6.1 为目标)。

更新:
我创建了一个演示解决方案:https
://github.com/chrfin/Horizo​​ntalOffsetError 如果主屏幕处于 100% 是正确的: 但如果主屏幕是 125% 则关闭: 但如果我添加*-1 到偏移量再次正确:
正确的

错误的

已更正

...但为什么?

4

1 回答 1

0

我做过类似的事情。我不得不去winforms来完成它:

/// <summary>
/// Calculates and sets the correct start location for a dialog to appear.
/// </summary>
/// <param name="form">The dialog to be displayed</param>
/// <param name="screen">Desired screen</param>
public static void SetStartLocation(Form form, Screen screen)
{
           form.StartPosition = FormStartPosition.Manual;
    // Calculate the new top left corner of the form, so it will be centered.
    int newX = (screen.WorkingArea.Width - form.Width) / 2 + screen.WorkingArea.X;
    int newY = (screen.WorkingArea.Height - form.Height) / 2 + screen.WorkingArea.Y;

    form.Location = new Point(newX, newY);
}

在调试器中运行此代码,然后查看您的屏幕检查所有屏幕变量并确保它们有意义,它们不会。你的假设。

看看 GetScreen()

/// <summary>
    /// Handles drags that go "outside" the screen and returns the mouse delta from the last mouse position.
    /// When the user wants to edit a value greater, but the mouse is at the edge of the screen we want to wrap the mouse position.
    /// 
    /// Wrapping the mouse is non trival do to multiple monitor setups but Forms has a screen class that encapsolates the 
    /// low level calls required to determine the screen the user is working on and it's bounds.
    /// Wrapping is confusing because there are edge cases which mess with the coordinate system. For example, if your primary monitor
    /// is your second monitor which is on the right and the app is on your left screen the mouse 
    /// coordinates will be in the negative and second monitors mouse coords would max at 1920 ( depending on resolution ).
    /// Alternatively if screen 1 is your primary and screen 2 is your secondary then X=3xxx will be your far right max mouse position.
    /// 
    /// When we wrap, we need to take that into account and not have the delta go out of whack.
    /// Note: This mouse wrapping works exactly the same as unity does when the user does a value drag.
    /// Note: When the mouse does a wrap, we musn't set get the position until the next move event, or the set will fail.
    /// </summary>
    /// <param name="delta"> the amount the mouse movement has changed since this was last called</param>
    /// <returns>true if delta was gotten succesfully</returns>
    private bool GetScreenWrappedDragVector(out Vector delta)
    {
        delta = new Vector(); // Always set the out parameter

        // We need to determine what our window is, otherwise the coordinate system will be off if your in a child window on other monitor!
        var element = Mouse.DirectlyOver as UIElement;
        if (element != null)
        {
            Window parentWindow = Window.GetWindow(element);

            if (parentWindow != null)
            {
                System.Windows.Forms.Screen screen = GetScreen(parentWindow);

                var mousePos = Win32.GetCursorPos();

                if ((int)mousePos.X >= screen.WorkingArea.Right - 1)
                {
                    Win32.SetCursorPos(screen.WorkingArea.Left, (int)mousePos.Y);
                    m_lastOnMouseMoveValue.X = screen.WorkingArea.Left;
                    return false;
                }
                if ((int)mousePos.X <= screen.WorkingArea.Left)
                {
                    Win32.SetCursorPos(screen.WorkingArea.Right, (int)mousePos.Y);
                    m_lastOnMouseMoveValue.X = screen.WorkingArea.Right;
                    return false;
                }

                delta = mousePos - m_lastOnMouseMoveValue;
                m_lastOnMouseMoveValue = mousePos;
            }
        }
        if (delta.Length <= 0)
            return false;

        return true;
    }
于 2017-09-12T23:43:16.613 回答