Финансы Сайт на котором знают все про финансы

Пишем простой cкринсейвер

Скринсейвер (или хранитель экрана) – это, по сути, обычная программа, в которой определены несколько специальных процедур для обработки сообщений, посылаемых ОС, при запуске и конфигурировании скринсейвера.
Программы-скринсейверы обычно хранятся в директории %WINDIR%\System32 и обладают расширением .scr. Таким образом, при переходе на страницу выбора заставки, Windows ищет в системной директории все файлы с соответствующим расширением и формирует из них список возможных скринсейверов. Также следует упомянуть несколько важных аспектов написания.

– Скринсейвер должен экспортировать функции ScreenSaverConfigureDialog, ScreenSaverProc.
– Название скринсейвера в окне настройки определяется строковым ресурсом с идентификатором IDS_DESCRIPTION, который должен быть равен 1.
– Идентификатор диалогового окна, которое будет появляться при нажатии клавиши “Параметры”, т.е. при попытке настроить скринсейвер, должен быть DLG_SCRNSAVECONFIGURE и равняться числу 2003.
– Программа также должна содержать реализацию функции RegisterDialogClasses.

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

Экран выбора скринсейвера

Настройки скринсейвера

Результат работы

Теперь рассмотрим сам код. Для начала подключим необходимые файлы, укажем lib-файлы, необходимые при линковке, и определим несколько констант.

#undef UNICODE

#include 
#include 
#include 
#include 
#include "resource.h"

#pragma comment(lib, "Scrnsave")
#pragma comment(lib, "comctl32")

//Можно было импортировать константу из math.h, но зачем...
#define M_PI 3.14159265358979323846
//Текст, который будет отображаться в превью скринсейвера
#define scr_name "Spining cube"
#define scr_auth "kaimi.ru"

Также неплохо было бы реализовать сохранение настроек, которые мы будем хранить в реестре.

#pragma pack(push, 1)
//Структура, которая хранит цвет, используемый в скринсейвере, и скорость вращения куба
static struct
{
    union
    {
        DWORD settings;
        struct
        {
            BYTE r;
            BYTE g;
            BYTE b;
            BYTE pos;
        };
    };
} scr_settings;
#pragma pack(pop)

//Функция загрузки/сохранения настроек
void LoadSaveSettings(BOOL do_save)
{
    HKEY key;
    DWORD disposition;
    DWORD type = REG_DWORD, size = sizeof(REG_DWORD);

    //Открываем ключ в реестре с правами на чтение/запись (если ключ не существует, то он будет создан)
    if(RegCreateKeyEx(HKEY_CURRENT_USER, "Software\Sample\SimpleScreenSaver", 0, NULL, 0, KEY_WRITE | KEY_READ, NULL, &key, &disposition) == ERROR_SUCCESS)
    {
        //Сохраняем/Загружаем данные в виде одного DWORD'а
        if(do_save)
            RegSetValueEx(key, "Settings", 0, type, (PBYTE)&scr_settings.settings, size);
        else
            RegQueryValueEx(key, "Settings", NULL, &type, (PBYTE)&scr_settings.settings, &size);

        RegCloseKey(key);
    }
}

Перейдем к основной процедуре, которая вызывается при запуске скринсейвера.

