Как да конвертирате WPF инчова единица в Winforms пиксели и обратно?

Имам прозорец, проектиран в WPF и го използвах в центъра на WinForms собственик. Сега искам да преместя формуляра на собственика и в момента моя WPF също трябва да бъде преместен в центъра на формуляра!

Но имам проблем, само когато прозорецът е в центъра на формуляра, който се образува в центъра на екрана. И иначе действайте в различна форма от координатите на Windows. Просто добавям стойностите на изместване на формата към местоположението на прозореца.

Сега стигнах до извода, че координатите на пикселите на WPF Windows са различни от WinForms!

Как да конвертирате WPF местоположение на прозореца в 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
  • dpiY / 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
Къде да вземем Visual? Имам Window. - 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