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?
I've done something similar. I had to go to winforms to accomplish it:
Run this code in the debugger then look at your screen inspect all the screen variable and make sure it they make sense, they won't. Your making an assumption.
look at GetScreen()