лекции / Shchupak_Yu._Win32_API_Razrabotka_prilozheniy_dlya_Windows
.pdfПримеры программ |
151 |
|
|
|
|
doc.UpdateHscroll(hWnd, xInc); break;
case WM_PAINT:
hDC = BeginPaint(hWnd, &ps);
doc.PutText(hWnd, hDC);
EndPaint(hWnd, &ps); break;
case WM_DESTROY: PostQuitMessage(0); break;
default:
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
return 0;
}
//////////////////////////////////////////////////////////////////////
Обратите внимание на следующие особенности реализации класса KDocument:
Для хранения текстового документа используется член класса lines, который объявлен как вектор строк типа string.
В методе Open заданный входной файл читается по строкам. Предполагается, что строки разделяются символом \n. Каждая прочитанная строка добавляет ся в конец вектора lines. Затем в цикле for вычисляется максимальная длина строки lineLenMax с учетом возможного присутствия символов табуляции. При сутствие символа табуляции в строке lines[i] проверяется методом find класса string, который возвращает позицию обнаруженного символа \t или значение –1, если символ табуляции не обнаружен. В методе ScrollSettings величина lineLenMax используется для установки максимального значения диапазона го ризонтальной полосы прокрутки.
В методе Initialize вычисляются размеры «единиц данных» для скроллинга. Ве личина cxChar задает шаг изменения в горизонтальном измерении, а величина yStep — шаг изменения в вертикальном измерении. Для вычислений исполь зуются метрики текста, полученные в параметре tm.
Метод ScrollSettings предназначен для установки параметров вертикальной и горизонтальной полос прокрутки при заданных ширине width и высоте height клиентской области окна.
Метод UpdateVscroll выполняет итоговую обработку всех сообщений от верти кальной полосы прокрутки. В нем же производятся прокрутка содержимого окна при помощи функции ScrollWindow, обновление положения движка с по мощью SetScrollInfo и обновление клиентской области при помощи функции UpdateWindow. Более подробные комментарии по этому этапу можно найти в разделе «Полосы прокрутки и вывод текста».
Обратите особое внимание на первые две инструкции в теле функции. Они предваряются комментариями «ограничение на положительное приращение» и «ограничение на отрицательное приращение». Первая инструкция обеспе
152 |
Глава 2. GDI — графический интерфейс устройства |
|
|
чивает прекращение прокрутки при продвижении документа к его концу, как только позиция движка достигнет значения vertRange – (int)vsi.nPage. В этом положении движка последняя страница документа полностью видна в ок не и дальнейшая прокрутка теряет смысл. Заметим, что преобразование к типу int для поля vsi.nPage необходимо для корректного выполнения макро са min.
Вторая инструкция обеспечивает прекращение прокрутки при продвижении документа к его началу, как только позиция движка достигнет значения vsi.nMin.
Метод UpdateHscroll производит итоговую обработку всех сообщений от гори зонтальной полосы прокрутки. Реализация метода аналогична реализации ме тода UpdateVscroll.
Метод PutText предназначен для вывода фрагмента текста из документа в кли ентскую область окна.
Для создания полей слева и справа от видимого в окне текста (в данном при мере шириной в один символ) используется регион отсечения hRgn. Он создается вызовом функции CreateRectRgnIndirect, причем в качестве аргумента передается адрес прямоугольника rect, который в вертикальном измерении совпадает с пря моугольником клиентской области, а в горизонтальном измерении уменьшен слева и справа на величину cxChar. Созданный регион выбирается в контекст устрой ства функцией SelectClipRgn.
Фрагмент текста выводится в цикле for в соответствии с текущим положением движка vsi.nPos на вертикальной полосе прокрутки и текущим положением движ ка hsi.nPos на горизонтальной полосе прокрутки. Если в текущей строке lines[i] нет символов табуляции, то вызывается функция TextOut. В противном случае ис пользуется функция TabbedTextOut.
По умолчанию в контексте устройства установлен режим отображения MM_TEXT, поэтому начало координат совмещено с левым верхним углом клиентской области. Координата y для вывода каждой следующей строки вычисляется с учетом сме щения yStep.
Если значение hsi.nPos совпадает со значением hsi.nMin, то координата x, вычисляемая по формуле cxChar × (hsi.nMin – hsi.nPos + 1), имеет значение cxChar. В этом случае строка выводится в окно, начиная с первого символа. А поскольку в контексте устройства выбран регион отсечения с дескриптором hRgn, то первый видимый символ в окне нельзя выводить левее, чем x = cxChar.
В случае hsi.nPos > 0 координата x будет принимать сначала нулевое, а потом отрицательные значения, абсолютная величина которых возрастает по мере про крутки документа по горизонтали вправо. То есть начало вывода строки прихо дится на точку экрана, лежащую слева за пределами окна приложения. Разуме ется, за счет отсечения, за которым следит Windows, пользователь будет видеть только ту часть строки, которая попадает в клиентскую область окна. А еще точ нее, только ту часть строки, которая попадает в регион отсечения.
Перед выходом из функции инструкция SelectClipRgn(hdc, NULL) удаляет из кон текста устройства выбранный ранее регион отсечения.
Примеры программ |
153 |
|
|
|
|
Теперь следует рассмотреть особенности кода, приведенного в файле TextViewer.cpp:
Для работы с документом объявлен глобальный объект doc класса KDocument.
В функции WinMain вызывается метод Open объекта doc, выполняющий загруз ку документа из файла, имя которого задается макросом FILE_NAME. В данном случае используется файл README.TXT, входящий в состав Microsoft Visual C++ 6.01. Конечно, в этом макросе вы можете определить путь к любому другому текстовому файлу.
Обратите внимание на флаги WS_VSCROLL | WS_HSCROLL, переданные конструк тору класса KWnd в составе параметра windowStyle. Именно они обеспечивают появление у главного окна приложения вертикальной и горизонтальной по лос прокрутки.
В оконной процедуре WndProc метод Initialize объекта doc вызывается при об работке сообщения WM_CREATE, а метод ScrollSettings — при обработке сообще ния WM_SIZE.
Обработка сообщения WM_VSCROLL завершается вызовом метода doc.UpdateVscroll(hWnd, yInc). А обработка сообщения WM_HSCROLL завершается вызовом метода doc.UpdateHscroll(hWnd, xInc).
Сам вывод текста происходит в блоке обработки сообщения WM_PAINT при по мощи метода doc.PutText(hWnd, hDC).
Выполните компиляцию этого проекта и поэкспериментируйте с изменения ми размеров окна приложения и прокруткой текста в окне.
Вывод временной диаграммы напряжения переменного электрического тока
Приложение U_T_Diagram демонстрирует использование страничной системы ко ординат, создание логических перьев, рисование различных линий, создание ло гических шрифтов, вывод текста с применением разных шрифтов.
В программе решается задача формирования и вывода на экран временной диаграммы напряжения переменного электрического тока. Действующее значе ние напряжения — 220 В, частота — 50 Гц. Диаграмма строится для промежутка времени, в котором происходят ровно два периода колебаний.
На рис. 2.37 показано окно приложения U_T_Diagram после его запуска. К сожалению, черно белый рисунок трансформирует различные цвета в оттенки серого и поэтому не передает всей красоты созданной диаграммы. Например, ли нии координатной сетки на самом деле имеют зеленоватый цвет, а синусоида гра фика — малиновый цвет. Если вы хотите все таки насладиться этой красотой, то придется ввести в компьютер прилагаемый ниже текст программы, откомпили ровать и запустить ее.
Период синусоидальных колебаний T связан с их частотой f соотношением T = 1/f, поэтому для частоты 50 Гц период составляет 0,02 с, или 20 мс. Также по лезно вспомнить, что действующее значение напряжения переменного тока U свя зано с его амплитудным значением Um соотношением U = 0,707 . Um. Из этого следу ет, что амплитудное значение напряжения равно 311,17 В.
1 На вашем компьютере путь к этому файлу может быть другим.
154 |
Глава 2. GDI — графический интерфейс устройства |
|
|
Рис. 2.37. Окно приложения U_T_Diagram
Для улучшения внешнего вида графика используются не только разные цвета линий, но и разная толщина. Линии координатной сетки рисуются толщиной 1 пиксел, оси X и Y — толщиной 3 пиксела, а сама синусоида — толщиной 5 пиксе лов.
Прежде чем привести листинг программы, сделаем некоторые пояснения по поводу выбора и настройки координатной системы. Размышления над сутью ре шаемой задачи приводят нас к убеждению, что здесь можно обойтись без аффин ных преобразований и, следовательно, нас вполне устроит страничная система координат.
Физическую область вывода (viewport) стоит привязать к клиентской области окна, размеры которой можно получить при помощи функции GetClientRect. Бла годаря этому рисунок будет автоматически масштабироваться при изменении размеров основного окна приложения.
Теперь подумаем, как нам организовать логическую область вывода (window). Казалось бы, здесь удобно использовать логические единицы, присущие данной проблемной области, то есть по оси абсцисс — миллисекунды, а по оси ординат — вольты. Но если выбрать эти логические единицы, то возникнет очень неприят ная проблема. Фактически, мы потеряем контроль над толщиной пера, использу емого для прорисовки линий. Это раз. Кроме того, толщина пера окажется разной при рисовании по горизонтали и по вертикали, так как она будет определяться масштабными коэффициентами mx и my1. Это два.
Причиной перечисленных неприятностей являются особенности реализа ции в Win32 API объекта логического пера. Дело в том, что в вызове функции
CreatePen(penStyle, nWidth, crColor) параметр nWidth задает толщину пера имен но в логических единицах. Исключением является нулевое значение nWidth,
1mx = xVE / xWE, my = yVE / yWE, где xVE, yVE — экстенты физической области вывода, а xWE, yWE — экстенты логической области вывода.
Примеры программ |
155 |
|
|
|
|
определяющее перо толщиной 1 пиксел независимо от установленных коор динатных преобразований.
Например, если для физической области вывода размеры экстентов равны xVE = 952, yVE = 7021, а для логической области будут выбраны значения экстентов xWE = 60 (мс), yWE = 1000 (В), то логическая единица по горизонтали будет соот ветствовать mx = 952 / 60 = 15,86 или, округленно, 16 пикселам. Логическая еди ница по вертикали будет соответствовать my = 702 / 1000 = 0,72 или, округленно, одному пикселу.
Чтобы все таки нарисовать график так, как это показано на рис. 2.37, будем придерживаться следующего плана действий. Установим режим отображения MM_ISOTROPIC, а значения экстентов для логической области вывода сделаем совпа дающими со значениями экстентов для физической области вывода. В этом слу чае будет получен полный контроль над толщиной пера. Проблему же масштаби рования придется решать «вручную», используя соответствующие масштабные коэффициенты:
nPixPerVolt = yVE / 1000.0; nPixPerMs = xVE / 60.0;
Код проекта приведен в листинге 2.3.
Листинг 2.3. Проект U_T_Diagram2
//////////////////////////////////////////////////////////////////////
// U_T_Diagram.cpp #include <windows.h> #include <math.h> #include "KWnd.h"
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); void DrawDiagram(HWND, HDC);
#define Pi 3.14159265
//==================================================================== int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
MSG msg;
KWnd mainWnd("Временная диаграмма напряжения переменного электрического тока", hInstance, nCmdShow, WndProc);
while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg);
}
return msg.wParam;
}
//====================================================================
продолжение
1Эти значения являются шириной и высотой клиентской области окна, созданного с размерами по умолчанию.
2 Не забудьте добавить к проекту файлы KWnd.h и KWnd.cpp (см. листинг 1.2 в главе 1).
156 |
Глава 2. GDI — графический интерфейс устройства |
|
|
Листинг 2.3 (продолжение) |
|
LRESULT CALLBACK WndProc(HWND |
hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) |
{ |
|
HDC hDC; |
|
PAINTSTRUCT ps; |
|
switch (uMsg)
{
case WM_PAINT:
hDC = BeginPaint(hWnd, &ps);
DrawDiagram(hWnd, hDC);
EndPaint(hWnd, &ps); break;
case WM_DESTROY: PostQuitMessage(0); break;
default:
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
return 0;
}
//==================================================================== void DrawDiagram(HWND hwnd, HDC hdc)
{
RECT rect; GetClientRect(hwnd, &rect);
const int xVE = rect.right - rect.left; const int yVE = rect.bottom - rect.top;
const |
int |
xWE |
= |
xVE; |
|
||
const |
int |
yWE |
= |
yVE; |
|
||
double |
nPixPerVolt |
= yVE / 1000.0; |
|
||||
double |
nPixPerMs = |
xVE / 60.0; |
// n Pixels Per Millisec |
||||
SetMapMode (hdc, MM_ISOTROPIC);
SetWindowExtEx (hdc, xWE, yWE, NULL);
SetViewportExtEx (hdc, xVE, -yVE, NULL);
SetViewportOrgEx (hdc, 10*nPixPerMs, yVE/2, NULL);
const int tMin = 0; |
// ms |
const int tMax = 40; |
// ms |
const int uMin = -400; |
// V |
const int uMax = 400; |
// V |
const int tGridStep = 5; |
|
const int uGridStep = 100; |
|
int x, y; |
|
char* xMark[] = { "0", "5", "10", "15", "20", "25", "30", "35", "40"}; char* yMark[] = { "-400","-300","-200","-100", "0", "100", "200", "300",
"400"};
// Сетка
HPEN hPen0 = CreatePen(PS_SOLID, 1, RGB(0, 160, 0)); HPEN hOldPen = (HPEN)SelectObject(hdc, hPen0);
int u = uMin;
Примеры программ |
157 |
||
|
|
|
|
|
int xMin = tMin * nPixPerMs; |
|
|
|
int xMax = tMax * nPixPerMs; |
|
|
|
for (int i = 0; i < 9; ++i) { |
|
|
|
y = u * nPixPerVolt; |
|
|
|
MoveToEx(hdc, xMin, y, NULL); |
|
|
|
LineTo(hdc, xMax, y); |
|
|
|
TextOut(hdc, xMin-40, y+8, yMark[i], strlen(yMark[i])); |
|
|
|
u += uGridStep; |
|
|
} |
|
|
|
|
int t = tMin; |
|
|
|
int yMin = uMin * nPixPerVolt; |
|
|
|
int yMax = uMax * nPixPerVolt; |
|
|
|
for (i = 0; i < 9; ++i) { |
|
|
|
x = t * nPixPerMs; |
|
|
|
MoveToEx(hdc, x, yMin, NULL); |
|
|
|
LineTo(hdc, x, yMax); |
|
|
|
TextOut(hdc, x-6, yMin-10, xMark[i], strlen(xMark[i])); |
|
|
|
t += tGridStep; |
|
|
} |
|
|
|
|
// Îñü "x" |
|
|
|
HPEN hPen1 = CreatePen(PS_SOLID, 3, RGB(0, 0, 0)); |
|
|
|
SelectObject(hdc, hPen1); |
|
|
|
MoveToEx(hdc, 0, 0, NULL); LineTo(hdc, xMax, 0); |
|
|
|
static LOGFONT lf; |
|
|
|
lf.lfPitchAndFamily = FIXED_PITCH | FF_MODERN; |
|
|
|
lf.lfItalic = TRUE; |
|
|
|
lf.lfWeight = FW_BOLD; |
|
|
|
lf.lfHeight = 20; |
|
|
|
lf.lfCharSet = DEFAULT_CHARSET; |
|
|
|
lstrcpy( (LPSTR)&lf.lfFaceName, |
"Arial" ); |
|
|
HFONT hFont0 = CreateFontIndirect(&lf); |
|
|
|
HFONT hOldFont = (HFONT)SelectObject(hdc, hFont0); |
|
|
|
SetTextColor(hdc, RGB(0, 0, 200)); |
|
|
|
TextOut(hdc, xMax+10, 10, "t (ms)", 6); |
|
|
|
// Îñü "y" |
|
|
|
MoveToEx(hdc, 0, yMin, NULL); LineTo(hdc, 0, yMax); |
|
|
|
TextOut(hdc, -10, yMax+30, "u (V)", 5); |
|
|
// График |
|
|
|
|
HPEN hPen2 = CreatePen(PS_SOLID, 5, RGB(200, 0, 100)); |
|
|
|
SelectObject(hdc, hPen2); |
|
|
|
int tStep = 1; |
|
|
|
double radianPerMs = 2 * Pi / 20; |
|
|
|
const double uAmplit = 311.17; |
// volt |
|
|
t = tMin; |
|
|
|
MoveToEx(hdc, 0, 0, NULL); |
|
|
|
while (t <= tMax) { |
|
|
|
u = uAmplit * sin( t * radianPerMs); |
|
|
|
LineTo(hdc, t * nPixPerMs, u * nPixPerVolt); |
|
|
|
t += tStep; |
|
|
} |
|
|
|
// Заголовок |
|
|
|
|
char* title = "Диаграмма напряжения переменного электр. тока"; |
|
|
|
|
продолжение |
|
158 Глава 2. GDI — графический интерфейс устройства
Листинг 2.3 (продолжение)
lf.lfItalic = FALSE; lf.lfWeight = FW_BOLD; lf.lfHeight = 30;
HFONT hFont1 = CreateFontIndirect(&lf); SelectObject(hdc, hFont1); SetTextColor(hdc, RGB(0, 200, 0));
TextOut(hdc, 0, yMax + 70, title, strlen(title));
SelectObject(hdc, hOldPen);
SelectObject(hdc, hOldFont);
}
//////////////////////////////////////////////////////////////////////
Основная работа в приведенной программе сосредоточена в функции DrawDiagram, которая вызывается в блоке обработки сообщения WM_PAINT.
Обратите внимание на то, как реализована настройка страничной системы ко ординат. В выбранной нами стратегии решения логические единицы совпадают с физическими единицами, поэтому введем новый термин — металогические еди ницы, которые отражают проблемную область. Одна металогическая единица по горизонтали соответствует 1 мс, а одна металогическая единица по вертикали со ответствует 1 В.
Заметим, что вызов функции
SetViewportExtEx (hdc, xVE, -yVE, NULL);
реализует преобразование отражения, благодаря которому значения по оси ор динат возрастают снизу вверх, как это принято в проблемной области.
Другой вызов:
SetViewportOrgEx (hdc, 10*nPixPerMs, yVE/2, NULL);
обеспечивает установку начала координат строго посередине по вертикали
ис отступом на 10 металогических единиц слева по горизонтали.
Впрограмме используются металогические величины tMin, tMax, uMin, uMax, tGridStep, uGridStep, являющиеся константами. Также используются и металоги ческие переменные t, u. Переход к соответствующим логическим (и совпадаю щим с ними физическим) величинам осуществляется через коэффициенты
nPixPerMs, nPixPerVolt.
Для построения графика в цикле while применяется кусочно линейная апп роксимация функции sin(x). После очередного приращения аргумента вычисля ется значение функции, по которому вычисляется текущее значение u. Затем при помощи функции LineTo проводится отрезок линии, соединяющий предыдущее и текущее значения u.
Так как функция sin требует в качестве аргумента значение угла в радианах, то используется понятие угловой частоты ω = 2 . π . f = 2 . π / T для вычисления соответствующего масштабного коэффициента:
radianPerMs = 2 * Pi / 20;
После компиляции и запуска на выполнение можно поэкспериментировать с окном программы, изменяя его размеры, чтобы увидеть, как будет масштабиро ваться изображение.
159
3GDI. Палитры, растры, метафайлы
В третьей главе будет продолжено рассмотрение графического интерфейса уст ройства — GDI. На очереди рассмотрение таких графических объектов, как палит ры, битовые образы, или растры, и метафайлы. Даже если вы не планируете исполь зовать палитры в создаваемых приложениях, беглое знакомство с ними может оказаться полезным, так как понятие цветовых таблиц используется в форматах DIB растров и DIB секций.
Палитры
Если устройство вывода поддерживает полный диапазон цветов, определенных 24 битным RGB значением1, то палитра вам не нужна. К сожалению, еще не вышли из употребления старые модели дисплеев, которые поддерживают только 256 цветов. Другие графические устройства, такие как принтеры и плоттеры, тоже имеют огра ничения по количеству воспроизводимых цветов.
Графические объекты, именуемые в Windows палитрами, обеспечивают над лежащее согласование цветов при работе приложений на разных аппаратных плат формах. Чтобы узнать, поддерживает ли конкретное устройство работу с палит рой, нужно вызвать функцию GetDeviceCaps, передав значение индекса RASTERCAPS, и убедиться, что возвращаемое значение содержит флажок RC_PALETTE.
Для упрощения изложения в следующих разделах, посвященных палитре, под устройством вывода будет подразумеваться дисплей.
Основные принципы управления палитрами
Цветовая палитра представляет собой таблицу (массив) цветов, которые способ но воспроизвести устройство. Вход в таблицу для доступа к каждому цвету осу ществляется по индексу.
В Win32 GDI используется несколько типов палитр.
Системная палитра (system palette) определяет все цвета, которые могут быть одновременно отображены устройством. Диапазон цветов зависит от аппаратной
1 24 битное значение позволяет поддерживать 16 777 216 цветов.
160 |
Глава 3. GDI. Палитры, растры, метафайлы |
|
|
палитры, реализуемой видеоадаптером, и от настроек экрана. В любом случае сис темная палитра является неким «программным представителем» аппаратной палит ры. Приложение не может непосредственно модифицировать системную палитру. Системная палитра обычно содержит 256 элементов (входов), из которых 20 заре зервировано для так называемых статических цветов, которые никогда не изменя ются. Эти цвета используются всеми приложениями для вывода меню, кнопок, фона окна, текстовых надписей и других элементов стандартного интерфейса.
Логическая палитра (logical palette) — это палитра уровня приложения. Она со здается приложением и закрепляется за контекстом устройства. Логическая палитра позволяет определять и использовать цвета, которые соответствуют по требностям приложения. После создания логической палитры с помощью функ ции CreatePalette или CreateHalftonePalette она должна быть реализована вызовом функции RealizePalette. В процессе реализации Windows заполняет неиспользуемые элементы в системной палитре цветами из логической палитры. Если неиспользо ванных элементов меньше, чем нужно, то Windows распределяет оставшиеся цвета из логической палитры, либо подыскивая ближайший цвет в аппаратной палитре, либо определяя подходящее смешение (dithering) цветов, если применяются сплош ные кисти.
Для успешного применения логической палитры нужно учесть еще один ню анс, о котором в MSDN практически ничего не говорится. Во всех функциях, тре бующих передачи аргумента типа COLORREF, соответствующее значение должно быть определено с помощью макроса PALETTERGB, а не при помощи макроса RGB.
Если приложение явно не создает логическую палитру, то используется па литра по умолчанию (default palette), содержащая только те 20 цветов, которые имеются в системной палитре. В этом случае для аппроксимации цвета, отсут ствующего в палитре, будут использоваться указанные 20 цветов.
Чтобы обеспечить одновременную работу нескольких приложений, использу ющих различные логические палитры, Windows предоставляет активному окну1 приоритет в установлении цветов для логической палитры. Неактивным окнам приходится пользоваться оставшимися цветами. Неактивные окна используют все незадействованные элементы в логической палитре и применяют ближайшие согласованные цвета для всех невыполненных запросов к палитре. Однако эта проблема обычно не очень существенна, поскольку в большинстве приложений используются только системные цвета.
Для координации функционирования активных и неактивных окон при работе с палитрами, прежде чем предоставить фокус ввода приложению, которое исполь зует логическую палитру, Windows отправляет ему сообщение WM_QUERYNEWPALETTE. Это сообщение дает возможность приложению еще раз реализовать палитру и вос становить цвета, которые могли быть изменены другими приложениями, пока окно было неактивным.
Сообщение WM_PALETTECHANGED отправляется всем окнам, когда одно из прило жений реализует свою логическую палитру. Для неактивных окон это сообщение несет информацию о том, что некоторые цвета в палитре могли быть изменены другим окном, имеющим фокус ввода.
1 Окну, имеющему фокус ввода.
