Учебник
Теоретический материал.
Глава III
Окна и элементы управления
Кроме главного окна, приложение может создавать и другие окна: перекрываемые WS_OVERLAPPED, всплывающие WS_POPUP, дочерние WS_CHILD. Каждое окно должно иметь свою оконную функцию, куда передаются посылаемые операционной системой сообщения.
Начнем рассмотрение с создания дочерних окон, особенностью которых является то, что они располагаются в клиентской области родительского окна и автоматически уничтожаются при его закрытии.
Для создания окна используется функция CreateWindow() или ее расширение Crea- teWindowEx():
HWND WINAPI CreateWindowExW(DWORD dwExStyle, LPCWSTR lpClassName, LPCWSTR lpWindowName, DWORD dwStyle, int X, int Y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam);
Функции отличаются лишь первым параметром расширенных стилей dwExStyle.Если посмотреть файл включений winuser.h, то можно увидеть, что все вызовы функции CreateWindow()заменяются ее расширенной версией, где первый параметр имеет нулевое значение.
ПРИМЕЧАНИЕВ главе 1 мы уже рассматривали значение аргументов этой функции при анализе "скелета" Windows-приложения. Вспомним последовательность шагов при создании окна:
Параметр hMenu используется для задания уникального идентификатора дочернего окна child_ID. В этом случае приходится задавать явное преобразование типа (HMENU)child_ID.
- Создается переменная типа "Класс окна" WNDCLASSEX.
- Регистрируется класс окна функцией RegisterClassEx().
- Вызывается функция CreateWindowEx() для создания окна.
- При помощи функции ShowWindow() окно отображается.
- Поскольку отображение окна происходит асинхронно, то обычно используют функцию UpdateWindow() для принудительной прорисовки окна.
ПРИМЕЧАНИЕРассмотрим процесс создания дочерних окон на примере реализации нескольких учебных задач.
Сейчас, как правило, используют расширенный класс окна WNDCLASSEX вместо WNDCLASS и, соответственно, расширенную функцию регистрации класса окна RegisterClassEx().
Дочерние окна
Построим приложение, имитирующее широко известную игру "крестики-нолики", где вторым партнером будет выступать компьютер (рис. 3.1). В качестве заготовки используем стандартный Win32-проект. Добавим один пункт меню New для запуска новой игры. Текст оконной функции приложения приведен в листинге 3.1.
Рис. 3.1. Крестики-нолики
Основная идея, которая реализуется в этой программе, заключается в том, что вместо рисования прямоугольников и вычисления затем позиции нажатия левой кнопки мыши, мы создадим 9 дочерних окон и будем обрабатывать сообщение о щелчке мышью в соответствующем окне.
LRESULT CALLBACK ChildProc(HWND, UINT, WPARAM, LPARAM);
TCHAR ChildClassName[MAX_LOADSTRING] = _T("WinChild"); ATOM MyRegisterChildClass()
{
WNDCLASSEX wcex = { 0 };
wcex.cbSize = sizeof(WNDCLASSEX); wcex.lpfnWndProc = ChildProc; wcex.hInstance = hInst;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszClassName = ChildClassName; return RegisterClassEx(&wcex);
}
static HFONT newFont; static HWND hChild[9]; unsigned char k[9] = { 0 };
char text[] = { ' ', 'X', '0' };
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lPa- ram)
{
int i;
static int sx, sy; switch (message)
{
case WM_CREATE: MyRegisterChildClass(); for (i = 0; i < 9; i++)
hChild[i] = CreateWindow(ChildClassName, NULL, WS_CHILD | WS_DLGFRAME | WS_VISIBLE, 0, 0, 0, 0, hWnd, NULL, hInst, NULL);
break; case WM_SIZE:
if (wParam == SIZE_MINIMIZED) break; //Кнопка свертывания окна sx = LOWORD(lParam)/3; //Ширина дочернего окна
sy = HIWORD(lParam)/3; //Высота дочернего окна if (newFont) DeleteObject(newFont);
newFont = CreateFont(min(sx,sy), 0, 0, 0, FW_NORMAL, 0, 0, 0, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, _T("Arial"));
for (i = 0; i < 9; i++)
{ MoveWindow(hChild[i],(i%3)*sx, (i/3)*sy, sx, sy, TRUE); UpdateWindow(hChild[i]);
}
break;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case ID_NEW:
for (i = 0; i < 9; i++)
{
k[i] = 0;
InvalidateRect(hChild[i], NULL, 1); UpdateWindow(hChild[i]);
}
break;
case IDM_EXIT: DestroyWindow(hWnd); break;
default: return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_DESTROY: PostQuitMessage(0); break;
default: return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
/////////////////////////////////////////////////////////////////////
LRESULT CALLBACK ChildProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lPa- ram)
{
PAINTSTRUCT ps;
HDC hdc; RECT rt;
int i, s; char *ch;
switch (message)
{
case WM_LBUTTONDOWN :
for (i = 0; hWnd != hChild[i]; i++); if (k[i]) break; else k[i] = 1; InvalidateRect(hWnd, NULL, 1); UpdateWindow(hWnd);
srand(lParam);
for(i = s = 0; i< 9; i++) if (k[i]) s++;
if(s == 9) MessageBox(hWnd, _T("Для следующего сеанса выбирайте\ New"), _T("Конец игры"), MB_OK | MB_ICONQUESTION);
else
{
while(true)
{ s = rand()*9/(RAND_MAX+1); if (k[s]) continue;
k[s] = 2;
InvalidateRect(hChild[s], NULL, 1); UpdateWindow(hChild[s]);
break;
}
}
break; case WM_PAINT:
for (i = 0; hWnd != hChild[i]; i++); if(k[i])
{
ch = text+k[i];
hdc = BeginPaint(hWnd, &ps); GetClientRect(hWnd, &rt); SelectObject(hdc, newFont);
DrawTextA(hdc,ch,1,&rt,DT_SINGLELINE|DT_CENTER|DT_VCENTER); EndPaint(hWnd, &ps);
} //Фоновая закраска окна
else DefWindowProc(hWnd, message, wParam, lParam); break;
default: return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
При созднии главного окна приложения в сообщении WM_CREATE создадим 9 дочерних окон. Для всех дочерних окон будем использовать один класс окна и, соответственно, одну оконную функцию ChildProc. В локальной функции MyRegisterChildClass() определим класс окна. Поскольку многие поля переменной нужно оставить со значением по умолчанию, обнулим всю структуру при опи- сании и заполним лишь необходимые поля. После чего зарегистрируем класс окна с именем "WinChild" .
Так как при обработке сообщения WM_CREATE главное окно приложения еще не прорисовано, дочерние окна создаем нулевого размера. Укажем стиль WS_VISIBLE для того, чтобы окно было отображаемым. Стиль рамкиWS_DLGFRAME запретит изменение размеров окна при буксировке его границ.
Определим на глобальном уровне массив типа HWND дескрипторов дочерних окон hChild[9], дескриптор шрифтаnewFont, а также статический массив k[9] типа unsigned char, где мы будем хранить признак заполнения окна:
0 — окно не занято;
1 — пользователь щелкнул левой кнопкой мыши по этому окну, 'Х';
2 — компьютер выбрал это окно для вывода, '0'.
Для корректировки размера дочерних окон используем обработчик сообщения WM_SIZE, поскольку это сообщение генерируется при любом изменении размера главного окна.
Если сообщение связано с кнопкой минимации и wParam == SIZE_MINIMIZED, перерисовывать окно нет необходимости, и мы заканчиваем обработку сообщения оператором break.
Ширину и высоту главного окна получим из параметра младшего и старшего слова
lParam. Размеры дочерних окон установим в 1/3 от полученного размера.
Создадим шрифт, определив его размер по размеру созданного окна. Дескриптор шрифта определен на глобальном уровне, он будет доступен и в оконной функции дочернего окна. Однако при изменении размера окна мы создаем новый шрифт, предварительно удаляя текущий, если он уже определен.
В цикле по переменной i нарисуем 9 окон функцией MoveWindow():
BOOL WINAPI MoveWindow(HWND hWnd, int x, int y, int nWidth, int nHeight, BOOL bRepaint);
Функция переопределяет позицию вывода окна (x,y) и его размеры nWidth, nHeight. Если последний параметр отличен от 0, генерируется сообщение WM_PAINT для перерисовки окна.
Координаты левого верхнего угла окна определяются формулами:
x = (i%3)*sx; y = (i/3)*sy;
Действительно, в клиентской области главного окна координату x можно вычислить как остаток от деления i на 3, умноженный на ширину дочернего окна; а координату y как целую часть от деления i на 3, умноженную на высоту дочернего окна.
Здесь, однако, необходимо использовать функцию UpdateWindow(), иначе окна не успеют перерисоваться.
Для того чтобы иметь возможность запустить новый сеанс игры, добавим в меню программы пункт New с идентификатором ID_NEW. В обработчике этой команды очистим массив k и перерисуем все дочерние окна.
Основная же логика задачи сосредоточена в оконной функции дочерних окон. Мы будем использовать одну функцию для всех 9 окон, а различать их будем по дескрипторам, которые сохранили в массиве hChild на глобальном уровне.
При обработке сообщения WM_LBUTTONDOWN сначала мы должны определить индекс окна, пославшего сообщение. Это можно сделать, организовав в цикле перебор дескрипторов дочерних окон и сравнив их с дескриптором окна, пославшего сообщение:
for (i = 0; hWnd != hChild[i]; i++);
По завершении цикла переменная i и будет искомым индексом.
Если значение элемента массива k[i] отлично от нуля, мы игнорируем эту операцию, прекращая обработку сообщения оператором break. Если же окно свободно, присваиваем элементу массива k[i] значение 1.
После этого нужно перерисовать дочернее окно последовательным вызовом функций InvalidateRect() иUpdateWindow().
Сейчас компьютер должен выбрать окно, куда он выведет ответный "нолик". Для этого мы используем функцию генерации случайного числа. Однако функция будет генерировать одну и ту же последовательность случайных чисел, что нас вряд ли устроит. Для того чтобы внести некоторое разнообразие в выборе ответа, мы ввели функцию srand(), которая задает различные числовые последовательности, но их выбор опять же зависит от ее аргумента, в качестве которого мы взяли составленный из координат курсора мыши. Так что, щелкая мышью в разных частях окна, мы получим разные последовательности случайных чисел. Прием, конечно, примитивный, но действенный.
Однако предварительно проверим, не все ли окна уже заняты? Просто подсчитаем количество ненулевых значений массива k и, если это количество равно 9, выведем окно сообщения, что игра завершена.
В противном случае в бесконечном цикле while(true) {. . .} генерируем случайное число в диапазоне от 0 до 8:
s = rand()*9/(RAND_MAX+1);
Мы учли, что функция rand() возвращает число от 0 до RAND_MAX = 0x7fff.
Проверяем, не занято ли окно с индексом s? Если не занято, присваиваем элементу массива k[s] значение 2 и перерисовываем это окно. Если же окно уже занято, то выполняем следующий шаг цикла, и так далее, пока не попадем на свободное окно. А такое окно обязательно существует, поскольку мы проверили, что занятых окон меньше 9.
Теперь осталось реализовать обработку сообщения WM_PAINT.
Начнем с определения индекса активного окна. Опять проверим, не является ли окно пустым? В этом случае ничего выводить не нужно, но для очистки окна лучше воспользоваться обработчиком сообщений по умолчанию DefWindowProc(), который закрасит окно фоновой кистью.
Если окно не является пустым, определим символ для вывода в окно. Для этого установим указатель ch на символ 'X', если k[i] == 1; или на символ '0', если k[i] == 2. Мы специально ввели в массив text первый символ пробела, чтобы упростить выражение:
ch = text+k[i];
Определим дескриптор контекста устройства BeginPaint(), вычислим ограничивающий прямоугольник окна функцией GetClientRect() и выберем в качестве текущего шрифт newFont.
Функцией DrawTextA() выведем один символ в центр окна.
Для этого укажем флаги форматирования текста: DT_SINGLELINE|DT_CENTER| DT_VCENTER. Функция EndPaint()завершает обработку сообщения WM_PAINT.
Всплывающие окна
Всплывающие окна могут быть выведены в любом месте рабочего стола и всегда появляются на переднем плане, в отличие от дочерних, которые размещаются в клиентской области родительского окна. Рассмотрим технику создания всплывающих окон на примере приложения, строящего xy-график.
Обычно графические построения не производятся в главном окне программы, для этого создают отдельное окно. Поставим задачу: создать программу, которая читает текстовый файл с массивом числовых данных, расположенных в двух столбцах, и строит стандартный xy-график. В качестве основы используем программу чтения текстового файла (см. листинг 2.3). Для упрощения кода перенесем описание вектора v и итератора it на глобальный уровень.
На глобальном уровне введем имя нового класса окна и опишем прототип оконной функции построения графика:
TCHAR WinClassGraphName[MAX_LOADSTRING] = _T("ChildClass"); LRESULT CALLBACK WndGraph(HWND, UINT, WPARAM, LPARAM);
Для вывода графика добавим пункт меню xy-график с идентификатором ID_LINE.
Обработчик этой команды разместим в функции главного окна:
case ID_LINE :
if (IsWindow(hGraph)) break; RegisterGraphClass();
hGraph = CreateWindow(WinClassGraphName, _T("xy-график"), WS_SYSMENU | WS_POPUP | WS_VISIBLE | WS_THICKFRAME | WS_CAPTION,
sx/4, sy/4, min(sx, sy), min(sx, sy), hWnd,0, hInst, NULL); break;
Стили окна обеспечат вывод всплывающего окна WS_ POPUP, ограниченного тонкой
рамкой WS_THICKFRAME, с заголовком WS_CAPTION и системным меню WS_SYSMENU, отображаемое сразу после создания; стиль WS_VISIBLE.
Дескриптор опишем в оконной функции главного окна:
static HWND hGraph;
Для предотвращения повторного создания окна предусмотрим проверку его существования:
if (IsWindow(hGraph)) break;
Положение всплывающего окна определяется в системе координат рабочего стола. Выберем такие параметры:
sx/4, sy/4, min(sx, sy), min(sx, sy)
ПРИМЕЧАНИЕРегистрацию класса окна выделим в отдельную функцию:
Для окна графика можно было бы выбрать стиль дочернего окна WS_CHILD. В этом случае, однако, необходим дополнительный стиль WS_EX_NOPARENTNOTIFY, чтобы предотвратить завершение приложения при закрытии дочернего окна.
ATOM RegisterGraphClass()
{
WNDCLASSEX wcgraph = {0};
wcgraph.cbSize = sizeof(WNDCLASSEX);
wcgraph.style = CS_HREDRAW | CS_VREDRAW; wcgraph.lpfnWndProc = WndGraph; wcgraph.hInstance = hInst;
wcgraph.hCursor = LoadCursor(NULL, IDC_CROSS); wcgraph.hbrBackground = (HBRUSH) (COLOR_WINDOW+1);
Окна и элементы управления 111
wcgraph.lpszClassName = WinClassGraphName;
wcgraph.hIconSm = LoadIcon(hInst,MAKEINTRESOURCE(IDI_ICON1)); return RegisterClassEx(&wcgraph);
}
ПРИМЕЧАНИЕ
Мы сделали присваивание wcgraph = {0}, чтобы не заполнять нулевые поля структуры WNDCLASSEX. Для графического окна используем курсор IDC_CROSS и новую пиктограмму, которую мы импортировали в наш проект. Это можно сделать в контекстном меню Add Resource… окна Resource View (рис. 3.2).

Рис. 3.2. Диалоговое окно Add Resource
По умолчанию новой иконке присваивается идентификатор IDI_ICON1. Рассмотрим оконную функцию всплывающего окна (листинг 3.2).
const int scaleX = 8; //Метки по оси x const int scaleY = 4; //Метки по оси y
const int indent = 25; //Отступ для вывода меток оси х struct DOUDLE_POINT { double x, y; };
const int GRAPHSIZE = 1200; const int GRAPHWIDTH = 1000;
LRESULT CALLBACK WndGraph(HWND hWnd, UINT message, WPARAM wParam, LPARAM lPa- ram)
{
PAINTSTRUCT ps;
HDC hdc;
static HPEN hline;
static HBRUSH hrect; RECT rt;
static int n, sx, sy, kx, ky;
static double max_x, max_y, min_x, min_y; int i, x, y;
static POINT *pt; TCHAR s[20]; DOUDLE_POINT t;
double z, hx, hy;
static DOUDLE_POINT *xy; switch (message)
{ case WM_CREATE:
if((n = v.size()) == 0)
{
MessageBox(hWnd, _T("Загрузите файл"), _T("Нет данных"), MB_OK | MB_ICONHAND);
DestroyWindow(hWnd); return 1;
}
pt = new POINT[n];
xy = new DOUDLE_POINT[n];
for (it = v.begin(), i = 0; i < n; i++, it++)
{
if(sscanf(it->c_str(),"%lf %lf",&t.x, &t.y) != 2)
{
MessageBoxA(hWnd, it->c_str(), "Ошибка данных", MB_OK| MB_ICONHAND);
DestroyWindow(hWnd); return 1;
}
xy[i] = t;
}
max_x = min_x = xy[0].x; max_y = min_y = xy[0].y; for (i = 1; i < n; i++)
{
if (max_x < xy[i].x) max_x = xy[i].x;
else if (min_x > xy[i].x) min_x = xy[i].x; if (max_y < xy[i].y) max_y = xy[i].y;
else if (min_y > xy[i].y) min_y = xy[i].y;
}
hline = CreatePen(PS_SOLID, 6, RGB(0, 0, 255)); hrect = CreateSolidBrush(RGB(255,0,0));
hx = max_x - min_x; hy = max_y - min_y;
for (i = 0; i < n; i++)
{
pt[i].x = int((xy[i].x - min_x)*GRAPHWIDTH/hx + 0.5); pt[i].y = int((xy[i].y - min_y)*GRAPHWIDTH/hy + 0.5);
}
break; case WM_SIZE:
sx = LOWORD(lParam); sy = HIWORD(lParam); break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps); hx = (max_x - min_x)/scaleX; hy = (max_y - min_y)/scaleY;
SetMapMode(hdc, MM_ANISOTROPIC); SetWindowExtEx(hdc, GRAPHSIZE, -GRAPHSIZE, NULL);
SetViewportExtEx(hdc, sx, sy, NULL); SetViewportOrgEx(hdc, 2*indent, sy-indent, NULL); SetTextAlign(hdc, TA_RIGHT | TA_TOP);
for (z = min_x, i = 0; i <= scaleX; z += hx, i++)
{
x = int((z - min_x)*GRAPHWIDTH/(max_x - min_x) + 0.5);
_stprintf(s, _T("%.1lf"), z); TextOut(hdc, x, 0, s, _tcslen(s)); MoveToEx(hdc, x, -10, NULL); LineTo(hdc, x, 10);
}
MoveToEx(hdc, 0, 0, NULL);
LineTo(hdc, GRAPHWIDTH, 0);
SetTextAlign(hdc, TA_RIGHT | TA_BOTTOM);
for (z = min_y, i = 0; i <= scaleY; z += hy, i++)
{
y = int((z - min_y)*GRAPHWIDTH/(max_y - min_y) + 0.5);
_stprintf(s, _T("%.1lf"), z); TextOut(hdc, 0, y, s, _tcslen(s)); MoveToEx(hdc, -10, y, NULL); LineTo(hdc, 10, y);
}
MoveToEx(hdc, 0, 0, NULL);
LineTo(hdc, 0, GRAPHWIDTH);
SelectObject(hdc, hline);
Polyline(hdc, pt, n); for ( i = 0; i < n; i++)
{
SetRect(&rt, pt[i].x-8, pt[i].y-8, pt[i].x+8, pt[i].y+8); FillRect(hdc, &rt, hrect);
}
EndPaint(hWnd, &ps); break;
case WM_DESTROY: DeleteObject(hline); DeleteObject(hrect); delete[] pt; delete[] xy;
break;
default: return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
Создадим текстовый файл с данными и построим график, изображенный на рис. 3.3. Предполагается, что точки графика упорядочены по возрастанию координаты x.

Рис. 3.3. Построение xy-графика
Константы, которые мы описали перед телом функции, служат для задания количества меток по осям координат, а также для выделения места для подписей осей. Мы выделили 25 логических единиц для подписи оси x и 50 единиц для оси y. Две константы GRAPHSIZE и GRAPHWIDTH определяют логические размеры окна и графика.
Диалоговые окна
Диалоговое окно является специальным типом окна, предназначенным для организации взаимодействия пользователя с программой. Диалоговое окно чаще всего используют как контейнер для элементов управления, оно предоставляет пользователю возможность выбирать или вводить данные. Начнем рассмотрение с модальных диалоговых окон. Модальное диалоговое окно при создании получает управление и не может возвратить его обратно вызывающему окну до завершения работы. Создается диалоговое окно при помощи вызова функции DialogBox():
INT_PTR WINAPI DialogBoxW (HINSTANCE hInstance, LPCWSTR lpTemplate,
HWND hWndParent, DLGPROC lpDialogFunc);
- hInstance — дескриптор текущего приложения;
- lpTemplate — указатель ресурса диалогового окна;
- hWndParent — дескриптор "родительского" окна, породившего диалоговое окно;
- lpDialogFunc — указатель на функцию диалогового окна.
ПРИМЕЧАНИЕДля создания диалогового окна перейдем на подложку обозревателя ресурсов Recourse View и в контекстном меню выберем Insert Dialog (рис. 3.4). Окно мастера создания нового диалога показано на рис. 3.5, где по умолчанию размещены две стандартные кнопки OK и Cancel.
На самом деле DialogBoxW()представляет собой макрос, который преобразуется в функцию DialogBoxParamW(), имеющую дополнительно параметр dwInitParam, равный 0 по умолчанию.

Рис. 3.4. Создание диалогового окна

Рис. 3.5. Вид диалогового окна с панелью инструментов
Операционная система Windows имеет множество элементов управления, которые, по сути, являются окнами, зарегистрированными в системе. Это кнопки (Button), флажки/переключатели управления (Check Box), переключатели (Radio Button), списки (List Box), комбинированные списки с полем ввода (Combo Box), поля ввода (Edit Control), полосы прокрутки (Scroll Bar), статические элементы (надписи) (Static Text) и др.
Элементы управления вставляются в диалоговое окно при помощи панели инструментов, хотя их можно вставить в приложение динамически, создавая окна элементов управления.
ПРИМЕЧАНИЕМы можем размещать элементы на поверхности диалогового окна буксировкой при нажатой левой кнопке мыши или выделением элемента на палитре и указанием прямоугольника для его размещения. После создания элемента управления можно изменить его размеры и другие свойства.
Каждый элемент управления имеет оконную функцию, принадлежащую операционной системе, которая используется совместно всеми приложениями. Управление элементами осуществляется посылкой их окну соответствующего сообщения. Данные передаются в параметрах сообщения, а результат возвращается функцией SendMessage().
Рассмотрим пример создания диалогового окна, на котором разместим несколько наиболее часто используемых элементов управления.
Тестирование элементов управления
Создадим тестовую программу для следующих элементов управления: кнопка, флажок, переключатель, список, комбинированный список (список с полем ввода), полоса прокрутки, статический элемент (надпись). В диалоговом окне мы будем изменять их состояние, а в главном окне выведем полученные значения. В качестве основы используем стандартную заготовку, а диалоговое окно, изображенное на рис. 3.6, построим в редакторе ресурсов. По умолчанию его идентификатор IDD_DIALOG1.
Рис. 3.6. Тест элементов управления
Каждому из элементов управления, размещенному на поверхности диалогового окна, за исключением статических полей, по умолчанию присваиваются уникальные идентификаторы, которые мы менять не будем. Заменим лишь идентификаторы статических полей, используемых для индикации состояния полос прокрутки (линеек скроллинга): IDC_HSCR, IDC_VSCR.
ПРИМЕЧАНИЕТекст оконной функции приложения и функции диалогового окна, управляющей этими элементами, приведен в листинге 3.3.
Для всех элементов управления установим свойство Tabstop = true, которое определит возможность перехода между элементами управления клавишей. Исходный порядок соответствует последовательности размещения элементов на диалоговом окне. Для изменения порядка перехода воспользуйтесь меню Format | Tab Order. Щелкните мышью по элементам в необходимой последовательности и нажмите клавишу .
INT_PTR CALLBACK Dialog1(HWND, UINT, WPARAM, LPARAM);
static int radio, check1, check2, scrlh, scrlv, lIndex, cIndex;
int *val[] = {&radio,&check1,&check2,&scrlh,&scrlv,&lIndex,&cIndex}; TCHAR *combo[100] = { _T("a"), _T("b"), _T("c") };
TCHAR *list[100] = { _T("string 1"), _T("string 2"), _T("string 3") }; TCHAR *ctrl = _T("Элементы управления:");
TCHAR *str_control[] = {_T("Radio Button"),_T("Check Button 1"),
_T("Check Button 2"),_T("HScroll Pos"),_T("VScroll Pos"),
_T("List Box Index"),_T("Combo Box Index")}; const int HNUM = 10, VNUM = 100;
const int List_size = 3, Combo_size = 3; const int INTERVAL = 20;
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lPa- ram)
{
PAINTSTRUCT ps;
HDC hdc;
TCHAR str[256];
int i;
switch (message)
{
case WM_COMMAND:
switch (LOWORD(wParam))
{
case ID_STDDIALOG :
DialogBox(hInst, MAKEINTRESOURCE(IDD_DIALOG1), hWnd, Dialog1);
break;
case IDM_EXIT: DestroyWindow(hWnd); break;
default: return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps); TextOut(hdc, 0, 0, ctrl, _tcslen(ctrl)); for (i = 0; i < 7;)
{
_stprintf(str, _T("%s = %d"), str_control[i], *val[i]); TextOut(hdc, 0, ++i*INTERVAL, str, _tcslen(str));
}
EndPaint(hWnd, &ps); break;
case WM_DESTROY: PostQuitMessage(0); break;
default: return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
////////////////////////////////////////////////////////////////// INT_PTR CALLBACK Dialog1(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
static int radio, check1, check2, scrlh, scrlv; static HWND hScroll, vScroll, hWndList, hWndComboBox; int i;
switch (message)
{
case WM_INITDIALOG:
radio = ::radio; CheckRadioButton(hDlg,IDC_RADIO1,IDC_RADIO3,IDC_RADIO1+radio); check1 = ::check1;
SendDlgItemMessage(hDlg, IDC_CHECK1, BM_SETCHECK, check1, 0); check2 = ::check2;
SendDlgItemMessage(hDlg, IDC_CHECK2, BM_SETCHECK, check2, 0); scrlh = ::scrlh;
SetDlgItemInt(hDlg, IDC_HSCR, scrlh, 0); hScroll = GetDlgItem(hDlg, IDC_SCROLLBAR1);
SetScrollRange(hScroll, SB_CTL, 0, HNUM, FALSE); SetScrollPos(hScroll, SB_CTL, scrlh, TRUE); scrlv = ::scrlv;
SetDlgItemInt(hDlg, IDC_VSCR, scrlv, 0); vScroll = GetDlgItem(hDlg, IDC_SCROLLBAR2);
SetScrollRange(vScroll, SB_CTL, 0, VNUM, FALSE); SetScrollPos(vScroll, SB_CTL, scrlv, TRUE); hWndList = GetDlgItem(hDlg, IDC_LIST1);
for (i = 0; i < List_size; i++)
SendMessage(hWndList, LB_ADDSTRING, 0, (LPARAM)list[i]); SendMessage(hWndList, LB_SETCURSEL, lIndex, 0); hWndComboBox = GetDlgItem(hDlg, IDC_COMBO1);
for (i = 0; i < Combo_size; i++)
SendMessage(hWndComboBox, CB_ADDSTRING, 0, (LPARAM)combo[i]); SendMessage(hWndComboBox, CB_SETCURSEL, cIndex, 0);
return TRUE; case WM_COMMAND:
switch(LOWORD(wParam))
{
case IDOK: lIndex = SendMessage(hWndList, LB_GETCURSEL, 0, 0); cIndex = SendMessage(hWndComboBox, CB_GETCURSEL,0,0);
::radio = radio;
::check1 = check1;
::check2 = check2;
::scrlh = scrlh;
::scrlv = scrlv; InvalidateRect(GetParent(hDlg), NULL, 1);
case IDCANCEL : return EndDialog(hDlg, 0); case IDC_CHECK1 : check1 = ~check1;
SendDlgItemMessage(hDlg, IDC_CHECK1, BM_SETCHECK, check1, 0); return TRUE;
case IDC_CHECK2 : check2 = ~check2;
SendDlgItemMessage(hDlg, IDC_CHECK2, BM_SETCHECK, check2, 0); return TRUE;
case IDC_RADIO1 : radio = 0; break; case IDC_RADIO2 : radio = 1; break; case IDC_RADIO3 : radio = 2; break;
}
CheckRadioButton(hDlg,IDC_RADIO1,IDC_RADIO3,IDC_RADIO1+radio); return TRUE;
case WM_HSCROLL:
switch(LOWORD(wParam))
{
case SB_LINELEFT : scrlh--; break; case SB_LINERIGHT: scrlh++; break;
case SB_PAGELEFT : scrlh -= HNUM/2; break;
case SB_PAGERIGHT: scrlh += HNUM/2; break;
case SB_THUMBPOSITION : scrlh = HIWORD(wParam); break;
}
scrlh = max(0, min(scrlh, HNUM));
if (scrlh != GetScrollPos(hScroll, SB_CTL))
{
SetScrollPos(hScroll, SB_CTL, scrlh, TRUE); SetDlgItemInt(hDlg, IDC_HSCR, scrlh, 0);
}
return TRUE; case WM_VSCROLL:
switch(LOWORD(wParam))
{
case SB_LINEUP : scrlv--; break; case SB_LINEDOWN : scrlv++; break;
case SB_PAGEUP : scrlv -= VNUM/10; break; case SB_PAGEDOWN : scrlv += VNUM/10; break;
case SB_THUMBPOSITION : scrlv = HIWORD(wParam);break;
}
scrlv = max(0, min(scrlv, VNUM));
if (scrlv != GetScrollPos(vScroll, SB_CTL))
{
SetScrollPos(vScroll, SB_CTL, scrlv, TRUE); SetDlgItemInt(hDlg, IDC_VSCR, scrlv, 0);
}
return TRUE; default: return FALSE;
}
return FALSE;
}
На глобальном уровне опишем переменные, которые будут отражать состояние элементов управления:
static int radio, check1, check2, scrlh, scrlv, lIndex, cIndex;
Поскольку принято, что в группе переключателей (Radio Button) может выбираться лишь один переключатель, то для описания всей группы достаточно одной переменной radio целого типа, значения которой {0,1,2} мы понимаем как включение первой, второй или третьей кнопки соответственно.
ПРИМЕЧАНИЕФлажки (Check Box) могут переключаться независимо друг от друга, поэтому для их описания определим две переменные check1 и check2. Признаком включения является ненулевое значение переменной.
Вообще-то нет никаких проблем для включения нескольких переключателей одновременно при помощи функции CheckDlgButton() или же просто посылкой сообщения BST_CHECKED для включения и BST_UNCHECKED для выключения, однако лучше придерживаться стандартных соглашений.
Переменные scrlh и scrlv отражают позицию горизонтальной и вертикальной полосы прокрутки Scroll Bar.
ПРИМЕЧАНИЕИ, наконец, переменные lIndex, cIndex необходимы для сохранения индекса выбранного элемента списка и комбинированного списка.
Здесь полосы прокрутки являются элементами управления, в отличие от рассмотренных ранее стилей, и могут располагаться в любом месте окна.
Эти переменные мы объявили как static лишь для того, чтобы они имели нулевое начальное значение.
Для начального заполнения списков мы описали два массива указателей list[100]
и combo[100] типа TCHAR*, которым присвоили лишь 3 первых значения.
В функции главного окна WndProc() необходимо добавить обработчик пункта меню
StdDialog с идентификатором ID_STDDIALOG:
DialogBox(hInst, MAKEINTRESOURCE(IDD_DIALOG1), hWnd, Dialog1);
Здесь будет сделан вызов диалогового окна IDD_DIALOG1.Вывод значений параметров сделаем в сообщении WM_PAINT главного окна, где выведем в цикле значение переменных, отслеживающих состояние элементов управления.
Для преобразования числового значения в строку проще всего воспользоваться функцией форматного вывода _stprintf(). Строка формата позволит "склеить" текст и числовое значение: "%s=%d". Выводим строки с интервалом в 20 логических единиц.
Вся логика обработки осуществляется в функции диалогового окна Dialog1(), рассмотрим подробнее ее работу.
Грамотно оформленное диалоговое окно должно иметь возможность закрыться как кнопкой OK, так и Cancel, поэтому нам нужен второй комплект локальных переменных для описания состояния элементов управления.
static int radio, check1, check2, scrlh, scrlv;
Для лучшей читаемости кода мы сохранили те же имена, помня, что локальная переменная всегда имеет приоритет перед глобальной. Для обращения к глобальной переменной используем явное указание области видимости "::".
При открытии диалогового окна всегда генерируется сообщение WM_INITDIALOG, где мы и присвоим начальное значение переменных, например:
radio = ::radio;
и произведем начальную установку элементов управления:
ПереключателиУстанавливаем выбранную кнопку функцией CheckRadioButton():
BOOL WINAPI CheckRadioButton(HWND hDlg, int nIDFirstButton, int nIDLastButton, int nIDCheckButton);
Функция принимает дескриптор диалогового окна hDlg, которое является родительским для всех элементов управления, диапазон идентификаторов переключателей (IDC_RADIO1,IDC_RADIO3), и идентификатор активной кнопкиIDC_RADIO1+radio. Для корректной работы данной функции необходимо создавать кнопки непрерывно одну за другой, чтобы их идентификаторы, которые эквивалентны целым числам, представляли собой последовательный ряд чисел. Должно быть понятно, что при значении переменной radio: 0,1,2; выражение IDC_RADIO1+radio будет эквивалентно одному из трех идентификаторов:
IDC_RADIO1, IDC_RADIO2, IDC_RADIO3.
ПРИМЕЧАНИЕФлажки
Вставим селекторные кнопки в рамку элемента управления Group Box, который будет выполнять чисто декоративную функцию.
Установим флажки, передавая им сообщение BM_SETCHECK функцией
SendDlgItemMessage():
LRESULT WINAPI SendDlgItemMessageW(HWND hDlg, int nIDDlgItem, UINT Msg, WPARAM wParam, LPARAM lParam);
Функция принимает дескриптор окна диалога hDlg, идентификатор кнопки и сообщение BM_SETCHECK. Отличное от нуля значение wParam означает установку флажка кнопки, а нулевое значение снимает флажок. lParam здесь не используется. Полоса прокрутки
Для отображения состояния горизонтальной и вертикальной полосы прокрутки мы вставили два статических элемента с идентификаторами IDC_HSCR и IDC_VSCR. Отобразим в них значение переменных scrlh и scrlv функциейSetDlgItemInt():
BOOL WINAPI SetDlgItemInt(HWND hDlg, int nIDDlgItem, UINT uValue, BOOL bSigned);
Функция принимает: hDlg — дескриптор окна, nIDDlgItem — идентификатор элемента управления, uValue — переменную целого типа, bSigned — признак вывода числа со знаком.Нужно получить дескриптор полосы прокрутки функцией GetDlgItem():
HWND WINAPI GetDlgItem(HWND hDlg, int nIDDlgItem);
Это специализированная функция для диалогового окна, которая позволяет по идентификатору элемента управления nIDDlgItem получить его дескриптор.Теперь нужно задать диапазон для горизонтальной полосы прокрутки. Это делается при помощи функции SetScrollRange():
BOOL WINAPI SetScrollRange(HWND hWnd, int nBar, int nMinPos, int nMaxPos, BOOL bRedraw);
Первым параметром указываем дескриптор полосы прокрутки. Второй параметр имеет значение SB_CTL, что идентифицирует полосу прокрутки как элемент управления, а не свойство окна, где он принимал значение SB_HORZ и SB_VERT. Следующие два параметра задают min и max значение позиции полос прокрутки (мы установим диапазон [0,HNUM], переменная HNUM описана в заголовке про- граммы), а последний параметр служит признаком перерисовки.
Затем задается текущая позиция движка функцией SetScrollPos():
int WINAPI SetScrollPos(HWND hWnd,int nBar,int nPos,BOOL bRedraw);
Первые два параметра имеют тот же смысл, что и в предыдущей функции, 3-й параметр — позиция движка, а последний параметр — признак перерисовки.Параметры вертикальной полосы прокрутки устанавливаем аналогично.
Список
Для первичного заполнения списка мы описали на глобальном уровне массив указателей TCHAR *list[], где значение присвоено только 3-м первым элементам.
Получим дескриптор списка hWndList и в цикле заполним список, передавая со- общение LB_ADDSTRING, где указатель на строку помещаем в lParam с явным преобразованием типа, иначе компилятор не сможет построить код этого выражения. Константа List_size описана на глобальном уровне и равна 3.
Для отображения выделенного элемента посылаем списку сообщение LB_SETCURSEL, передавая в wParam значение индекса lIndex.
Комбинированный список
Опишем на глобальном уровне массив указателей TCHAR *combo[] для начальной инициализации комбинированного списка.
Далее все делается так же, как у списка, только префикс сообщений вместо LB будет CB.
ПРИМЕЧАНИЕСтатические элементы (надписи)
Если для списка или комбинированного списка установлен стиль Sort, отображаться элементы будут в отсортированном по умолчанию порядке.
Обычно статические элементы используют для вывода в окне поясняющих надписей, но в этом случае к ним нет необходимости обращаться, и мы оставим идентификаторы со значением по умолчанию IDC_STATIC. Однако двум элементам, которые используются для вывода состояния полос прокрутки, присвоим
уникальные идентификаторы IDC_HSCR и IDC_VSCR. Чтобы выделить эти элементы в окне установим true для стиля Border и Client Edge.
Выводят в окно статический элемент обычно функциями: SetDlgItemText() или SetDlgItemInt(), передавая им в качестве параметра указатель на строку текста или переменную целого типа.
При совершении операций с элементами управления в диалоговую функцию передается сообщение WM_COMMAND, а в LOWORD(wParam) содержится идентификатор этого элемента. Поэтому для обработки сообщений от элементов управления внутри обработчика сообщения WM_COMMAND обычно создают переключатель switch, в котором и выполняют селекцию поступающих сообщений. Причем, если оконная функция диалогового окна обрабатывает сообщение, она должна возвращать TRUE.
case WM_COMMAND: switch(LOWORD(wParam))
{
...
}
return TRUE;
Начнем рассмотрение с кнопок OK и Cancel. Мастер построения диалогового окна автоматически вставляет эти кнопки, присваивая им стандартные идентификаторы IDOK и IDCANCEL.
ПРИМЕЧАНИЕПри нажатии кнопки OK необходимо сохранить на глобальном уровне значение локальных переменных, контролирующих элементы управления, например:
Идентификатор IDCANCEL имеет значение, равное 2, такое же сообщение генерируется при нажатии системной кнопки закрытия окна .
::radio = radio;
а индексы элементов, выбранных в списке и комбинированном списке, необходимо получить, послав этим элементам сообщения LB_GETCURSEL и CB_GETCURSEL соответственно. Возвращаемое значение функции SendMessage() вернет искомый индекс. Далее необходимо инициировать перерисовку главного окна вызовом функции InvalidateRect(), где дескриптор родительского окна мы получим как возвращаемое значение функции GetParent():
HWND WINAPI GetParent(HWND hWnd);
ПРИМЕЧАНИЕПри нажатии кнопки Cancel выполнение кода фрагмента начнется с функции
Мы намеренно не использовали оператор break в конце обработчика сообщения от кнопки OK. В этом случае будет выполняться код следующего оператора case и произойдет нормальное закрытие диалогового окна.
EndDialog():
BOOL WINAPI EndDialog(HWND hDlg, int nResult);
здесь мы не сохраняем локальные переменные и не перерисовываем окно.При обработке сообщений от флажка IDC_CHECK1 и IDC_CHECK2 инвертируем значение переменной. Поскольку ее начальное значение 0, то ~0 равно -1, а ~(-1) равно 0. Таким образом, переменная будет принимать одно из двух значений 0или -1, что интерпретируется как состояние флажка "выключено" (сброшен) и "включено" (установлен).
Для изменения состояния флажка посылаем ему сообщение BM_SETCHECK. Нулевое значение wParam сбросит, а ненулевое — установит флажок.
А вот переключатели (селекторные кнопки) нужно обрабатывать вместе, поскольку во включенном состоянии может быть только один. Мы поместили обработку сообщений от этих переключателей в конец блока, чтобы иметь возможность после установки значения переменной radio передать управление функции CheckRadioButton() для изменения их состояния.
Обработка сообщений от полос прокрутки принципиально отличается от прочих элементов управления тем, что они не генерируют сообщения WM_COMMAND, а точно так же, как и полосы прокрутки — свойства окна, генерируют сообщения: WM_HSCROLL — для горизонтальной и WM_VSCROLL — для вертикальной полосы прокрутки. Обработка этих сообщений будет осуществляться точно так же, как и в листинге 2.3, однако имеется одно принципиальное отличие — в качестве идентификатора полосы прокрутки — элемента управления — используется SB_CTL.
Мы будем обрабатывать сообщения от полосы прокрутки: SB_LINELEFT, SB_LINELEFT, SB_LINEUP, SB_LINEDOWN, SB_PAGELEFT, SB_PAGEUP, SB_PAGERIGHT, SB_PAGEDOWN, SB_THUMBPOSITION.
Так, для горизонтальной полосы прокрутки мы изменяем значение переменной scrlh, причем, поскольку мы выбрали для HNUM значение 10, то при щелчке на полосе прокрутки сделаем шаг передвижения равным 5 (HNUM/2).
После установки нового значения переменной, как и ранее, при помощи стандарт- ной конструкции, позаботимся о том, чтобы не выйти за пределы интервала [0,HNUM]. После чего отобразим значение переменной scrlh в статическом эле- менте (надписи) IDC_HSCR функцией SetDlgItemInt().
Вертикальный скроллинг организуем аналогично, имея в виду, что переменная scrlv, которая его контролирует, изменяется на отрезке [0,VNUM], а шаг здесь равен 10 (VNUM/10). Для отображения состояния вертикального скроллинга используется статический элемент с идентификатором IDC_VSCR.
Итак, все сообщения от элементов управления обработаны и возвращают TRUE, все же остальные сообщения игнорируются и возвращают FALSE.
После закрытия диалогового окна главное окно перерисовывается для вывода значений переменных, контролирующих состояние элементов управления. Поскольку мы определили переменные на глобальном уровне, то они доступны и в оконной функции главного окна. Здесь же опишем массив строк с поясняющим текстом.
Чтобы не писать код для вывода каждой переменной, опишем массив указателей и присвоим его элементам адреса этих переменных:
int *val[] = {&radio,&check1,&check2,&scrlh,&scrlv,&lIndex,&cIndex};
Теперь при обработке сообщения WM_PAINT, после вывода заголовка ctrl можно организовать цикл для вывода значений переменных. При формировании строки вывода мы воспользовались функцией форматного вывода _stprintf(). Выражение ++i*INTERVAL обеспечит увеличение индекса и наращивание y-координаты на 20 единиц.
Общие элементы управления
Элементы управления, которые появились лишь при создании ОС Windows 95, получили название общих элементов управления (Common Controls). Они расширяют возможности стандартных элементов управления и придают программам современный вид. Один из этих элементов мы уже рассмотрели в главе 2 — это панель инструментов (Tool Bar). Сейчас рассмотрим еще 3 элемента — наборный счетчик (Spin Control), ползунковый регулятор (Slider Control) и индикатор выполнения (Progress Bar Control).
Техника их использования мало чем отличается от стандартных элементов управления: в редакторе ресурсов перемещаем нужные элементы с палитры инструментов в диалоговое окно, присваиваем идентификаторы и устанавливаем необходимые свойства.
Особенностью же общих элементов управления является то, что они не входят в стандартную библиотеку и для них необходимо добавить файл включений commctrl.h, а также подключить к проекту библиотеку общих элементов управления comctl32.lib, как мы это делали в предыдущей главе (см. рис. 2.3).
К тому же, до использования общих элементов управления приложение должно инициализировать библиотеку функцией InitCommonControls().
ПРИМЕЧАНИЕ
Хотя панель инструментов и является общим элементом управления, для ее использования нет необходимости в вызове функции InitCommonControls().
Так же, как и стандартные элементы управления, общие элементы являются специальными элементами, которые посылают родительскому окну сообщение WM_COMMAND или WM_NOTIFY. Управление осуществляется посылкой им соответствующих сообщений. Демонстрационная задача приведена на рис. 3.7 и далее в листинге 3.4.
Создадим диалоговое окно, которое вызывается в пункте меню StdDialog, и разместим там элементы управления: наборный счетчик (спин), ползунковый регулятор (ползунок), индикатор выполнения и два статических элемента для вывода значений спина и ползунка. При закрытии окна кнопкой OK возвращаем установленные значения элементов управления и выводим в окне, при нажатии на кнопку Cancel ничего возвращать не будем.
ПРИМЕЧАНИЕ
Все рассмотренные общие элементы управления (Spin Control, Slider Control, Progress Bar Control) имеют внутреннюю память для хранения состояния элемента.
Для обмена данными опишем на глобальном уровне три переменные: spin, track, progress. Вывод в окно осуществим в сообщении WM_PAINT, где сформируем строку вывода функцией форматного вывода в строку _stprintf(). Причем используем универсальную функцию, работающую как с С-строкой, так и со строкой Unicode. Для вывода воспользуемся функцией DrawText(), поскольку она позволяет форма- тировать выводимый текст. Так что мы сможем одним оператором вывести три строки текста, выравнивая данные табуляцией.

Рис. 3.7. Диалоговое окно с общими элементами управления
Slider (Track Bar Control) — ползунок
Ползунок или ползунковый регулятор состоит из указателя, движущегося вдоль шкалы. Он является разновидностью горизонтальной полосы прокрутки и генерирует сообщение WM_HSCROLL. В lParam передается дескриптор элемента управления, породившего сообщение, и мы можем решить вопрос о том, каким элементом управления оно вызвано, если таких элементов несколько.В функции диалогового окна опишем переменную для хранения состояния ползунка и его дескриптор:
static int track; static HWND hTrack;
В сообщении об инициализации диалога необходимо определить дескриптор, минимальную и максимальную позиции ползунка и установить начальное значение:
track = ::track;
hTrack = GetDlgItem(hDlg, IDC_SLIDER1); SendMessage(hTrack, TBM_SETRANGEMIN, 0, 0);
SendMessage(hTrack, TBM_SETRANGEMAX, 0, 100); SendMessage(hTrack, TBM_SETPOS, TRUE, track);
Здесь минимальное и максимальное значения позиции ползунка определяются посылкой ему сообщения TBM_SETRANGEMIN и TBM_SETRANGEMAX, а само значение передается в lParam, у нас — [0,100]. Текущая позиция задается посылкой сообщения TBM_SETPOS, где значение TRUE в wParam означает необходимость перерисовки ползунка, а позиция track передается в lParam.
ПРИМЕЧАНИЕТеперь осталось вывести значение track в статическом элементе IDC_TR1. SetDlgItemInt(hDlg, IDC_TR1, track, 0);
Вместо двух сообщений для установки минимального и максимального значений ползунка можно использовать сообщение TBM_SETRANGE, где минимум задается в младшем слове lParam, а максимум — в старшем.SendMessage(hTrack, TBM_SETRANGE, 0, 100<<16);
Для манипуляций с ползунком в сообщении WM_HSCROLL передадим ему сообщение
ПРИМЕЧАНИЕSpin (Up-Down Control) — наборный счетчик
Когда ползунок находится в фокусе ввода (элемент управления, находящийся в фокусе, готов принимать команды с клавиатуры), его состоянием можно управлять клавишами управления курсором:— минимальное значение, — максимальное значение, < > и < > — увеличение значения на 1, < > и < > — уменьшение значения на 1, — увеличение значения на шаг, — уменьшение значения на шаг. Шаг ползунка определяется автоматически в 1/5 от диапазона его изменения.
Наборный счетчик представляет собой специальный вид полосы вертикального скроллинга, он состоит из кнопок вверх и вниз так же, как и элемент управления Vertical Scroll Bar, и порождает сообщение WM_VSCROLL. В lParam передается дескриптор элемента управления, породившего сообщение.
В оконной функции диалогового окна необходимо описать дескриптор спина hSpin, а в сообщении об инициализации диалога определить этот дескриптор и установить его начальное значение.
Опишем в функции диалогового окна дескриптор спина:
static HWND hSpin;
Для отображения значения спина в статическом элементе можно поступить как для ползунка — вывести значение функцией SetDlgItemInt(). Однако счетчик предоставляет пользователю дополнительный сервис. Если при построении диалогового окна установить свойство спина Set Buddy Integer, то можно этому спину назначить "приятельское" (buddy) окно IDC_SP1, а, посылая ему сообщение UDM_SETBUDDY, в wParam передать дескриптор "приятеля" hBuddy:
hSpin = GetDlgItem(hDlg, IDC_SPIN1); hBuddy = GetDlgItem(hDlg, IDC_SP1);
SendMessage(hSpin, UDM_SETBUDDY, (WPARAM)hBuddy, 0);
В этом случае счетчик сам позаботится о выводе своего значения в приятельское окно, и нам об этом беспокоиться больше не нужно.
Сообщение UDM_SETRANGE устанавливает диапазон изменения счетчика так же, как для ползунка.
SendDlgItemMessage(hDlg, IDC_SPIN1, UDM_SETRANGE, 0, 100);
ПРИМЕЧАНИЕСообщение UDM_SETPOS устанавливает позицию спина, передаваемую в lParam. SendMessage(hSpin, UDM_SETPOS, 0, spin);
Если в диапазоне значений спина минимальное значение превышает максимальное, LOWORD(lParam) > HIWORD(lParam), инвертируется направление изменения спина.
Поскольку мы установили для наборного счетчика (спина) приятельское окно, нет необходимости в обработке сообщения WM_VSCROLL для индикации числового значения.
Progress Bar Control — индикатор выполнения
Индикатор выполнения — это элемент управления, который показывает течение процесса, например, при копировании файла в файловом менеджере. Управление осуществляется передачей сообщений, сам же индикатор сообщений не генерирует. Как и для предыдущих элементов управления, опишем в диалоговой оконной функции дескриптор индикатора:
static HWND hProgress;
и определим его при инициализации диалогового окна.
hProgress = GetDlgItem(hDlg, IDC_PROGRESS1);
Для индикатора необходимо определить диапазон изменения и задать начальную позицию. Диапазон [0,100] определим посылкой сообщения PBM_SETRANGE, где минимум задается в младшем слове lParam, а максимум — в старшем. Шаг индикатора, равный 1, зададим в wParam сообщения PBM_SETSTEP. Начальную позицию, равную t, зададим посылкой сообщения PBM_SETPOS. Здесь t — статическая переменная целого типа, описанная в оконной функции диалога, будет контролировать состояние индикатора.
SendMessage(hProgress, PBM_SETRANGE, 0, 100<<16);
SendMessage(hProgress, PBM_SETSTEP, 1, 0); SendMessage(hProgress, PBM_SETPOS, t, 0);
Для создания иллюзии движения создадим таймер с интервалом в 0,1 секунды:
SetTimer(hDlg, 1, 100, NULL);
Теперь в обработчике сообщения от таймера WM_TIMER будем увеличивать значение переменной на 1 и переустанавливать состояние индикатора.
if (++t > 99) t = 0;
SendMessage(hProgress, PBM_SETPOS, t, 0);
Оператором if через каждые 10 секунд сбрасываем индикатор в начальное состояние.
Осталось только перед закрытием диалогового окна кнопкой OK передать глобальной переменной значение индикатора и уничтожить таймер:
progress = t; KillTimer(hDlg,1);
Поскольку состояние ползунка отслеживает переменная track, передадим ее на
глобальный уровень:
::track = track;
Считываем состояние наборного счетчика, передавая ему сообщение UDM_GETPOS, и перерисовываем главное окно функцией InvalidateRect() (см. листинг 3.4). После чего диалоговое окно можно закрыть.
При нажатии кнопки Cancel уничтожаем таймер и закрываем диалоговое окно без перерисовки главного окна. Листинг 3.4. Тест общих элементов управления
#include
INT_PTR CALLBACK Dialog1(HWND, UINT, WPARAM, LPARAM);
static int spin, track, progress;
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lPa- ram)
{
PAINTSTRUCT ps;
HDC hdc;
TCHAR str[256]; RECT rt;
switch (message)
{
case WM_CREATE: InitCommonControls(); break;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case ID_COMMCTRL:
DialogBox(hInst, MAKEINTRESOURCE(IDD_DIALOG1), hWnd, Dialog1); break;
case IDM_EXIT: DestroyWindow(hWnd); break;
default: return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_PAINT:
SetRect(&rt, 0, 0, 100, 100); hdc = BeginPaint(hWnd, &ps);
_stprintf(str,_T("spin\t= %d\ntrack\t= %d\nprogress= %d"), spin, track, progress);
DrawText(hdc, str,_tcslen(str), &rt, DT_LEFT |DT_EXPANDTABS); EndPaint(hWnd, &ps);
break;
case WM_DESTROY: PostQuitMessage(0); break;
default: return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
///////////////////////////////////////////////////////////////////
INT_PTR CALLBACK Dialog1(HWND hDlg, UINT message, WPARAM wParam, LPARAM lPa- ram)
{
static int t, track;
static HWND hSpin, hBuddy, hTrack, hProgress; switch (message)
{
case WM_INITDIALOG:
track = ::track;
SetDlgItemInt(hDlg, IDC_TR1, track, 0); hTrack = GetDlgItem(hDlg, IDC_SLIDER1);
SendMessage(hTrack, TBM_SETRANGE, 0, 100<<16); SendMessage(hTrack, TBM_SETPOS, TRUE, track); hSpin = GetDlgItem(hDlg, IDC_SPIN1);
hBuddy = GetDlgItem(hDlg, IDC_SP1); SendMessage(hSpin, UDM_SETBUDDY, (WPARAM)hBuddy, 0); SendMessage(hSpin, UDM_SETRANGE, 0, 100); SendMessage(hSpin, UDM_SETPOS, 0, spin);
hProgress = GetDlgItem(hDlg, IDC_PROGRESS1); SendMessage(hProgress, PBM_SETRANGE, 0, 100<<16);
SendMessage(hProgress, PBM_SETSTEP, 1, 0); SendMessage(hProgress, PBM_SETPOS, t, 0); SetTimer(hDlg, 1, 100, NULL);
return TRUE; case WM_TIMER :
if (++t > 99) t = 0;
SendMessage(hProgress, PBM_SETPOS, t, 0); return TRUE;
case WM_HSCROLL:
track = LOWORD(SendMessage(hTrack, TBM_GETPOS, 0, 0)); SetDlgItemInt(hDlg, IDC_TR1, track, 0);
return TRUE;
case WM_COMMAND: switch(LOWORD(wParam))
{
case IDOK : progress = t;
::track = track;
spin = SendMessage(hSpin, UDM_GETPOS, 0, 0); InvalidateRect(GetParent(hDlg),NULL,1);
case IDCANCEL: KillTimer(hDlg,1); EndDialog(hDlg, 0); return TRUE;
default: return FALSE;
}
default: return FALSE;
}
return FALSE;
}
Окно редактирования
Для ввода данных чаще всего используется элемент управления — окно редактирования (Edit Box Control). Управление окном осуществляется передачей ему сообщений или специализированными функциями диалогового окна:
- GetDlgItemText(hDlg, IDC_EDIT, text, length) — возвращает в TCHAR массив text не более length символов окна редактирования;
- SetDlgItemText(hDlg, IDC_EDIT, text) — заполняет окно редактирования содержимым TCHAR-строки text;
- GetDlgItemInt(hDlg, IDC_EDIT, lpTranslated, bSigned) — функция возвращает целое число, в которое преобразуется содержимое окна редактирования. lpTranslated — указатель переменной типа BOOL, которая устанавливается в TRUE при успешном преобразовании числа, и FALSE — в противном случае. Если bSigned равно TRUE, преобразуется число со знаком иначе, число рассматривается как беззнаковое;
- SetDlgItemInt(hDlg, IDC_EDIT1, Value, bSigned) — устанавливается значение переменной целого типа Value в окне редактирования. Если bSigned равно TRUE, рассматривается число со знаком.
Дополнительно выведем в нижней части главного окна строку состояния (строку статуса), где предусмотрим отображение количества строк списка.

Рис. 3.8. Демонстрационная программа "Окно редактирования"
Листинг 3.5. Окно редактирования и список
#include
#include
#include
INT_PTR CALLBACK Dialog1(HWND, UINT, WPARAM, LPARAM);
typedef std::basic_string, std::allocator > String;
std::vector v;
HWND hWndStatusBar; //Дескриптор строки состояния
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lPa- ram)
{
PAINTSTRUCT ps;
HDC hdc; TEXTMETRIC tm;
static int cy, sx, sy; int y;
RECT rt;
std::vector::iterator it; switch (message)
{
case WM_CREATE:
hdc = GetDC(hWnd); GetTextMetrics(hdc,&tm);
cy = tm.tmHeight + tm.tmExternalLeading;
ReleaseDC(hWnd, hdc);
hWndStatusBar = CreateStatusWindow(WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | CCS_BOTTOM | SBARS_SIZEGRIP,_T("Ready"), hWnd, 1);
break; case WM_SIZE:
sx = LOWORD(lParam); sy = HIWORD(lParam);
GetWindowRect(hWndStatusBar, &rt); y = rt.bottom-rt.top;
MoveWindow(hWndStatusBar, 0, sy - y, sx, sy, TRUE); break;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case ID_DIALOG_READLISTBOX:
DialogBox(hInst, MAKEINTRESOURCE(IDD_READ), hWnd, Dialog1); break;
case IDM_EXIT: DestroyWindow(hWnd); break;
default: return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
for (y = 0, it = v.begin(); it != v.end(); ++it, y += cy) TextOut(hdc, 0, y, it->data(), it->size());
EndPaint(hWnd, &ps); break;
case WM_DESTROY: PostQuitMessage(0); break;
default: return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
////////////////////////////////////////////////////////////////////// INT_PTR CALLBACK Dialog1(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
static HWND hList, hEdit; TCHAR text[256];
int i, k;
switch (message)
{
case WM_INITDIALOG:
hList = GetDlgItem(hDlg, IDC_LIST1);
hEdit = GetDlgItem(hDlg, IDC_EDIT1); return TRUE;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDC_ADD:
GetDlgItemText(hDlg, IDC_EDIT1, text, 256); SetDlgItemText(hDlg, IDC_EDIT1, _T("")); SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)text); SetFocus(hEdit);
return TRUE; case IDC_DEL:
k = SendMessage(hList, LB_GETCURSEL, 0, 0); SendMessage(hList, LB_DELETESTRING , (WPARAM)k, 0); return TRUE;
case ID_OK: v.clear();
k = SendMessage(hList, LB_GETCOUNT, 0, 0); for (i = 0; i < k; i++)
{
SendMessage(hList, LB_GETTEXT, (WPARAM)i, (LPARAM)text); v.push_back(text);
}
InvalidateRect(GetParent(hDlg), NULL, 1);
_stprintf(text,_T("Список: %d строк"), k); SendMessage(hWndStatusBar, WM_SETTEXT, 0, (LPARAM)text);
case WM_DESTROY: EndDialog(hDlg, LOWORD(wParam)); return TRUE;
}
default: return FALSE;
}
return FALSE;
}
В качестве контейнера для хранения текстовых строк, получаемых из списка, используем контейнер vector для типа данных String, производного от шаблонного класса basic_string для типа TCHAR (см. листинг 1.3).
В заголовке программы добавим файлы включений:
#include
#include
Опишем на глобальном уровне вектор, явно указывая область видимости:
std::vector v;
Итератор вектора нет смысла описывать на глобальном уровне, поэтому опишем его в функции главного окна:
std::vector::iterator it;
Нужно позаботиться о выводе результатов в главное окно, поэтому в сообщении WM_CREATE найдем высоту строки текущего шрифта при помощи функции GetTextMetrics(), как мы это делали в листинге 1.13.
Переменную tm типа TEXTMETRIC и статическую переменную cy, необходимую для хранения высоты шрифта, предварительно опишем в оконной функции.
Диалоговое окно будем вызывать через главное меню, где создадим пункт с идентификатором ID_DIALOG_READLISTBOX. Dialog1 — имя оконной функции диалога. Именно в этой функции и реализуется вся логика работы с элементами управления.
На поверхности диалогового окна разместим следующие элементы:
- поле ввода (EditBox) с идентификатором IDC_EDIT1;
- список (ListBox) с идентификатором IDC_LIST1;
- кнопку IDC_ADD;
- кнопку IDC_DEL;
- кнопку ID_OK;
- иконку IDC_STATIC с изображением IDI_TRASH;
- битовый образ с изображением IDB_MOUSE.
Начнем рассмотрение функции диалогового окна Dialog1(). В сообщении WM_INITDIALOG определим дескрипторы окна редактирования и списка:
hList = GetDlgItem(hDlg, IDC_LIST1); hEdit = GetDlgItem(hDlg, IDC_EDIT1);
Эти переменные описаны в заголовке функции:
static HWND hList, hEdit;
Теперь обработаем реакцию на нажатие кнопки с изображением ">>". Функцией GetDlgItemText() читаем содержимое окна редактирования в массив text и функцией SetDlgItemText() очищаем окно, посылая "пустую" строку.
Посылая списку сообщение LB_ADDSTRING, добавим к нему строку text. Теперь установим фокус ввода обратно на окно редактирования для последующего ввода:
SetFocus(hEdit);
ПРИМЕЧАНИЕОбработаем реакцию на нажатие кнопки "<<" для удаления выделенной в списке строки. Посылая списку сообщение LB_GETCURSEL, получим индекс выделенного элемента, а сообщением LB_DELETESTRING удалим элемент с найденным индексом.
Сообщение LB_ADDSTRING позволяет добавлять строки текста в конец списка, если для него не установлено свойство Sort. Если же свойство установлено, то строки будут добавляться в порядке, установленном критерием сортировки по умолчанию.
Нажатием на кнопку OK (идентификатор которой заменим на ID_OK, и установим в False свойство Default Button, чтобы избежать стандартной реакции на нажатие клавиши
Очистим содержимое контейнера:
v.clear();
Найдем размер списка, передавая ему сообщение
LB_GETCOUNT: k = SendMessage(hList, LB_GETCOUNT, 0, 0);
В цикле читаем последовательно содержимое списка, посылая ему сообщение
LB_GETTEXT, где третьим параметром служит индекс извлекаемого элемента, а четвертый параметр — массив text для хранения строки текста. Прочитав строку тек- ста, помещаем ее в контейнер методом push_back(). TCHAR-строка автоматически преобразуется к типу String.
for (i = 0; i < k; i++)
{
SendMessage(hList, LB_GETTEXT, (WPARAM)i, (LPARAM)text); v.push_back(text);
}
ПРИМЕЧАНИЕФункцией InvalidateRect() инициируем перерисовку главного окна программы, где дескриптор главного окна получим обращением к функции GetParent(hDlg).
В этой задаче применение контейнера для хранения строк не является необходимым, поскольку количество строк в списке известно, и можно было бы выделить массив типа String.
Мы не поставили оператор return перед следующим оператором case намеренно, поскольку выполнение кода будет продолжаться, произойдет переход на операторы закрытия диалогового окна и выхода из функции:
EndDialog(hDlg, LOWORD(wParam)); return TRUE;
что и требовалось.Сообщение с кодом WM_DESTROY будет генерироваться при нажатии на кнопку завершения приложения системного меню. В этом случае приложение завершится без сохранения данных списка и перерисовки главного окна.
Мы поместили на диалоговом окне еще два элемента Picture Control, оставив для них идентификатор по умолчанию IDC_STATIC, поскольку нет необходимости доступа к ним. Для первого элемента выбрали тип Icon, а в качестве изображения выбрали идентификатор IDI_TRASH импортированной иконки, которую подобрали в специализированной библиотеке.
Для второго элемента выберем тип Bitmap, а в качестве битового образа — IDB_MOUSE. Этот идентификатор мы получим, импортируя bmp-файл с растровым изображением. Для полноты картины заменим и иконку приложения. Для этого импортируем еще одну иконку из найденной коллекции и присвоим ей идентификатор IDI_FLGRUS.
Теперь осталось изменить стиль окна, для чего отредактируем лишь одно поле класса окна:
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_FLGRUS));
Вот и все, теперь в качестве иконки приложения будет использоваться найденное нами изображение.
Строка состояния
Для того чтобы придать приложению, представленному в листинге 3.5, более современный вид, добавим в окно строку состояния. Эта строка располагается обычно в нижней части окна и выводит справочную информацию о состоянии приложения. Выведем здесь количество строк списка.Поскольку окно состояния является общим элементом управления, необходимо добавить файл включений commctrl.h и, соответственно, библиотечный файл comctl32.lib.
Дескриптор окна состояния опишем на глобальном уровне:
HWND hWndStatusBar;
и создадим окно при обработке сообщения WM_CREATE главного окна функцией
CreateStatusWindow():
HWND WINAPI CreateStatusWindowW(LONG style, LPCWSTR lpszText, HWND hwndParent, UINT wID);
где:
- style — стиль окна;
- lpszText — текст по умолчанию;
- hwndParent — дескриптор родительского окна;
- wID — идентификатор окна.
GetWindowRect(hWndStatusBar, &rt); y = rt.bottom-rt.top;
MoveWindow(hWndStatusBar, 0, sy - y, sx, sy, TRUE);
Теперь нужно решить вопрос — где мы будем выводить информацию в строку состояния? Проще всего это сделать при обработке сообщения о нажатии кнопки OK диалогового окна. Для этого мы сформируем строку вывода text:
_stprintf(text,_T("Список: %d строк"), k);
и выведем эту строку в окно состояния, послав ему сообщение WM_SETTEXT:
SendMessage(hWndStatusBar, WM_SETTEXT, 0, (LPARAM)text);
Простой текстовый редактор на элементе управления Edit Box Control Мы рассмотрели использование окна редактирования Edit Box для ввода строки текста, однако этот элемент управления имеет гораздо больше возможностей. В качестве демонстрационного примера построим на его основе простой текстовый редактор. Как мы вскоре убедимся, все операции по вводу и редактированию текста элемент Edit Box берет на себя, нам остается лишь реализовать внешний интерфейс. Следует оговориться, что элемент управления Edit Box использует внутреннюю память для хранения текста, а это приводит к ограничениям на размер редактируемого текста в 32 767 символов (0x7fff).
ПРИМЕЧАНИЕВ качестве основы используем стандартный проект Win32. Добавим три пункта меню: New, Open, Save, панель инструментов с кнопками, соответствующими этим пунктам меню, а также строку состояния (см. листинг 3.6). Будем создавать универсальный проект, однако, поскольку приложение ориентировано на работу с текстовыми файлами в однобайтной кодировке, будем явно указывать тип char* для входных и выходных массивов данных.
В операционной системе Windows NT и выше размер буфера Edit Box может быть увеличен посылкой ему сообщения EM_LIMITTEXT до 0x7FFFFFFE байтов.
Листинг 3.6. Текстовый редактор с элементом управления Edit Box
#include
#include
{{STD_FILENEW, ID_FILE_NEW, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, 0, 0},
{STD_FILEOPEN, ID_FILE_OPEN,TBSTATE_ENABLED,TBSTYLE_BUTTON, 0, 0, 0, 0},
{STD_FILESAVE, ID_FILE_SAVE,TBSTATE_ENABLED,TBSTYLE_BUTTON, 0, 0, 0, 0}
#include TBBUTTON tbb[] =
};
////////////////////////////////////////////////////////////////////// VOID StatusOut(HWND hStatus, int count, TCHAR *str)
{
TCHAR text[256];
_stprintf(text,_T("Строк: %d"), count); SendMessage(hStatus, SB_SETTEXT, (WPARAM)0, (LPARAM)text); SendMessage(hStatus, SB_SETTEXT, (WPARAM)1, (LPARAM)str);
}
////////////////////////////////////////////////////////////////////// LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static OPENFILENAME file;
static int n, sx, sy;
static HWND hEdit, hWndToolBar, hWndStatusBar; RECT rt;
int m, k, aWidths[2]; static TCHAR name[256]; char szText[0x7fff]; std::ifstream in; std::ofstream out; switch (message)
{
case WM_CREATE:
hWndToolBar = CreateToolbarEx(hWnd, WS_CHILD|WS_VISIBLE|CCS_TOP, 2, 0, HINST_COMMCTRL, IDB_STD_SMALL_COLOR, tbb, 3, 0, 0, 0, 0,
sizeof(TBBUTTON));
hEdit = CreateWindow(WC_EDIT,NULL,WS_CHILD|WS_VISIBLE|WS_HSCROLL| WS_VSCROLL|ES_LEFT|ES_MULTILINE|ES_AUTOHSCROLL|ES_AUTOVSCROLL, 0, 0, 0, 0, hWnd, (HMENU) 1, hInst, NULL);
file.lStructSize = sizeof(OPENFILENAME); file.hInstance = hInst;
file.lpstrFilter = _T("Text\0 *.txt\0Все файлы\0 *.*"); file.lpstrFile = name;
file.nMaxFile = 256; file.lpstrInitialDir = _T(".\\"); file.lpstrDefExt = _T("txt");
hWndStatusBar = CreateWindow(STATUSCLASSNAME, NULL, WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, hWnd, NULL, hInst, NULL);
break; case WM_SIZE:
sx = LOWORD(lParam); sy = HIWORD(lParam); aWidths[0] = 100; aWidths[1] = sx;
GetWindowRect(hWndToolBar, &rt); m = rt.bottom - rt.top;
SendMessage(hWndToolBar, TB_AUTOSIZE, 0, 0); GetWindowRect(hWndStatusBar, &rt);
k = rt.bottom - rt.top;
MoveWindow(hWndStatusBar, 0, sy - k, sx, sy, TRUE); SendMessage(hWndStatusBar, SB_SETPARTS, (WPARAM)2, (LPARAM)aWidths); StatusOut(hWndStatusBar, n, name);
MoveWindow(hEdit, 0, m, sx, sy - m - k, TRUE); UpdateWindow(hEdit);
SetFocus(hEdit); return 0;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case ID_FILE_NEW: szText[0] = '\0';
SetWindowTextA(hEdit, szText); StatusOut(hWndStatusBar, 0, _T("")); break;
case ID_FILE_OPEN:
file.lpstrTitle = _T("Открыть файл для чтения"); file.Flags = OFN_HIDEREADONLY;
if (!GetOpenFileName(&file)) return 1; in.open(name, std::ios::binary); in.read(szText, 0x7fff);
if ((m = in.gcount()) == 0x7fff)
{
MessageBox(hWnd, _T("Слишком большой файл"),
_T("Edit"),MB_OK | MB_ICONSTOP);
in.close(); return 0;
}
szText[m] = '\0';
in.close(); SetWindowTextA(hEdit, szText);
n = SendMessage(hEdit, EM_GETLINECOUNT, 0, 0); StatusOut(hWndStatusBar, n, name);
break;
case ID_FILE_SAVE:
file.lpstrTitle = _T("Открыть файл для записи"); file.Flags = OFN_NOTESTFILECREATE | OFN_HIDEREADONLY;
if (!GetSaveFileName(&file)) return 1; out.open(name, std::ios::binary);
m = GetWindowTextA(hEdit, szText, 0x7fff); out.write(szText, m);
out.close();
n = SendMessage(hEdit, EM_GETLINECOUNT, 0, 0); StatusOut(hWndStatusBar, n, name);
break;
case IDM_EXIT: DestroyWindow(hWnd); break;
default: return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_DESTROY: PostQuitMessage(0); break;
default: return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
Нам понадобятся файлы включений: commdlg.h, commctrl.h, fstream. Доступ к стандартной области имен открывать не будем, а опишем переменные с явным указанием области видимости. Как и в программе просмотра файлов, рассмотренной в главе 2 (см. листинг 2.3), опишем в массиве TBBUTTON 3 кнопки инструментальной панели. Саму панель инструментов создадим при обработке сообщения WM_CREATE и получим ее дескриптор hwndToolBar:
hwndToolBar = CreateToolbarEx(hWnd, WS_CHILD|WS_VISIBLE|CCS_TOP, 2, 0,
HINST_COMMCTRL,IDB_STD_SMALL_COLOR,tbb, 3, 0, 0, 0, 0,sizeof(TBBUTTON));
Корректировку размера панели инструментов вставим в обработчик сообщения
WM_SIZE, посылая ей сообщение TB_AUTOSIZE: SendMessage(hwndToolBar, TB_AUTOSIZE, 0, 0);
Здесь же создадим элемент управления Edit Box:
hEdit = CreateWindow(WC_EDIT, NULL, WS_CHILD|WS_VISIBLE|WS_HSCROLL| WS_VSCROLL|ES_LEFT|ES_MULTILINE|ES_AUTOHSCROLL|ES_AUTOVSCROLL, 0, 0, 0, 0, hWnd, (HMENU) 1, hInst, NULL);
В параметре Класс окна функции CreateWindow() указываем WC_EDIT — предопределенный в системе идентификатор окна Edit Box. Заголовка у окна нет, поэтому второй параметр NULL.
Стиль окна будет складываться из следующих компонент:
- WS_CHILD — дочернее окно;
- WS_VISIBLE — окно отображается при создании;
- WS_HSCROLL — имеет горизонтальную полосу скроллинга;
- WS_VSCROLL — имеет вертикальную полосу скроллинга;
- ES_LEFT — выравнивание текста влево;
- ES_MULTILINE — многострочное окно редактирования;
- ES_AUTOHSCROLL — автоматическая прокрутка текста при вводе строки;
- ES_AUTOVSCROLL — автоматическая прокрутка текста, когда окно заполнено.
Далее укажем дескриптор родительского окна hWnd. Следующий параметр — ука- затель меню — в данном контексте служит для задания идентификатора окна: присвоим ему номер 1. Укажем следующим параметром дескриптор приложения — hInst и последний параметр — NULL, поскольку дополнительные параметры отсутствуют.
Окно создано, но мы хотели бы наложить его на главное окно программы, размеры которого нужно определять в сообщении WM_SIZE. Сделаем это функцией:
MoveWindow(hEdit, 0, m, sx, sy - m - k, TRUE);
Функция MoveWindow() позволяет изменить положение (0, m) и размер окна (sx, sy – m - k). Поскольку окно редактирования объявлено как дочернее, то располагается оно в клиентской области главного окна и начало его нужно поместить в левом верхнем углу клиентской области. Но там располагается панель инструментов, высоту которой мы определим, обратившись к функции GetWindowRect():
GetWindowRect(hwndToolBar,& rt); m = rt.bottom - rt.top;
Таким образом, левый верхний угол окна редактирования нужно сместить вниз по оси y на m. Высоту окна следует уменьшить на вертикальный размер панели инструментов и строки состояния, которая также располагается в клиентской области главного окна: sy – m - k.
Строку состояния создадим с нулевыми размерами функцией CreateWindow() в сообщении WM_CREATE:
hWndStatusBar = CreateWindow(STATUSCLASSNAME, NULL, WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, hWnd, NULL, hInst, NULL);
ПРИМЕЧАНИЕРеальный же размер зададим в сообщении
Идентификатор строки состояния STATUSCLASSNAME определен в файле включений как: "msctls_statusbar32".
WM_SIZE: MoveWindow(hWndStatusBar, 0, sy - k, sx, sy, TRUE);
где sx, sy — ширина и высота главного окна, а k — высота окна состояния.Воспользуемся некоторыми особенностями этого окна управления. Имеется возможность разделить строку состояния на несколько частей и выводить информацию в каждую часть отдельно. Для этого необходимо, послав сообщение SB_SETPARTS, указать в wParam количество частей, а в lParam их правые границы, заданные массивом aWidths:
SendMessage(hWndStatusBar, SB_SETPARTS, (WPARAM)2, (LPARAM)aWidths);
Поделим строку состояния на две части, установив фиксированный размер первой части, а для второй части отдадим остаток строки:
aWidths[0] = 100; aWidths[1] = sx;
Вывод в строку состояния организуем в локальной функции StatusOut(), куда передаем количество строк текста count и имя файла str.Сформируем вывод в первую часть строки состояния функцией _stprintf() и от- правим данные, послав окну сообщение SB_SETTEXT. Номер части указывается в WPARAM сообщения. Во вторую часть поместим имя файла str, полученное из диалогового окна открытия файла.
После установки всех размеров необходимо обратиться к функции:
UpdateWindow(hEdit);
иначе окно не успеет перерисоваться.Что еще важно, так это обеспечить получение фокуса ввода окном редактирования:
SetFocus(hEdit);
Собственно, что касается окна редактирования, то оно уже должно работать — можно вводить текст и редактировать его, доступны скроллинг и контекстное меню.Оставшийся код необходим для организации чтения файла в окно редактирования и записи в файл отредактированного текста.
Как и в задаче, представленной листингом 2.3, в сообщении о создании окна заполним структуру OPENFILENAME. И лишь два поля этой структуры: lpstrTitle и Flags отличающихся при чтении и записи, мы заполним при обработке команд чтения и записи файла. Диалог выбора файла реализуется функцией GetOpenFileName().
Но здесь имеется одна особенность — дело в том, что окно редактирования имеет собственную память, и нам нет необходимости вводить контейнер для хранения текста, вместо этого нужно поместить прочитанный файл в окно редактирования. Нужно учесть еще и ограничение — максимальный размер текста в окне редактирования 32К.
ПРИМЕЧАНИЕИ еще один момент — строки в окне редактирования разделяются парой символов "\r\n" так же, как и в файле, и это не случайно.
Размер буфера окна редактирования Edit Box может быть увеличен посылкой ему сообщения EM_SETLIMITTEXT.
Можно, конечно, организовать чтение файла построчно и добавлять эти строки в окно редактирования, однако проще прочитать файл как двоичный в буфер szText и оттуда отправить весь текст в окно, так мы и поступим.
Итак, суммируя все сказанное: открываем файл как двоичный и читаем блоком не более 0x7fff символов.
in.open(name, std::ios::binary); in.read(szText, 0x7fff);
Если прочитали ровно 0x7fff символов, то файл имеет больший размер и не может быть помещен в окно редактирования. Мы выводим предупреждающее сообщение и прекращаем чтение:
if ((m = in.gcount()) == 0x7fff)
{
MessageBox(hWnd,_T("Слишком большой файл"),_T("Edit"),MB_OK|MB_ICONSTOP); in.close();
return 0;
}
Если же размер файла меньше критического, добавим признак завершения строки '\0', поскольку метод read() возвращает массив символов, а нам нужна С-строка:
szText[m] = '\0';
После чего закрываем файл.
Теперь поместим текст в виде строки в окно редактирования. Это делается функцией:
SetWindowText(hEdit, szText);
Осталось только подсчитать количество текстовых строк, передавая окну редактора сообщение EM_GETLINECOUNT, и обновить строку состояния. Для сохранения файла, при обработке пункта меню Save, нужно решить обратную задачу. Так же открываем двоичный файл для записи, но метод для записи блока требует задания его размера, поэтому при копировании текста из окна редактирования в буфер функцией GetWindowText() сохраняем его размер m в байтах. После чего записываем буфер в файл.
out.open(name, std::ios::binary);
m = GetWindowText(hEdit, szText, 0x7fff); out.write(szText, m);
out.close();
Вновь определяем количество строк текста и обновляем строку состояния.Еще проще выглядит обработка команды меню New. Мы сделаем буфер текста "пустым", просто присвоив первому байту массива символ '\0'. Теперь осталось послать эту "пустую" строку в окно редактирования, и окно очистится:
szText[0] = '\0'; SetWindowText(hEdit, szText);
Теперь нужно очистить строку состояния. Вот и все! Вид работающего приложения представлен на рис. 3.9. Вся логика ввода и редактирования текста, а также скроллинг и контекстное меню уже реализованы в окне редактирования, и нам осталось лишь пожинать плоды выполненной работы.

Рис. 3.9. Простейший редактор текстов с элементом управления Edit Box Control
Немодальные окна
До сих пор мы имели дело с модальными окнами, которые после создания становятся активными, и пока они открыты, управление не может быть передано другому окну приложения. Немодальные же окна работают принципиально иначе — после создания они могут как потерять управление, так и получить его вновь. Примером такого окна может служить диалоговое окно поиска Найти и заменить (Find and Replace) в текстовом редакторе Word.
Для демонстрации работы немодального окна построим приложение с диалоговым окном выбора цвета фона главного окна, состоящего из трех линеек скроллинга, для базовых цветов: красного, зеленого, синего (рис. 3.10).
В качестве основы используем стандартную заготовку, куда добавим пункт меню Color, при обработке которого создадим диалоговое окно, но, поскольку нам нуж- но немодальное окно, создавать окно будем функцией CreateDialog():
hDlgColor = CreateDialog(hInst,MAKEINTRESOURCE(IDD_COLOR),hWnd,Dialog);
Функция имеет тот же набор параметров, что и DialogBox(), но возвращает дескриптор окна, который мы опишем на глобальном уровне. На самом деле это макрос, который преобразуется в функцию CreateDialogParam().
ПРИМЕЧАНИЕ
При создании диалогового окна нужно установить для него свойство Visible. Иначе окно, созданное функцией CreateDialog(), не будет отображаться.
Поскольку немодальное окно может потерять управление, то цикл обработки сообщений головной функции WinMain() должен определить источник сообщения, кото- рое может исходить либо от главного, либо немодального окна. Для этого добавим оператор if в цикл обработки сообщений:

Рис. 3.10. Немодальное диалоговое окно
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
if (!IsDialogMessage(hDlgColor, &msg))
{
TranslateMessage(&msg); DispatchMessage(&msg);
}
}
Функция IsDialogMessage() определяет, предназначено ли сообщение для указанного диалогового окна и, если это так, обрабатывает сообщение, возвращая TRUE, иначе обработка сообщения происходит традиционным образом оконной функцией главного окна. Так происходит "диспетчеризация" сообщений и, например, щелчок мыши на диалоговом окне приведет к обработке этого сообщения функцией диалогового окна, а такой же щелчок в главном окне будет обрабатываться функцией главного окна. В связи с этим принципиально важно, чтобы для всех сообщений, обрабатываемых в оконной функции немодального диалога, возвращаемое значение было TRUE, а для всех прочих сообщений — FALSE.
Далее приведем листинг 3.7 с текстом функции окна немодального диалогового окна и отметим, что закрываться немодальное окно должно функцией DestroyWindow().
Листинг 3.7. Оконная функция немодального диалогового окна
INT_PTR CALLBACK Dialog(HWND, UINT, WPARAM, LPARAM);
/////////////////////////////////////////////////////////////////////
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lPa- ram)
{
switch (message)
{
case WM_COMMAND:
switch (LOWORD(wParam))
{
case ID_DIALOG_COLOR:
hDlgColor = CreateDialog(hInst, MAKEINTRESOURCE(IDD_COLOR), hWnd, Dialog);
break;
case IDM_EXIT: DestroyWindow(hWnd); break;
default: return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_DESTROY: PostQuitMessage(0); break;
default: return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
//////////////////////////////////////////////////////////////////////
INT_PTR CALLBACK Dialog(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
static int ID_SCROLL[3] = {IDC_RED, IDC_GREEN, IDC_BLUE};
static int ID[3] = {IDC_R, IDC_G, IDC_B};
static HWND hWnd, hScroll[3]; //{hRed, hGreen, hBlue} static int color[3] = {255, 255, 255}; //{red, green, blue} HBRUSH hBrush;
int index; switch (message)
{
case WM_INITDIALOG:
for (index = 0; index < 3; index++)
{
hScroll[index] = GetDlgItem(hDlg, ID_SCROLL[index]); SetScrollRange(hScroll[index], SB_CTL, 0, 255, FALSE); SetScrollPos (hScroll[index], SB_CTL, color[index], TRUE); SetDlgItemInt(hDlg, ID[index], color[index], 0);
}
return TRUE; case WM_VSCROLL :
for (index = 0; index < 3; index++)
if ((HWND)lParam == hScroll[index]) break; SetFocus(hScroll[index]); switch(LOWORD(wParam))
{
case SB_LINEUP : color[index]--; break; case SB_LINEDOWN : color[index]++; break; case SB_PAGEUP : color[index] -= 16; break; case SB_PAGEDOWN : color[index] += 16; break; case SB_THUMBTRACK:
case SB_THUMBPOSITION : color[index] = HIWORD(wParam); break;
}
color[index] = max(0, min(color[index], 255));
if (color[index] != GetScrollPos(hScroll[index], SB_CTL))
{
SetDlgItemInt(hDlg,ID[index],color[index],0); SetScrollPos(hScroll[index], SB_CTL, color[index], TRUE); hBrush = CreateSolidBrush(RGB(color[0],color[1],color[2])); hWnd = GetParent(hDlg); DeleteObject((HBRUSH)GetClassLong(hWnd, GCL_HBRBACKGROUND)); SetClassLong(hWnd, GCL_HBRBACKGROUND, (LONG)hBrush); InvalidateRect(hWnd, NULL, TRUE);
}
return TRUE; case WM_COMMAND:
if (LOWORD(wParam) == IDCANCEL) {DestroyWindow(hDlg); return TRUE;} return FALSE;
}
return FALSE;
}
Рассмотрим оконную функцию диалогового окна. Поскольку диалоговое окно содержит 3 линейки скроллинга и 3 статических элемента для отображения состояния скроллинга, то нам удобнее описать эти элементы массивами, присвоив их элементам значение соответствующих идентификаторов:
static int ID_SCROLL[3] = {IDC_RED, IDC_GREEN, IDC_BLUE};
static int ID[3] = {IDC_R, IDC_G, IDC_B};
Так же опишем массив дескрипторов для линеек скроллинга и массив для хранения трех базовых цветов:
static HWND hScroll[3]; //{hRed, hGreen, hBlue} static int color[3] = {255, 255, 255}; //{red, green, blue}
Опишем дескриптор окна hWnd и кисти hBrush, а также переменную целого типа
index.
Далее организуем обработку сообщений. При инициализации диалогового окна в цикле определяем дескрипторы линеек скроллинга:
hScroll[index] = GetDlgItem(hDlg, ID_SCROLL[index]);
Устанавливаем диапазон скроллинга [0;255]:
SetScrollRange(hScroll[index], SB_CTL, 0, 255, FALSE);
и начальную позицию в 255, поскольку начальный цвет окна — белый, соответствует значению RGB(255, 255, 255).
SetScrollPos (hScroll[index], SB_CTL, color[index], TRUE);
После чего выведем значение в статическом элементе:
SetDlgItemInt(hDlg, ID[index], color[index], 0);
Теперь рассмотрим сообщение WM_VSCROLL, генерируемое при воздействии на линейку скроллинга. Вначале нужно определить, от какой линейки пришло сообщение. Сделаем это в цикле, сравнивая дескриптор элемента, породившего сообщение, и передаваемого в lParam, с дескрипторами линеек скроллинга:
for (index = 0; index < 3; index++)
if ((HWND)lParam == hScroll[index]) break;
При выходе из цикла переменная index будет соответствовать индексу линейки скроллинга, которая сгенерировала сообщение. Установим фокус ввода на выбранную линейку:
SetFocus(hScroll[index]);
Это не обязательно, но позволит нам использовать для управления клавиатуру.Обработку сообщений скроллинга осуществим стандартным образом так же, как в листинге 2.3, только вместо переменной будем оперировать элементом массива color[index]:
Убедившись, что состояние скроллинга изменилось, установим новое значение в статическом элементе:
SetDlgItemInt(hDlg, ID[index], color[index],0);
а также новую позицию движка линейки:
SetScrollPos(hScroll[index], SB_CTL, color[index], TRUE);
Теперь создаем новую кисть:
hBrush = CreateSolidBrush(RGB(color[0],color[1],color[2]));
и уничтожаем старую. Дескриптор старой кисти получим обращением к функции
GetClassLong():
DWORD WINAPI GetClassLong(HWND hWnd, int nIndex);
если передать в качестве параметра GCL_HBRBACKGROUND:
DeleteObject((HWND)GetClassLong(hWnd, GCL_HBRBACKGROUND));
Уничтожить старую кисть необходимо, чтобы не "замусорить" память.
ПРИМЕЧАНИЕОсталось установить новое значение параметра класса окна функцией SetClassLong(), эта функция, напротив, позволит изменить свойства класса окна, зарегистрированного в системе:
Здесь нам понадобился дескриптор главного окна hWnd. Можно перенести его определение на глобальный уровень или воспользоваться функцией GetParent().
DWORD WINAPI SetClassLong(HWND hWnd, int nIndex, LONG dwNewLong);
передавая ей тот же параметр GCL_HBRBACKGROUND и дескриптор новой кисти.
SetClassLong(hWnd, GCL_HBRBACKGROUND, (LONG)hBrush);
После чего объявляем окно недействительным для перерисовки фона:
InvalidateRect(hWnd, NULL, TRUE);
ПРИМЕЧАНИЕНам осталось обработать сообщение о закрытии окна. Мы убрали кнопку закрытия диалогового окна и воспользовались кнопкой системного меню, которая приводит к тому же результату.
Функция GetClassLong() позволяет получить, а SetClassLong() установить большинство параметров класса окна, которые определяются значением передаваемого индекса: GCL_MENUNAME,GCL_HBRBACKGROUND,GCL_HCURSOR,GCL_HICON,GCL_HMODULE,GCL_ CBWNDEXTRA,GCL_CBCLSEXTRA,GCL_WNDPROC,GCL_STYLE.
Все остальные сообщения оконная функция диалога игнорирует, возвращая FALSE.
Вот собственно и все: запустив программу на выполнение, мы можем регулировать цвет фона, передвигая движки линеек скроллинга для трех базовых цветов.
Стандартное диалоговое окно выбора цвета
Картина будет неполной, если мы не рассмотрим стандартное диалоговое окно выбора цвета, реализованное в Windows. Прототипы функции ChooseColor() и структуры CHOOSECOLOR, необходимых для создания диалогового окна, как и для всех других диалоговых окон, описаны в файле включений commdlg.h, который нужно добавить в заголовке программы. Для создания диалогового окна создадим пункт меню StdColor с идентификатором ID_STDCOLOR. При обработке этого пункта меню вызовем диалоговое окно выбора цвета, которое изображено на рис. 3.11, а код оконной функции, вызывающий это окно, в листинге 3.8.

Рис. 3.11. Стандартное диалоговое окно выбора цвета
Листинг 3.8. Оконная функция задачи выбора цвета фона с помощью стандартного диалогового окна
#include
COLORREF stdColor = RGB(255,255,255);
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lPa- ram)
{
static CHOOSECOLOR ccs;
static COLORREF acrCustClr[16]; static HBRUSH hBrush;
switch (message)
{
case WM_CREATE:
ccs.lStructSize = sizeof(CHOOSECOLOR); ccs.hwndOwner = hWnd;
ccs.rgbResult = stdColor;
ccs.Flags = CC_RGBINIT | CC_FULLOPEN; ccs.lpCustColors = (LPDWORD)acrCustClr; break;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case ID_STDCOLOR:
if (ChooseColor(&ccs))
{
stdColor = ccs.rgbResult;
if (hBrush) DeleteObject(hBrush); hBrush = CreateSolidBrush(stdColor);
SetClassLong(hWnd, GCL_HBRBACKGROUND, (LONG)hBrush); InvalidateRect(hWnd, NULL, TRUE);
}
break;
case IDM_EXIT: DestroyWindow(hWnd); break;
default: return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_DESTROY: PostQuitMessage(0); break;
default: return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
Для создания диалогового окна выбора цвета необходимо описать переменную типа структуры CHOOSECOLOR, массив acrCustClr типа COLORREF для хранения 16 дополнительных цветов, а также переменную color, которую окно будет использовать для задания начального цвета.
ПРИМЕЧАНИЕМы задали начальное значение stdColor = RGB(255, 255, 255), чтобы начать выбор с белого цвета.
В диалоговом окне выбора цвета имеется возможность кроме основного цвета задать еще 16 дополнительных цветов. Причем даже если они не нужны, массив acrCustClr должен быть определен.
static CHOOSECOLOR ccs;
static COLORREF acrCustClr[16];
Здесь же опишем дескриптор кисти.
static HBRUSH hBrush;
Все переменные класса памяти static, чтобы они не потеряли значения при выходе из оконной функции. Необходимые поля структуры CHOOSECOLOR заполняются при обработке сообщения
WM_CREATE.
ccs.lStructSize = sizeof(CHOOSECOLOR); //Задается размер структуры
ccs.hwndOwner = hWnd; // Дескриптор родительского окна
ccs.rgbResult = stdColor; //Начальный цвет диалога
ccs.Flags = CC_RGBINIT | CC_FULLOPEN;
Флаг работы CC_RGBINIT заставляет диалоговое окно использовать цвет, указанный в rgbResult, как начальный цветовой выбор; флаг CC_FULLOPEN — отображает дополнительные средства управления для создания дополнительных цветов в массиве acrCustClr.
ccs.lpCustColors = (LPDWORD)acrCustClr;
Диалоговое окно выбора цвета вызывается функцией ChooseColor(), аргументом которой является адрес структуры CHOOSECOLOR. При удачном результате работы функции она возвращает TRUE, а выбранный цвет мы получаем из поляccs.rgbResult.
Теперь, чтобы не оставлять "мусор" в памяти, уничтожим старую кисть:
if (hBrush) DeleteObject(hBrush);
Оператор if используем, чтобы обойти функцию DeleteObject() при первом об- ращении к диалогу, когда дескриптор hBrush равен NULL.
Создаем новую кисть:
hBrush = CreateSolidBrush(stdColor);
и используем ее в качестве фонового цвета окна:
SetClassLong(hWnd, GCL_HBRBACKGROUND, (LONG)hBrush);
Осталось только объявить окно недействительным для смены фона:
InvalidateRect(hWnd, NULL, TRUE);
и задача будет решена.
Вопросы к главе
- Техника создания окна, аргументы функции CreateWindow().
- Чем отличаются дочерние и всплывающие окна?
- Назначение функции UpdateWindow()?
- Создание диалогового окна, функции DialogBox() и CreateDialog().
- Стандартные и общие элементы управления. В чем заключается особенность общих элементов управления?
- Как осуществляется обмен данными с элементами управления?
- Приятельские окна для спина.
- Полоса прокрутки — свойство окна и элемент управления.
- Как происходит передача управления от немодального окна?
- Стандартное диалоговое окно выбора цвета, функция ChooseColor().
Задания для самостоятельной работы
- Написать программу, изображающую шахматную доску, где каждая клетка будет дочернем окном, которое меняет цвет при щелчке левой кнопкой мыши на его поверхности, а при нажатии на правую кнопку появляется всплывающее окно с координатами клетки в шахматной нотации. Всплывающее окно можно создать на элементе управления STATIC.
- Модифицировать программу построения графика (листинг 3.2), предусмотрев блокировку пункта меню xy-graph до тех пор, пока не будет загружен файл с данными. После загрузки файла блокировку снять.
- В программе построения графика предусмотреть вывод координат точки во всплывающем окне после нажатия правой кнопки мыши. При отпускании клавиши мыши окно убрать.
Указание: воспользуйтесь элементом управления STATIC. В случае перекрытия курсора мыши всплывающим окном воспользуйтесь стандартным приемом "захвата мыши".
Предусмотрите возможность интерактивного редактирования графика путем "буксировки" точек данных левой кнопкой мыши. После закрытия окна графика сохраните отредактированные данные. - Написать тестовую программу для работы со спином при помощи "приятельского" окна Edit Box.
- Написать программу для построения круговой диаграммы в отдельном всплывающем окне. Реализовать диалоговое окно для настройки цветов заполнения.
- Для предыдущей задачи организовать ввод исходных данных для построения круговой диаграммы в диалоговом окне.
- Добавить к окну программы просмотра текстовых файлов (листинг 2.3) строку состояния, вывести в нее количество строк текста, максимальную длину строки и полное имя файла.
- С помощью немодального диалогового окна реализовать функцию поиска сло- ва для программы просмотра текстовых файлов (листинг 2.3). Если слово най- дено, обеспечить прокрутку текста до необходимой позиции и выделить най- денное слово цветом.
- Решить предыдущую задачу, используя элемент управления Find.