Как преобразовать дюймовую единицу WPF в пиксели Winforms и наоборот?

У меня есть окно, созданное в WPF. и я использовал это в центре WinForms владелец. Теперь я хочу переместить форму владельца, и на данный момент мой Окно WPF также должно быть перемещено в центр формы!

Но у меня проблема, Только когда окно находится в центре формы, форма в центре экрана. И иначе действовать в другой форме, чем координаты Windows. Я просто добавляю значения смещения формы к расположению окна.

Теперь я пришел к выводу, что координаты пикселей на WPF Windows отличаются от WinForms!

Как преобразовать расположение окна WPF в < href="https://msdn.microsoft.com/en-us/library/dd30h2yb%28v=vs.110%29.aspx" rel="noreferrer">WinForms базовое расположение и наоборот?

Коды формы владельца:

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);
    }
}

person Behzad    schedule 24.05.2015    source источник
comment
Какой код у вас есть сейчас, чтобы расположить окно WPF в центре формы WinForms и сохранить его там?   -  person Paul    schedule 24.05.2015
comment
Единица измерения в WPF в дюймах, а не в пикселях. Google wpf конвертирует дюймы в пиксели для очевидных попаданий.   -  person Hans Passant    schedule 24.05.2015


Ответы (2)


Лучший способ получить значение DPI в WPF

Способ 1

Точно так же вы делали это в Windows Forms. System.Drawing.Graphics предоставляет удобные свойства для получения горизонтального и вертикального DPI. Набросаем вспомогательный метод:

/// <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)) { }
}

Вы можете использовать его для преобразования как координат, так и значений размеров. Это довольно просто, надежно и полностью в управляемом коде (по крайней мере, с точки зрения вас, потребителя). Передача IntPtr.Zero в качестве параметра HWND или HDC приводит к созданию объекта Graphics, который охватывает контекст устройства всего экрана.

Однако есть одна проблема с этим подходом. Он зависит от инфраструктуры Windows Forms/GDI+. Вам нужно будет добавить ссылку на сборку System.Drawing. Большое дело? Не уверен насчет вас, но для меня это проблема, которую следует избегать.


Способ 2

Давайте сделаем еще один шаг и сделаем это с помощью Win API. Функция GetDeviceCaps извлекает различную информацию для указанного устройства и может получать горизонтальный и вертикальный DPI, когда мы передаем ей параметры LOGPIXELSX и LOGPIXELSY соответственно.

Функция GetDeviceCaps определена в gdi32.dll и, вероятно, используется System.Drawing.Graphics под капотом.

Давайте посмотрим, во что превратился наш помощник:

[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.");
}

Таким образом, мы заменили зависимость от управляемого GDI+ зависимостью от причудливых вызовов Win API. Это улучшение? На мой взгляд, да, пока мы работаем в Windows, Win API является наименьшим общим знаменателем. Он легкий. На других платформах у нас, вероятно, не было бы такой дилеммы.

И не ведитесь на это ArgumentNullException. Это решение такое же надежное, как и первое. System.Drawing.Graphics вызовет такое же исключение, если не сможет также получить контекст устройства.


Способ 3

Как официально задокументировано здесь существует специальный ключ в реестре: HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\FontDPI. Он хранит значение DWORD, которое является именно тем, что пользователь выбирает для DPI в диалоге настроек дисплея (там это называется размером шрифта).

Прочитать его не составит труда, но я бы не советовал. Видите ли, есть разница между официальным API и хранилищем различных настроек. API — это публичный контракт, который остается неизменным, даже если внутренняя логика полностью переписана (если это не так, то вся платформа — отстой, не так ли?).

Но никто не гарантирует, что внутреннее хранилище останется прежним. Возможно, он длился пару десятилетий, но важнейший проектный документ, описывающий его перемещение, возможно, уже ожидает утверждения. Ты никогда не узнаешь.

Всегда придерживайтесь API (каким бы он ни был, нативным, Windows Forms, WPF и т. д.). Даже если базовый код считывает значение из известного вам местоположения.


Способ 4

Это довольно элегантный подход WPF, который я нашел в документе этой записи в блоге. Он основан на функциональности, предоставляемой классом System.Windows.Media.CompositionTarget, который в конечном итоге представляет собой поверхность отображения, на которой рисуется приложение WPF. Класс предоставляет 2 полезных метода:

  • TransformFromDevice
  • TransformToDevice

Имена говорят сами за себя, и в обоих случаях мы получаем объект System.Windows.Media.Matrix, который содержит коэффициенты отображения между единицами устройства (пикселями) и независимыми единицами. M11 будет содержать коэффициент для оси X, а M22 – для оси Y.

Поскольку до сих пор мы рассматривали направление единиц -> пикселей, давайте перепишем наш помощник с помощью CompositionTarget.TransformToDevice.. При вызове этого метода M11 и M22 будут содержать значения, которые мы рассчитали как:

  • dpiX / 96
  • точек на дюйм / 96

Таким образом, на машине с DPI, установленным на 120, коэффициенты будут 1,25.

Вот новый помощник:

/// <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);
}

Мне пришлось добавить в метод еще один параметр, Visual. Он нужен нам как база для вычислений (предыдущие примеры использовали для этого контекст устройства всего экрана). Я не думаю, что это большая проблема, так как вы, скорее всего, будете иметь под рукой Visual при запуске вашего приложения WPF (иначе зачем вам нужно переводить координаты пикселей?). Однако, если ваш визуал не был прикреплен к источнику презентации (то есть он еще не был показан), вы не можете получить источник презентации (таким образом, у нас есть проверка на NULL и построение нового HwndSource).

Справочник

person Behzad    schedule 08.06.2015
comment
Где взять визуал? У меня есть Окно. - person Alan Baljeu; 19.09.2019
comment
@AlanBaljeu Если вы используете WPF, вы можете получить DPI из Visual следующим методом: VisualTreeHelper.GetDpi(visual ?? System.Windows.Application.Current.MainWindow) - person Behzad; 23.09.2019

Я только что обнаружил и протестировал это (в VB):

formVal = Me.LogicalToDeviceUnits(WPFval)

formVal и WPFval могут быть либо целыми числами, либо размерами.

person UnhandledException-InvalidChar    schedule 14.03.2020