7. Вывод информации в окно. Механизм перерисовки окна. Понятие области обновления окна. Операции с областью обновления окна


Посылка синхронных сообщений окнуамс



страница6/16
Дата14.06.2018
Размер5,7 Mb.
1   2   3   4   5   6   7   8   9   ...   16

Посылка синхронных сообщений окнуамс

Оконное сообщение можно отправить непосредственно оконной процедуре вызовом

SendMessage:

LRESULT SendMessage(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

Оконная процедура обработает сообщение, и только по окончании обработки функция SendMessage вернет управление.

Вот как работает SendMessage. Если поток вызывает SendMessage для посылки сообщения окну, созданному им же, то функция просто обращается к оконной процедуре соответствующего окна как к подпрограмме. Закончив обработку, оконная процедура передает функции SendMessage некое значение, а та возвращает его вызвавшему потоку. Однако, если поток посылает сообщение окну, созданному другим потоком, операции, выполняемые функцией SendMessage, значительно усложняются. Windows требует, чтобы оконное сообщение обрабатывалось потоком, создавшим окно. Поэтому, если вызвать SendMessage для отправки сообщения окну, созданному в другом процессе и, естественно, другим потоком, Ваш поток не сможет обработать это сообщение — ведь он не работает в адресном пространстве чужого процесса, а потому не имеет доступа к коду и данным соответствующей оконной процедуры. И действительно, Ваш поток приостанавливается, пока другой поток обрабатывает сообщение. Поэтому, чтобы один поток мог отправить сообщение окну, созданному другим потоком, система должна выполнить следующие действия:

Во-первых, переданное сообщение присоединяется к очереди сообщений потока-приемника, в результате чего для этого потока устанавливается флаг QS_SENDMESSAGE.

Во-вторых, если поток-приемник в данный момент выполняет какой-то код и не ожидает сообщений (через вызов GetMessage, PeekMessage или WaitMessage), переданное сообщение обработать не удастся — система не прервет работу потока для немедленной обработки сообщения. Но когда поток-приемник ждет сообщений, система сначала проверяет, установлен ли флаг пробуждения QS_SENDMESSAGE, и, если да, просматривает очередь синхронных сообщений, отыскивая первое из них. В очереди может находиться более одного сообщения. Скажем, несколько потоков одновременно послали сообщение одному и тому же окну. Тогда система просто ставит эти сообщения в очередь синхронных сообщений потока.

Итак, когда поток ждет сообщений, система извлекает из очереди синхронных сообщений первое и вызывает для его обработки нужную оконную процедуру. Если таких сообщений больше нет, флаг QS_SENDMESSAGE сбрасывается. Пока поток-приемник обрабатывает сообщение, поток, отправивший сообщение через SendMessage, простаивает, ожидая появления сообщения в очереди ответных сообщений. По окончании обработки значение, возвращенное оконной процедурой, передается асинхронно в очередь ответных сообщений потока-отправителя. Теперь он пробудится и извлечет упомянутое значение из ответного сообщения. Именно это значение и будет результатом вызова SendMessage. С этого момента поток-отправитель возобновляет работу в обычном режиме.

Ожидая возврата управления функцией SendMessage, поток в основном простаивает. Но кое-чем он может заняться: если другой поток посылает сообщение окну,созданному первым (ожидающим) потоком, система тут же обрабатывает это сообщение, не дожидаясь, когда поток вызовет GetMessage, PeekMessage или WaitMessage. Поскольку Windows обрабатывает межпоточные сообщения описанным выше образом, Ваш поток может зависнуть. Допустим, в потоке, обрабатывающем синхронное сообщение, имеется «жучок», из-за которого поток входит в бесконечный цикл. Что же произойдет с потоком, вызвавшим SendMessage? Возобновится ли когда-нибудь его выполнение? Значит ли это, что ошибка в одном приложении «подвесит» другое? Ответ — да! Это верно даже в том случае, если оба потока принадлежат одному процессу.
Оконные сообщения

Избегать подобных ситуаций позволяют четыре функции, и первая из них —SendMessageTimeout:

LRESULT SendMessageTimeout(HWND hwnd, UINT uMsg,WPARAM wParam, LPARAM lParam,

UINT fuFlags, UINT uTimeout, PDWORD_PTR pdwResult);

Она позволяет задавать отрезок времени, в течение которого Вы готовы ждать ответа от другого потока на Ваше сообщение. Ее первые четыре параметра идентичны параметрам функции SendMessage. В параметре fuFlags можно передавать флаги SMTO_NORMAL (0), SMTO_ABORTIFHUNG, SMTO_BLOCK, SMTO_NOTIMEOUTIFNOTHUNG или комбинацию этих флагов.

Флаг SMTO_ABORTIFHUNG заставляет SendMessageTimeout проверить, не завис ли поток-приемник2 , и, если да, немедленно вернуть управление. Флаг SMTO_NOTIMEOUTIFNOTHUNG сообщает функции, что она должна игнорировать ограничение по времени, если поток-приемник не завис. Флаг SMTO_BLOCK предотвращает обработку вызывающим потоком любых других синхронных сообщений до возврата из SendMessageTimeout. Флаг SMTO_NORMAL определен в файле WinUser.h как 0; он используется в том случае, если Вы не указали другие флаги.

Я уже говорил, что ожидание потоком окончания обработки синхронного сообщения может быть прервано для обработки другого синхронного сообщения. Флаг SMTO_BLOCK предотвращает такое прерывание. Он применяется, только если поток, ожидая окончания обработки своего сообщения, не в состоянии обрабатывать прочие синхронные сообщения. Этот флаг иногда приводит к взаимной блокировке потоков до конца таймаута. Так, если Ваш поток отправит сообщение другому, а тому нужно послать сообщение Вашему, ни один из них не сможет продолжить обработку, и оба зависнут.

Параметр uTimeout определяет таймаут — время (в миллисекундах), в течение которого Вы готовы ждать ответного сообщения. При успешном выполнении функция возвращает TRUE, а результат обработки сообщения копируется по адресу, указанному в параметре pdwResult. Кстати, прототип этой функции в заголовочном файле WinUser.h неверен. Функцию следовало бы определить как возвращающую значение типа BOOL, поскольку значение типа LRESULT на самом деле возвращается через ее параметр. Это создает определенные проблемы, так как SendMessageTimeout вернет FALSE, если Вы передадите неверный описатель окна или если закончится заданный период ожидания. Единственный способ узнать причину неудачного завершения функции — вызвать GetLastError. Последняя вернет 0 (ERROR_SUCCESS), если ошибка связана с окончанием периода ожидания. А если причина в неверном описателе, GetLastError даст код 1400 (ERROR_INVALID_WINDOW_HANDLE). Если Вы обращаетесь к SendMessageTimeout для посылки сообщения окну, созданному вызывающим потоком, система просто вызывает оконную процедуру, помещая возвращаемое значение в pdwResult. Операционная система считает поток зависшим, если он прекращает обработку сообщений более чем на 5 секунд.

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

BOOL SendMessageCallback(HWND hwnd, UINT uMsg, WPARAM wParam,LPARAM lParam,

SENDASYNCPROC pfnResultCallBack, ULONG_PTR dwData);

И вновь первые четыре параметра идентичны параметрам функции SendMessage. При вызове Вашим потоком SendMessageCallback отправляет сообщение в очередь синхронных сообщений потока-приемника и тут же возвращает управление вызывающему (т. е. Вашему) потоку. Закончив обработку сообщения, поток-приемник асинхронно отправляет свое сообщение в очередь ответных сообщений Вашего потока. Позже система уведомит Ваш поток об этом, вызвав написанную Вами функцию; у нее должен быть следующий прототип:

VOID CALLBACK ResultCallBack(HWND hwnd, UINT uMsg, ULONG_PTR dwData, LRESULT lResult);

Адрес этой функции обратного вызова передается SendMessageCallback в параметре pfnResultCallBack. А при вызове ResultCallBack в первых двух параметрах передаются описатель окна, закончившего обработку сообщения, и код (значение) самого сообщения. Параметр dwData функции ResultCallBack всегда получает значение, переданное SendMessageCallback в одноименном параметре. (Система просто берет то, что указано там, и передает Вашей функции ResultCallBack.) Последний параметр функции ResultCallBack сообщает результат обработки сообщения, полученный от оконной процедуры. Поскольку SendMessageCallback, передавая сообщение другому потоку, немедленно возвращает управление, ResultCallBack вызывается после обработки сообщения потоком-приемником не сразу, а с задержкой. Сначала поток-приемник асинхронно ставит сообщение в очередь ответных сообщений потока-отправителя. Затем при первом же вызове потоком-отправителем любой из функций GetMessage, PeekMessage, WaitMessage или одной из Send-функций сообщение извлекается из очереди ответных сообщений, и лишь потом вызывается Ваша функция ResultCallback.

Существует и другое применение функции SendMessageCallback. В Windows предусмотрен метод, позволяющий разослать сообщение всем перекрывающимся окнам (overlapped windows) в системе; он состоит в том, что Вы вызываете SendMessage и в параметре hwnd передаете ей HWND_BROADCAST (определенный как –1). Этот метод годится только для широковещательной рассылки сообщений, возвращаемые значения которых Вас не интересуют, поскольку функция способна вернуть лишь одно значение, LRESULT. Но, используя SendMessageCallback, можно получить результаты обработки «широковещательного» сообщения от каждого перекрытого окна. Ваша функция SendMessageCallback будет вызываться с результатом обработки сообщения от каждого из таких окон. Если SendMessageCallback вызывается для отправки сообщения окну, созданному вызывающим потоком, система немедленно вызывает оконную процедуру, а после обработки сообщения — функцию ResultCallBack. После возврата из ResultCallback выполнение начинается со строки, следующей за вызовом SendMessageCallback.

Третья функция, предназначенная для передачи межпоточных сообщений:

BOOL SendNotifyMessage(HWND hwnd, UINT uMsg, WPARAM wParam,LPARAM lParam);

Поместив сообщение в очередь синхронных сообщений потока-приемника, она немедленно возвращает управление вызывающему потоку. Так ведет себя и PostMessage, помните? Но два отличия SendNotifyMessage от PostMessage все же есть.

Во-первых, если SendNotifyMessage посылает сообщение окну, созданному другим потоком, приоритет данного синхронного сообщения выше приоритета асинхронных сообщений, находящихся в очереди потока-приемника. Иными словами, сообщения, помещаемые в очередь с помощью SendNotifyMessage, всегда извлекаются до выборки сообщений, отправленных через PostMessage.

Во-вторых, если сообщение посылается окну, созданному вызывающим потоком, SendNotifyMessage работает точно так же, как и SendMessage, т. е. не возвращает управление до окончания обработки сообщения. Большинство синхронных сообщений посылается окну для уведомления — чтобы сообщить ему об изменении состояния и чтобы оно как-то отреагировало на это, прежде чем Вы продолжите свою работу. Например, WM_ACTIVATE, WM_DESTROY, WM_ENABLE, WM_SIZE, WM_SETFOCUS, WM_MOVE и многие другие сообщения — это просто уведомления, посылаемые системой окну в синхронном, а не асинхронном режиме. Поэтому система не прерывает свою работу только ради того, чтобы оконная процедура могла их обработать. Прямо противоположный эффект дает отправка сообщения WM_CREATE — тогда система ждет, когда окно закончит его обработку. Если возвращено значение –1, значит, окно не создано.

И, наконец, четвертая функция, связанная с обработкой межпоточных сообщений:

BOOL ReplyMessage(LRESULT lResult);

Она отличается от трех описанных выше. В то время как Send-функции используются посылающим сообщения потоком для защиты себя от зависания, ReplyMessage вызывается потоком, принимающим оконное сообщение. Вызвав ее, поток как бы говорит системе, что он уже получил результат обработки сообщения и что этот результат нужно упаковать и асинхронно отправить в очередь ответных сообщений потока-отправителя. Последний сможет пробудиться, получить результат и возобновить работу.

Поток, вызывающий ReplyMessage, передает результат обработки сообщения через параметр lResult. После вызова ReplyMessage выполнение потока-отправителя возобновляется, а поток, занятый обработкой сообщения, продолжает эту обработку. Ни один из потоков не приостанавливается — оба работают, как обычно. Когда поток, обрабатывающий сообщение, выйдет из своей оконной процедуры, любое возвращаемое значение просто игнорируется.

Заметьте: ReplyMessage надо вызывать из оконной процедуры, получившей сообщение, но не из потока, вызвавшего одну из Send-функций. Поэтому, чтобы написать «защищенный от зависаний» код, следует заменить все вызовы SendMessage вызовами одной из трех Send-функций и не полагаться на то, что оконная процедура будет вызывать именно ReplyMessage.

Учтите также, что вызов ReplyMessage при обработке сообщения, посланного этим же потоком, не влечет никаких действий. На это и указывает значение, возвращаемое ReplyMessage: TRUE — при обработке межпоточного сообщения и FALSE — при попытке вызова функции для обработки внутрипоточного сообщения. Если Вас интересует, является обрабатываемое сообщение внутрипоточным или межпоточным, вызовите функцию InSendMessage:

BOOL InSendMessage();

Она возвращает TRUE, если поток обрабатывает межпоточное синхронное сообщение, и FALSE — при обработке им внутрипоточного сообщения (синхронного или асинхронного). Возвращаемые значения функций InSendMessage и ReplyMessage идентичны. Есть еще одна функция, позволяющая определить тип сообщения, которое обрабатывается Вашей оконной процедурой:

DWORD InSendMessageEx(PVOID pvReserved);

Вызывая ее, Вы должны передать NULL в параметре pvReserved. Возвращаемое значение указывает на тип обрабатываемого сообщения. Значение ISMEX_NOSEND (0) говорит о том, что поток обрабатывает внутрипоточное синхронное или асинхронное сообщение. Остальные возвращаемые значения представляют собой комбинацию битовых флагов:

ISMEX_SEND Поток обрабатывает межпоточное синхронное сообщение, посланное

ISMEX_NOTIFY Поток обрабатывает межпоточное синхронное сообщение, посланное

ISMEX_CALLBACK Поток обрабатывает межпоточное синхронное сообщение, посланное

ISMEX_REPLIED Поток обрабатывает межпоточное синхронное сообщение и уже выз-

через SendMessage или SendMessageTimeout; если флаг ISMEX_REPLIED не установлен, поток-отправитель блокируется в ожидании ответа через SendNotifyMessage; поток-отправитель не ждет ответа и не блокируется через SendMessageCallback; поток-отправитель не ждет ответа и не блокируется вал ReplyMessage; поток-отправитель не блокируется
Передача данных через сообщения

В некоторых оконных сообщениях параметр lParam задает адрес блока памяти. Например, сообщение WM_SETTEXT использует lParam как указатель на строку (с нулевым символом в конце), содержащую новый текст для окна. Рассмотрим такой вызов:

SendMessage(FindWindow(NULL, "Calculator"), WM_SETTEXT, 0, (LPARAM) "A Test Caption");

Вроде бы все достаточно безобидно: определяется описатель окна Calculator и делается попытка изменить его заголовок на «A Test Caption». Но приглядимся к тому, что тут происходит.

В lParam передается адрес строки (с новым заголовком), расположенной в адресном пространстве Вашего процесса. Получив это сообщение, оконная процедура программы Calculator берет lParam и пытается манипулировать чем-то, что, «по ее мнению», является указателем на строку с новым заголовком. Но адрес в lParam указывает на строку в адресном пространстве Вашего процесса, а не программы Calculator. Вот Вам и долгожданная неприятность — нарушение доступа к памяти. Но если Вы все же выполните показанную ранее строку, все будет работать нормально. Что за наваждение? А дело в том, что система отслеживает сообщения WM_SETTEXT и обрабатывает их не так, как большинство других сообщений. При вызове SendMessage внутренний код функции проверяет, не пытаетесь ли Вы послать сообщение WM_SETTEXT. Если это так, функция копирует строку из Вашего адресного пространства в проекцию файла и делает его доступным другому процессу. Затем сообщение посылается потоку другого процесса. Когда поток-приемник готов к обработке WM_SETTEXT, он определяет адрес общей проекции файла (содержащей копию строки) в адресном пространстве своего процесса. Параметру lParam присваивается значение именно этого адреса, и WM_SETTEXT направляется нужной оконной процедуре. После обработки этого сообщения, проекция файла уничтожается. Не слишком ли тут накручено, а? К счастью, большинство сообщений не требует такой обработки — она осуществляется, только если сообщение посылается другому процессу. (Заметьте: описанная обработка выполняется и для любого сообщения, параметры wParam или lParam которого содержат указатель на какую-либо структуру данных.)

А вот другой случай, когда от системы требуется особая обработка, — сообщение WM_GETTEXT. Допустим, Ваша программа содержит код:

char szBuf[200];

SendMessage(FindWindow(NULL, "Calculator"), WM_GETTEXT,

Sizeof(szBuf), (LPARAM) szBuf);

WM_GETTEXT требует, чтобы оконная процедура программы Calculator поместила в буфер, на который указывает szBuf, заголовок своего окна. Когда Вы посылаете это сообщение окну другого процесса, система должна на самом деле послать два сообщения. Сначала — WM_GETTEXTLENGTH. Оконная процедура возвращает число символов в строке заголовка окна. Это значение система использует при создании проекции файла, разделяемой двумя процессами. Создав проекцию файла, система посылает для его заполнения сообщение WM_GET- TEXT. Затем переключается обратно на процесс, первым вызвавший функцию SendMessage, копирует данные из общей проекции файла в буфер, на который указывает szBuf, и заставляет SendMessage вернуть управление. Что ж, все хорошо, пока Вы посылаете сообщения, известные системе. А если мы определим собственное сообщение (WM_USER + x), собираясь отправить его окну другого процесса? Система не «поймет», что нам нужна общая проекция файла для корректировки указателей при их пересылке. Но выход есть — это сообщение WM_COPYDATA:

COPYDATASTRUCT cds;

SendMessage(hwndReceiver, WM_COPYDATA, (WPARAM) hwndSender, (LPARAM) &cds);

COPYDATASTRUCT — структура, определенная в WinUser.h:

typedef struct tagCOPYDATASTRUCT {

ULONG_PTR dwData;

DWORD cbData;

PVOID lpData;

} COPYDATASTRUCT;

Чтобы переслать данные окну другого процесса, нужно сначала инициализировать эту структуру. Элемент dwData резервируется для использования в Вашей программе. В него разрешается записывать любое значение. Например, передавая в другой процесс данные, в этом элементе можно указывать тип данных. Элемент cbData задает число байтов, пересылаемых в другой процесс, а lpData указывает на первый байт данных. Адрес, идентифицируемый элементом lpData, находится, конечно же, в адресном пространстве отправителя. Увидев, что Вы посылаете сообщение WM_COPYDATA, SendMessage создает проекцию файла размером cbData байтов и копирует данные из адресного пространства Вашей программы в эту проекцию. Затем отправляет сообщение окну-приемнику. При обработке этого сообщения принимающей оконной процедурой параметр lParam указывает на структуру COPYDATASTRUCT, которая находится в адресном пространстве процесса-приемника. Элемент lpData этой структуры указывает на проекцию файла в адресном пространстве процесса-приемника.

Вам следует помнить о трех важных вещах, связанных с сообщением WM_COPYDATA.

1) Отправляйте его всегда синхронно; никогда не пытайтесь делать этого асинхронно. Последнее просто невозможно: как только принимающая оконная процедура обработает сообщение, система должна освободить проекцию файла. При передаче WM_COPYDATA как асинхронного сообщения появится неопределенность в том, когда оно будет обработано, и система не сможет освободить память, занятую проекцией файла.

