I am currently working on some high DPI issues in our WPF app (.NET 4.6.1 - System DPI-awareness is active).
Generally the app does what we expect it to do - scale depending on the current displays DPI setting, also when moving it from screen A @ 100% to screen B @ 150% it changes it's overall scale correctly "at the half-point".
Most of the open issues where there because we had some pixel-/DIP-based calculations which did not took the DPI-setting into consideration. This I fixed by calculating in the correct DPI values:
var source = PresentationSource.FromVisual(this);
var dpiX = source?.CompositionTarget?.TransformToDevice.M11 ?? 1;
var dpiY = source?.CompositionTarget?.TransformToDevice.M22 ?? 1;
There I found out the first strange thing (at least for me):
- If the primary display is set to e.g. 125% I get 1.25 for
dpiX
for all screens, even the secondary screen @ 100%, but there all pixel-values are already multiplied by 1.25 (meaning a 1600x1200 pixel screen has a working size of 2000x1500). - And it is exactly the other way around if the primary screen is at 100% and the secondary screen is at e.g. 150%: I always get 1 for
dpiX
, but all values are already correct and no correction is necessary (=> or multiply/dived by 1 does not break it).
But now to my actual problem:
I have some pop-ups I am placing at the center of their placement-targets with the following binding:
<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>
and converter:
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(); }
}
For case 2 everything already works correctly without the commented out code, but for case 1 I tried dividing and multiplying the DPI value, but in the end the correct thing was to multiply it by -1
to get it to work correctly.
Why ist that the case?
And how can I savely detect when this is needed? dpiX > 1
?
I am also open for other solutions to the scaling issue or to the center-placement as a whole.
P.S.: I am running Windows 10 1703 with .NET 4.7 installed (App still targets 4.6.1 for some other reasons).
UPDATE:
I created a demo-solution: https://github.com/chrfin/HorizontalOffsetError
If the main screen is at 100% it is correct:
but if the main screen is e.g. 125% it is off:
BUT if I than add *-1 to the offset it is correct again:
...but why?