Handmatig plaatsen van een WPF-window

Bij het positioneren van visuals, gebruikt WPF ‘device independent pixels’ (DIP). Een DIP is altijd 196 inch groot.

WPF zorgt zelf voor opschaling wanneer de schermresolutie anders is dan 96 pixels per inch (dpi). Bijvoorbeeld in het geval van een laptop die een 120 dpi scherm heeft.

Bij het handmatig positioneren van een WPF window, maak je gebruik van de Left en Top properties. En ook dit zijn DIP properties.

Een tijdje terug wilde ik een WPF window positioneren op de plek van een button. Een gebruiker klikt op een button en het window verschijnt precies op die plek, zodat er geen grote muisbewegingen nodig zijn.

Ik loste dat als volgt op:

Point screenPos = button.PointToScreen(new Point(0,0));
dialog.Left = screenPos.X;
dialog.Top = screenPos.Y;

En dat werkte….. op mijn 96 dpi scherm. Helaas kwam ik al snel tot de ontdekking dat het op mijn laptop met 120 dpi scherm niet werkte.

Wat blijk nu? PointToScreen retourneert geen DIP-coördinaat, maar een *echt* schermcoördinaat.  Tsja… als ik de naamgeving van deze methode wat serieuzer had genomen, had ik het kunnen vermoeden.

Gelukkig is het mogelijk om een schermcoördinaat te transformeren naar een DIP coördinaat. Dat gaat via de PresentationSource klasse. De volgende helper method maakt het nu mogelijk om een DIP  coördinaat van een willekeurige WPF visual op te vragen:

public static Point DeviceIndependentPosition(Visual element)
{
    Point screenPos = element.PointToScreen(new Point(0, 0));
    PresentationSource src = PresentationSource.FromVisual(element);
    return src.CompositionTarget.TransformFromDevice.Transform(screenPos);
}

Eventueel kan hier een extension method (voor Visual) van gemaakt worden. Het plaatsen van een WPF window op de positie van een button is nu eenvoudig:

Point buttonDip = DeviceIndependentPosition(button);
dialog.Left = buttonDip.X;
dialog.Top = buttonDip.Y;