Учебник

Теоретический материал.

Глава I

  • Интерфейс Windows-приложения

    Стиль программирования Windows-приложений принципиально отличается от того, который сложился в операционных системах раннего поколения. В MS-DOS программа монопольно владеет всеми ресурсами системы и является инициатором взаимодействия с операционной системой. Совсем иначе дело обстоит в операционной системе Windows, которая строилась как многозадачная, и именно операционная система является инициатором обращения к программе. Все ресурсы Windows являются разделяемыми, и программа, в дальнейшем будем называть ее приложением, не может владеть ими монопольно. В связи с такой идеологией построения операционной системы приложение должно ждать посылки сообщения операционной системы и лишь после его получения выполнить определенные действия, затем вновь перейти в режим ожидания очередного сообщения. На рис. 1.1 схематично изображена диаграмма типичного Windows-приложения.

    Рис. 1.1. Структура Windows-приложения

    Windows генерирует множество различных сообщений, которые направляются приложению, например, щелчок кнопки мыши или нажатие клавиши на клавиатуре. Если приложение не обрабатывает какие-то сообщения, реакция на них осуществляется операционной системой стандартным способом, так что задачей программиста является обработка лишь тех сообщений, которые необходимы приложению.
    Разработчиками операционной системы Windows была создана библиотека функций, при помощи которых и происходит взаимодействие приложения с операционной системой, так называемые функции Программного интерфейса приложений (Application Program Interface, API).
    Подмножество этих функций, предназначенных для графического вывода на дисплей, графопостроитель и принтер, представляет собой Интерфейс графических устройств (Graphics Device Interface, GDI).
    Библиотека API-функций разрабатывалась в расчете на то, что ее можно использовать для любого языка программирования, а поскольку разные языки имеют различные типы данных, то были созданы собственные Windows-типы, которые приводятся к типам данных языков программирования. Отметим только, что в Windows нет логического типа bool, но есть Windows-тип BOOL, который эквивалентен целому типу int. Будем рассматривать типы данных Windows по мере необходимости.
    Еще одной особенностью API-функций является использование обратного, по отношению к принятому в языке С, порядка передачи параметров, как это реализовано в языке Pascal. В С для идентификации таких функций использовалось служебное слово pascal, в Windows введены его синонимы CALLBACK, APIENTRY или WINAPI. По умолчанию С-функции передают параметры, начиная с конца списка так, что первый параметр всегда находится на вершине стека. Именно это позволяет использовать в языке С функции с переменным числом параметров, что в API- функциях невозможно.

  • Каркас Windows-приложения

    Каркас Windows-приложения В отличие от программы, выполняемой в операционной системе MS-DOS, даже для создания простейшего приложения под Windows придется проделать намного больше работы. Чтобы иметь возможность работать с оконным интерфейсом, заготовка или каркас Windows-приложения должна выполнить некоторые стандартные действия:
    - Определить класс окна.
    - Зарегистрировать окно.
    - Создать окно данного класса.
    - Отобразить окно.
    - Запустить цикл обработки сообщений.

    ПРИМЕЧАНИЕ
    Термин интерфейс здесь следует понимать как способ взаимодействия пользователя и приложения. Класс окна — структура, определяющая его свойства.
    Рассмотрим сначала, как можно "вручную" создать минимальное Win32-приложение. Загрузив Visual Studio 2010, выполним команду File | New | Project… и выберем тип проекта — Win32 Project. В раскрывающемся списке Location выберем путь к рабочей папке, а в поле Name имя проекта (рис. 1.2). В следующем диалоговом окне, приведенном на рис. 1.3, нажимаем кнопку Next, а в окне опций проекта (рис. 1.4) выберем флажок Empty project (Пустой проект) и нажмем кнопку Finish — получим пустой проект, в котором нет ни одного файла.

    Рис. 1.2. Выбор типа проекта

  • Рис. 1.3. Выбор типа проекта

  • Рис. 1.4. Окно опций проекта

  • Рис. 1.5. Добавление к проекту нового объекта с помощью контекстного меню

  • Рис. 1.6. Выбор шаблона объекта

  • С помощью контекстного меню (рис. 1.5) добавим файл для кода приложения, имя файла введем в ходе диалога выбора шаблона объекта на рис. 1.6. (Тот же самый диалог мы могли бы получить по команде меню Project | Add New Item….)

    ПРИМЕЧАНИЕ
    Пока мы создаем простые решения, состоящие из одного проекта, можно убрать флажок Create directory for solution. Это упростит структуру каталога.
    
    #include 
    #include 
    LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
    TCHAR WinName[] = _T("MainFrame");
    int APIENTRY _tWinMain(HINSTANCE This, // Дескриптор текущего приложения HINSTANCE Prev, // В современных системах всегда 0
    LPTSTR cmd, // Командная строка
    int mode) // Режим отображения окна
    {
    HWND hWnd; // Дескриптор главного окна программы MSG msg; // Структура для хранения сообщения WNDCLASS wc; // Класс окна
    // Определение класса окна wc.hInstance = This;
    wc.lpszClassName = WinName; // Имя класса окна wc.lpfnWndProc = WndProc; // Функция окна wc.style = CS_HREDRAW | CS_VREDRAW; // Стиль окна
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); // Стандартная иконка wc.hCursor = LoadCursor(NULL,IDC_ARROW); // Стандартный курсор wc.lpszMenuName = NULL; // Нет меню
    wc.cbClsExtra = 0; // Нет дополнительных данных класса wc.cbWndExtra = 0; // Нет дополнительных данных окна
    // Заполнение окна белым цветом wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    if(!RegisterClass(&wc)) return 0; // Регистрация класса окна
    // Создание окна
    hWnd = CreateWindow(WinName, // Имя класса окна
    _T("Каркас Windows-приложения"), // Заголовок окна WS_OVERLAPPEDWINDOW, // Стиль окна CW_USEDEFAULT, // x
    CW_USEDEFAULT, // y Размеры окна CW_USEDEFAULT, // Width
    8 Глава 1
    image
    
    CW_USEDEFAULT, // Height
    HWND_DESKTOP, // Дескриптор родительского окна NULL, // Нет меню
    This, // Дескриптор приложения
    NULL); // Дополнительной информации нет ShowWindow(hWnd, mode); //Показать окно
    // Цикл обработки сообщений while(GetMessage(&msg, NULL, 0, 0))
    {
    TranslateMessage(&msg);// Функция трансляции кодов нажатой клавиши DispatchMessage(&msg); // Посылает сообщение функции WndProc()
    }
    return 0;
    }
    // Оконная функция вызывается операционной системой
    // и получает сообщения из очереди для данного приложения LRESULT CALLBACK WndProc(HWND hWnd, UINT message,
    WPARAM wParam, LPARAM lParam)
    { // Обработчик сообщений switch(message)
    {
    case WM_DESTROY : PostQuitMessage(0);
    break; // Завершение программы
    // Обработка сообщения по умолчанию
    default : return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
    }
    
               
    Программа не делает ничего полезного, поэтому, запустив ее на выполнение кнопкой (Start Debugging), мы получим изображенное на рис. 1.7 пустое окно, имеющее заголовок и набор стандартных кнопок.

    Рис. 1.7. Окно первой Windows-программы

  • Исследование каркаса Windows-приложения

    Давайте подробно рассмотрим текст нашей программы. Первая строка содержит файл включений, который обязательно присутствует во всех Windows-программах.>
    #include
    Если в ранних версиях Visual Studio этот файл содержал основные определения, то сейчас он служит для вызова других файлов включений, основные из которых: windef.h, winbase.h, wingdi.h, winuser.h; а также несколько дополнительных файлов, в которых помещены определения API-функций, констант и макросов.
    Дополнительно подключим:
    #include
    В этом файле содержатся определения некоторых полезных макросов, например, макрос _T() служит для создания строки Unicode на этапе компиляции и определен примерно так:
    #define _T(x)
    #ifdef _UNICODE
    T(x)
    #define T(x) L ## x
    #else
    #define T(x) x
    #endif
    Макрос преобразуется в оператор "L", который является инструкцией компилятору для образования строки Unicode, если определена константа _UNICODE; и в "пустой оператор", если константа не определена. Константа _UNICODE устанавливается в зависимости от установок свойства проекта Character Set (рис. 1.8). Диалоговое окно свойств Property Pages доступно сейчас на подложке Property Manager панели управления Solution Explorer.

    Рис. 1.8. Страница общих свойств проекта

  • Таким образом, этот макрос позволяет компилировать проект как в кодировке Unicode, так и в Windows-кодировке. Мы подробно рассмотрели данный макрос потому, что многие определения Windows описаны подобным образом.
    Далее следует прототип оконной функции:
    LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);
    Оконная функция также является функцией обратного вызова, что связано с некоторыми особенностями организации вызовов операционной системы. Эта функция регистрируется в системе, а ее вызов осуществляет операционная система, когда требуется обработать сообщение. Тип возвращаемого значения функции LRESULT эквивалентен long для Win32-проекта.
    На глобальном уровне описывается имя класса окна приложения в виде текстовой строки:
    TCHAR WinName[] = _T("MainFrame");
    Тип TCHAR также преобразуется в wchar_t, если определена константа _UNICODE, и в char, если константа не определена.

    ПРИМЕЧАНИЕ
    Тип wchar_t эквивалентен типу short и служит для хранения строк в кодировке Un- icode, где для одного символа выделяется 16 бит.

    • Имя класса окна используется операционной системой для его идентификации. Имя может быть произвольным, в частности содержать кириллический текст.
    Рассмотрим заголовок головной функции:
    int APIENTRY _tWinMain(HINSTANCE This, // Дескриптор текущего приложения HINSTANCE Prev, // В современных системах всегда 0
    LPTSTR cmd, // Командная строка
    int mode) // Режим отображения окна
    Для Windows-приложений с Unicode она носит имя wWinMain(), а в 8-битной кодировке — WinMain(), выбор варианта определяется префиксом _t, что также является стандартным приемом в библиотеке API-функций. Функция имеет четыре параметра, устанавливаемых при загрузке приложения:
    This — дескриптор, присваиваемый операционной системой при загрузке приложения;
    Prev — параметр предназначен для хранения дескриптора предыдущего экземпляра приложения, уже загруженного системой. Сейчас он потерял свою актуальность и сохранен лишь для совместимости со старыми приложениями (начиная с Windows 95, параметр устанавливается в нулевое значение);
    cmd — указатель командной строки, но без имени запускаемой программы. Тип LPTSTR эквивалентен TCHAR*;
    mode — режим отображения окна.

    ПРИМЕЧАНИЕ
    Здесь впервые появляется Windows-тип данных — дескриптор (описатель), который используется для описания объектов операционной системы. Дескриптор напоминает индекс хеш-таблицы и позволяет отслеживать состояние объекта в памяти при его перемещении по инициативе операционной системы. Предусмотрено много типов дескрипторов: HINSTANCE, HWND и др., но все они являются 32-разрядными целыми числами.
    Внутри головной функции описаны три переменные:
    hWnd — предназначена для хранения дескриптора главного окна программы;
    msg — это структура, в которой хранится информация о сообщении, передаваемом операционной системой окну приложения:
    struct MSG
    {
    HWND hWnd; // Дескриптор окна UINT message; // Номер сообщения
    WPARAM wParam; // 32-разрядные целые содержат
    LPARAM lParam; // дополнительные параметры сообщения DWORD time; // Время посылки сообщения в миллисекундах POINT pt; // Координаты курсора (x,y)
    };
    struct POINT
    {
    LONG x,y;
    };

    ПРИМЕЧАНИЕ
    Тип WPARAM — "короткий параметр" был предназначен для передачи 16-разрядного значения в 16-разрядной операционной системе, в Win32 это такое же 32-разрядное значение, что и LPARAM.

    wc — структура, содержащая информацию по настройке окна. Требуется заполнить следующие поля:
    wc.hInstance = This;
    Дескриптор текущего приложения.
    wc.lpszClassName = WinName;
    Имя класса окна.
    wc.lpfnWndProc = WndProc;
    Имя оконной функции для обработки сообщений.
    wc.style = CS_HREDRAW | CS_VREDRAW;
    Такой стиль определяет автоматическую перерисовку окна при изменении его ширины или высоты.
    wc.hIcon = LoadIcon(NULL,IDI_APPLICATION);
    Дескриптор пиктограммы (иконки) приложения. Функция LoadIcon() обеспечивает ее загрузку. Если первый параметр NULL, используется системная пиктограмма, которая выбирается по второму параметру из следующего набора:
    IDI_APPLICATION — стандартная иконка;
    IDI_ASTERISK — звездочка;
    12 Глава 1
    IDI_EXCLAMATION — восклицательный знак;
    IDI_HAND — ладонь;
    IDI_QUESTION — вопросительный знак;
    IDI_WINLOGO — логотип Windows;
    wc.hCursor = LoadCursor(NULL,IDC_ARROW);
    Аналогичная функция LoadCursor() обеспечивает загрузку графического об- раза курсора, где нулевой первый параметр также означает использование системного курсора, вид которого можно выбрать из списка:
    IDC_ARROW — стандартный курсор;
    IDC_APPSTARTING — стандартный курсор и маленькие песочные часы;
    IDC_CROSS — перекрестие; IDC_IBEAM — текстовый курсор; IDC_NO — перечеркнутый круг;
    IDC_SIZEALL — четырехлепестковая стрелка;
    IDC_SIZENESW — двухлепестковая стрелка, северо-восток и юго-запад; IDC_SIZENWSE — двухлепестковая стрелка, северо-запад и юго-восток; IDC_SIZENS — двухлепестковая стрелка, север и юг;
    IDC_SIZEWE — двухлепестковая стрелка, запад и восток;
    IDC_UPARROW — стрелка вверх;
    IDC_WAIT — песочные часы;
    wc.lpszMenuName = NULL;
    Ссылка на строку главного меню, при его отсутствии NULL. wc.cbClsExtra = 0;
    Дополнительные параметры класса окна.
    wc.cbWndExtra = 0;
    Дополнительные параметры окна.
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    Дескриптор кисти, которая используется для заполнения окна. Стандартная конструкция, создает системную кисть белого цвета WHITE_BRUSH. Требуется явное преобразование типа — HBRUSH.
    После того как определены основные характеристики окна, можно это окно создать при помощи API-функции CreateWindow(), где также нужно задать па- раметры:
    1. WinName — имя, которое присвоено классу окна.
    2. _T("Каркас Windows-приложения") — заголовок окна в виде строки Unicode либо С-строки.
    3. WS_OVERLAPPEDWINDOW — макрос, определяющий стиль отображения стандартного окна, имеющего системное меню, заголовок, рамку для изменения размеров, а также кнопки минимизации, развертывания и закрытия. Это наиболее общий стиль окна, он определен так:
    #define WS_OVERLAPPEDWINDOW (WS_OVERLAPPED|WS_CAPTION|WS_SYSMENU| WS_THICKFRAME|WS_MINIMIZEBOX|WS_MAXIMIZEBOX)
    Можно создать другой стиль, используя комбинацию стилевых макросов при помощи операции логического сложения, вот некоторые из них:
    WS_OVERLAPPED — стандартное окно с рамкой;
    WS_CAPTION — окно с заголовком; WS_THICKFRAME — окно с рамкой; WS_MAXIMIZEBOX — кнопка распахивания окна; WS_MINIMIZEBOX — кнопка минимизации; WS_SYSMENU — системное меню;
    WS_HSCROLL — горизонтальная панель прокрутки; WS_VSCROLL — вертикальная панель прокрутки; WS_VISIBLE — окно отображается;
    WS_CHILD — дочернее окно;
    WS_POPUP — всплывающее окно;
    4. Следующие два параметра определяют координаты левого верхнего угла окна (x,y), еще два параметра: Width — ширину и Height — высоту окна в пикселах. Задание параметра CW_USEDEFAULT означает, что система сама выберет для ото- бражения окна наиболее (с ее точки зрения) удобное место и размер.
    5. Следующий параметр — указатель на структуру меню, или NULL, при его отсут- ствии.
    6. Далее требуется указать дескриптор приложения, владельца окна — This.
    7. И, наконец, указатель на дополнительную информацию, в нашем случае — NULL.
    Окно создано, и с ним можно работать, но пока оно не отображается. Для того чтобы окно увидеть, необходимо его отобразить с помощью функции ShowWindow(hWnd, mode), которая принимает два параметра: hWnd — дескриптор окна и mode — режим отображения. В нашем случае мы используем значение, полученное при открытии приложения через параметр головной функции.
    Далее, заключительная часть головной функции — цикл обработки сообщений. Он задается оператором while, аргументом которого является функция GetMessage(&msg, NULL, 0, 0). Такой цикл является обязательным для всех Win- dows-приложений, его цель — получение и обработка сообщений, передаваемых операционной системой. Операционная система ставит сообщения в очередь, отку- да они извлекаются функцией GetMessage() по мере готовности приложения:
    - первым параметром функции является &msg — указатель на структуру MSG, где и хранятся сообщения;
    - второй параметр hWnd — определяет окно, для которого предназначено сообщение, если же необходимо перехватить сообщения всех окон данного приложения, он должен быть NULL;
    - остальные два параметра определяют [min, max] диапазон получаемых сообщений. Чаще всего необходимо обработать все сообщения, тогда эти параметры должны быть равны 0.

    ПРИМЕЧАНИЕ
    Сообщения определяются их номерами, символические имена для них определены в файле включений winuser.h. Префикс всех системных сообщений WM_.

    Внутри цикла расположены две функции: TranslateMessage(&msg); DispatchMessage(&msg);
    Первая из них транслирует код нажатой клавиши в клавиатурные сообщения
    WM_CHAR. При этом в переменную wParam структуры msg помещается код нажатой клавиши в Windows-кодировке CP-1251, в младшее слово lParam — количество повторений этого сообщения в результате удержания клавиши в нажатом состоя- нии, а в старшее слово — битовая карта со значениями, приведенными в табл. 1.1.

    Бит Значение
    15 1, если клавиша отпущена, 0 — если нажата
    14 1, если клавиша была нажата перед посылкой сообщения
    13 1, если нажата клавиша Alt
    12-9 Резерв
    8 1, если нажата функциональная клавиша
    7-0 Scan-код клавиши

    Использование этой функции не обязательно и нужно только для обработки сообщений от клавиатуры.
    Вторая функция, DispatchMessage(&msg), обеспечивает возврат преобразованного сообщения обратно операционной системе и инициирует вызов оконной функции данного приложения для его обработки.
    Данным циклом и заканчивается головная функция.
    Нам осталось лишь описать оконную функцию WndProc(), и построение каркаса Windows-приложения будет закончено.
    Основной компонент этой функции — переключатель switch, обеспечивающий выбор соответствующего обработчика сообщений по его номеру message. В нашем случае мы предусмотрели обработку лишь одного сообщения WM_DESTROY. Это сОобщение посылается, когда пользователь завершает программу. Получив его, оконная функция вызывает функцию PostQuitMessage(0), которая завершает приложение и передает операционной системе код возврата — 0. Если говорить точ- нее, генерируется сообщение WM_QUIT, получив которое функция GetMessage() воз- вращает нулевое значение. В результате цикл обработки сообщений прекращается и происходит завершение работы приложения.
    Все остальные сообщения обрабатываются по умолчанию функцией DefWindowProc(), имеющей такой же список параметров и аналогичное возвращаемое значение, по- этому ее вызов помещается после оператора return.

  • Стандартная заготовка Windows-приложения

    Мастер Visual Studio позволяет автоматически генерировать стандартную заготовку Windows-приложения. Для этого в стартовом окне построителя Win32- приложения (см. рис. 1.3) достаточно выбрать кнопку Finish. Проект состоит из набора файлов, показанных на рис. 1.9.

    Рис. 1.9. Состав стандартной заготовки Win32-приложения

    Рассмотрим подробнее представленный в листинге 1.2 проект, опуская некоторые комментарии и несущественные детали.
    
    //stdafx.h ////////////////////////////////////////////////////
    #pragma once
    #include "targetver.h"
    #define WIN32_LEAN_AND_MEAN //Отключает некоторые редко используемые
    //возможности компилятора, для ускорения компиляции
    #include  //Стандартный набор файлов включений
    #include  //для Win32-проекта
    #include 
    #include 
    #include 
    
    //Standard.h////////////////////////////////////////////////
    #pragma once
    #include "resource.h"
    
    //targetver.h///////////////////////////////////////////////
    #pragma once
    #include 
    
    //resource.h///////////////////////////////////////////////
    #define IDS_APP_TITLE 103
    #define IDR_MAINFRAME 128
    #define IDD_STANDARD_DIALOG 102
    #define IDD_ABOUTBOX 103
    #define IDM_ABOUT 104
    #define IDM_EXIT 105
    #define IDI_STANDARD 107
    #define IDI_SMALL 108
    #define IDC_STANDARD 109
    #define IDC_MYICON 2
    #ifndef IDC_STATIC
    #define IDC_STATIC -1
    #endif
    #ifdef APSTUDIO_INVOKED
    #ifndef
    APSTUDIO_READONLY_SYMBOLS
    #define
    _APS_NO_MFC
    130
    #define
    _APS_NEXT_RESOURCE_VALUE
    129
    #define
    _APS_NEXT_COMMAND_VALUE
    32771
    #define
    _APS_NEXT_CONTROL_VALUE
    1000
    #define
    _APS_NEXT_SYMED_VALUE
    110
    #endif
    #endif
    //stdafx.cpp ////////////////////////////////////////////////
    #include "stdafx.h"
    
    //Standard.cpp //////////////////////////////////////////////
    
    #include "stdafx.h"
    #include "Standard.h"
    #define MAX_LOADSTRING 100 HINSTANCE hInst;
    TCHAR szTitle[MAX_LOADSTRING];
    TCHAR szWindowClass[MAX_LOADSTRING];
    
    ATOM MyRegisterClass(HINSTANCE hInstance);
    BOOL InitInstance(HINSTANCE, int); LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
    
    int APIENTRY _tWinMain(HINSTANCE hInstance,
    HINSTANCE hPrevInstance, LPTSTR lpCmdLine,
    int nCmdShow)
    {
    UNREFERENCED_PARAMETER(hPrevInstance); UNREFERENCED_PARAMETER(lpCmdLine);
    MSG msg;
    HACCEL hAccelTable;
    LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); LoadString(hInstance, IDC_STANDARD, szWindowClass, MAX_LOADSTRING); MyRegisterClass(hInstance);
    if (!InitInstance (hInstance, nCmdShow))
    {
    return FALSE;
    }
    hAccelTable=LoadAccelerators(hInstance,MAKEINTRESOURCE(IDC_STANDARD)); while (GetMessage(&msg, NULL, 0, 0))
    {
    if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
    {
    TranslateMessage(&msg); DispatchMessage(&msg);
    }
    }
    return (int)msg.wParam;
    }
    
    ATOM MyRegisterClass(HINSTANCE hInstance)
    {
    WNDCLASSEX wcex;
    wcex.cbSize = sizeof(WNDCLASSEX);
    wcex.style = CS_HREDRAW | CS_VREDRAW;
    
    
    wcex.lpfnWndProc = WndProc; wcex.cbClsExtra = 0;
    wcex.cbWndExtra = 0; wcex.hInstance = hInstance;
    wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_STANDARD)); wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wcex.lpszMenuName = MAKEINTRESOURCE(IDC_STANDARD); wcex.lpszClassName = szWindowClass;
    wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL)); return RegisterClassEx(&wcex);
    }
    
    BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
    {
    HWND hWnd;
    hInst = hInstance;
    hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
    if (!hWnd)
    {
    return FALSE;
    }
    ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd);
    return TRUE;
    }
    
    LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
    int wmId, wmEvent; PAINTSTRUCT ps;
    HDC hdc;
    switch (message)
    {
    case WM_COMMAND:
    wmId = LOWORD(wParam); // эквивалентно (wParam & 0xffff) wmEvent = HIWORD(wParam); // эквивалентно (wParam >> 16) switch (wmId)
    {
    case IDM_ABOUT:
    DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About); break;
    case IDM_EXIT: DestroyWindow(hWnd); break;
    default: return DefWindowProc(hWnd, message, wParam, lParam);
    }
    break; case WM_PAINT:
    hdc = BeginPaint(hWnd, &ps);
    // Здесь добавляем код для вывода в окно EndPaint(hWnd, &ps);
    break;
    case WM_DESTROY: PostQuitMessage(0); break;
    default: return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
    }
    
    INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
    {
    UNREFERENCED_PARAMETER(lParam);
    switch (message)
    {
    case WM_INITDIALOG: return (INT_PTR)TRUE; case WM_COMMAND:
    if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
    {
    EndDialog(hDlg, LOWORD(wParam)); return (INT_PTR)TRUE;
    }
    break;
    }
    return (INT_PTR)FALSE;
    }
    				

    Некоторые комментарии к стандартной заготовке

    Мастер создает четыре файла включений к стандартной заготовке, а также два файла реализации. Это сделано в целях сокращения времени компиляции приложения. Дело в том, что при внесении изменений в проект происходит лишь частичная компиляция измененных файлов. Поэтому основные библиотеки подключаются файлом stdafx.cpp. В файлах включений stdafx.h и targetver.h размещены основные определения и ключи компиляции, а в файле resource.h — определения символических констант.
    Рассмотрим подробнее файл standard.cpp.
    Следует обратить внимание на первые две строки головной функции _tWinMain(): UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);
    Эти определения служат лишь указанием компилятору, что параметры hPrevInstance и lpCmdLine не используются и не стоит обращать на них внимания.
    Текстовые строки с именем окна и класса окна, совпадающие по умолчанию с именем проекта, размещаются в ресурсе приложения и загружаются функцией LoadString(). Для редактирования ресурсов приложения используется редактор ресурсов, о нем мы поговорим позднее. Сейчас лишь отметим то, что, при загрузке приложения в память ресурсы загружаются после кода и могут быть извлечены при помощи соответствующего набора функций.
    В функции MyRegisterClass() после заполнения полей структуры WNDCLASSEX происходит регистрация класса окна RegisterClassEx(). Макрос MAKEINTRESOURCE():
    
    #define MAKEINTRESOURCEA(i) ((LPSTR)((ULONG_PTR)((WORD)(i))))
    #define MAKEINTRESOURCEW(i) ((LPWSTR)((ULONG_PTR)((WORD)(i))))
    #ifdef UNICODE
    #define MAKEINTRESOURCE MAKEINTRESOURCEW
    #else
    #define MAKEINTRESOURCE MAKEINTRESOURCEA
    #endif // !UNICODE
    				
    Используется для приведения идентификатора ресурса к типу, необходимому функции LoadIcon().
    Обратите внимание, что здесь используются расширенные версии структуры класса окна и API-функций, на это указывает суффикс "Ex", они отличаются от исход- ных версий лишь дополнительным полем, которое заполняется в данном случае по умолчанию.
    Создание окна выделено в отдельную функцию InitInstance(). Обратите внимание, что дескриптор приложения сохранили на глобальном уровне в переменной hInst. Окно создается так же, как и в листинге 1.1, но с проверкой — удачно ли оно создано? Если обращение к функции CreateWindow() завершилось неудачей, возвращается 0 и работа приложения будет завершена. Отображается окно функцией ShowWindow() с текущим режимом отображения nCmdShow.
    Здесь появилась функция UpdateWindow(), которая проверяет — прорисовалось ли окно? Если окно отображено, функция ничего не делает, иначе функция ждет, пока окно не будет прорисовано. В большинстве случаев можно обойтись без этой функции.
    После того как окно будет отображено, происходит загрузка таблицы клавиш- акселераторов "горячих клавиш", которые создаются в редакторе ресурсов для быстрого обращения к меню приложения. Загрузка их из ресурса приложения осуществляется функцией LoadAccelerators().
    В цикле обработки сообщений появилась строка:
    if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)),
    				
    которая проверяет, не является ли сообщение результатом нажатия на "горячую клавишу"? Если это так, происходит генерация сообщения WM_COMMAND, как и для соответствующего пункта меню, иначе обработка сообщения происходит стандартным способом.
    Обратите также внимание, что прекращение работы приложения происходит с возвращаемым значением (int)msg.wParam. Если приложение завершается обычным образом после сообщения WM_QUIT, то это 0, однако здесь появляется возможность изменить код возвращаемого значения при аварийном завершении приложения.
    Рассмотрим сейчас оконную функцию WndProc(). Две переменные wmId и wmEvent типа int предназначены для хранения дополнительной информации из младшего и старшего слова wParam. Для извлечения их значений используются макросы LOWORD() и HIWORD(). О том, как обрабатываются сообщения при выборе пункта меню, мы поговорим позднее.
    Еще две переменные:
    
    PAINTSTRUCT ps;
    HDC hdc;
    				
    необходимы для вывода в окно при обработке сообщения WM_PAINT. Вывод в окно мы обсудим при рассмотрении следующей задачи в листинге 1.3.
    Функция About() нужна для обработки пункта меню "О программе". Оставим обсуждение этой функции до главы 3.

    Обработка сообщений

    Операционная система способна генерировать сообщения с номерами до 0х400, номера же от 0х400 и далее зарезервированы для пользовательских сообщений. В файле включений winuser.h размещены макроимена системных сообщений. Этот файл вызывается неявно через файл windows.h. Рассмотрим технику обработ- ки наиболее распространенных сообщений Windows.

    Нажатие клавиши

    При нажатии любой алфавитно-цифровой клавиши на клавиатуре вырабатывается сообщение WM_CHAR.
    ПРИМЕЧАНИЕ
    На самом деле генерируются сообщения о нажатии и отпускании клавиши WM_KEYDOWN, WM_KEYUP и лишь затем WM_CHAR.
    Чтобы обработать это сообщение, необходимо добавить в переключатель оконной функции еще одну строку альтернативы:
    
    case WM_CHAR:
    				
    и описать необходимые действия для обработки нажатия клавиши.
    Поставим самую простую задачу — при нажатии на клавишу выводить текущий символ в окне приложения, т. е. обеспечить эхо-печать в одну строку, пока не бес- покоясь о выходе строки за границу окна.
    Для решения этой задачи воспользуемся шаблонным классом basic_string<> и создадим класс String, который будет работать как с С-строкой, так и со строкой Unicode. Для этого в качестве типа данных используем TCHAR.
    ПРИМЕЧАНИЕ
    В библиотеке STL (Standard Template Library) описаны классы string и wstring, ко- торые являются реализацией базового класса basic_string<> для типов char и wchar_t соответственно. В Visual Studio 2010 этот класс размещен в файле xstring и принадлежит стандартному пространству имен std, как и вся библиотека STL.
    Рассмотрим в листинге 1.3 оконную функцию задачи, удалив для простоты обра- ботку сообщений меню.
    
    #include 
    typedef std::basic_string, std::allocator > String;
    LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lPa- ram)
    {
    PAINTSTRUCT ps;
    HDC hdc;
    static String str; switch (message)
    {
    case WM_CHAR:
    str += (TCHAR)wParam; InvalidateRect(hWnd, NULL, TRUE); break;
    case WM_PAINT:
    hdc = BeginPaint(hWnd, &ps);
    TextOut(hdc, 0, 0, str.data(), str.size()); EndPaint(hWnd, &ps);
    break;
    case WM_DESTROY: PostQuitMessage(0); break;
    default: return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
    }
    				
    Для ускорения компиляции не будем подключать всю область стандартных имен и явно укажем область видимости "std::" для всех переменных из этой области.
    У нас имеется две возможности описания переменной str — либо на глобальном уровне, либо в статической области памяти оконной функции.
    
    static String str;
    				
    Дело в том, что после ввода очередного символа оконная функция теряет управле- ние и, если это будет автоматическая переменная, созданная на стеке, она потеряет свое значение после выхода из функции.
    При обработке сообщения WM_CHAR извлекаем очередной символ из младшего слова wParam и добавляем к строке перегруженным оператором "+=".
    Теперь нужно вывести символ в окно. Можно это сделать здесь же, но так поступать не принято. Весь вывод обычно стараются осуществлять при обработке сообщения WM_PAINT. Дело в том, что инициатором перерисовки окна может быть не только само приложение, но и операционная система. Поскольку Windows является системой многозадачной, то окно приложения может быть полностью или частично перекрыто окном другого приложения, или окном системной утилиты. В этом случае возникает необходимость восстановления окна либо его части. Операционная система Windows решает эту задачу, объявляя "недействительным прямоугольником" либо все окно, либо его часть. Такое объявление автоматически приводит к генерации сообщения WM_PAINT для этого окна.
    Если мы будем осуществлять вывод в окно при обработке любого другого сообщения, то потеряем его при перерисовке окна, инициированного системой. То же самое произойдет при "сворачивании" и "распахивании" окна.
    ПРИМЕЧАНИЕ
    В Windows принято, что за содержимое окна несет ответственность приложение, его создавшее. Операционная система может лишь послать сообщение о необходимости перерисовки окна или его части.
    Когда необходимо перерисовать окно, его объявляют недействительным. Для этого имеется функция
    
    				
    InvalidateRect():
    BOOL WINAPI InvalidateRect(HWND hWnd, CONST RECT *lpRect, BOOL bErase),
    которая объявляет недействительный прямоугольник *lpRect в окне hWnd.
    				
    Воспользуемся этим приемом, указывая вторым параметром NULL, что приведет к перерисовке всего окна. Значение TRUE третьего параметра является указанием перерисовать фон окна. Теперь рассмотрим вывод строки в окно приложения в сообщении WM_PAINT. Для этого необходимо получить контекст устройства. В Windows все функции, выво- дящие что-либо в окно, используют в качестве параметра дескриптор контекста устройства hdc, который представляет собой структуру, описывающую свойства данного устройства вывода. В оконной функции опишем эту переменную:
    
    DC hdc;
    				
    В обработчике сообщения WM_PAINT вызовом функции BeginPaint() получим
    
    hdc: HDC WINAPI BeginPaint(HWND hWnd, LPPAINTSTRUCT lpPaint);
    				
    Вся необходимая информация для перерисовки окна будет представлена в структуре PAINTSTRUCT:
    
    struct PAINTSTRUCT {
    HDC hdc; //Контекст устройства
    BOOL fErase; //Если TRUE — фон окна перерисовывается RECT rcPaint; //Недействительный прямоугольник
    BOOL fRestore; //Резерв
    BOOL fIncUpdate; //Резерв
    BYTE rgbReserved[32]; //Резерв
    };
    				
    Теперь можно выводить текст в окно с помощью функции TextOut(): BOOL WINAPI TextOutW(HDC hdc, int x, int y, LPCWSTR str, int len); которая принимает в качестве параметров контекст устройства hdc, (x,y) — координаты начала вывода текста, указатель на символьный массив str и длину выводимой строки len. Среди GDI-функций нет функции, выводящей отдельный символ, поэтому мы будем выводить всю строку полностью с начальной позиции, чтобы не вычислять каждый раз позицию "нового" символа. Функция TextOut() не требует строкового типа, ей достаточно указателя первого символа массива, поскольку следующим параметром задается количество выводимых символов, поэтому мы можем воспользоваться методами класса String и получить требуемый указатель массива символов и размер строки:
    
    TextOut(hdc, 0, 0, str.data(), str.size());
    				
    Вывод осуществляется с начала окна (0,0). По умолчанию система координат имеет начало в левом верхнем углу клиентской области окна (т. е. внутри рамки, ниже заголовка и строки меню), ось x направлена по горизонтали вправо, ось y — вниз (рис. 1.10). Одна логическая единица равна 1 пикселу.

    Рис. 1.10. Вид окна программы эхо-печати

    Завершает обработчик сообщения функция EndPaint():
    
    				BOOL WINAPI EndPaint(HWND hWnd, CONST PAINTSTRUCT *lpPaint)
    				
    которая обеспечивает освобождение контекста устройства. Необходимо учитывать, что контекст устройства является критически важным ресурсом операционной системы, и, после того как необходимость в данном контексте отпадет, его нужно уничтожить, т. е. освободить этот ресурс.
    ПРИМЕЧАНИЕ
    Обычно Windows-приложения работают с "общим контекстом экрана", который и создается в стандартной заготовке. Однако приложение может создавать и "частные контексты экрана", которые существуют все время жизни приложения. Для этого класс окна должен быть зарегистрирован со стилем CS_OWNDC. Однако это приводит к неэффективному расходованию оперативной памяти.
    Еще одно обстоятельство нужно учитывать при выводе в окно — только пара функций BeginPaint() и EndPaint() удаляют из очереди сообщение WM_PAINT после его обработки. Если же сообщение не удалить, система будет "до бесконечности" перерисовывать окно.
    Сообщение WM_PAINT является асинхронным и имеет достаточно низкий приоритет. Это приводит к тому, что может сложиться ситуация, когда окно получает сообщение WM_PAINT, а предыдущее сообщение еще не обработано. В этом случае операционная система "складывает" недействительные прямоугольники и объединяет перерисовку окна в одном сообщении.
  • Сообщение мыши

    Нажатие на кнопки мыши приводит к ряду сообщений, вот некоторые из них:
    - WM_LBUTTONDOWN — нажатие на левую кнопку мыши;
    - WM_LBUTTONUP — отпускание левой кнопки мыши;
    - WM_RBUTTONDOWN — нажатие на правую кнопку мыши;
    - WM_RBUTTONUP — отпускание правой кнопки мыши;
    - WM_MOUSEMOVE — перемещение мыши.
    Рассмотрим в листинге 1.4 тестовую программу для обработки этих сообщений. Причем покажем, как можно осуществить вывод в окно непосредственно в обработчике сообщения о нажатии кнопки мыши. Для подобной задачи это вполне допустимо.
    
    				
    TCHAR *r_str = _T("Нажата правая кнопка мыши"); TCHAR *l_str = _T("Нажата левая кнопка мыши");
    LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lPa- ram)
    {
    HDC hdc; int x, y;
    switch (message)
    {
    case WM_RBUTTONDOWN:
    x = LOWORD(lParam); y = HIWORD(lParam); hdc = GetDC(hWnd);
    TextOut(hdc, x, y, r_str, _tcsclen(r_str)); ReleaseDC(hWnd, hdc);
    break;
    case WM_LBUTTONDOWN:
    
    x = LOWORD(lParam); y = HIWORD(lParam); hdc = GetDC(hWnd);
    TextOut(hdc, x, y, l_str, _tcsclen(l_str)); ReleaseDC(hWnd, hdc);
    break; case WM_RBUTTONUP: case WM_LBUTTONUP:
    InvalidateRect(hWnd, NULL, TRUE); break;
    case WM_DESTROY: PostQuitMessage(0); break;
    default: return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
    }
    				
    Текстовые строки для вывода в окне мы описали на глобальном уровне, что в данном случае не обязательно. При обработке сообщения о нажатии кнопки мыши можно извлечь текущие координаты курсора из младшего и старшего слова lParam: x = LOWORD(lParam); y = HIWORD(lParam);
    Контекст устройства получаем из функции GetDC(), передавая ей параметром дескриптор окна hWnd. Выводим текст по координатам курсора. Единственная проблема здесь — это вычисление длины строки. Если бы это была С-строка, мы обра- тились бы к функции strlen(), если это строка Unicode — wcslen(). А в общем случае используем макрос _tcsclen, который обеспечит правильный выбор. Соответствующий макрос размещен в файле tchar.h.
    Освобождаем контекст устройства функцией ReleaseDC().
    При отпускании кнопки мыши хотелось бы очистить окно — это можно сделать, объявив его недействительным функцией InvalidateRect(). Причем мы вообще убрали обработчик сообщения WM_PAINT, нам достаточно того, что сделает функция обработки сообщения по умолчанию DefWindowProc().
    Иногда бывает полезно отследить состояние регистровых клавиш и при нажатии на кнопку мыши, коды этих клавиш передаются в переменную wParam и могут быть извлечены при обработке сообщения мыши, например:
    
    case WM_LBUTTONDOWN:
    if (MK_SHIFT & wParam)
    if (MK_CONTROL & wParam)
    {
    //Нажаты клавиши Shift и Ctrl
    }
    
    {
    
    }
    else
    
    {
    
    else
    
    //Нажата клавиша Shift
    
    if (MK_CONTROL & wParam)
    
    }
    
    break;
    //Нажата клавиша Ctrl
    
    else
    {
    //Регистровые клавиши не нажаты
    }
    
    				
    Можно упростить условный оператор, используя возможность логического сложения условий. Так, для проверки одновременного нажатия клавиш и при щелчке левой кнопки мыши запишем выражение:
    
    	if (MK_SHIFT|MK_CONTROL|MK_LBUTTON == wParam). . .
    	
    				
  • Создание окна

    При создании окна генерируется сообщение WM_CREATE еще до его отображения, что позволяет производить некоторые начальные установки. Мы продемонстриру- ем использование этого сообщения в следующем примере.
  • Таймер

    В программе можно установить один или несколько таймеров, которые позволяют производить отсчет времени. Таймер создается функцией SetTimer():
    UINT_PTR WINAPI SetTimer(HWND hWnd, UINT_PTR nIDEvent, UINT uElapse, TIMERPROC lpTimerFunc);
    				
    Параметры функции:
    - hWnd — дескриптор окна;
    - nIDEvent — номер таймера, задается целым числом;
    - uElapse — временной интервал таймера в миллисекундах;
    - lpTimerFunc — указатель на функцию, вызываемую при обработке таймера. Функция таймера определена следующим образом:
    
    VOID CALLBACK TimerProc(
    HWND hWnd, // Дескриптор окна UINT uMsg, // WM_TIMER сообщение
    UINT_PTR idEvent, // Номер таймера
    DWORD dwTime // Текущее системное время
    );
    				
    если lpTimerFunc = NULL, для обработки сообщения таймера вызывается функция окна-владельца. Обычно так и поступают.
    Рассмотрим в листинге 1.5 задачу отсчета времени с момента начала работы приложения. Создадим таймер при создании окна в обработчике сообщения WM_CREATE:
    SetTimer(hWnd, 1, 1000, NULL);
    				
    Таймеру присвоим номер 1, а интервал отсчета зададим в 1000 миллисекунд (1 секунда), в качестве же обработчика таймера используем оконную функцию.
    В обработчике сообщения от таймера WM_TIMER увеличим переменную t на 1 секунду и объявляем окно недействительным:
    t++;
    InvalidateRect(hWnd, NULL, TRUE);
    				
    Cтроку текста для вывода в окне сформируем в обработчике сообщения WM_PAINT:
    _tcscat(str, _itot(t, s, 10)); TextOut(hdc, 0, 0, str, _tcsclen(str));
    				
    К строке str добавим значение переменной t, для чего преобразуем ее в символьный вид. Здесь мы опять воспользовались макросами из tchar.h:

    С-строка — _tcscat преобразуется в strcat, _itot в _itoa;
    Unicode — _tcscat преобразуется в wcscat, _itot в _itow.

    В функциях _itoa(), _itow() для преобразования целого числа в строку, третий параметр — основание системы счисления 10, возвращаемое значение — указатель на строку результата s. Функцией strcat() или wcscat() "приклеим" к исходной строке с текстом "Секунды: " и выведем полученную строку функцией TextOut().
    Перед закрытием окна уничтожим таймер функцией
    
    KillTimer(): BOOL WINAPI KillTimer(HWND hWnd, UINT_PTR uIDEvent);
    Второй параметр функции uIDEvent — номер таймера — имеет тип UINT_PTR, эквивалентный unsigned int для 32-разрядных приложений и введен для совмести- мости с 64-разрядными приложениями, где он уже ассоциируется с unsigned int64.
    Листинг 1.5. Оконная функция для таймера
    
    LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
    PAINTSTRUCT ps;
    HDC hdc; static int t;
    TCHAR s[10], str[20] = _T("Секунды: "); switch (message)
    Интерфейс Windows-приложения 29
    image
    
    {
    case WM_CREATE :
    SetTimer(hWnd, 1, 1000, NULL); break;
    case WM_TIMER :
    t++;
    InvalidateRect(hWnd, NULL, TRUE); break;
    case WM_PAINT:
    hdc = BeginPaint(hWnd, &ps);
    _tcscat(str+9, _itot(t, s, 10)); TextOut(hdc, 0, 0, str, _tcsclen(str)); EndPaint(hWnd, &ps);
    break; case WM_DESTROY:
    KillTimer(hWnd, 1); PostQuitMessage(0); break;
    default: return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
    }
    
    				
  • Content

    Content

    Content

    Content

    Content

    Content

    Content

    Content

    Рисование в окне

    При рисовании в окне можно использовать достаточно большой набор GDI- функций. Действуют следующие соглашения: все линии рисуются текущим пером, а области заполняются текущей кистью. Как устанавливать перо и кисть мы обсудим позднее, а пока будем рисовать пером и кистью по умолчанию. Все эти функции возвращают ненулевое значение в случае успешного выполнения, и 0 — в случае ошибки. Графические функции работают с логическими координатами, одна логическая единица по умолчанию равна одному пикселу.
    ПРИМЕЧАНИЕ
    Пиксел — точка на экране монитора. Размер зависит от выбранного разрешения.
    По умолчанию устанавливается графический режим с началом координат в левом верхнем углу клиентской области окна, ось x направлена вправо, ось y — вниз.
  • Рисование линии

    Для того чтобы нарисовать линию, используется функция LineTo(): BOOL WINAPI LineTo(HDC hdc, int x, int y); где hdc — дескриптор контекста устройства, x, y — координаты конца линии. Начало линии определяется положением текущей позиции рисования. При создании окна текущая позиция определяется в начале координат (0,0). Если же необходимо нарисовать линию из другой точки, положение текущей позиции рисования может быть изменено.
  • Установка текущей позиции

    Функция MoveToEx():
    BOOL WINAPI MoveToEx(HDC hdc, int x, int y, LPPOINT oldPoint);
    				
    устанавливает текущую позицию в точку с координатами (x,y) и передает в структуру POINT координаты предыдущей позиции.
    typedef struct tagPOINT
    { LONG x; LONG y;
    } POINT;
    				
    Если последний параметр NULL, то предыдущие координаты не сохраняются.
    Обычно эти функции используются в паре, обе они возвращают ненулевое значе- ние, в случае успешного завершения, и 0 — в случае ошибки.
    Например, если нам нужно нарисовать линию между двумя точками, можно сде- лать это так:
    MoveToEx(hdc, 50, 100, NULL);
    LineTo(hdc, 350, 100);
    				
  • Определение размера клиентской области

    При выводе данных в окно необходимо определить фактический размер этого окна, точнее, его клиентской области. Для решения данной задачи существует функция GetClientRect():
    
    BOOL WINAPI GetClientRect(HWND hWnd, LPRECT lpRect);
    	
    Размеры клиентской области окна возвращаются в структуре RECT: typedef struct tagRECT
    
    { LONG left; //x – координата левого верхнего угла
    LONG top; //y - координата левого верхнего угла LONG right; //x – координата правого нижнего угла LONG bottom; //y - координата правого нижнего угла
    } RECT;	
    ПРИМЕЧАНИЕ
    В данном контексте применения функции GetClientRect() поля left и top струк- туры RECT будут равны 0, а поля right и bottom будут равны ширине и высоте кли- ентской области окна.
    Имеется, однако, более простое решение для определения клиентской области окна. Воспользуемся сообщением WM_SIZE, которое генерируется системой при создании окна после сообщения WM_CREATE и при каждом изменении его размеров:
    case WM_SIZE:
    sx = LOWORD(lParam); //ширина окна
    sy = HIWORD(lParam); //высота окна break;
    				
    lParam возвращает в младшем слове ширину, а в старшем слове — высоту клиентской области окна. Если мы опишем две статические переменные целого типа sx и sy, то всегда будем иметь текущую ширину и высоту окна.
    Теперь у нас достаточно информации, чтобы рассмотреть простой пример, приведенный на рис. 1.11, где мы построили сетку горизонтальными и вертикальными линиями с шагом в 1/10 размера окна, см. листинг 1.6. Причем сетка должна перестраиваться при изменении размеров окна.
    
    LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
    PAINTSTRUCT ps;
    HDC hdc; int x, y;
    static int sx, sy; switch (message)
    {
    case WM_SIZE:
    sx = LOWORD(lParam); sy = HIWORD(lParam); break;
    case WM_PAINT:
    hdc = BeginPaint(hWnd, &ps); for (x = 0; x < sx; x += sx/10)
    {
    MoveToEx(hdc, x, 0, NULL); LineTo(hdc, x, sy);
    }
    for (y = 0; y < sy; y += sy/10)
    {
    MoveToEx(hdc, 0, y, NULL); LineTo(hdc, sx, y);
    }
    EndPaint(hWnd, &ps); break;
    case WM_DESTROY: PostQuitMessage(0); break;
    default: return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
    }
    				

    Рис. 1.11. Построение сетки

  • Рисование прямоугольника

    Нарисовать прямоугольник можно при помощи функции: BOOL WINAPI Rectangle(HDC hdc, int x1, int y1, int x2, int y2); где (x1,y1) — координаты левого верхнего угла прямоугольника, а (x2,y2) — координаты правого нижнего угла. Прямоугольник заполняется текущей кистью, поэтому, если в предыдущем примере (см. листинг 1.6) добавить в обработчик сообщения WM_PAINT следующую строку: Rectangle(hdc, sx/4, sy/4, sx*3/4, sy*3/4); мы получим прямоугольник в половину окна, расположенный по центру.
  • Рисование эллипса

    Функция для отображения эллипса имеет те же параметры, поскольку эллипс определяется ограничивающим его прямоугольником:
    BOOL WINAPI Ellipse(HDC hdc, int x1, int y1, int x2, int y2);
    внутренняя область также заполняется текущей кистью. Таким образом, если мы добавим строку:
    Ellipse(hdc, sx/3, sy/4, sx*2/3, sy*3/4);
    то получим внутри прямоугольника эллипс (рис. 1.12).

    Рис. 1.12. Прямоугольник и эллипс в ¼ окна

  • Рисование точки

    Функция SetPixel() позволяет вывести в окно одну точку (пиксел):
    COLORREF WINAPI SetPixel(HDC hdc, int x, int y, COLORREF color);
    где hdc — контекст устройства, (x,y) — координаты точки, color — цвет точки. Функция возвращает прежний цвет точки в случае успешного завершения, и -1 при ошибке.
    Тип COLORREF представляет длинное целое число, где три младших байта кодируют соответственно красный, синий, зеленый цвета. Старший байт всегда 0. Числовое значение байта определяет интенсивность каждого цвета, а цвет точки определяется смешиванием трех базовых цветов. Для получения нужного цвета проще всего воспользоваться предопределенным в файле wingdi.h макросом RGB(), который описан как:
    #define RGB(r,g,b) ((COLORREF)(((BYTE)(r)|((WORD)((BYTE)(g))<<8))|(((DWORD)(BYTE)(b))<<16)))
    Например, если нам нужен чисто зеленый цвет, мы могли бы определить перемен- ную color так:
    COLORREF color = RGB(0,255,0);
    Эту функцию редко используют для рисования, поскольку точка в 1 пиксел выглядит достаточно мелко.
    С помощью листинга 1.7, используя рассмотренные выше графические примитивы, построим кардиоиду, уравнение которой в полярных координатах:
    	
    #define _USE_MATH_DEFINES
    #include  const int R = 100;
    LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lPa- ram)
    {
    PAINTSTRUCT ps;
    HDC hdc;
    static int sx, sy;
    int a, b, x, y; //Экранные координаты double angle; //Физические координаты switch (message)
    {
    case WM_SIZE:
    sx = LOWORD(lParam); //Ширина окна sy = HIWORD(lParam); //Высота окна break;
    case WM_PAINT:
    
    hdc = BeginPaint(hWnd, &ps); a = sx/2;
    b = sy/2;
    MoveToEx(hdc, 0, b, NULL); LineTo(hdc, sx, b); MoveToEx(hdc, a, 0, NULL); LineTo(hdc, a, sy); MoveToEx(hdc, a, b, NULL);
    for (angle = 0.0; angle < 2*M_PI; angle += 0.1)
    {
    x = a + R*(1 - cos(angle))*cos(angle); y = b - R*(1 - cos(angle))*sin(angle); LineTo(hdc, x, y);
    }
    EndPaint(hWnd, &ps); break;
    case WM_DESTROY: PostQuitMessage(0); break;
    default: return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
    }
    				
    				
    Нам необходим файл включений cmath, где определены основные математические функции. Для доступа к константам, заданным в этом файле, необходимо разрешить их использование, определив символическую константу:
    
    #define _USE_MATH_DEFINES			
    				
    Теперь описываем экранные координаты типа int и физическую координату типа double. Размеры окна определим, как обычно, в сообщении WM_SIZE.
    В сообщении WM_PAINT строим координатные оси, устанавливаем текущую координату в центре окна и в цикле строим фигуру ломаной линией (рис. 1.13). В качестве масштабного множителя используем константу R, равную 100.

    Рис. 1.13. Кардиоида

    Обратите внимание, что координаты x и y мы отсчитываем от центра окна, причем для координаты y выражение записано со знаком минус, поскольку ось y в окне направлена вниз.
    Здесь мы воспользовались побочным эффектом функции LineTo(), которая уста- навливает текущей координатой конечную точку линии.