2) На создание копии данных в адресном пространстве другого процесса неизбежно уходит какое-то время. Значит, пока SendMessage не вернет управление, нельзя допускать изменения содержимого общей проекции файла каким-либо другим потоком.

3) Сообщение WM_COPYDATA позволяет 16-разрядным приложениям взаимодействовать с 32-разрядными (и наоборот), как впрочем и 32-разрядным — с 64-разрядными (и наоборот). Это удивительно простой способ общения между новыми и старыми приложениями. К тому же, WM_COPYDATA полностью поддерживается как в Windows 2000, так и в Windows 98. Но, если Вы все еще пишете 16-разрядные Windows-приложения, учтите, что сообщение WM_COPYDATA и структура COPYDATASTRUCT в Microsoft Visual C++ версии 1.52 не определены. Вам придется добавить их определения самостоятельно:

// включите этот код в свою 16-разрядную Windows-программу

#define WM_COPYDATA 0x004A

typedef VOID FAR* PVOID;

typedef struct tagCOPYDATASTRUCT {

DWORD dwData;

DWORD cbData;

PVOID lpData;

} COPYDATASTRUCT, FAR* PCOPYDATASTRUCT;

Сообщение WM_COPYDATA — мощный инструмент, позволяющий разработчикам экономить массу времени при решении проблем связи между процессами.
6. Ввод данных с манипулятора «мышь». Обработка сообщений мыши. Ввод данных с клавиатуры. Понятие фокуса ввода. Обработка сообщений от клавиатуры.

