Другое

Масштабирование DPI в VBA UserForm: исправление проблем рендеринга элементов управления изображениями

Узнайте, почему элементы управления изображениями в VBA отображаются некорректно при разных масштабах отображения Windows и реализуйте правильные решения без искусственных исправлений. Полное руководство по проблемам масштабирования DPI в VBA.

Почему элемент управления Image в VBA UserForm отображается некорректно при разных масштабах отображения Windows, и как это можно исправить?

Я разрабатываю VBA UserForm, который должен адаптироваться к разным масштабам отображения Windows (100% и 200%). У меня есть два BMP-изображения:

  • 1x изображение: 500px на 500px
  • 2x изображение: 1000px на 1000px

Я установил элемент управления изображением размером 375 пунктов в ширину и 375 пунктов в высоту (используя соотношение 0.75 пикселя к пункту) и загружаю соответствующее изображение в зависимости от масштаба отображения.

При масштабе 100% (96DPI) изображение 500x500px отображается правильно размером 500x500 пикселей.

Однако при масштабе 200% (192DPI), когда я загружаю изображение 1000x1000px, оно отображается размером 1072x1072 пикселей вместо ожидаемых 1000x1000 пикселей, что вызывает артефакты масштабирования.

Чтобы исправить это, мне нужно применить коэффициент подгонки 0.933 (1000/1072), чтобы изображение отображалось правильного размера.

Шаги для воспроизведения:

  1. Создайте проект VBA с UserForm, содержащим два элемента управления изображением (Image1 и Image2x), оба установлены размером 375x375 пунктов с режимом масштабирования “zoom”.
  2. Загрузите изображение BMP размером 500x500px в Image1 и изображение BMP размером 1000x1000 в Image2x.
  3. Используйте следующий код и протестируйте при разных масштабах отображения:
vba
Private Declare PtrSafe Function GetDC Lib "user32" (ByVal hwnd As LongPtr) As LongPtr
Private Declare PtrSafe Function GetDeviceCaps Lib "gdi32" (ByVal hdc As LongPtr, ByVal nIndex As Long) As Long
Private Declare PtrSafe Function ReleaseDC Lib "user32" (ByVal hwnd As LongPtr, ByVal hdc As LongPtr) As Long

Private Const LOGPIXELSX = 88
Private Const LOGPIXELSY = 90

Private Sub UserForm_Initialize()
    Select Case GetDPIScale
        ' Если масштаб отображения 200%, загружаем 2x изображение и применяем коэффициент подгонки
        Case 2
            With Me.Image1
                .Picture = Me.Image2x.Picture
                .Width = .Width * 0.933
                .Height = .Height * 0.933
            End With
    End Select
End Sub

Private Function GetDPIScale() As Double
    Dim hdc As LongPtr
    Dim dpiX As Long

    hdc = GetDC(0)
    dpiX = GetDeviceCaps(hdc, LOGPIXELSX)
    ReleaseDC 0, hdc

    GetDPIScale = dpiX / 96 ' 96 DPI - стандартная базовая величина
End Function

Почему это происходит, и существует ли правильное решение вместо использования коэффициента подгонки?

Элемент управления изображением VBA отображается некорректно при разных масштабах отображения Windows из-за плохой обработки масштабирования DPI и преобразования точек в пиксели в VBA, что приводит к тому, что элемент управления интерпретирует измерения в точках по-разному в зависимости от настроек DPI системы, а не использует ожидаемое соотношение 1:1 между точками и масштабированными пикселями.

Содержание

Понимание проблемы масштабирования DPI

Основная проблема заключается в том, как VBA обрабатывает системы координат и масштабирование отображения. Когда Windows изменяет масштаб отображения с 100% до 200%, она не просто удваивает все измерения в пикселях - она создает коэффициент масштабирования, который приложения должны учитывать. Однако, элементы управления UserForm в VBA были разработаны в эпоху, когда масштабирование DPI было менее распространенным, что приводит к непоследовательному поведению.

При масштабе 100% (96 DPI) 1 точка равна 1 пикселю, поэтому ваш элемент управления размером 375pt × 375pt отображается как 375 × 375 пикселей. Когда вы загружаете изображение размером 500 × 500 пикселей в это пространство, оно пропорционально уменьшается (375/500 = 0,75), сохраняя соотношение сторон.

При масштабе 200% (192 DPI) Windows ожидает, что приложения будут учитывать коэффициент масштабирования, но VBA корректно это не обрабатывает. Элемент управления размером 375pt × 375pt должен отображаться как 750 × 750 пикселей (375 × 2), но вместо этого отображается как 1072 × 1072 пикселей, что указывает на то, что внутреннее преобразование пикселей в точки в VBA не учитывает системные настройки DPI должным образом.

Почему элемент управления изображением VBA ведет себя таким образом

Плохая поддержка масштабирования DPI в VBA обусловлена несколькими техническими ограничениями:

Устаревшая система координат: VBA использует фиксированную систему координат, основанную на точках (1/72 дюйма), которая не адаптируется к изменениям системного DPI. В то время как современные приложения используют независимые от устройства единицы измерения, VBA рассматривает точки как абсолютные измерения.

Рендеринг на основе GDI: UserForm в VBA полагаются на Windows GDI (Graphics Device Interface) для рендеринга, который был разработан для времени, когда все дисплеи имели примерно 96 DPI. Современные дисплеи с высоким DPI требуют GDI+ или DirectWrite для корректного масштабирования.

Проблемы с контейнером элементов управления: Элемент управления изображением не правильно наследует осведомленность DPI от своего родительского контейнера (UserForm), что приводит к непоследовательному поведению масштабирования при разных системных настройках DPI.

Отсутствие API с поддержкой DPI: VBA не предоставляет современные API Windows для осведомленности о DPI, такие как SetProcessDPIAware или ProcessDPIAware, которые приложения используют для корректной обработки сценариев с высоким DPI.

Это архитектурное ограничение означает, что приложения VBA часто требуют ручного вмешательства для корректной работы на разных масштабах отображения, что делает необходимым использование искусственных коэффициентов.

Правильные решения без искусственных коэффициентов

Хотя в VBA нет встроенной поддержки DPI, вы можете реализовать более надежные решения, чем произвольные искусственные коэффициенты:

Метод 1: Динамическое преобразование точек в пиксели

Создайте корректную функцию преобразования, учитывающую системный DPI:

vba
Private Declare PtrSafe Function GetDC Lib "user32" (ByVal hwnd As LongPtr) As LongPtr
Private Declare PtrSafe Function GetDeviceCaps Lib "gdi32" (ByVal hdc As LongPtr, ByVal nIndex As Long) As Long
Private Declare PtrSafe Function ReleaseDC Lib "user32" (ByVal hwnd As LongPtr, ByVal hdc As LongPtr) As Long
Private Declare PtrSafe Function GetSystemMetrics Lib "user32" (ByVal nIndex As Long) As Long

Private Const LOGPIXELSX = 88
Private Const LOGPIXELSY = 90
Private Const SM_CXSCREEN = 0
Private Const SM_CYSCREEN = 1

Private Function PointsToPixels(ByVal points As Double, Optional ByVal dpiScale As Double = -1) As Double
    If dpiScale = -1 Then
        dpiScale = GetDPIScale()
    End If
    PointsToPixels = points * (dpiScale * 96) / 72
End Function

Private Function PixelsToPoints(ByVal pixels As Double, Optional ByVal dpiScale As Double = -1) As Double
    If dpiScale = -1 Then
        dpiScale = GetDPIScale()
    End If
    PixelsToPoints = pixels * 72 / (dpiScale * 96)
End Function

Метод 2: Масштабирование элементов управления с поддержкой DPI

Модифицируйте инициализацию UserForm для использования корректного масштабирования DPI:

vba
Private Sub UserForm_Initialize()
    Dim dpiScale As Double
    dpiScale = GetDPIScale()
    
    ' Установка размеров элементов управления на основе масштаба DPI
    With Me.Image1
        ' Расчет правильного размера в пикселях для элемента управления изображением
        Dim targetPixelSize As Double
        targetPixelSize = 375 * dpiScale ' Преобразование точек в ожидаемые пиксели
        
        ' Загрузка соответствующего изображения
        If dpiScale >= 1.75 Then ' Учитываем масштаб 200%
            .Picture = LoadPicture("путь\к\1000x1000.bmp")
            ' Установка размера элемента управления для соответствия нативному размеру изображения при этом DPI
            .Width = targetPixelSize
            .Height = targetPixelSize
        Else ' Масштаб 100%
            .Picture = LoadPicture("путь\к\500x500.bmp")
            .Width = targetPixelSize
            .Height = targetPixelSize
        End If
        
        ' Убедимся, что режим масштабирования подходит
        .SizeMode = fmSizeModeZoom ' или fmSizeModeStretch в зависимости от потребностей
    End With
End Sub

Метод 3: Внешняя библиотека масштабирования DPI

Для более сложных приложений рассмотрите возможность использования внешней библиотеки или надстройки, предоставляющей лучшую поддержку DPI. Вы можете создать простой COM DLL на C# или C++, который корректно обрабатывает масштабирование DPI, и предоставить его доступ в VBA.

Альтернативные подходы для приложений VBA с поддержкой DPI

Подход с использованием элемента управления Web Browser

Вместо использования встроенного элемента управления изображением, рассмотрите возможность использования элемента управления Web Browser, который имеет лучшую поддержку DPI:

vba
Private Sub UserForm_Initialize()
    Dim dpiScale As Double
    dpiScale = GetDPIScale()
    
    ' Использование HTML/CSS для лучшего масштабирования изображений
    Dim htmlContent As String
    htmlContent = "<html><body style='margin:0;padding:0;'>"
    htmlContent = htmlContent & "<img src='путь\к\изображению.jpg' style='width:100%;height:100%;object-fit:contain;'>"
    htmlContent = htmlContent & "</body></html>"
    
    Me.WebBrowser1.Navigate "about:blank"
    Do While Me.WebBrowser1.Busy
        DoEvents
    Loop
    Me.WebBrowser1.Document.Write htmlContent
