2017-01-14 2 views
-2

При добавлении значка в системном трее из Windows существуют две версии API, которые мы можем передать Shell_NotifyIcon() через структуру NOTIFYICONDATA. Существуют тонкие различия между двумя API, и они нигде не указаны в MSDN. Мне потребовалось несколько усилий, чтобы выяснить некоторые из различий, которые я собираюсь рассказать сейчас. Усовершенствования/дополнения к ответам всегда приветствуются.Разница между NOTIFYICON_VERSION и NOTIFYICON_VERSION_4, используемой в структуре NOTIFYICONDATA?

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

ответ

-2

uVersion член структуры NOTIFYICONDATA может иметь 3 возможных значения, представляющих версию API, используемую для создания значка на панели задач.

  • Используйте это значение для приложений, предназначенных для версий Windows, до Windows 2000.
  • NOTIFYICON_VERSION Используйте поведение Windows 2000. Используйте это значение для приложений, предназначенных для Windows 2000 и более поздних версий.
  • NOTIFYICON_VERSION_4 Использовать текущее поведение. Используйте это значение для приложений, предназначенных для Windows Vista и более поздних версий.

Когда дело доходит до обработчика сообщений для иконе подноса, wParam и uParam имеют различия, как показано на следующем рисунке.

NOTIFYICON_VERSION vs NOTIFYICON_VERSION_4

Обратите внимание, что в NOTIFYICON_VERSION_4 WPARAM дает X и Y координаты различных событий, но не предусмотрена для получения координат в NOTIFYICON_VERSION. Это вызывает интересное поведение (что было причиной ошибки, которую я пытался решить). Если вы используете NOTIFYICON_VERSION, а затем вызовите контекстное меню значка в трее, то курсор мыши, где бы он ни находился, когда вы вызываете меню, попадает прямо в центр значка в трее. Даже если вы используете клавиатуру (WINDOWS + B) для вызова контекстного меню значка, курсор мыши по-прежнему перемещается к значку.

Это не может вас заинтересовать, пока вы не посмотрите на этот конкретный BUG, ​​который я пытаюсь решить в приложении Pico torrent.

Вот сценарий.

  • ОС: Windows 10
  • Приложение не за монитор DPI в курсе, но на уровне системы DPI осознанным.
  • Существует начальное значение набора масштабирования рабочего стола, скажем 150%, когда пользователь входит в систему.
  • Pico торрент работает.
  • DPI значение масштабирования изменяется, скажет контекстное меню 125%
  • Пико торрентов вызываются контекстное меню не будет отображаться на своем месте, и будет смещаться немного, показывая девиации.

Чтобы узнать, что происходит, см. Следующие изображения.

DPI scaling level : 150 % DPI scaling level : 125 %

Проблема заключается в том, что, хотя MSDN говорит, что GET_X_LPARAM(wParam) и GET_Y_LPARAM(wParam) должны давать правильные значения в обработчике трей, но это не делает, в присутствии DPI масштабирования (т.е. для изменение масштабирования DPI без выписки и входа в систему). С другой стороны, API GetCursorPos() возвращает правильное значение координат курсора мыши. Обратите внимание, что NOTIFYICON_VERSION_4 вместе с GetCursorPos() не будет работать, так как контекстное меню может быть вызвано с помощью клавиатуры, при которой курсор мыши может находиться где угодно на экране (экранах).

Итак, как вы объедините все знания, которые только что узнали, чтобы правильно отображать контекстное меню значка в трее, когда масштабирование DPI выполняется в соответствии с вышеописанным способом, не делая ваше приложение на мониторе DPI в курсе (для приложений, поддерживающих DPI-мониторинг) GET_X_LPARAM(wParam) и GET_Y_LPARAM(wParam) всегда возвращают правильное значение)?

Используйте NOTIFYICON_VERSION вместо NOTIFYICON_VERSION_4, это будет позиционировать курсор мыши на значке в трее, когда вызывается контекстное меню, а затем используйте GetCursorPos(), чтобы получить позицию курсора мыши. Отобразите контекстное меню с помощью TrackPopupMenu() с координатами.

PS: В приведенном выше примере значение масштабирования DPI изменяется от 150% до 125%. Отклонение контекстного меню более выражено, когда масштабирование DPI выполняется от большего значения до меньшего значения, когда область значка вашего лотка находится в нижней правой части экрана. Это происходит из-за того, что при масштабировании DPI окна увеличивают элементы пользовательского интерфейса, которые не контролируются монитором, используя виртуализацию DPI, тогда все перемещается вправо и вниз. например. если в приложении прямоугольник окон (0,0,100,100) (координаты экрана), то после увеличения до 150% он может стать (0,0,150,150). Теперь в меню значка в трее, если вы укажете координаты, лежащие за нижним правом экрана, тогда ОС все равно будет отображаться в нижнем правом положении, которое находится внутри экрана и которое гарантирует правильное отображение меню. например. если экран имеет разрешение 1920x1080, а для меню - TrackPopupMenu() (10000 10000), меню будет отображаться внутри прямоугольника экрана 1920x1080. Таким образом, увеличение масштабирования DPI больше не будет перемещать контекстное меню, если оно уже достигло самой правой позиции в правом нижнем углу.

+3

Правильное решение проблемы - сделать программу DPI-aware. При необходимости динамически загружайте 'SetProcessDpiAwareness()' во время выполнения. – andlabs

+1

Также обратите внимание, что при появлении всплывающего окна значка в лотке с помощью клавиатуры вы можете использовать ['Shell_NotifyIconGetRect()'] (https://msdn.microsoft.com/en-us/library/windows/desktop/dd378426 (v = vs.85) .aspx), чтобы получить текущую позицию экрана самого значка, вместо использования 'GetCursorPos()', чтобы получить текущую позицию экрана мыши. –

+0

@andlabs Нет, это не так.Потому что, если вы сделаете процесс для каждого монитора DPI осведомленным, вам придется обрабатывать масштабирование каждого элемента пользовательского интерфейса самостоятельно. Многие разработчики по-прежнему хотят использовать виртуализацию DPI в некоторой степени. Более того, если ваше приложение использует двоичный файл от третьего лица, который создает элементы пользовательского интерфейса, вы не сможете обрабатывать масштабирование DPI в нем, так как у вас нет кода. Это была основная причина, побудившая Microsoft внедрить новый API в WIndows 10 Anniversary Update, SetThreadDpiAwarenessContext(). –

1

@ sahil-singh, вы правы в этом, и я согласен со всеми остальными, что ваше приложение должно быть осведомлено о DPI, но когда это не пункт здесь.

У меня была аналогичная проблема, когда мое приложение (по-прежнему) не поддерживает DPI, а GET_X_LPARAM (wParam) возвращает не виртуальные координаты. После передачи этого значения в TrackPopupMenu() я получаю неправильную позицию на экране.

Лучший способ - использовать GetMessagePos() вместо wParam. В этом случае Windows предоставит вам новый DWORD с виртуальными координатами, а затем GET_X_LPARAM/GET_Y_LPARAM, чтобы получить значения, которые вы можете передать в TrackPopupMenu().

+0

будет работать GetMessagePos(), если значок вашего лотка вызывается с помощью клавиатуры, а не мыши? –

Смежные вопросы