Ввод с клавиатуры и фокус

Ввод с клавиатуры направляется потоком необработанного ввода (RIT) в очередь виртуального ввода какого-либо потока, но только не в окно. RIT помещает события от клавиатуры в очередь потока безотносительно конкретному окну. Когда поток вызывает GetMessage, событие от клавиатуры извлекается из очереди и перенаправляется окну (созданному потоком), на котором в данный момент сосредоточен фокус ввода (рис. 27-2). Чтобы направить клавиатурный ввод в другое окно, нужно указать, в очередь какого потока RIT должен помещать события от клавиатуры, а также «сообщить» переменным состояния ввода потока, какое окно будет находиться в фокусе. Одним вызовом SetFocus эти задачи не решить. Если в данный момент ввод от RIT получает поток 1, то вызов SetFocus с передачей описателей окон A, B или C приведет к смене фокуса.

ОПЕРАЦИИ С ОКНАМИ

Окно, теряющее фокус, убирает используемый для обозначения фокуса прямоугольник или гасит курсор ввода, а окно, получающее фокус, рисует такой прямоугольник или показывает курсор ввода.

Предположим, однако, что поток 1 по-прежнему получает ввод от RIT и вызывает SetFocus, передавая ей описатель окна E. В этом случае система не дает функции что-либо сделать, так как окно, на которое Вы хотите перевести фокус, не использует очередь виртуального ввода, подключенную в данный момент к RIT. Когда поток 1 выполнит этот вызов, на экране не произойдет ни смены фокуса, ни каких-либо изменений.

Возьмем другую ситуацию: поток 1 подключен к RIT, а поток 2 вызывает SetFocus, передавая ей описатель окна E. На этот раз значения переменных локального состояния ввода потока 2 изменяются так, что — когда RIT в следующий раз направит события от клавиатуры этому потоку — ввод с клавиатуры получит окно E. Этот вызов не заставит RIT направить клавиатурный ввод в очередь виртуального ввода потока 2. Так как фокус теперь сосредоточен на окне E потока 2, оно получает сообщение WM_SETFOCUS. Если окно E — кнопка, на нем появляется прямоугольник, обозначающий фокус, и в результате на экране могут появиться два окна с такими прямоугольниками (окна A и E). Сами понимаете, это вряд ли кому понравится. Поэтому вызывать SetFocus следует с большой осторожностью — чтобы не создавать подобных ситуаций. Вызов SetFocus безопасен, только если Ваш поток подключен к RIT. Кстати, если Вы переведете фокус на окно, которое, получив сообщение WM_SETFOCUS, показывает курсор ввода, не исключено одновременное появление на экране нескольких окон с таким курсором. Это тоже вряд ли кого обрадует. Когда фокус переводится с одного окна на другое обычным способом (например, щелчком окна), теряющее фокус окно получает сообщение WM_KILLFOCUS. Если окно, получающее фокус, принадлежит другому потоку, переменные локального состояния ввода потока, который владеет окном, теряющим фокус, обновляются так, чтобы показать: окон в фокусе нет. И вызов GetFocus возвращает при этом NULL, заставляя поток считать, что окон в фокусе нет.

HWND SetActiveWindow(HWND hwnd); активизирует в системе окно верхнего уровня и переводит на него фокус.Как и SetFocus, эта функция ничего не делает, если поток вызывает ее с описателем окна, созданного другим потоком.

Модель аппаратного ввода и локальное состояние ввода

Функцию SetActiveWindow дополняет GetActiveWindow:

HWND GetActiveWindow();

Она работает так же, как и GetFocus, но возвращает описатель активного окна, указанного в переменных локального состояния ввода вызывающего потока. Так что, если активное окно принадлежит другому потоку, функция возвращает NULL.

Есть и другие функции, влияющие на порядок размещения окон, их статус (активно или неактивно) и фокус:

BOOL BringWindowToTop(HWND hwnd);

BOOL SetWindowPos(HWND hwnd,HWND hwndInsertAfter,int x,int y,int cx,int cy,UINT fuFlags);

Обе эти функции работают одинаково (фактически BringWindowToTop вызывает SetWindowPos, передавая ей HWND_TOP во втором параметре). Когда поток, вызывающий любую из этих функций, не связан с RIT, они ничего не делают. В ином случае(когда поток связан с RIT) система активизирует указанное окно. Обратите внимание, что здесь не имеет значения, принадлежит ли это окно вызвавшему потоку. Окно становится активным, а к RIT подключается тот поток, который создал данное окно. Кроме того, значения переменных локального состояния ввода обоих потоков обновляются так, чтобы отразить эти изменения. Иногда потоку нужно вывести свое окно на передний план. Например, Вы запланировали какую-то встречу, используя Microsoft Outlook. Где-то за полчаса до назначенного времени Outlook выводит на экран диалоговое окно с напоминанием о встрече. Если поток Outlook не связан с RIT, это диалоговое окно появится под другими окнами, и Вы его не увидите. Поэтому нужен какой-то способ, который позволил бы привлекать внимание к определенному окну, даже если в данный момент пользователь работает с окном другого приложения.

Вот функция, которая выводит окно на передний план и подключает его поток к RIT:

BOOL SetForegroundWindow(HWND hwnd);

Одновременно система активизирует окно и переводит на него фокус. Функция, парная SetForegroundWindow:

HWND GetForegroundWindow();

Она возвращает описатель окна, находящегося сейчас на переднем плане.

В более ранних версиях Windows функция SetForegroundWindow срабатывала всегда. То есть поток, вызвавший ее, всегда мог перевести указанное окно на передний план (даже если оно было создано другим потоком). Однако разработчики стали злоупотреблять этой функцией и нагромождать окна друг на друга. Представьте, я пишу журнальную статью, и вдруг выскакивает окно с сообщением о завершении печати. Если бы я не смотрел на экран, то начал бы вводить текст не в свой документ, а в это окно. Еще больше раздражает, когда пытаешься выбрать команду в меню, а на экране появляется какое-то окно и закрывает меню.
ОПЕРАЦИИ С ОКНАМИ

Чтобы прекратить всю эту неразбериху, Microsoft сделала SetForegroundWindow чуть поумнее. В частности, эта функция срабатывает, только если вызывающий поток уже подключен к RIT или если поток, связанный с RIT в данный момент, не получал ввода на протяжении определенного периода (который задается функцией SystemParametersInfo и значением SPI_SETFOREGROUNDLOCKTIMEOUT). Кроме того, SetForegroundWindow терпит неудачу, когда активно какое-нибудь меню. Если SetForegroundWindow не удается переместить окно на передний план, то его кнопка на панели задач начинает мигать. Заметив это, пользователь будет в курсе, что окно требует его внимания. Чтобы выяснить, в чем дело, пользователю придется активизировать это окно вручную. Управлять режимом мигания окна позволяет функция SystemParametersInfo со значением SPI_SETFOREGROUNDFLASHCOUNT. Из-за такого поведения SetForegroundWindow в систему встроено несколько новых функций. Первая из них, AllowSetForegroundWindow, разрешает потоку указанного процесса успешно вызвать SetForegroundWindow, но только если и вызывающий ее поток может успешно вызвать SetForegroundWindow. Чтобы любой процесс мог выводить окно «поверх» остальных окон, открытых Вашим потоком, передайте в параметре dwProcessId значение ASFW_ANY (определенное как –1):

BOOL AllowSetForegroundWindow(DWORD dwProcessId);

Кроме того, можно полностью заблокировать работу SetForegroundWindow, вызвав LockSetForegroundWindow:

BOOL LockSetForegroundWindow(UINT uLockCode);

В параметре uLockCode она принимает либо LSFW_LOCK, либо LSFW_UNLOCK.

Данная функция вызывается системой, когда на экране активно какое-нибудь системное меню, — чтобы никакое окно не могло его закрыть. (Поскольку меню Start не является встроенным, то при его открытии Windows Explorer сам вызывает эти функции.)

Система автоматически снимает блокировку с функции SetForegroundWindow, когда пользователь нажимает клавишу Alt или активизирует какое-либо окно. Так что приложение не может навечно заблокировать SetForegroundWindow.

Другой аспект управления клавиатурой и локальным состоянием ввода связан с массивом синхронного состояния клавиш (synchronous key state array). Этот массив включается в переменные локального состояния ввода каждого потока. В то же время массив асинхронного состояния клавиш (asynchronous key state array) — только один, и он разделяется всеми потоками. Эти массивы отражают состояние всех клавиш на данный момент, и функция GetAsyncKeyState позволяет определить, нажата ли сейчас заданная клавиша:

SHORT GetAsyncKeyState(int nVirtKey);

Параметр nVirtKey задает код виртуальной клавиши, состояние которой нужно проверить. Старший бит результата определяет, нажата в данный момент клавиша (1) или нет (0). Я часто пользовался этой функцией, определяя при обработке сообщения, отпустил ли пользователь основную (обычно левую) кнопку мыши. Передав значение VK_LBUTTON, я ждал, когда обнулится старший бит. Заметьте, что GetAsyncKeyState всегда возвращает 0 (не нажата), если ее вызывает другой поток, а не тот, который создал окно, находящееся сейчас в фокусе ввода. Функция GetKeyState отличается от GetAsyncKeyState тем, что возвращает состояние клавиатуры на момент, когда из очереди потока извлечено последнее сообщение от клавиатуры: SHORT GetKeyState(int nVirtKey);
Управление курсором мыши

В концепцию локального состояния ввода входит и управление состоянием курсора мыши. Поскольку мышь, как и клавиатура, должна быть доступна всем потокам, Windows не позволяет какому-то одному потоку монопольно распоряжаться курсором мыши, изменяя его форму или ограничивая область его перемещения. Посмотрим, как система управляет этим курсором.

Один из аспектов управления курсором мыши заключается в его отображении или гашении. Если поток вызывает ShowCursor(FALSE), то система скрывает курсор, когда он оказывается на любом окне, созданном этим потоком, и показывает курсор всякий раз, когда он попадает в окно, созданное другим потоком. Другой аспект управления курсором мыши — возможность ограничить его перемещение каким-либо прямоугольным участком. Для этого надо вызвать функцию: BOOL ClipCursor(CONST RECT *prc);

Она ограничивает перемещение курсора мыши прямоугольником, на который указывает параметр prc. И опять система разрешает потоку ограничить перемещение курсора заданным прямоугольником. Но, когда возникает событие асинхронной активизации, т. е. когда пользователь переключается в окно другого приложения, нажимает клавиши Ctrl+Esc или же поток вызывает SetForegroundWindow, система снимает ограничения на передвижение курсора, позволяя свободно перемещать его по экрану.

И здесь мы подошли к концепции захвата мыши (mouse capture). «Захватывая» мышь (вызовом SetCapture), окно требует, чтобы все связанные с мышью сообщения RIT отправлял в очередь виртуального ввода вызывающего потока, а из нее — установившему захват окну до тех пор, пока программа не вызовет ReleaseCapture. Как и в предыдущих случаях, это тоже снижает отказоустойчивость системы, но без компромиссов, увы, не обойтись. Вызывая SetCapture, поток заставляет RIT помещать все сообщения от мыши в свою очередь виртуального ввода. При этом SetCapture соответственно настраивает переменные локального состояния ввода данного потока. Обычно приложение вызывает SetCapture, когда пользователь нажимает кнопку мыши. Но поток может вызвать эту функцию, даже если нажатия кнопки мыши не было. Если SetCapture вызывается при нажатой кнопке, захват действует для всей системы. Как только система определяет, что все кнопки мыши отпущены, RIT перестает направлять сообщения от мыши исключительно в очередь виртуального ввода данного потока. Вместо этого он передает сообщения в очередь ввода, связанную с окном, «поверх» которого курсор находится в данный момент. И это нормальное поведение системы, когда захват мыши не установлен. Однако для вызвавшего SetCapture потока ничего не меняется. Всякий раз, когда курсор оказывается на любом из окон, созданных установившим захват потоком, сообщения от мыши направляются в окно, применительно к которому этот захват и установлен. Иначе говоря, когда пользователь отпускает все кнопки мыши, захват осуществляется на уровне лишь данного потока, а не всей системы. Если пользователь попытается активизировать окно, созданное другим потоком, система автоматически отправит установившему захват потоку сообщения о нажатии и отжатии кнопок мыши. Затем она изменит переменные локального состояния ввода потока, чтобы отразить тот факт, что поток более не работает в режиме захвата. Словом, Microsoft считает, что захват мыши чаще всего применяется для выполнения таких операций, как щелчок и перетаскивание экранного объекта.
ОПЕРАЦИИ С ОКНАМИ