End Sub

Элементы управления сторонних разработчиков

Изучите элементы управления ActiveX сторонних разработок, разработанные для лучшей обработки DPI, такие как:

  • Элементы управления Infragistics UI
  • Элементы управления DevExpress WinForms (при встраивании в VBA)
  • Другие коммерческие библиотеки для расширения возможностей VBA

Поддержка DPI на уровне системы

Для наиболее надежного решения вы можете создать обертку приложения на более современном языке (например, C# или VB.NET), которая корректно обрабатывает DPI и взаимодействует с VBA через COM или interop.

Лучшие практики для кросс-DPI совместимости

Стратегия проектирования

  • Используйте векторную графику: По возможности используйте изображения на основе векторов (SVG, EMF) вместо растровых
  • Ресурсы высокого разрешения: Создавайте ресурсы в нескольких разрешениях (1x, 2x, 3x) и загружайте соответствующие в зависимости от DPI
  • Относительное масштабирование: Используйте процентное масштабирование вместо абсолютных размеров в пикселях
  • Матрица тестирования: Тестируйте на разных настройках DPI (100%, 125%, 150%, 200%)

Рекомендации по реализации

  1. Определяйте DPI при запуске: Всегда определяйте системный DPI во время инициации приложения
  2. Динамический макет: Разрабатывайте макеты, которые могут адаптироваться к разным настройкам DPI
  3. Последовательное масштабирование: Применяйте один и тот же коэффициент масштабирования во всем приложении
  4. Предпочтения пользователя: Рассмотрите возможность добавления параметров переопределения DPI для пользователей с особыми потребностями

Структура кода

vba
' Модуль управления DPI
Option Explicit

Private m_dpiScale As Double
Private m_isDpiAware As Boolean

Public Sub InitializeDpiAwareness()
    m_dpiScale = GetDPIScale()
    ' Установка любых глобальных настроек, связанных с DPI
    m_isDpiAware = True
End Sub

Public Function ScaleValue(ByVal value As Double) As Double
    If m_isDpiAware Then
        ScaleValue = value * m_dpiScale
    Else
        ScaleValue = value
    End If
End Function

' В ваших формах и модулях используйте:
' Control.Width = ScaleValue(baseWidth)

Методы тестирования и валидации

Для обеспечения корректной работы масштабирования DPI реализуйте всестороннее тестирование:

Автоматизированное тестирование

vba
Public Sub TestDpiScaling()
    Dim testDpis As Variant
    testDpis = Array(96, 120, 144, 192) ' 100%, 125%, 150%, 200%
    
    Dim dpi As Variant
    For Each dpi In testDpis
        ' Имитация настройки DPI (необходимо изменить системный DPI или использовать виртуальные машины)
        Debug.Print "Тестирование DPI: " & dpi & " (" & (dpi / 96) & "x масштаб)"
        
        ' Тестирование рендеринга изображений
        TestImageRendering dpi / 96
        
        ' Тестирование размеров элементов управления
        TestControlSizing dpi / 96
    Next dpi
End Sub

Private Sub TestImageRendering(ByVal scale As Double)
    ' Загрузка тестового изображения и проверка размеров
    ' Это потребовало бы имитации DPI окружения
End Sub

Чек-лист для ручного тестирования

  • [ ] Тестирование при масштабе DPI 100% (96 DPI)
  • [ ] Тестирование при масштабе DPI 125% (120 DPI)
  • [ ] Тестирование при масштабе DPI 150% (144 DPI)
  • [ ] Тестирование при масштабе DPI 200% (192 DPI)
  • [ ] Тестирование с дисплеями смешанного DPI (основной vs вторичные мониторы)
  • [ ] Тестирование с параметрами масштабирования доступности
  • [ ] Тестирование с режимом высокого контраста Windows
  • [ ] Тестирование с разными разрешениями экрана

Инструменты валидации

  • Настройки разрешения экрана Windows: Вручную изменяйте масштаб для проверки поведения
  • Process Monitor: Мониторинг вызовов GDI и использования ресурсов
  • Spy++: Исследование сообщений окна и свойств элементов управления
  • Специализированные инструменты тестирования DPI: Создание утилит для имитации разных DPI окружений

Хотя нативная поддержка DPI в VBA ограничена, реализация этих правильных решений обеспечит более надежные и поддерживаемые результаты, чем полагание на произвольные искусственные коэффициенты. Ключ к успеху - понимание основных проблем системы координат и реализация последовательного масштабирования во всем приложении.

Источники

  1. Документация Microsoft VBA - Элементы управления UserForm
  2. Рекомендации по масштабированию DPI Windows
  3. Лучшие практики для UserForm VBA
  4. GDI vs GDI+ для масштабирования изображений
  5. Техническая ссылка на масштабирование отображения Windows
Авторы
Проверено модерацией
Модерация