Главная | Новости | Статьи | Книги | Ссылки |
Хранитель экрана, который мы сегодня вместе с вами сделаем, будет работать в фоновом режиме, при этом, естественно, он не должен мешать работе других приложений и потреблять минимум ресурсов. Технически хранитель экрана является обычным исполняемым файлом Windows (*.exe) полностью управляемым сообщениями ОС, но переименованным в *.scr.
При разработке будет использоваться среда Microsoft Visual C++, так как автор статьи довольно долго с ней работает. Вместе с тем, для нашей задачи вполне можно было использовать любой другой компилятор, например, Borland C++ Builder или Watcom C++. Для уменьшения объема исполняемого файла в описанной программе не используется библиотека высокого уровня MFC или CLX(VCL), вся работа выполняется только средствами Win32 API. Также не используются объектно-ориентированные расширения языка. В результате размер программы удается уменьшить приблизительно до 35 Kб.
Для создания хранителей экрана в комплект Visual C++ входит заголовочный файл scrnsave.h (D:\Program Files\Microsoft Visual Studio\VC98\Include\scrnsave.h), в котором находятся определения всех констант и функций, необходимых для работы screensaver'а в среде Windows 9x/NT, а также статическая библиотека scrnsave.lib. Точка входа в программу (функция WinMain) находится в самой scrnsave.lib, что очень сильно облегчает нам жизнь. Наш хранитель пишется ориентировочно для Windows NT (другого у меня нету), хотя должен работать на всех платформах. Различие состоит в том, что для Windows 9x приходится писать еще одну функцию, отвечающую за смену пароля. В NT и выше эту роль выполняет системный процесс Winlogon. Если ключ HKEY_CURRENT_USER\Control Panel\Desktop\ScreenSaverIsSecure в системном реестре Windows не равен нулю, то Winlogon будет запрашивать пароль перед выходом из скринсейвера. Хотя без этой функции можно и обойтись.
Итак, приступим к написанию самого кода. Загружаем среду Visual C++ (я использую 6.0). Создаем проект Win32 Application (File > New > Projects > Win32 Application). В Project Name вводим ssaver, в Location выбираем папку, где будет хранится наш проект, у меня это D:\PROJECTS\). Жмем OK. Появится окошко Win32 Application - Step 1 of 1. Оставляем все без изменений, жмем Finish. Имеем пустой проект. Добавляем новый файл исходного кода в проект (меню File > New > Files > C++ source files). В File name пишем ssaver, жмем ОК. Итак, имеем файл ssaver.cpp. Перед Вами откроется пустое окно, в котором, собственно, и будет писаться программа. Настраиваем среду. В меню Build >Set active configuration выбираем ssaver - Win32 Release, ОК. Подключаем библиотеку scrnsave.lib к проекту: меню Project > Settings, вкладка Link. Здесь в строке Object library/modules перечислены библиотеки, подключаемые по умолчанию к Вашему проекту, нам надо лишь дописать scrnsave.lib ( Рис. 1).
Для работы хранителя необходимо написать всего 3 функции (фактически только одну):
Итак, в новом ранее созданном окне пишем следующий код:
#include <windows.h> #include <scrnsave.h> LRESULT WINAPI ScreenSaverProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { return 0; } BOOL WINAPI ScreenSaverConfigureDialog (HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { return true; } BOOL WINAPI RegisterDialogClasses (HANDLE hInst) { return true; }
В первых двух строках подключаются заголовочные файлы с прототипами функций (windows.h - объявления Win32 API, scrnsave.h - функции для работы с хранителем экрана). Далее объявляются 3 основных функции, которые и обеспечивают работу хранителя экрана. Сейчас у нас ScreenSaverProc ничего не делает (будем постепенно ее наращивать). Так как мы не используем никаких специальных настроек, то втор ая функция тоже пуста. Нам не нужно создавать дополнительных системных классов, поэтому третья функция должна возвратить true. Жмем F7, среда Visual C++ скомпилирует программу, и если не было ошибок, мы получим полноценный хранитель экрана - правда, он у нас пока ничего не делает. Зайдите в папку с Вашим проектом, а затем в папку Release. Переименуйте ssaver.exe в ssaver.scr. Теперь поместите ssaver.scr в системную папку Windows (В NT/2000 это C:\WINNT\System32, в 9x - С:\WINDOWS\SYSTEM). Зайдите на панель управления, запустите апплет Экран, дальше вкладка Заставка. В списке появится наш хранитель под именем aver (Рис. 2). Если файл начинается с ss, то эти две буквы не показываются.
При нажатии кнопки Просмотр ничего не происходит - так и должно быть, ведь функция ScreenSaverProc пуста. Наш хранитель запускается и тут же завершает свою работу. Давайте наполним эту функцию каким-нибудь полезным кодом. Впишем, например, в нее код, рисующий два вложенных квадрата, вращающихся в противоположные стороны. Для этого подключим еще один заголовочный файл math.h для работы с математикой: #include <math.h>. В начале файла после директив #include определим глобально число pi, а заодно 2*pi и pi/2, чтобы каждый раз их не вычислять:
double pi = 3.1415926;
double pi2 = 2*pi;
double hpi = pi/2;
Функцию ScreenSaverProc наполним следующим содержанием:
static PAINTSTRUCT ps = {NULL}; static HDC hDC = NULL; static HPEN hPen1; static UINT uTimer = 0; static int x_max, y_max; static double step = 0.01, angle = 0; static int center_x, center_y; switch(message) { case WM_CREATE: x_max = GetSystemMetrics(SM_CXSCREEN); y_max = GetSystemMetrics(SM_CYSCREEN); center_x = x_max / 2; center_y = y_max / 2; uTimer = SetTimer(hWnd, 1, 10, NULL); hPen1 = (HPEN)GetStockObject(WHITE_PEN); break; case WM_DESTROY: if(uTimer) KillTimer(hWnd, uTimer); PostQuitMessage(0); break; case WM_TIMER: angle += step; if(angle > pi2) angle = 0; RECT lpr; lpr.left = center_x - 102; lpr.top = center_y - 102; lpr.right = center_x + 102; lpr.bottom = center_y + 102;; InvalidateRect(hWnd, &lpr, true); break; case WM_PAINT: x_max = GetSystemMetrics(SM_CXSCREEN); y_max = GetSystemMetrics(SM_CYSCREEN); center_x = x_max / 2; center_y = y_max / 2; hDC = BeginPaint(hWnd, &ps); if(fChildPreview) { SetBkColor(hDC, RGB(0, 0, 0)); SetTextColor(hDC, RGB(255, 255, 0)); char szPreview[] = "Мой хранитель :-)"; TextOut(hDC, 15, 45, szPreview, strlen(szPreview)); } else { SetBkColor(hDC, RGB(1, 0, 0)); SetTextColor(hDC, RGB(120, 120, 120)); SelectObject(hDC, hPen1); MoveToEx(hDC, center_x + (int)(cos(angle)*(double)100), center_y + (int)(sin(angle)*(double)(-100)), NULL); LineTo(hDC, center_x + (int)(cos(hpi + angle)*(double)100), center_y + (int)(sin(hpi + angle)*(double)(-100))); LineTo(hDC, center_x + (int)(cos(pi + angle)*(double)100), center_y + (int)(sin(pi + angle)*(double)(-100))); LineTo(hDC, center_x + (int)(cos(hpi + pi + angle)*(double)100), center_y + (int)(sin(hpi + pi + angle)*(double)(-100))); LineTo(hDC, center_x + (int)(cos(angle)*(double)100), center_y + (int)(sin(angle)*(double)(-100))); MoveToEx(hDC, center_x + (int)(cos(-angle)*(double)50), center_y + (int)(sin(-angle)*(double)(-50)), NULL); LineTo(hDC, center_x + (int)(cos(hpi - angle)*(double)50), center_y + (int)(sin(hpi - angle)*(double)(-50))); LineTo(hDC, center_x + (int)(cos(pi - angle)*(double)50), center_y + (int)(sin(pi - angle)*(double)(-50))); LineTo(hDC, center_x + (int)(cos(hpi + pi - angle)*(double)50), center_y + (int)(sin(hpi + pi - angle)*(double)(-50))); LineTo(hDC, center_x + (int)(cos(-angle)*(double)50), center_y + (int)(sin(-angle)*(double)(-50))); static char szAuthor[] = "Programmed by Ivan Gavrilyuk. mailto: ivg@hotbox.ru"; TextOut(hDC, 0, y_max - 20, szAuthor, strlen(szAuthor)); } EndPaint(hWnd, &ps); break; default: return DefScreenSaverProc(hWnd, message, wParam, lParam); }
А теперь подробно что делает каждая строчка кода. В первых семи определяются переменные для дальнейшего использования. ps - экземпляр структуры PAINTSTRUCT (рассмотрим далее), hDC - идентификатор контекста дисплея, hPen1 - кисть для рисования, uTimer - идентификатор таймера (используется для анимации квадратов), x_max, y_max - в этих переменных будет храниться разрешение экрана, step, angle - приращение угла поворота и сам угол поворота квадрата, center_x, center_y - координаты центра квадрата. Далее с помощью функции switch() организуется ветвление в зависимости от того, какое сообщение пришло от системы (message).
WM_CREATE - это сообщение приходит один раз при создании приложения. При помощи Win32-API функции GetSystemMetrix получаем разрешение экрана Вашего монитора и помещаем в переменные x_max и y_max. Находим координаты цента экрана обычным делением на 2 предыдущих параметров и помещаем в center_x и center_y. Устанавливаем при помощи функции SetTimer виртуальный таймер в систему. Здесь hWnd - идентефикатор окна, которое будет получать сообщения от таймера. Второй параметр - порядковый номер таймера в нашем приложении (можно установить несколько), третий - время в миллисекундах, через которое приложение должно получать сообщения от таймера; устанавливаем на 10 миллисекунд. Четвертый - функция таймера, она будет получать управление через количество миллисекунд, заданное в третьем параметре. Так как мы написали NULL, то таймер будет извещать окно приложения, посылая ему сообщение WM_TIMER. Осталось разобраться с функцией GetStockObject(). Она извлекает графический объект из стандартного репозитория Windows. В нашем случае мы достаем белую кисть (WHITE_PEN), которой будем рисовать в дальнейшем.
WM_DESTROY приходит тоже один раз, при уничтожении окна нашего приложения. Здесь мы при помощи функции KillTimer() снимаем таймер с нашего окна. hWnd - идентефикатор нашего окна, uTimer - указатель на таймер, полученный функцией SetTimer. Наконец, функцией PostQuitMessage посылаем сообщение системе о выходе из приложения. Если этого не сделать, то окно будет уничтожено, но программа будет продолжать работать.
WM_TIMER - это сообщение будет приходить от установленного нами виртуального таймера каждые 10 миллисекунд. Здесь мы увеличиваем угол поворота наших квадратов на step, проверяя, не больше ли он, чем 2*pi (полный оборот) - если да, то обнуляем. Затем мы посылаем сообщение нашему приложению при помощи функции InvalidateRect() о необходимости перерисовать область окна, размеры которого задаются во втором параметре (структурой lpr), предварительно проинициализировав ее. Обновляется квадратная область размером 204x204 в центе экрана. Третий параметр в функции InvalidateRect() указывает на необходимость очищать область перед обновлением (true - да, false - нет). hWnd - указатель на окно, которое нужно обновлять.
WM_PAINT - это сообщение происходит при перерисовке окна. Первыми 4 строками мы опять же узнаем разрешение экрана и высчитываем центр. Далее вызываем функцию BeginPaint(). Она подготавливает определенное окно для рисования (hWnd), заполняет структуру типа PAINTSTRUCT (ps) информацией о рисовании и возвращает в hDC указатель на контекст устройства, в нашем случае дисплея. В условном операторе определяем, находимся ли мы в режиме просмотра, или окно развернуто на полный экран; это можно проверить при помощи флага fChildPreview (true - просмотр). Если в режиме просмотра, то выводим текстовую строку <Мой хранитель :-)>. Для этого сначала функцией SetBkColor() устанавливаем цвет фона для нашего контекста устройства (hDC), макрос RGB(r, g, b) преобразует интенсивность красного (r), зеленого (g) и желтого (b) цветов в тип COLORREF, переменную такого типа принимает в качестве второго параметра функция SetBkColor(). Аналогично функцией SetTextColor() устанавливаем цвет текста. Наконец, выводим строку szPreview при помощи TextOut() на дисплей. Она принимает в качестве параметров указатель контекста, координаты строки, саму строку и ее длину (вычисляем при помощи встроенной функции strlen()). У Вас должна получиться картинка, представленная на Рис. 3.
Теперь обрабатываем случай, когда окно развернуто на весь экран (после else). Первые две строчки Вам знакомы - установка цвета фона и текста. SelectObject() выбирает в контекст дисплея (hDC) белую кисть hPen1, которую мы достали, когда приходило сообщение WM_CREATE. Далее используем стандартные GDI-функции ядра Windows для вывода наших вложенных квадратов, повернутых на угол angle. Здесь используются две функции: MoveToEx и LineTo. Первая служит для перемещения графического курсора на контексте, заданном в первом параметре, в точку с координатами во втором и третьем параметрах. Четвертый параметр обычно не используется (NULL). По умолчанию ось OX проходит слева направо, OY - сверху вниз, а отсчет ведется в пикселях. LineTo() рисует линию на контексте hDC из текущей позиции курсора в точку, заданную вторым и третьим параметрами текущей кистью (у нас она белая), дополнительно передвигая графический курсор. Одна вершина квадрата вычисляется по формулам x = cos(angle), y = sin(angle), остальные поворачиваются на углы pi/2, pi, 3*pi/2 относительно нее, тем самым они оказываются в вершинах квадрата. И сдвигаем центр поворота из начала координат в центр экрана. Вершины второго квадрата вычисляются аналогично, но поворачиваются на -angle, чтобы он вращался в противоположную сторону. angle изменяется от 0 до 2*pi, пробегая при этом полную окружность. B конце выводим строку в нижней части экрана. Вот и все. Осталось сообщить системе, что мы закончили рисовать - это делается при помощи функции EndPaint(), она принимает параметры, аналогичные BeginPaint().
Теперь компилируйте проект (F7), переименовывайте ssaver.exe в ssaver.scr, пихайте в системную папку Windows и наслаждайтесь :-). Должно получиться что-то вроде представленного на Рис. 4.
Готовый проект можно взять здесь: sssource.exe (9.1 Кб), откомпилированный - здесь: ssaver.scr (36.8 Кб).
Автор: Иван Гаврилюк