LONG WINAPI ScreenSaverProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    //Определяем необходимые переменные
    static HDC hDC;
    static HPEN hPen;
    static UINT timer_id;
    static RECT rect;
    static PAINTSTRUCT ps = {0};
    //Предвычисленные массивы для хранения значений sin/cos 
    static double s[360], c[360];
    //Массивы для хранения оригинальных координат линий и координат после поворота
    static double lO[12][2][4], lR[12][2][4];
    //Размер куба
    static int side_size = 500;
    //Координаты центра куба
    static int xCenter = 0, yCenter = 0, zCenter = 0;
    //Углы поворота
    static int theta = 0, phi = 0;
    static int thetaRot = 2, phiRot = 2;
    //Массивы для хранения координат ребер куба
    static double scrX[12][2], scrY[12][2];

    static int i;

    switch(message)
    {
        case WM_CREATE:
            //Загружаем настройки в структуру
            LoadSaveSettings(FALSE);
            //Задаем цвет и толщину линий куба
            hPen = CreatePen(PS_SOLID, 5, RGB(scr_settings.r, scr_settings.g, scr_settings.b));
            //Вычисляем центры проекции куба на основе разрешения экрана
            xCenter = GetSystemMetrics (SM_CXSCREEN) / 2;
            yCenter = GetSystemMetrics (SM_CYSCREEN) / 2;
            zCenter = xCenter + yCenter - 4;
            //Задаем область, которую в дальнейшем будем перерисовывать
            rect.left = xCenter - side_size;
            rect.top = yCenter - side_size;
            rect.right = xCenter + side_size;
            rect.bottom = yCenter + side_size;

            //Вычисляем значения sin и cos
            for(i = 0; i < 360; i++)
            {
                s[i] = sin(i * (M_PI / 180));
                c[i] = cos(i * (M_PI / 180));
            }
            //Задаем координаты ребер куба
            lO[0][0][0] = -side_size; lO[0][0][1] = side_size; lO[0][0][2] = side_size;
            lO[0][1][0] = side_size;  lO[0][1][1] = side_size; lO[0][1][2] = side_size;

            lO[1][0][0] = side_size; lO[1][0][1] = -side_size; lO[1][0][2] = side_size;
            lO[1][1][0] = side_size; lO[1][1][1] = side_size;  lO[1][1][2] = side_size;

            lO[2][0][0] = side_size; lO[2][0][1] = side_size; lO[2][0][2] = -side_size;
            lO[2][1][0] = side_size; lO[2][1][1] = side_size; lO[2][1][2] = side_size;

            lO[3][0][0] = -side_size; lO[3][0][1] = -side_size; lO[3][0][2] = side_size;
            lO[3][1][0] = -side_size; lO[3][1][1] = side_size;  lO[3][1][2] = side_size;

            lO[4][0][0] = -side_size; lO[4][0][1] = side_size; lO[4][0][2] = -side_size;
            lO[4][1][0] = -side_size; lO[4][1][1] = side_size; lO[4][1][2] = side_size;

            lO[5][0][0] = -side_size; lO[5][0][1] = -side_size; lO[5][0][2] = side_size;
            lO[5][1][0] = side_size;  lO[5][1][1] = -side_size; lO[5][1][2] = side_size;

            lO[6][0][0] = -side_size; lO[6][0][1] = side_size; lO[6][0][2] = -side_size;
            lO[6][1][0] = side_size;  lO[6][1][1] = side_size; lO[6][1][2] = -side_size;

            lO[7][0][0] = -side_size; lO[7][0][1] = -side_size; lO[7][0][2] = -side_size;
            lO[7][1][0] = side_size;  lO[7][1][1] = -side_size; lO[7][1][2] = -side_size;

            lO[8][0][0] = -side_size; lO[8][0][1] = -side_size; lO[8][0][2] = -side_size;
            lO[8][1][0] = -side_size; lO[8][1][1] = side_size;  lO[8][1][2] = -side_size;

            lO[9][0][0] = side_size; lO[9][0][1] = -side_size; lO[9][0][2] = -side_size;
            lO[9][1][0] = side_size; lO[9][1][1] = -side_size; lO[9][1][2] = side_size;

            lO[10][0][0] = side_size; lO[10][0][1] = -side_size; lO[10][0][2] = -side_size;
            lO[10][1][0] = side_size; lO[10][1][1] = side_size;  lO[10][1][2] = -side_size;

            lO[11][0][0] = -side_size; lO[11][0][1] = -side_size; lO[11][0][2] = -side_size;
            lO[11][1][0] = -side_size; lO[11][1][1] = -side_size; lO[11][1][2] = side_size;
            //Запускаем таймер, по которому будет перерисовываться куб
            timer_id = SetTimer(hWnd, 1, 500 - 5 * scr_settings.pos + 1, NULL);

        break;

        case WM_DESTROY:
            //При закрытии окна скринсейвера останавливаем таймер и удаляем созданный объект
            if(timer_id)
                KillTimer(hWnd, timer_id);

            if(hPen)
                DeleteObject(hPen);
            //Сообщаем системе о том, что текущий поток сделал запрос на прекращение работы
            PostQuitMessage(0);
        break;

        case WM_TIMER:
                //Вычисляем координаты ребер куба
                for(i = 0; i < 12; i++) { lR[i][0][0] = -lO[i][0][0] * s[theta] + lO[i][0][1] * c[theta]; lR[i][0][1] = -lO[i][0][0] * c[theta] * s[phi] - lO[i][0][1] * s[theta] * s[phi] - lO[i][0][2] * c[phi] + 1; lR[i][0][2] = -lO[i][0][0] * c[theta] * c[phi] - lO[i][0][1] * s[theta] * c[phi] + lO[i][0][2] * s[phi]; lR[i][1][0] = -lO[i][1][0] * s[theta] + lO[i][1][1] * c[theta]; lR[i][1][1] = -lO[i][1][0] * c[theta] * s[phi] - lO[i][1][1] * s[theta] * s[phi] - lO[i][1][2] * c[phi] + 1; lR[i][1][2] = -lO[i][1][0] * c[theta] * c[phi] - lO[i][1][1] * s[theta] * c[phi] + lO[i][1][2] * s[phi]; if(lR[i][0][2] + zCenter > 0 || lR[i][0][2] + zCenter < 0) { scrX[i][0] = 256 * (lR[i][0][0] / (lR[i][0][2] + zCenter)) + xCenter; scrY[i][0] = 256 * (lR[i][0][1] / (lR[i][0][2] + zCenter)) + yCenter; } if(lR[i][1][2] + zCenter > 0 || lR[i][1][2] + zCenter < 0)
                    {
                        scrX[i][1] = 256 * (lR[i][1][0] / (lR[i][1][2] + zCenter)) + xCenter;
                        scrY[i][1] = 256 * (lR[i][1][1] / (lR[i][1][2] + zCenter)) + yCenter;
                    }
                }
                //Помечаем область, содержащую куб, как требующую перерисовки
                InvalidateRect(hWnd, &rect, TRUE);
        break;

        case WM_PAINT:
                //Вызываем функцию, подготавливающую окно к выводу графической информации
                hDC = BeginPaint(hWnd, &ps);
                //fChildPreview - глобальная переменная, показывающая смотрим мы превью или полноэкранную версию скринсейвера
                if(!fChildPreview)
                {
                    //Выбираем перо, которое будет использоваться для рисования куба
                    SelectObject(hDC, hPen);
                    //Рисуем ребра куба
                    for(i = 0; i < 12; i++)
                    {
                        MoveToEx(hDC, (int)scrX[i][0], (int)scrY[i][0], NULL);
                        LineTo(hDC, (int)scrX[i][1], (int)scrY[i][1]);
                    }

                    theta = (theta + thetaRot) % 360;
                    phi = (phi + phiRot) % 360;
                }
                else
                {
                    //Выводим текст с описанием скринсейвера
                    SetBkColor(hDC, RGB(0, 0, 0));
                    SetTextColor(hDC, RGB(0, 255, 0));
                    TextOut(hDC, 35, 30, scr_name, strlen(scr_name));
                    TextOut(hDC, 50, 50, scr_auth, strlen(scr_auth));
                }
                //Завершаем рисование в окне
                EndPaint(hWnd, &ps);
        break;

        default:
            return DefScreenSaverProc(hWnd, message, wParam, lParam);
    }

    return 0;
}

