c# - HorizontalOffset goes in the wrong direction for High DPI primary screen -
i working on high dpi issues in our wpf app (.net 4.6.1 - system dpi-awareness active).
generally app expect - scale depending on current displays dpi setting, when moving screen @ 100% screen b @ 150% changes it's overall scale correctly "at half-point".
most of open issues there because had pixel-/dip-based calculations did not took dpi-setting consideration. fixed calculating in correct dpi values:
var source = presentationsource.fromvisual(this); var dpix = source?.compositiontarget?.transformtodevice.m11 ?? 1; var dpiy = source?.compositiontarget?.transformtodevice.m22 ?? 1; there found out first strange thing (at least me):
- if primary display set e.g. 125% 1.25
dpixscreens, secondary screen @ 100%, there pixel-values multiplied 1.25 (meaning 1600x1200 pixel screen has working size of 2000x1500). - and other way around if primary screen @ 100% , secondary screen @ e.g. 150%: 1
dpix, values correct , no correction necessary (=> or multiply/dived 1 not break it).
but actual problem:
have pop-ups placing @ center of placement-targets 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] 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 works correctly without commented out code, case 1 tried dividing , multiplying dpi value, in end correct thing multiply -1 work correctly.
why ist case?
, how can savely detect when needed? dpix > 1?
i open other solutions scaling issue or center-placement whole.
p.s.: running windows 10 1703 .net 4.7 installed (app still targets 4.6.1 other reasons).
update:
created demo-solution: https://github.com/chrfin/horizontaloffseterror
if main screen @ 100% correct:

if main screen e.g. 125% off:

if add *-1 offset correct again:
...but why?
i've done similar. had go winforms accomplish it:
/// <summary> /// calculates , sets correct start location dialog appear. /// </summary> /// <param name="form">the dialog displayed</param> /// <param name="screen">desired screen</param> public static void setstartlocation(form form, screen screen) { form.startposition = formstartposition.manual; // calculate new top left corner of form, 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); } run code in debugger @ screen inspect screen variable , make sure make sense, won't. making assumption.
look @ getscreen()
/// <summary> /// handles drags go "outside" screen , returns mouse delta last mouse position. /// when user wants edit value greater, mouse @ edge of screen want wrap mouse position. /// /// wrapping mouse non trival multiple monitor setups forms has screen class encapsolates /// low level calls required determine screen user working on , it's bounds. /// wrapping confusing because there edge cases mess coordinate system. example, if primary monitor /// second monitor on right , app on left screen mouse /// coordinates in negative , second monitors mouse coords max @ 1920 ( depending on resolution ). /// alternatively if screen 1 primary , screen 2 secondary x=3xxx far right max mouse position. /// /// when wrap, need take account , not have delta go out of whack. /// note: mouse wrapping works same unity when user value drag. /// note: when mouse wrap, musn't set position until next move event, or set fail. /// </summary> /// <param name="delta"> amount mouse movement has changed since last called</param> /// <returns>true if delta gotten succesfully</returns> private bool getscreenwrappeddragvector(out vector delta) { delta = new vector(); // set out parameter // need determine our window is, otherwise coordinate system off if in child window on other monitor! var element = mouse.directlyover 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; }
Comments
Post a Comment