Последняя переменная локального состояния ввода, связанная с мышью, относится к форме курсора. Всякий раз, когда поток вызывает SetCursor для изменения формы курсора, переменные локального состояния ввода соответствующим образом обновляются. То есть переменные локального состояния ввода всегда запоминают последнюю форму курсора, установленную потоком.

Допустим, пользователь перемещает курсор мыши на окно Вашей программы, окно получает сообщение WM_SETCURSOR, и Вы вызываете SetCursor, чтобы преобразовать курсор в «песочные часы». Вызвав SetCursor, программа начинает выполнять какую-то длительную операцию. (Бесконечный цикл — лучший пример длительной операции. Шутка.) Далее пользователь перемещает курсор из окна Вашей программы в окно другого приложения, и это окно может изменить форму курсора. Для такого изменения переменные локального состояния ввода не нужны. Но переведем курсор обратно в то окно, поток которого по-прежнему занят обработкой. Системе «хочется» послать окну сообщения WM_SETCURSOR, но процедура этого окна не может выбрать их из очереди, так как его поток продолжает свою операцию. Тогда система определяет, какая форма была у курсора в прошлый раз (информация об этом содержится в переменных локального состояния ввода данного потока), и автоматически восстанавливает ее (в нашем примере — «песочные часы»). Теперь пользователю четко видно, что в этом окне работа еще не закончена и придется подождать.

7. Вывод информации в окно. Механизм перерисовки окна. Понятие области обновления окна. Операции с областью обновления окна.



Вывод информации в окно.

Разделение дисплея между прикладными программами осуществляется с помощью окон.

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

ОС не хранит графическую копию рабочей (пользовательской) части каждого окна. Она возлагает ответственность за правильное отображения окна на прикладную программу, посылая ей WM_PAINT каждый раз, когда все окно или его часть требует перерисовки.

При операциях с окнами система помечает разрушенные части окна, как подлежащие обновлению и помещает информацию о них, в область обновления - UPDATEREGION

На основании содержимого этой области и происходит восстановление. ОС посылает окну сообщение WM_PAINT всякий раз, когда область обновления окна оказывается не пустой и при условии, что в очереди сообщений приложения нет ни одного сообщения. При получении сообщения WM_PAINT, окно должно перерисовать лишь свою внутреннюю часть, называемую рабочей областью (ClientArea). Все остальные области окна перерисовывает ОС поWM_NCPAINT.

Для ускорения графического вывода Windows осуществляет отсечение. На экране перерисовываются лишь те области окна, которые действительно требуют обновления. Вывод за границами области отсечения игнорируется. Это дает право прикладной программе перерисовывать всю рабочую область в ответ на сообщение WM_PAINT. Лишний вывод ОС отсекает.

Инициатором сообщения WM_PAINT может выступать не только ОС, но и прикладная программа. Чтобы спровоцировать перерисовку окна необходимо вызвать функцию:

void InvalidateRect( HWND, //handleокна

RECT* //эта область требует перерисовки,

BOOL//нужно ли перед перерисовкой очищать область обновления

);

Очистка производится сообщением WM_ERASE_BACKGROUND.

После вызова функции InvalidateRect, окно не перерисовывается сразу (до WM_PAINT). Перерисовка произойдет только при опросе программой очереди сообщений. Когда перерисовка требуется немедленно, то вслед за InvalidateRect вызывается функция: void UpdateWindow(HWND);
Механизм перерисовки окна.

Перерисовка содержимого окна основана на получении контекста устройства, связанного с окном, рисование (вывод графических примитивов) в этом контексте устройства, и освобождение контекста устройства.

В разных случаях получение контекста устройства осуществляется разными функциями ОС. В ответ на сообщение WM_PAINT контекст устройства получается с помощью функции:

HDC BeginPaint(HWND,PAINTSTRUCT*);

//рисование

void EndPaint(HWND,PAINTSTRUCT*);

Между вызовами этих 2х функций заключаются вызовы графических примитивов (Rectangle(),Line()).

Функции BeginPain и EndPaint можно вызывать только на сообщение WM_PAINT.

Иногда бывает необходимо выполнить перерисовку окна в какой-то другой момент времени (по сообщению от таймера). В этом случае контекст дисплея получается с помощью функции:

HDC GetDC(HWND);

А освобождается функцией:

int ReleaseDC(HWND, HDC);

Вызовы этих функций обязательно должны быть сбалансированы, иначе возникнут сбои в работе ОС.

В оконном классе существует стиль, который назначает окну собственный контекст устройства (CS_OWNDC). По умолчанию этот флаг сброшен.

В результате, при получении контекста дисплея для окна, возвращается контекст рабочей области Desktop, на которой расположено окно, но с настроенными параметрами для окна.

Если флаг установлен – для окна создается свой собственный контекст дисплея. Функции получения контекста всего лишь возвращают его.

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



Отправка сообщения в окно.

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

Для начала рассмотрим некоторые базовые принципы. Один процесс в Windows может создать до 10 000 User-объектов различных типов — значков, курсоров, оконных классов, меню, таблиц клавиш-акселераторов и т. д. Когда поток из какого-либо процесса вызывает функцию, создающую один из этих объектов, последний переходит во владение процесса. Поэтому, если процесс завершается, не уничтожив данный объект явным образом, операционная система делает это за него. Однако два User-объекта (окна и ловушки) принадлежат только создавшему их потоку. И вновь, если поток создает окно или устанавливает ловушку, а потом завершается, операционная система автоматически уничтожает окно или удаляет ловушку. Этот принцип принадлежности окон и ловушек создавшему их потоку оказывает существенное влияние на механизм функционирования окон: поток, создавший окно, должен обрабатывать все его сообщения.Допустим, поток создал окно, а затем прекратил работу. Тогда его окно уже не получит сообщение WM_DESTROY или WM_NCDESTROY, потому что поток уже завершился и обрабатывать сообщения, посылаемые этому окну, больше некому.

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

Очередь сообщений потока Как я уже говорил, одна из главных целей Windows — предоставить всем приложениям отказоустойчивую среду. Для этого любой поток должен выполняться в такой среде, где он может считать себя единственным. Точнее, у каждого потока должны быть очереди сообщений, полностью независимые от других потоков. Кроме того, для каждого потока нужно смоделировать среду, позволяющую ему самостоятельно управлять фокусом ввода с клавиатуры, активизировать окна, захватывать мышь и т. д. Создавая какой-либо поток, система предполагает, что он не будет иметь отношения к поддержке пользовательского интерфейса. Это позволяет уменьшить объем выделяемых ему системных ресурсов. Но, как только поток обратится к той или иной GUI-функции (например, для проверки очереди сообщений или создания окна), система автоматически выделит ему дополнительные ресурсы, необходимые для выполнения задач, связанных с пользовательским интерфейсом. А если конкретнее, то система создает структуру THREADINFO и сопоставляет ее с этим потоком. Элементы этой структуры используются, чтобы обмануть поток — заставить его считать, будто он выполняется в среде, принадлежащей только ему. THREADINFO — это внутренняя (недокументированная) структура, идентифицирующая очередь асинхронных сообщений потока (posted-message queue), очередь синхронных сообщений потока (sent-message queue), очередь ответных сообщений (reply-message queue), очередь виртуального ввода (virtualized input queue) и флаги пробуждения (wake flags); она также включает ряд других переменных-членов, характеризующих локальное состояние ввода для данного потока. Структура THREADINFO — фундамент всей подсистемы передачи сообщений.