Теперь функция, которая вызывается при нажатии кнопки “Параметры”, и функция RegisterDialogClasses, которая в этом примере не используется и просто возвращает TRUE.

BOOL WINAPI ScreenSaverConfigureDialog(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message)
    {
        case WM_INITDIALOG:
            //Загружаем текущие настройки
            LoadSaveSettings(FALSE);
            //Восстанавливаем состояние элементов управления в соответствии с настройками
            SendDlgItemMessage(hDlg, SPIN_SLIDER, TBM_SETPOS, TRUE, scr_settings.pos);
            SetDlgItemInt(hDlg, R_EDIT, scr_settings.r, FALSE);
            SetDlgItemInt(hDlg, G_EDIT, scr_settings.g, FALSE);
            SetDlgItemInt(hDlg, B_EDIT, scr_settings.b, FALSE);
            //Задаем диапазон возможных значений для элементов Spin Control
            SendDlgItemMessage(hDlg, R_SPIN, UDM_SETRANGE32, 0, 255);
            SendDlgItemMessage(hDlg, G_SPIN, UDM_SETRANGE32, 0, 255);
            SendDlgItemMessage(hDlg, B_SPIN, UDM_SETRANGE32, 0, 255);
            //Задаем соответствие между Spin и Edit Control'ами
            SendDlgItemMessage(hDlg, R_SPIN, UDM_SETBUDDY, (WPARAM) GetDlgItem(hDlg, R_EDIT), 0);
            SendDlgItemMessage(hDlg, G_SPIN, UDM_SETBUDDY, (WPARAM) GetDlgItem(hDlg, G_EDIT), 0);
            SendDlgItemMessage(hDlg, B_SPIN, UDM_SETBUDDY, (WPARAM) GetDlgItem(hDlg, B_EDIT), 0);

        return TRUE;

        case WM_COMMAND:
            switch(LOWORD(wParam))
            {
                //При нажатии клавиши "OK"
                case IDOK:
                    //Получаем значения контролов с диалогового окна
                    scr_settings.pos = (BYTE)SendDlgItemMessage(hDlg, SPIN_SLIDER, TBM_GETPOS, 0, 0);
                    scr_settings.r = GetDlgItemInt(hDlg, R_EDIT, NULL, FALSE);
                    scr_settings.g = GetDlgItemInt(hDlg, G_EDIT, NULL, FALSE);
                    scr_settings.b = GetDlgItemInt(hDlg, B_EDIT, NULL, FALSE);
                    //Сохраняем настройки
                    LoadSaveSettings(TRUE);
                    //Закрываем диалоговое окно
                    EndDialog(hDlg, LOWORD(wParam));
                break;
                //При нажатии клавиши "Отмена"
                case IDCANCEL:
                    EndDialog(hDlg, LOWORD(wParam));
                break;
            }
        return TRUE;
    }

    return FALSE;
}

BOOL WINAPI RegisterDialogClasses(HANDLE hInst)
{
    return TRUE;
}

И, наконец, экспортируем необходимые функции через .def-файл.

EXPORTS
    ScreenSaverConfigureDialog
    ScreenSaverProc

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

Исходный код и бинарник: скачать