2017-09-12 2 views
3

현재 WPF 앱의 일부 높은 DPI 문제 (.NET 4.6.1 - 시스템 DPI 인식이 활성화 됨)에서 작업 중입니다.HorizontalOffset이 높은 DPI 기본 화면에 대해 잘못된 방향으로 이동합니다.

일반적으로 응용 프로그램은 현재 디스플레이 DPI 설정에 따라, 또한 화면 A @ 100 %에서 화면 B @ 150 %로 이동하면 전체 배율이 올바르게 변경됩니다. " -포인트".

대부분의 공개 문제는 DPI 설정을 고려하지 않은 픽셀 -/DIP 기반 계산이 있었기 때문에 발생했습니다. 이 나는 올바른 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 %이고 보조 화면이 예를 들어 100 % 인 경우 정확히 다른 방법입니다. 150 % : 나는 항상 dpiX에 대해 1을 얻지 만, 모든 값은 이미 정확하고 보정이 필요하지 않습니다 (=> 곱하기/나누기가 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으로 iply하십시오.

왜 그런 경우가 아닙니까?
그리고 이것이 필요할 때 어떻게 적절하게 감지 할 수 있습니까? dpiX > 1?

스케일링 문제 나 센터 배치와 관련하여 다른 솔루션에 대해서도 열려 있습니다.

피씨 : .NET 4.7이 설치된 Windows 10 1703을 실행 중입니다 (다른 이유로 인해 앱은 여전히 ​​4.6.1을 대상으로합니다).

UPDATE :
Correct
하지만 기본 화면 예컨대 인 경우 메인 화면이 올바른지 100 % 인 경우 https://github.com/chrfin/HorizontalOffsetError
: I는 데모 용액을 만든

Wrong
하지만보다하다면 * 추가 ​​-1이 다시 올 오프셋에 : 125 %는이 꺼져
Corrected

... 그런데 왜?

+0

왜 위치 할 때 dpi로 처리해야합니까? 실제 크기를 측정하고 (크기를 조정하지 마십시오) 위치를 지정하십시오. – Sinatr

+0

@Sinatr : 케이스 1에서 작동하지 않기 때문에 ;-). 이 경우 팝업이 잘못된 쪽으로 이동합니다. 예 : 타겟 크기가 130px이고 팝업 크기가 230px -> offset = -50px => 경우 2의 경우 수평 오프셋을 -50으로 설정하면 대상의 중앙에 팝업이 표시되지만 사례 1의 경우에는 100px가됩니다 0 또는 50 픽셀 대신 오프셋을 +50으로 설정하면 팝업이 올바르게 표시되고 + 및 언제 또는 언제 어떻게 감지하는지 알 수 없습니다. – ChrFin

+0

@HansPassant : 입력 해 주셔서 감사합니다.하지만 내가 그 부분으로는 분명하지 않다고 생각합니다. 예를 들어 단 하나의 화면 만 있다면 문제가 발생합니다. 150 % 또는 창 이동되지 않습니다. 기본 (또는 유일한) 화면이 100 %가 아닌 즉시 문제가 발생합니다. 내일 작은 데모 솔루션을 만들려고 노력할 것입니다 ... – ChrFin

답변

0

나는 비슷한 것을했습니다. 그들은하지 않습니다, 디버거에서이 코드는 다음 화면에서 볼

/// <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; 
    } 
+0

이것이 내 문제에 어떻게 도움이 될지 모르겠습니다. 좀 더 자세히 설명해 주시겠습니까? – ChrFin

+0

DPI의 작동 방식을 감지 할 수 없습니다. 대신 GetScreen을 사용하여 그 값을보고 DPI를 결정하십시오. 그 링크의 https://stackoverflow.com/questions/1918877/how-can-i-get-the-dpi-in-wpf 대부분의 방법은 작동하지 않습니다. GetScreen을 사용해 코드 검증만으로 먼저 검증하십시오. 모니터를 탐지 할 때 WPF에는 많은 오류가 있습니다. –

+0

내 비주얼이 이미 화면에 표시되어있어 (보이지 않음) 올바른 화면과 DPI가 내 문제가 아니라는 것을 감지 할 수 있습니다. - 내 부분에서 제대로 작동하고 잘 작동합니다 ... – ChrFin

관련 문제