8. Принципы построения графической подсистемы ОС Windows. Понятие контекста устройства. Вывод графической информации на физическое устройство. Управление цветом. Палитры цветов. Графические инструменты. Рисование геометрических фигур

Взаимодействие приложения с GDI осуществляется при непременном участии еще одного посредника — так называемого контекста устройства.

Контекст устройства (device context)— это внутренняя структура данных, которая определяет набор графических объектов и ассоциированных с ними атрибутов, а также графических режимов, влияющих на вывод.

В следующем списке приведены основные графические объекты: Перо(реn) для рисования линий. Кисть(brush) для заполнения фона или заливки фигур. Растровое изображение(bitmap) для отображения в указанной области окна. Палитра (palette) для определения набора доступных цветов.Шрифт (font) для вывода текста. Регион(region) для отсечения области вывода.

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

Контекст устройства содержит много атрибутов, определяющих поведение функций GDI. Благодаря этому списки параметров функций GDI содержат только самую необходимую информацию, например начальные координаты или размеры графического объекта. Все остальное система извлекает из контекста устройства. Только если значения по умолчанию не устраивают разработчика, необходимо вызывать функции GDI, изменяющие значения соответствующих атрибутов.

Win32 API поддерживает следующие типы контекстов устройства: контекст дисплея; контекст принтера; контекст в памяти (совместимый контекст); метафайловый контекст; информационный контекст.

Управление цветом:



  • typedef DWORD COLORREF;

  • COLORREF color = RGB(255, 0, 0); // 0x000000FF

  • BYTE GetRValue(DWORD rgb), GetGValue, GetBValue.

  • COLOREF GetNearestColor(HDC, COLORREF);

  • COLORREF SetBkColor(HDC, COLORREF), GetBkColor.

  • LOGPALETTE, CreatePalette, SetPaletteEntries, GetPaletteEntries, SelectPalette, RealizePalette, DeleteObject.

Инструменты для рисования:

  • DC – 1 Bitmap , 1 Region, 1 Pen, 1 Brush, 1 Palette, 1 Font.

  • HPEN – LOGPEN, CreatePenIndirect, CreatePen.

  • HBRUSH – LOGBRUSH, CreateBrushIndirect, CreateBrush.

  • HFONT – LOGFONT, CreateFontIndirect, CreateFont.

  • HANDLE GetStockObject(int);

  • HANDLE SelectObject(HDC, HANDLE);

  • bool DeleteObject(HANDLE);

Вывод на физическое устройство:

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

В отличие от контекста отображения, контекст физического устройства не получается, а создается, для чего используется функция CreateDC :

HDC WINAPI CreateDC(

LPCSTR lpszDriver, // имя драйвера

LPCSTR lpszDevice, // имя устройства

LPCSTR lpszOutput, // имя файла или порта вывода

const void FAR* lpvInitData); // данные для инициализации

Рисование геометрических фигур

BOOL LineTo( HDC hdc, int nXEnd, int nYEnd );

BOOL MoveToEx(HDC hdc, int X, int Y, LPPOINT lpPoint/*old current position*/);

BOOL Rectangle(HDC hdc, int nLeftRect, int nTopRect, int nRightRect, int nBottomRect );

BOOL Ellipse(HDC hdc, int nLeftRect, int nTopRect, int nRightRect, int nBottomRect);

BOOL Polygon(

HDC hdc, // handle to DC

CONST POINT *lpPoints, // polygon vertices

int nCount // count of polygon vertices

);

BOOL PolyBezier(

HDC hdc, // handle to device context

CONST POINT* lppt, // endpoints and control points

DWORD cPoints // count of endpoints and control points

);



Графические инструменты

Pen – CreatePen,

Brush – CreateSolidBrush,

Font – CreateFont,

Холст – CreateCompatibleDC (?)
Управление цветом.

RGB –формат. Для кодирования цвета используются переменные с типом данных COLORREF, который определен через тип данных UINT.

COLORREF col;

Col = RGB(255,0,0); // в памяти по байтам: 0,B,G,R.

BYTE RedValue;

RedValue=GetRValue(color) //значения составляющих GetGValue, GetBValue

Позволяет иметь более 16 миллионов оттенков.

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

COLOREF GetNearestColor(HDC, COLORREF).

HDC поддерживает понятие фона и фонового цвета.

Некоторые функции могут осуществлять предварительную заливку области фоновым цветом (функции вывода текста).

Установка фонового цвета SetBkColor(…), получение GetBkColor().
Палитры цветов.

Для того чтобы создать палитру, приложение должно заполнить структуру LOGPALETTE , описывающую палитру, и массив структур PALETTEENTRY , определяющий содержимое палитры.

Структура LOGPALETTE и указатели на нее определены в файле windows.h:

typedef struct tagLOGPALETTE

{

WORD palVersion;

WORD palNumEntries;

PALETTEENTRY palPalEntry[1];

} LOGPALETTE;

В поле palNumEntries нужно записать размер палитры (количество элементов в массиве структур PALETTEENTRY).

Сразу после структуры LOGPALETTE в памяти должен следовать массив структур PALETTEENTRY, описывающих содержимое палитры:

typedef struct tagPALETTEENTRY

{

BYTE peRed;

BYTE peGreen;

BYTE peBlue;

BYTE peFlags;

} PALETTEENTRY;

typedef PALETTEENTRY FAR* LPPALETTEENTRY;

Поле peFlags определяет тип элемента палитры и может иметь значения NULL, PC_EXPLICIT , PC_NOCOLLAPSE и PC_RESERVED .
Если поле peFlags содержит значение NULL, в полях peRed, peGreen и peBlue находятся RGB-компоненты цвета. В процессе реализации логической палитры для этого элемента используется описанный нами ранее алгоритм.
Если поле peFlags содержит значение PC_EXPLICIT, младшее слово элемента палитры содержит индекс цвета в системной палитре.
Если поле peFlags содержит значение PC_NOCOLLAPSE, в процессе реализации логической палитры данный элемент будет отображаться только на свободную ячейку системной палитры. Если же свободных ячеек нет, используется обычный алгоритм реализации.
Последнее возможное значение для поля peFlags (PC_RESERVED) используется для анимации палитры с помощью функции AnimatePalette . Анимация палитры позволяет динамически вносить изменения в палитру. Такой элемент палитры после реализации не подвергается изменениям при реализации других палитр, он становится зарезервированным.
После подготовки структуры LOGPALETTE и массива структур PALETTEENTRY приложение может создать логическую палитру, вызвав функцию CreatePalette :

HPALETTE WINAPI CreatePalette(const LOGPALETTE FAR* lplgpl);
В качестве параметра следует передать функции указатель на заполненную структуру LOGPALETTE.
Функция возвращает идентификатор созданной палитры или NULL при ошибке.

Выбор палитры в контекст отображения
Созданная палитра перед использованием должна быть выбрана в контекст отображения. Выбор палитры выполняется функцией SelectPalette :

HPALETTE WINAPI SelectPalette(

HDC hdc, HPALETTE hpal, BOOL fPalBack);
Функция выбирает палитру hpal в контекст отображения hdc, возвращая в случае успеха идентификатор палитры, которая была выбрана в контекст отображения раньше. При ошибке возвращается значение NULL.
Указав для параметра fPalBack значение TRUE, вы можете заставить GDI в процессе реализации палитры использовать алгоритм, соответствующий фоновому окну. Если же этот параметр равен FALSE, используется алгоритм для активного окна (т. е. все ячейки системной палитры, кроме зарезервированных для статических цветов, отмечаются как свободные и используются для реализации палитры).

Реализация палитры
Процедура реализации палитры заключается в вызове функции RealizePalette :

UINT WINAPI RealizePalette(HDC hdc);
Возвращаемое значение равно количеству цветов логической палитры, которое удалось отобразить в системную палитру.

Рисование с использованием палитры
Итак, вы создали палитру, выбрали ее в контекст отображения и реализовали. Теперь приложение может пользоваться цветами из созданной палитры. Но как?
Если приложению нужно создать перо или кисть, определить цвет текста функцией SetTextColor или закрасить область функцией FloofFill (т. е. вызвать одну из функций, которой в качестве параметра передается переменная типа COLORREF, содержащая цвет), вы можете вместо макрокоманды RGB воспользоваться одной из следующих макрокоманд:

