How to convert WPF inch-unit to Winforms pixels and vice versa?

I have a window created in WPF and I used this in WinForms hub . Now I want to move the owner form and at the moment my WPF window should also be moved to the center of the form!

But I only have a problem when the window is in the center of the form that is being formed in the center of the screen. And otherwise act in a different form than Windows coordinates. I just add the offset values ​​of the form to the position of the window.

Now I have come to the conclusion that the pixel coordinates in WPF Windows are different WinForms!

How to convert WPF to WinForms location and vice versa?

Owner code :

public partial class Form1 : Form
{
    private WPF_Window.WPF win;

    public Form1()
    {
        InitializeComponent();

        win = new WPF();
        win.Show();
        CenterToParent(win);
    }

    private void CenterToParent(System.Windows.Window win)
    {
        win.Left = this.Left + (this.Width - win.Width) / 2;
        win.Top = this.Top + (this.Height - win.Height) / 2;
    }

    protected override void OnMove(EventArgs e)
    {
        base.OnMove(e);
        CenterToParent(win);
    }
}

      

+3


source to share


1 answer


Best way to get DPI value in WPF

Method 1

This is the same way you did it in Windows Forms. System.Drawing.Graphics

the object provides convenient properties for getting horizontal and vertical DPI. Let's draw a helper method:

/// <summary>
/// Transforms device independent units (1/96 of an inch)
/// to pixels
/// </summary>
/// <param name="unitX">a device independent unit value X</param>
/// <param name="unitY">a device independent unit value Y</param>
/// <param name="pixelX">returns the X value in pixels</param>
/// <param name="pixelY">returns the Y value in pixels</param>
public void TransformToPixels(double unitX,
                              double unitY,
                              out int pixelX,
                              out int pixelY)
{
    using (Graphics g = Graphics.FromHwnd(IntPtr.Zero))
    {
        pixelX = (int)((g.DpiX / 96) * unitX);
        pixelY = (int)((g.DpiY / 96) * unitY);
    }

    // alternative:
    // using (Graphics g = Graphics.FromHdc(IntPtr.Zero)) { }
}

      

You can use it to transform both coordinates and dimension values. Its pretty simple and reliable and completely manageable code (at least like you, the consumer). Passing IntPtr.Zero

as a parameter HWND

or HDC

results in an object Graphics

that wraps the device context for the entire screen.

However, there is one problem with this approach. It depends on the Windows Forms / GDI + framework. You will need to add a reference to the System.Drawing assembly. Big deal? Not confident, but for me this is a problem to be avoided.


Method 2

Let's take it one step deeper and do it the Win API way. The function GetDeviceCaps

extracts various information for the specified device and can extract horizontal and vertical DPI when we pass parameters to them LOGPIXELSX

and LOGPIXELSY

respectively.

GetDeviceCaps

the function is defined in gdi32.dll

and is probably used System.Drawing.Graphics

under the hood.

Let's see what our helper has become:

[DllImport("gdi32.dll")]
public static extern int GetDeviceCaps(IntPtr hDc, int nIndex);

[DllImport("user32.dll")]
public static extern IntPtr GetDC(IntPtr hWnd);

[DllImport("user32.dll")]
public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDc);

public const int LOGPIXELSX = 88;
public const int LOGPIXELSY = 90;

/// <summary>
/// Transforms device independent units (1/96 of an inch)
/// to pixels
/// </summary>
/// <param name="unitX">a device independent unit value X</param>
/// <param name="unitY">a device independent unit value Y</param>
/// <param name="pixelX">returns the X value in pixels</param>
/// <param name="pixelY">returns the Y value in pixels</param>
public void TransformToPixels(double unitX,
                              double unitY,
                              out int pixelX,
                              out int pixelY)
{
    IntPtr hDc = GetDC(IntPtr.Zero);
    if (hDc != IntPtr.Zero)
    {
        int dpiX = GetDeviceCaps(hDc, LOGPIXELSX);
        int dpiY = GetDeviceCaps(hDc, LOGPIXELSY);

        ReleaseDC(IntPtr.Zero, hDc);

        pixelX = (int)(((double)dpiX / 96) * unitX);
        pixelY = (int)(((double)dpiY / 96) * unitY);
    }
    else
        throw new ArgumentNullException("Failed to get DC.");
}

      

So we swapped the dependency on managed GDI + for the dependency on fancy Win API calls. Is this an improvement? In my opinion, yes, as long as we run the Windows Win API, this is the least common denominator. It is light weight. On other platforms, we probably wouldn't have had this dilemma in the first place.

And don't be fooled by this ArgumentNullException

. This solution is as reliable as the first. System.Drawing.Graphics

will throw the same exception if it also fails to get the device context.


Method 3



As officially registered here in the registry there is a special key: It stores the DWORD value that's exactly what the user chooses for DPI in the display settings dialog (it's called font size there). HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\FontDPI.

Reading is not easy, but I would not recommend it. You can see there is a difference between the official API and the repository for different settings. The API is a public contract that stays the same even if the internal logic is completely rewritten (if it doesn't support the entire platform, does it?).

But no one guarantees that the internal storage will remain the same. This may have been going on for several decades, but a critical project document describing his move may already be approved. You never know.

Always stick to the API (no matter what it is, native, Windows Forms, WPF, etc.). Even though the underlying code reads the value from your location.


Method 4

This is a pretty elegant WPF approach Ive found in this blog post . It builds on the functionality provided by a class that ultimately represents the display surface on which the WPF application is drawn. The class provides 2 useful methods: System.Windows.Media.CompositionTarget

  • TransformFromDevice

  • TransformToDevice

The names are self-explanatory, and in both cases we end up with an object System.Windows.Media.Matrix

that contains the mapping factors between device units (pixels) and independent units. M11 will contain the coefficient for the X axis and M22 for the Y axis.

Since we have been looking at the direction of units-> pixels so far, let's overwrite our helper with CompositionTarget.TransformToDevice.

. When this method is called, M11 and M22 will contain the values ​​that we calculated as:

  • dpiX / 96
  • dpiY / 96

So on a machine with a DPI set to 120, the ratios would be 1.25.

Here's the new helper:

/// <summary>
/// Transforms device independent units (1/96 of an inch)
/// to pixels
/// </summary>
/// <param name="visual">a visual object</param>
/// <param name="unitX">a device independent unit value X</param>
/// <param name="unitY">a device independent unit value Y</param>
/// <param name="pixelX">returns the X value in pixels</param>
/// <param name="pixelY">returns the Y value in pixels</param>
public void TransformToPixels(Visual visual,
                              double unitX,
                              double unitY,
                              out int pixelX,
                              out int pixelY)
{
    Matrix matrix;
    var source = PresentationSource.FromVisual(visual);
    if (source != null)
    {
        matrix = source.CompositionTarget.TransformToDevice;
    }
    else
    {
        using (var src = new HwndSource(new HwndSourceParameters()))
        {
            matrix = src.CompositionTarget.TransformToDevice;
        }
    }

    pixelX = (int)(matrix.M11 * unitX);
    pixelY = (int)(matrix.M22 * unitY);
}

      

I had to add another parameter to the method Visual

. We need it as a basis for calculations (the previous samples used the device context for the entire screen for this). I don't think this is a big problem as you are more likely to have Visual

WPF when you run your application (otherwise why would you need to translate pixel coordinates?). However, if your visual has not been attached to the presentation source (that is, it has not been shown yet), then you cannot get the presentation source (so we have a NULL check and create a new one HwndSource

).

Link

+7


source







All Articles