![]() |
|||||||||||||||
|
Чудо-курсорАвтор: AdraxПодсистема Win32 базируется на сервере оконной подсистемы csrss.exe и драйвере win32k.sys и представляет собой "святую троицу": kernel32.dll, user32.dll и gdi32.dll. Kernel32.dll обеспечивает базовые функции работы с памятью и дисками. User32.dll нужен для создания окон. Gdi32.dll обеспечивает графический интерфейс GDIВ общем, как-то раз на форуме Исходников.Ру загорелся я идеей создать курсор в виде перекрестья во весь экран. Основная загвоздка - Windows ограничивает размер курсора максимум 32x32, потому, чтобы получить такое перекрестье, надо было написать приложение, отслеживающее позицию курсора и проводящее через эту точку две линии крест-накрест. Остальные форумчане отнеслись к этой идее с пониманием, а Deus даже создал рабочий прототип. Сегодня мы с вами сами сварганим такой курсорчик. Но сначала правильно будет сказать пару слов о GDI GDI расшифровывается как Graphics Device Interface, и представляет собой интерфейс, который Windows использует для рисования 2D графики: точек, линий и даже букв. Этот интерфейс универсален для любых устройств: чтобы вывести изображение на экран, принтер, плоттер, используются одни и те же функции. Программа получает контекст устройства, рисует на нём средствами GDI, а потом уже драйвер устройства получает это изображение и пытается его вывести, исходя из своих возможностей (ведь нарисованная на контексте устройства картинка может иметь больше цветовых оттенков, чем может вывести девайс, и т.д.). Таким образом, контекст устройства служит нам как бы альбомным листом Windows понимает 4 типа контекста устройств: контекст дисплея, контекст принтера, контекст памяти и контекст информации. Насчёт первых двух, надеюсь, всё понятно, контекст памяти позволяет рисовать на загруженной в память картинке, а контекст информации нужен, как написано, "для восстановления данных устройства"
Разумеется, чтобы рисовать, нужны инструменты. GDI предоставляет нам широкий выбор: это разнообразные перья, кисти, текстовые шрифты и битовые изображения Чудо-курсор я буду реализовывать средствами GDI и GDI+, а это значит, что я абсолютно не гарантирую работоспособности этой программы на до-XP'шных версиях Windows Итак, начнём с основ: попытаемся понять, как же рисовать в окне. Чтобы в окне можно было рисовать, оно должно иметь так называемую клиентскую область. Если таковая имеется, то при обновлении окна Windows пошлёт ему сообщение WM_PAINT. В ответ на это сообщение принято вызывать функцию BeginPaint, которая делает две важные вещи: получает хэндл контекста устройства, на котором мы будем рисовать, и структуру PAINTSTRUCT, содержащую координаты invalid rectangle - той части окна, что нуждается в перерисовке. Получив хэндл контекста устройства, можно начинать свои художества, не забыв в конце вызвать функцию EndPaint. Таков традиционный путь Можно и иначе: не вызывать BeginPaint, а самостоятельно получить хэндл контекста устройства функцией GetDC. Главное - не забыть его освободить потом, вызвав ReleaseDC Ниже я приведу описуху вышеперечисленных функций из MSDN: HDC GetDC(HWND hWnd); // хэндл окна HDC BeginPaint( HWND hwnd, // хэндл окна LPPAINTSTRUCT lpPaint // указатель на PAINTSTRUCT ); Обе эти функции возвращают в eax хэндл контекста устройства. Поработав с ним, необходимо его освободить: int ReleaseDC( HWND hWnd, // хэндл окна HDC hDC // хэндл контекста устройства ); BOOL EndPaint( HWND hWnd, // хэндл окна CONST PAINTSTRUCT *lpPaint // указатель на PAINTSTRUCT ); И ReleaseDC, и EndPaint возвращают 1, если всё хорошо, и 0, если всё плохо Я предлагаю подойти к реализации нашего чудо-курсора постепенно, по пути осваивая премудрости GDI. Поэтому разобьём нашу статью на пункты:
1.Рисуем в окне линиюЧтобы нарисовать линию, мы должны определить её начало и конец. Начальная точка устанавливается функцией MoveToEx, координаты конечной - передаются функции LineTo. Вот их прототипы из MSDN:BOOL MoveToEx( HDC hdc, // хэндл контекста устройства int X, // абсцисса точки int Y, // ордината точки LPPOINT lpPoint // указатель на переменную типа POINT для хранения прежних координат ); BOOL LineTo( HDC hdc, // хэндл контекста устройства int nXEnd, // абсцисса точки int nYEnd // ордината точки ); Несколько слов про структуру POINT: она состоит из двух двойных слов, первое из которых хранит абсциссу точки, а второе - ординату. Функция MoveToEx, выделяя новую точку, сохраняет координаты прежней выделенной точки в переменную типа POINT, но нам прежние координаты до лампочки, поэтому вместо указателя на переменную мы подсунем функции NULL Для рисования линии нужно перо. Перо в GDI создаётся функцией CreatePen, её прототип: HPEN CreatePen( int fnPenStyle, // стиль пера int nWidth, // ширина пера в пикселях COLORREF crColor // цвет пера );
Стилей у пера несколько: PS_SOLID - сплошная линия, PS_DASH - пунктирная линия, PS_DOT - линия рисуется точками, PS_DASHDOT - штрихпунктирная линия, PS_DASHDOTDOT - пунктир с двумя точками, PS_NULL - невидимая линия, PS_INSIDEFRAME - тоже сплошная, но какая-то особенная... Создав перо, нужно его взять функцией SelectObject, принимающей в качестве параметров хэндлы контекста устройства и пера. Возвращает эта функция хэндл прежнего пера, который просто так выкинуть нельзя - нужно его сохранить. Порисовав, нужно освободить перо, вызвав функцию DeleteObject и передав ей хэндл пера Как видите, при работе с GDI приходится соблюдать неписаное правило "взял - положь!": взяли контекст - освободили контекст, взяли перо - освободили перо и т.д. В противном случае, если забывать освобождать объекты, мы получим т.н. "утечку", которая выразится в огромном потреблении памяти и может привести к краху нашего приложения Ладненько, правило уяснили, апишки выучили... Let's code now!:
Всё просто, не правда ли? Переходим к следующему этапу... 2.Поиграем с прозрачностьюВо всех предыдущих статьях мы вызывали функцию CreateWindowEx с первым параметром, равным 0. Сегодня мы подставим на место этого нуля расширенный стиль окна WS_EX_LAYERED. Обычный стиль окна мы также изменим - на WS_POPUP+WS_VISIBLE, чтобы у окошка не было элементов управления в заголовкеДо окна, имеющего расширенный стиль WS_EX_LAYERED, можно достучаться функцией SetLayeredWindowAttributes, имеющей следующий прототип: SetLayeredWindowAttributes( HWND hwnd, // хэндл окна COLORREF crKey, // цвет прозрачности BYTE bAlpha, // степень прозрачности DWORD dwFlags // флаги ); Флагов у этой функции два: LWA_ALPHA и LWA_COLORKEY. Если мы используем LWA_ALPHA, то цвет прозрачности для нас значения не имеет: мы будем управлять прозрачностью всего окна - важна лишь степень: от 00h до FFh, причём, чем больше - тем непрозрачнее. Для примера смастерим такой код:
Поиграйте со значениями степени прозрачности и убедитесь в том, что применение флага LWA_ALPHA позволяет управлять прозрачностью целого окна. Но нам это не подходит: нам нужно сделать прозрачный фон и на нём - видимую линию. Поэтому мы применим флаг LWA_COLORKEY, который позволяет "прозрачнить" определённый цвет. Вот только нам придётся изменить параметры структуры wc типа WNDCLASS: мы используем как цвет окна COLOR_BTNFACE+1 - неоднородный, градиентный. А для "запрозрачнивания" нужен однородный оттенок. Поэтому код перепишем так (привожу только начало):
Вот, уже ближе к тому, что мы хотели. Осталось только растянуть окно во весь экран и поставить его поверх остальных... Но как мы тогда будем работать мышью с нижележащими окнами? Не волнуйтесь, расширенные стили окна позволяют легко решить этот вопрос 3.КурсорИтак, мы научились делать окна прозрачными и рисовать на них. Однако, для того, чтобы отвечать поставленной задаче, наше окно должно быть ещё и "прозрачным для мыши", т.е. "пропускать" мышиные клики до нижележащих окон. Это элементарно реализуется путём использования расширенных стилей окна. Поэтому задумаемся над другим вопросом: как отслеживать курсор?Для начала нужно иметь представление о самой системе экранных координат. В своей программе я использовал следующие имена переменных: xx и yy - для абсциссы и ординаты текущего положения курсора, shirina и vysota - для ширины и высоты экрана соответственно. Система экранных координат будет выглядеть следующим образом:
Чтобы узнать ширину и высоту экрана, мы вызовем функцию GetSystemMetrics с параметрами SM_CXSCREEN и SM_CYSCREEN соответственно. Для определения текущей позиции курсора заюзаем GetCursorPos, передав ей указатель на переменную типа POINT, куда функция запишет координаты. Отслеживать перемещение курсора будем по возникающему сообщению WM_MOUSEMOVE. Let's code!
Картина впечатляющая: перекрестье линий верно следует за курсором, однако линии не стираются, а потому очень скоро ничего не будет видно:
4.ЛастикЧто же нам делать? Перья в инструментарии GDI есть, а вот ластиков не придумали. Поэтому придётся обратиться к битовой арифметике: 1 xor 1 = 0. Значит, нам нужно перо, реализующее операцию "исключающее ИЛИ". Такое перо создаётся функцией SetROP2, миксующей цвета фона и пера:int SetROP2( HDC hdc, // хэндл контекста устройства int fnDrawMode // режим рисования ); Режимов куча! Перечислю: R2_BLACK - без комментариев, R2_COPYPEN - повторяет цвет пера, R2_MASKNOTPEN - микс общих оттенков фона и инвертированного цвета пера, R2_MASKPEN - микс общих оттенков пера и фона, R2_MASKPENNOT - микс общих оттенков цвета пера и инвертированного фона, R2_MERGENOTPEN - микс цвета фона и инвертированного цвета пера, R2_MERGEPEN - цвет пера + цвет фона, R2_MERGEPENNOT - цвет пера + инвертированный фон, R2_NOP - цвет не меняется (невидимая линия?), R2_NOT - инвертированный цвет фона, R2_NOTCOPYPEN - инвертированный цвет пера, R2_NOTMASKPEN - инвертированный R2_MASKPEN, R2_NOTMERGEPEN - инвертированный R2_MERGEPEN, R2_NOTXORPEN - инвертированный R2_XORPEN, R2_WHITE - без комментариев, R2_XORPEN - микс оттенков, различных у пера и фона Мне приглянулся режим №5. Его я и буду использовать в своей программе. Суть такова: провели пером один раз - линия появилась на экране, провели по тому же месту ещё раз - линия исчезла. Вполне удобно и отвечает нашей задумке. Воплотим же идею в коде! По сравнению с предыдущим исходником изменился лишь обработчик сообщения WM_PAINT, его я здесь и привожу:
Вау! То, что нужно! Но огорчает одна маленькая деталь: мерцание перекрестья, причём частота мерцания меняется в зависимости от загруженности процессора (читатели раздела Windows уже, наверное, догадались, что всё дело в функции Sleep, отдающей квант процессорного времени). Видимо, стирать линии надо как-то иначе... Есть у меня и другой вариант обработчика сообщения WM_PAINT:
Этот вариант ещё лучше! Никакого мерцания, всё замечательно... кроме 70-100%-й загрузки проца:(
Скорее всего, это связано с тем, что после обработчика WM_PAINT у меня сразу же идёт обработчик WM_MOUSEMOVE, и программа, исполняясь линейно, лишний раз перерисовывает окно. Я пытался с этим бороться, но тупая вставка jmp finish в конец обработчика или смена их взаимного расположения приводят к неработоспособности программы, а вызов Sleep приводит к мерцанию перекрестья, что тоже нехорошо В общем, так я до конца и не реализовал свою идею. Задача написания чудо-курсора остаётся нерешённой. Я надеюсь, читателям эта тема покажется интересной, и они помогут мне сбалансировать и оптимизировать код этой маленькой программки. Если вам удастся улучшить её - пишите мне, и мы вместе напишем следующую статью по этой теме. А в том, что статьи будут, не сомневайтесь - на примере этой маленькой программки я хочу показать вам множество интереснейших вещей, добавляя в исходный код новые фишки До новых встреч на страницах сайта RFTeam!! Добавлено Снизить нагрузку на процессор поможет invoke Sleep,1 в самом начале обработчика сообщения WM_MOUSEMOVE. Читатели, знакомые с основами вытесняющей многозадачности Windows, уже поняли, что таким образом мы будем осуществлять немедленное переключение контекста, передавая право на исполнение другому процессу. Этот способ, конечно, не лищён недостатков, но помогает существенно снизить ресурсоёмкость нашего приложения, не вызывая заметного мерцания перекрестья | ||||||||||||||
| |||||||||||||||