Итак, этот пост является выжимкой из следующих постов:
- Painting only when your window is visible on the screen.
- Taxes: Remote Desktop Connection and painting.
- Taxes: Detecting session state changes, such as a locked workstation.
В своих оконных программах вы можете делать множество вещей, которые отвечают за ваш внешний вид. Чаще всего этот код приносит пользу. Но бывают ситуации, когда ему лучше бы не работать. Вот три примера таких ситуаций:
- Если окно не видимо - нет смысла его перерисовывать (включая вычисления и прочие сопутствующие действия).
- Если приложение запущенно через удалённый рабочий стол - вам лучше бы минимизировать обновления или программа будет тормозить.
- Если человек за машиной переключился на другого пользователя или просто заблокировал машину - вам лучше бы отключить всю работу, отвечающую за красивости.
Обновление окна только когда оно видимо
Иногда вы хотели бы выполнить действия, такие как обновление статуса операции в окне, только когда окно не закрыто другими окнами.Простейшим способом определить это - не пытаться определять вообще. Например, вот как панель задач обновляет себя:
- Она вычисляет, сколько осталось времени до конца следующей минуты.
- Она вызывает SetTimer для срабатывания через этот промежуток времени.
- Когда таймер срабатывает, панель задач вызывает на себя InvalidateRect и удаляет таймер.
- Обработчик WM_PAINT рисует текущее время, потом возвращается к шагу 1.
Ключевой момент здесь - удаление и пересоздание таймера. Заметьте, как при этом вы получаете режим спячки просто ничего не делая. Этот метод также перестанет обновлять ваше окно, если запустится хранитель экрана, машина окажется заблокированной или будет выполнено переключение на другого пользователя. Эта техника также применима, если вам нужно обновлять не окно целиком, а только часть его (определённую область или элемент управления) - просто вызывайте InvalidateRect для конкретной области, а не окна целиком. В обработчике WM_PAINT устанавливайте таймер только, если область обновления пересекается с заданной конкретной областью.
Поддержка удалённого рабочего стола
Ценность поддержки удалённого рабочего стола постоянно возрастает. Когда пользователь подключён через удалённый рабочий стол, видео-операции передаются по сети на клиентское место для отображения. Поскольку сети имеют большие задержки и невысокую скорость (в сравнении с локальной шиной PCI или AGP), то вам нужно адаптировать количество изменений на экране.Если вы рисуете на экрана линию, то по сети передаётся команда "рисуй линию". Если вы рисуете текст, то отправляется команда "рисуй текст" (вместе с самим текстом и его координатами для рисования). Пока неплохо. Но если вы выводите на экран рисунок, то этот рисунок тоже будет передан по сети.
Попробуйте сделать программу, которая рисует двадцать строк столбиком через TextOut. Убедитесь, что опция "Показывать содержимое окна при перетаскивании" включена, и попробуйте изменять размер вашего окна. Ух, какое гадкое получается мерцание.
Исправьте этот недостаток с использованием обычной техники двойного буфера: рисуйте строки в специально подготовленный bitmap и выводите его на окно одним махом. Если вы запустите программу теперь, то увидите, что окно изменяет размеры гладко и красиво.
Теперь подключитесь к своему компьютеру через удалённый рабочий стол и запустите программу снова (не забудьте включить по максимуму опции соединения рабочего стола). Попробуйте изменять размеры окна или максимизировать и восстанавливать его. Заметьте задержку при этих операциях. Это потому что мы проталкиваем огромный bitmap через сеть.
Вернитесь к предыдущей версии вашей программы, где вы выводили текст напрямую, и попробуйте запустить её в этой же сессии удалённого рабочего стола. Ох, а эта работает очень быстро. Это потому что более простая версия не копирует большие рисунки по сети - она просто отправляет двадцать вызовов TextOut. Это занимает намного меньше трафика, чем 1024x768 bitmap.
Мы определили, что один метод работает быстрее через удалённый рабочий стол, а другой метод работает быстрее при локальном использовании. Какой же нам использовать?
Мы используем оба, выбирая метод в зависимости от окружения. Для определения того, что вы запущены через удалённый рабочий стол - используйте вызов GetSystemMetrics(SM_REMOTESESSION).
Теперь у нас есть лучшее от обоих миров. Когда мы запущены локально - мы используем двойную буферизацию для красивого рисования без мерцания, но когда мы запущены через удалённый рабочий стол - мы рисуем прямо на экран, минуя буфер.
Поддержка Fast User Switching
Другой ситуацией, где вы могли бы играть дружелюбнее, является сценарий с быстрым переключением пользователей. Когда рабочая станция заблокирована или отключена (это включает в себя вызов "смены пользователя", а также отключение от сессии удалённого рабочего стола без её завершения), вам следует выключить все не важные таймеры, минимизировать фоновую активность и, вообще говоря, отправить свою программу в максимально спящее состояние. Если вы уже используете техники, описанные выше, вы можете получить такое поведение на халяву, поскольку заблокированная или отключенная рабочая станция не генерирует сообщений перерисовки.Но если помимо перерисовки вы выполняете и другие действия, то вам нужно отключать их вручную, когда пользователь отключается от рабочей станции. Вы можете зарегистрироваться, чтобы получать уведомления о смене состояния рабочей станции.
Для этого, вызовите функцию WTSRegisterSessionNotification(Handle, NOTIFY_FOR_THIS_SESSION) при старте вашего приложения и ловите сообщение WM_WTSSESSION_CHANGE:
if Msg = WM_WTSSESSION_CHANGE then case wParam of WTS_CONSOLE_DISCONNECT, WTS_REMOTE_DISCONNECT, WTS_SESSION_LOCK, WTS_SESSION_LOGOFF: // отключить фоновую работу WTS_CONSOLE_CONNECT, WTS_REMOTE_CONNECT, WTS_SESSION_UNLOCK, WTS_SESSION_LOGON: // включить фоновую работуВ такой программе вы регистрируетесь на уведомления после создания главного окна и ждёте уведомлений. Если вы видите одно из сообщений "отошёл" - вы переходите в состояние спячки, если вы видите одно из сообщений "вернулся" - вы просыпаетесь.
Замечу, что за время жизни одного и того же экземпляра программы он может работать как локально, так и через удалённый рабочий стол.
ОтветитьУдалитьНапример, мы запустили программу локально на клиентской ОС. Потом, не выходя из системы, подключились к машине этим же пользователем через удалённый рабочий стол. Windows подключит вас к тому же сеансу, что уже открыт, заблокировав локальную консоль. Поработали удалённо - вернулись за свою машину и вошли в систему. Тогда Windows выкинет удалённого клиента и подключит сеанс к локальной консоли.
Иными словами, программа за свою жизнь может работать как локально, так и удалённо, но не одновременно.
Я это к тому, что если вы кэшируете проверку GetSystemMetrics(SM_REMOTESESSION) в глобальной переменной, то вам лучше бы пересчитывать её по приходу WM_WTSSESSION_CHANGE.
Спасибо, очень интересно.
ОтветитьУдалитьМногие мои клиенты работают с моей программой с помощью Remote Desktop Connection, но я никогда не задумывался об этом.
Более того, я даже не помню уже, используется ли у меня в программе двойная буферизация или нет.
Но я пожалуй попробую отслеживать указанные в статье сообщения, и в случае обнаружения удалённого подключения буду отключать спецэффекты.
В новых Delphi достаточно переключить свойство DoubleBuffered и, если ParentDoubleBuffered у всех стоит в True, этого будет достаточно для переключения буферизации у всех контролов.
ОтветитьУдалитьОднако, некоторые навороченные контролы вполне могут использовать двойную буферизацию где-то внутри себя вне зависимости от свойства DoubleBuffered. Причём, не давая никакого способа это изменить.
Ну, тут уж ничего не сделаешь.
Получается что контролы которые для отрисовки себя используют gdi+ (например скиновые компоненты) наверное тоже будут рисоваться на порядок медленнее.
ОтветитьУдалитьСпасибо за статью, интересно, но возникает один вопрос - а стоит ли овчинка выделки ? Стоят ли затраченные усилия на ускорение отрисовки через уделенный рабочий стол изменений в коде и его дальнейшего сопровождения ? Каналы для становятся со временем все толще, скорости обмена все выше.
ОтветитьУдалитьСпециально после прочтения статьи полез на удаленный рабочий стол позапускать всяких разных программ, начиная от Open Office до программ собственного производства, на Delphi, но без особых украшательств. И не увидел ужасающей разницы в видимом быстродействии.
Да, Far медленнее перерисовывает окно при обновлении каталога, чем на локальном рабочем столе. С другой стороны, сетевой каталог он и на локальном рабочем столе не мгновенно обновляет. Open Office листает довольно большой Word-овский документ с картинками вполне приемлемо по скорости, скроллбар не так быстро работает, но в целом, повторюсь, терпимо.
Application Readiness for Remote Desktop Services, Remote Desktop Services Programming Guidelines.
ОтветитьУдалитьИнтересно, не задумывался о различии для удалёнки! Хотя и так неплохо работает - виндовое средство весьма и весьма нетребовательно к скорости сети!
ОтветитьУдалитьНо буду иметь в виду.