#define PALETTEINDEX (i) \

((COLORREF)(0x01000000L | (DWORD)(WORD)(i)))

#define PALETTERGB (r,g,b) (0x02000000L | RGB(r,g,b))
Макрокоманда PALETTEINDEX позволяет указать вместо отдельных компонент цвета индекс в логической палитре, соответствующий нужному цвету.
Макрокоманда PALETTERGB имеет параметры, аналогичные знакомой вам макрокоманде RGB, однако работает по-другому.
Если цвет определен с помощью макрокоманды RGB, в режиме низкого и среднего цветового разрешения для рисования будет использован ближайший к указанному статический цвет. В режиме высокого цветового разрешения полученный цвет будет полностью соответствовать запрошенному.
Если же для определения цвета использована макрокоманда PALETTERGB, GDI просмотрит системную палитру и подберет из нее цвет, наилучшим образом соответствующий указанному в параметрах макрокоманды.
В любом случае при работе с палитрой GDI не использует для удовлетворения запроса из логической палитры смешанные цвета.
Какой из двух макрокоманд лучше пользоваться?
На этот вопрос нет однозначного ответа.
Макрокоманда PALETTEINDEX работает быстрее, однако с ее помощью можно использовать только те цвета, которые есть в системной палитре. Если ваше приложение будет работать в режиме True Color, лучшего эффекта можно добиться при использовании макрокоманды PALETTERGB, так как для режимов высокого цветового разрешения эта макрокоманда обеспечит более точное цветовое соответствие.

Удаление палитры
Так как палитра является объектом, принадлежащим GDI, а не создавшему ее приложению, после использования палитры приложение должно обязательно ее удалить. Для удаления логической палитры лучше всего воспользоваться макрокомандой DeletePalette , определенной в файле windowsx.h:

#define DeletePalette(hpal) \

DeleteObject((HGDIOBJ)(HPALETTE)(hpal))
В качестве параметра этой макрокоманде следует передать идентификатор удаляемой палитры.
Учтите, что как и любой другой объект GDI, нельзя удалять палитру, выбранную в контекст отображения. Перед удалением следует выбрать старую палитру, вызвав функцию SelectPalette.
9. Растровые изображения. Виды растровых изображений. Значки и курсоры. Способ вывода растровых изображений с эффектом прозрачного фона. Аппаратно-зависимые и аппаратно-независимые растровые изображения. Операции с растровыми изображениями. Вывод растровых изображений.
Типы растровых изображений:

  • Bitmap – базовый формат растрового изображения.

  • Icon – значок: AND-маска и XOR-маска.

  • Cursor – курсор: две маски и точка касания – Hot Spot.

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

На экране значки могут иметь не прямоугольную форму, что достигается за счет описания значка двумя точечными рисунками:

  • AND-mask. Монохромная.

  • XOR-mask. Цветная.

При выводе значков, ОС комбинирует маски по следующему правилу:

Экран = (Экран ANDМонохромная маска)XORЦветная маска.

Накладывая AND-mask, ОС вырезает на экране пустую область с заданным контуром. ВAND-mask фигура кодируется с помощью 0, а прозрачный фон с помощью 1. После вывода AND-mask ОС накладывает XOR-mask, содержащую изображения фигур. Изображение фигуры является цветным. На диске значки сохраняются в *.ico формате. В ОС существует несколько форматов значков, которые отличаются по размеру и цвету (16х16, 32х32, 16х32, 64х64).
Курсоры. Указатели мыши. Небольшой образ. По своему представлению в файле и памяти курсор напоминает значки, но существуют некоторые значки. Курсоры могут быть размером 16х16 и 32х32. Важным существенным отличием является наличие в нем горячей точки (hotspot), которая ассоциируется с позицией указателя мыши на экране. *.CUR.

Точечные рисунки – это изображение, представление графической информации, ориентированное на матричное устройство вывода. Точечный рисунок состоит из пикселей, организованных в матрицу. ОС позволяет использовать точечные рисунки двух видов:

1)Аппаратно-зависимые. Device Dependent Bitmpap. Рассчитаны только на определенный

тип графического адаптера или принтера. Их точки находятся в прямом соответствии с пикселями экрана или другой поверхности отображения.

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

При хранении на диске используется аппаратно-независимый формат - BMP,DIB.

2)Аппаратно-независимые. Device Independent Bitmpap. Формат хранения аппаратно-независимых точечных рисунков не зависит от используемой аппаратуры. Здесь информация о цвете и самом изображении хранится раздельно.

Цвета собраны в таблицу, а точки изображения кодируют номера цветов таблицы. Под каждую точку изображения может отводиться 1,4,8,16,24 битов изображения. Они могут храниться на диске в сжатом виде.

Для сжатия применяется алгоритм RunLengthEncoding(RLE). Разжатие производится автоматически. Недостаток: обеспечивается более низкая скорость работы.

Вывод растрового изображения с эффектом прозрачного фона: AND-маска – монохромная. Фигура кодируется нулем, прозрачный фон – единицей. Вырезает на экране «черную дыру» там, где должна быть фигура.

Растровая операция – способ комбинирования пикселей исходного изображения с пикселями поверхности отображения целевого контекста устройства. При масштабировании в сторону сжатия некоторые цвета могут пропадать. При растяжении, таких проблем не существует. При сжатии возможно 3 способа сжатия.

РИСОВАНИЕ ТОЧЕЧНЫХ РИСУНКОВ ПРОГРАММНЫМ СПОСОБОМ

После создания контекста виртуального устройства CreateCompatibleDC() поверхность отображения в этом контексте устройства имеет нулевые размеры. Если в полученном контексте устройства необходимо нарисовать изображение, то сначала необходимо создать в контексте устройства поверхность отображения (точечный рисунок). И установить его в контексте устройства. Для создания рисунка:

HBITMAP CreateCompatibleBitmap(HDC hdc, //handletoDC

int nWidth, // width of bitmap, in pixels

int nHeight// height of bitmap, in pixels

);

После вызова этой функции нужно не забыть вызвать SelectObject. Созданный точечный рисунок –DDB. Часто при работе с изображениями (растровыми) применяется:

BOOL PatBlt(

HDC hdc, // handle to DC

int nXLeft, // x-coord of upper-left rectangle corner

int nYLeft, // y-coord of upper-left rectangle corner

int nWidth, // width of rectangle

int nHeight, // height of rectangle

DWORD dwRop// raster operation code

);

Она служит для заливки битового образа некоторым цветом. Цвет определяется цветом кисти.

XOR-маска – цветная. Фигура кодируется цветом, прозрачный фон – нулем. Врезает на экране фигуру на месте «черной дыры».

LoadIcon(),LoadCursor(),LoadBitmap() – не загружают изображение из файла. Изображения из файла загружает функция LoadImage().В результате загрузки изображения, ОС возвращает дескриптор созданного в памяти объекта. После использования объект должен быть удален функцией:

Функция HBITMAP LoadBitmap(HINSTANCE hInstance, LPCTSTR lpBitmapName) загpужает поименованный pесуpс каpты бит.

Функция HANDLE LoadImage(HINSTANCE hinst, LPCTSTR lpszName, UINT uType, int cxDesired, int cyDesired, UINT fuLoad) загружает значок, курсор, "живой " курсор или точечный рисунок.

Функция HANDLE CopyImage(HANDLE hImage, UINT uType, int cxDesired, int cyDesired, UINT fuFlags) создает новое изображение (значок, курсор, или точечный рисунок) и копирует атрибуты указанного изображения в новое изображение. Если необходимо, функция растягивает биты, чтобы размер нового изображения подогнать под требуемый.

Функция BOOL DrawIcon(HDC hDC, int X, int Y, HICON hIcon) делает иконой в области клиента окна определенного контекста устройства.

Вывести Bitmap вызовом одной функции нельзя. Необходимо создать дополнительное устройство в памяти – memory DC, выбрать в нем Bitmap в качестве поверхности рисования, выполнить перенос изображения из memory DC в window DC.

Вывод растрового изображения:

void ShowBitmap(HWND hWnd, HBITMAP hBmp)

{

HDC winDC = GetDC(hWnd);

HDC memDC = CreateCompatibleDC(winDC);

HBITMAP oldBmp = SelectObject(memDC, hBmp);

BitBlt(winDC, 10, 10, 64, 64, memDC, 0, 0, SRCCOPY);

SelectObject(memDC, oldBmp);

DeleteDC(memDC);

ReleaseDC(hWnd, winDC);

}

10. Библиотека работы с двумерной графикой Direct2D. Инициализация библиотеки. Фабрика графических объектов библиотеки Direct2D. Вывод графики средствами библиотеки Direct2D.

Для удовлетворения новых потребностей рынка IT-техноолгий корпорация Microsoft летом 2009 года разработала на базе технологии DirectX 10 набор библиотек для работы и вывода двумерной графики — Direct2D.

Direct2D включает в себя 4 заголовочных файла и одну библиотеку:

. d2d1.h — содержит объявления основных функций Direct2D API на языке С и С++;

. d2d1helper.h — содержит вспомогательный структуры, классы, функции;

. d2dbasetypes.h — определяет основные примитивы Direct2D, включен в d2d1.h;

. d2derr.h — определяет коды ошибок Direct2D. Включен в d2d1.h;

. d2d1.lib — двоичная библиотека, содержащая все объявленные в заголовочных файлах функции.

Как и DirectX, Direct2D построен на модели COM. Основным объектом, который предоставляет интерфейсы для создания других объектов, является Фабрика Direct2D, или объект класса ID2D1Factory. Он имеет в своем составе методы типа CreateResourse, которые позволяют создавать объекты более специфических типов.

Все объекты (ресурсы) в Direct2D делятся на два больших типа — устройство-зависимые (device-dependent) и устройство-независимые (device-independent). Устройство-зависимые объекты ассоциируются с конкретным устройством вывода и должны быть реинициализированы каждый раз, когда устройство, с которым они ассоциируются, требует реинициализации. Устройство-независимые ресурсы существуют без привязки к какому-либо устройству и уничтожаются в конце жизненного цикла программы или по желанию программиста. Классификация и основные примеры ресурсов приведены на рисунке 1.



Рисунок 1 — Классификация ресурсов Direct2D.



Объекты класса ID2DRenderTarget — это устройство-зависимые объекты, которые ассоциируются с конкретным устройством вывода. Это может быть конкретное окно приложения, битовый образ (изображение) или другое устройство. ID2DRenderTarget имеет в своем составе методы BeginDraw() и EndDraw(), между которыми выполняются все операции вывода графической информации на устройство вывода.



В качестве инструмента вывода используются объекты класса ID2DBrush, которые задают цвет и другие парамеиры выводимых объектов (в т.ч. градиентные заливки). И ID2DBrush, и ID2DRenderTarget — устройство-зависимые ресурсы и будучи созданными однажды для конкретного устройства, могут применяться лишь к нему, и должны быть уничтожены всякий раз, когда уничтожается их устройство.

Объект класса ID2DGeometry — устройство-независимый ресурс. Будучи созданным однажды, он может быть использован любым объектов ID2DRenderTarget. ID2DGeometry задает двумерную форму, интерполированную треугольниками.



После того, как работа с ресурсами и объектами завершена, они должны быть уничтожены функцией Release(), которая унаследована ими от базового COM-объекта. При этом по возможности стоит избегать частого создания и освобождения ресурсов, так как этот процесс требует достаточно много ресурсов процессора. Общая схема работы с Direct2D представлена на рисунке 2.





Рисунок 2 — Общая схема использования компонент Direct2D

11. Вывод текста в ОС Windows. Понятие шрифта. Характеристики шрифта. Понятия физического и логического шрифта. Операции с физическими шрифтами. Операции с логическими шрифтами. Параметры ширины и высоты логического шрифта.

1) BOOL TextOut

Функция TextOut записывает строку символов в заданном месте, используя текущий выбранный шрифт, цвет фона и цвет текста.

Если функция завершается с ошибкой, величина возвращаемого значения – ноль, иначе – не ноль.

SetTextAlign - устанавливает флажки выравнивания текста для заданного контекста устройства.

GetTextAlign - извлекает настройки выравнивания текста для заданного контекста устройства.

SetTextColor - функция устанавливает цвет текста для заданного контекста устройства.

GetTextColor - функция возвращает цвет текста для заданного контекста устройства.

2) BOOL ExtTextOut

Выводит текст используя текущий выбранный шрифт, цвета фона и текста.

Если строка рисуется, возвращаемое значение является отличным от нуля.

3) BOOL PolyTextOut

рисует несколько строк, используя шрифт и цвета текста, в настоящее время выбранные в заданном контексте устройства. При ошибке – 0, иначе – не ноль.

4)LONG TabbedTextOut

Пишет строку символов в заданном месте, разворачивая позиции табуляции в значения, указанные в массиве позиций табуляции. Текст пишется в текущем выбранном шрифте, цвете фона и цвете текста.

Если функция завершается ошибкой, возвращаемое значение – нуль.

5) int DrawText

Рисует отформатированный текст в заданном прямоугольнике. Если функция завершается успешно, возвращаемое значение - высота текста в логических единицах измерения.

6) int DrawTextEx

Рисует форматированный текст в заданном прямоугольнике.

Если функция завершается с ошибкой, величина возвращаемого значения - ноль.

Шрифт – множество символов со сходными размерами и начертанием контуров. Семейство шрифта – набор шрифтов со сходной шириной символов и гарнитурой:

Шрифты в программе:

Физические – устанавливаемые в операционную систему, файлы.

Логические – запрашиваемые программой у операционной системы, LOGFONT.

Физический шрифт

Установка шрифта:

Скопировать файл шрифта в C:\Windows\Fonts.

Вызвать int AddFontResource(LPCTSTR lpszFilename).

Вызвать SendMessage с кодом WM_FONTCHANGE.

Удаление шрифта:

Вызвать bool RemoveFontResource(LPCTSTR lpszFilename).

Удалить файл шрифта из C:\Windows\Fonts.

Вызвать SendMessage с кодом WM_FONTCHANGE.

Логический шрифт

Создание логического шрифта (LOGFONT):

CreateFontIndirect / CreateFont,

SelectObject,

DeleteObject.

В ширине шрифта различают 3 вида размеров:

Размер А – отступ слева перед написанием символа

Размер В – ширина символа

Размер С – отступ справа от символа

Отступы А и С могут быть отрицательными (когда символ пишется курсивом). Получить значения А,В,С можно с помощью функций:

GetCharABCWidth– только дляTrueTypeFont

GetCharABCWidthFloat

GetCharWidth32

GetCharWidthFloat

Функции для высоты и ширины:

GetTextExtentPoint32

TabbedTextExtent– если есть табуляция. Функция для расчета переноса слов.

GetTextExtentPoint

Ascent – параметр шрифта, называющийся подъемом.

Descent – спуск.

LineGap – пропуск между строками.

Для того, что бы получить все параметры шрифта, необходимо обратиться к одной из двух функций, которые возвращают параметры физического шрифта. Существует 2 уровня представления шрифта:

Высокоуровневый логический – соответствует структура LOGFONT

Низкоуровневый физический – структуры TEXTMETRICS, OUTLINETEXTMETRICS.

Префиксы:

Tm – TEXTMETRICS

Otm – OUTLINETEXTMETRICS

Физические параметры шрифта можно получить с помощью:

GetTextMetrics

GetOutLineTextMetrics

Параметры подъем и спуск шрифта имеют различный смысл:

Существуют пропорциональные и непропорциональные шрифты. Шрифт, который мы создаем – логический.

Физический шрифт – это шрифт, расположенный в каталоге Fonts.

ОС на базе физического создает необходимый нам логический.

Существуют 2 типа шрифтов:

Растровые (масштабируемые шрифты TrueType)

Векторные (в чистом виде в Windows таких нет)

Масштабируемые шрифты TrueType описываются сплайнами 3 порядка.PostScript– описываются сплайнами 2ого порядка.

Сплайны третьего порядка позволяют более тонко управлять шрифтом.



Поделитесь с Вашими друзьями:
1   2   3   4   5   6   7   8   9   ...   16


База данных защищена авторским правом ©grazit.ru 2019
обратиться к администрации

войти | регистрация
    Главная страница


загрузить материал