Операционная система Microsoft Windows 3.1 для программиста© Александр Фролов, Григорий ФроловТом 12, М.: Диалог-МИФИ, 1993, 255 стр. 2.3. Полоса просмотраПолосы просмотра (Scrollbar) широко используются в приложениях Windows для просмотра текста или изображения, которое не помещается в окне. Из руководства пользователя Windows вы знаете, что полосы просмотра бывают горизонтальные или вертикальные. Обычно они располагаются, соответственно, в нижней и правой части окна. Полоса просмотра представляет собой орган управления, созданный на базе предопределенного класса "scrollbar". Горизонтальная и вертикальная полоса просмотра посылают в функцию родительского окна сообщения WM_HSCROLL и WM_VSCROLL, соответственно. Параметр WParam этих сообщений несет в себе информацию о действии, которое вы выполнили над полосой просмотра. Полоса просмотра состоит из нескольких объектов, имеющих различное назначение. На рис. 2.7 показано назначение объектов вертикальной полосы просмотра при ее использовании для свертки текстовых документов.
Рис. 2.7. Вертикальная полоса просмотра Если вы устанавливаете курсор мыши на верхнюю кнопку (со стрелкой) полосы просмотра и нажимаете левую клавишу мыши, документ сдвигается в окне вниз на одну строку. Если вы выполняете аналогичную операцию с нижней кнопкой полосы просмотра, документ сдвигается на одну строку вверх. Положение ползунка при этом изменяется соответствующим образом. Если установить курсор мыши в область полосы просмотра выше ползунка, но ниже верхней кнопки и нажимать на левую клавишу мыши, документ сдвигается вниз на одну страницу. Аналогично, если щелкнуть левой клавишей мыши в области ниже ползунка, но выше нижней кнопки, документ сдвинется на одну страницу вверх. Ползунок при этом устанавливается скачком в новое положение. Дискретность перемещения ползунка задается приложением при инициализации полосы просмотра. Ползунок можно перемещать мышью вдоль полосы просмотра. При этом, в зависимости от того, как организована работа приложения с полосой просмотра, в процессе перемещения содержимое окна может либо сворачиваться, либо нет. В первом случае синхронно с перемещением ползунка происходит сдвиг документа в окне. Во втором случае после перемещения ползунка документ отображается в позиции, которая зависит от нового положения ползунка. Понятие "страница" и "строка" больше подходят для текстовых документов. Для свертки других типов документов можно использовать другие термины, однако в любом случае полоса просмотра обеспечивает два типа позиционирования - грубое и точное. Грубое позиционирование выполняется при помощи ползунка или областей полосы просмотра, расположенных между ползунком и кнопками со стрелками, точное позиционирование выполняется кнопками, расположенными на концах полосы просмотра. Следует заметить, что понятия "грубое позиционирование" и "точное позиционирование" отражают факт наличия двух типов позиционирования. Вся логика, обеспечивающая свертку окна, выполняется вашим приложением при обработке сообщений, поступающих от полосы просмотра. Горизонтальная полоса просмотра состоит из тех же объектов, что и вертикальная. Она обеспечивает свертку документа в горизонтальном направлении. Создание полосы просмотраСуществует два способа создания полос просмотра в окне приложения. Во-первых, вы можете создать полосу просмотра с помощью функции CreateWindow, указав предопределенный класс окна "scrollbar". Этот способ аналогичен используемому при создании кнопок или статических органов управления. Во-вторых, при создании окна на базе своего собственного класса вы можете указать, что окно должно иметь горизонтальную, вертикальную или обе полосы просмотра. Использование класса "scrollbar"Для создания полосы просмотра с помощью функции CreateWindow вы должны в первом параметре функции указать класс окна "scrollbar": #define IDC_SCROLLBAR 1 HWND hScroll; hScroll = CreateWindow("scrollbar", NULL, WS_CHILD | WS_VISIBLE | SBS_HORZ, 20, 40, 100, 50, hWnd, IDC_SCROLLBAR, hInstance, NULL); Заголовок окна не используется, поэтому второй параметр функции должен быть указан как NULL. Третий параметр, определяющий стиль окна, наряду с константами WS_CHILD и WS_VISIBLE должен содержать определение стиля полосы просмотра. Существует девять стилей для полосы просмотра. Соответствующие символические константы определены в файле windows.h и имеют префикс имени SBS_ (например, SBS_HORZ). Девятый параметр функции CreateWindow должен задавать идентификатор полосы просмотра. Стили полосы просмотраПри создании полосы просмотра функцией CreateWindow вы можете указать в третьем параметре следующие стили.
Определение полос просмотра при создании окнаЭтот способ создания полос просмотра чрезвычайно прост, но с его помощью вы сможете создать только одну вертикальную и одну горизонтальную полосу просмотра, расположенные, соответственно, в правой и нижней части окна. Для того чтобы у окна появились вертикальная и горизонтальная полосы просмотра, при регистрации класса окна в третьем парамере функции CreateWindow необходимо указать стили окна WS_VSCROLL или WS_HSCROLL (для того чтобы окно имело и вертикальную, и горизонтальную полосы просмотра, следует указать оба стиля): hwnd = CreateWindow(szClassName, szWindowTitle, // стиль окна WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInstance, NULL); Сообщения от полосы просмотраВсе горизонтальные полосы просмотра, определенные для окна (одним из описанных выше способов) посылают в окно сообщение WM_HSCROLL, а все вертикальные - WM_VSCROLL. Если полоса просмотра была создана первым способом (как орган управления), эти сообщения будет получать функция родительского окна. Если полоса просмотра была создана вторым способом (определена при создании окна), сообщения от нее будут поступать в функцию окна, имеющего полосы просмотра. Параметр wParam сообщений полосы просмотра содержит так называемый код полосы просмотра. Этот код соответствует действию, совершенном пользователем над полосой просмотра. Возможны следующие значения (символические константы для них определены в файле windows.h).
В ответ на сообщения полосы просмотра соответствующая функция окна должна вернуть нулевое значение. Для сообщений SB_THUMBTRACK и SB_THUMBPOSITION младшее слово параметра lParam определяет текущую позицию ползунка на полосе просмотра. Для других сообщений полосы просмотра младшее слово параметра lParam не используется. Старшее слово параметра lParam содержит идентификатор окна для полосы просмотра (если временное окно имеет полосу просмотра, старшее слово параметра lParam не используется). Несмотря на то, что в файле windows.h определены константы SB_LEFT, SB_TOP, SB_RIGHT, SB_BOTTOM, полоса просмотра никогда не посылает сообщений со значением параметра wParam, равным этим константам. Однако приложению имеет смысл предусмотреть обработчик для таких сообщений. Это нужно для подключения к полосе просмотра клавиатурного интерфейса. Если запустить любое стандартное приложение Windows, работающее с текстовыми документами, можно убедится в том, что для свертки окна, отображающего документ, можно использовать не только полосу просмотра, но и клавиши. Обычно это клавиши перемещения курсора и клавиши <PgUp>, <PgDn>. Как правило, с помощью клавиш <Home> и <End> вы можете перейти, соответственно, в начало и в конец документа. Так как действия, выполняемые при свертке, одинаковы для полосы просмотра и дублирующих ее клавиш, имеет смысл предусмотреть единый обработчик сообщений от полосы просмотра. Для добавления клавиатурного интерфейса обработчик клавиатурного сообщения WM_KEYDOWN может посылать в функцию окна сообщения полосы просмотра. Например, если обработчик сообщения WM_KEYDOWN обнаружил, что вы нажали клавишу <PgUp>, он может послать в функцию окна сообщение WM_VSCROLL со значением wParam, равным SB_PAGEUP. Результат будет в точности такой же, как будто для свертки документа на одну страницу вверх вы воспользовались полосой просмотра, а не клавиатурой. Если же обработчик клавиатурного сообщения WM_KEYDOWN обнаружил, что вы нажали клавишу <Home> или <End>, он может послать в функцию окна, соответственно, сообщение WM_VSCROLL со значением wParam, равным SB_TOP или SB_BOTTOM. Если в приложении имеются обработчики этих сообщений, они выполнят переход в начало или в конец документа. Такой подход позволяет локализовать всю логику свертки в обработчике сообщений полосы просмотра. При этом сильно упрощается процедура подключения клавиатурного интерфейса - обработчик клавиатурного сообщения WM_KEYDOWN должен послать в функцию окна сообщение полосы просмотра, соответствующее коду нажатой клавиши. Но ему не надо выполнять свертку окна. Инициализация полосы просмотраДля полосы просмотра определены понятия текущая позиция и диапазон изменения значений позиции. При передвижении ползунка вдоль полосы просмотра текущая позиция принимает дискретные значения внутри диапазона изменения значений позиции. Если ползунок находится в самом левом (для горизонтальной полосы просмотра) или самом верхнем (для вертикальной полосы просмотра) положении, текущая позиция равна минимальной. Если же ползунок находится в самом правом или самом нижнем положении, текущая позиция равна максимальной. После того как вы создали полосу просмотра одним из описанных выше способов, ее необходимо проинициализировать, указав диапазон изменений значений позиции. Для этого следует вызвать функцию SetScrollRange: void WINAPI SetScrollRange(HWND hwnd, int fnBar, int nMin, int nMax, BOOL fRedraw); Параметр hwnd определяет идентификатор окна, имеющего полосу просмотра, или идентификатор полосы просмотра, созданного как орган управления. Параметр fnBar определяет тип полосы просмотра, для которой выполняется установка диапазона изменения значений позиции:
Параметры nMin и nMax определяют, соответственно, минимальное и максимальное значение для диапазона. Разность между nMax и nMin не должна превышать 32767. Параметр fRedraw определяет, следует ли перерисовывать полосу просмотра для отражения изменений. Если указано значение TRUE, после установки диапазона полоса просмотра будет перерисована, а если FALSE - не будет. В любой момент времени вы можете узнать диапазон для полосы просмотра, вызвав функцию GetScrollRange: void WINAPI GetScrollRange(HWND hwnd, int fnBar, int FAR* lpnMinPos, int FAR* lpnMaxPos); Параметры hwnd и fnBar аналогичны параметрам функции SetScrollRange. Первый из них определяет идентификатор окна или органа управления, второй - тип полосы просмотра. Параметры lpnMinPos и lpnMaxPos - указатели на переменные, в которые после возврата из функции будут записаны, соответственно, минимальное и максимальное значение диапазона. Управление полосой просмотраДля установки ползунка в заданную позицию следует использовать функцию SetScrollPos: int WINAPI SetScrollPos(HWND hwnd, int fnBar, int nPos, BOOL fRepaint); Параметры hwnd и fnBar определяют, соответственно, идентификатор окна или органа управления и тип полосы просмотра. Параметр nPos определяет новое положение ползунка. Значение этого параметра должно находиться в пределах установленного диапазона. Параметр fRepaint определяет, нужно ли перерисовывать полосу просмотра после установки новой позиции. Если указано TRUE, полоса будет перерисована, если FALSE - нет. Функция SetScrollPos возвращает значение предыдущей позиции или 0 в случае ошибки. Для определения текущей позиции надо вызвать функцию GetScrollPos: int WINAPI GetScrollPos(HWND hwnd, int fnBar); Параметры этой функции определяют, соответственно, идентификатор окна или органа управления и тип полосы просмотра Функция возвращает текущую позицию или 0, если идентификатор окна указан неправильно или окно не имеет полосы просмотра. Иногда бывает нужно убрать из окна одну или обе полосы просмотра. Это нужно, например, в тех случаях, когда, например, после изменения размера окна документ поместился в нем целиком. С помощью функции ShowScrollBar вы можете скрывать или показывать полосы просмотра: void WINAPI ShowScrollBar(HWND hwnd, int fnBar, BOOL fShow); Параметр hwnd определяет идентификатор окна, имеющего полосу просмотра, или идентификатор полосы просмотра, созданного как орган управления. Параметр fnBar определяет тип полосы просмотра, для которой выполняется установка диапазона изменения значений позиции. Кроме описанных нами ранее констант SB_CTL, SB_HORZ и SB_VERT вы можете использовать константу SB_BOTH. Эта константа предназначена для работы сразу с обеими полосами просмотра, определенными в стиле окна. Параметр fShow определяет действие, выполняемое функцией. Если этот параметр равен TRUE, полоса просмотра (или обе полосы просмотра, если указано SB_BOTH) появляются в окне. Если же указать значение FALSE, полоса просмотра исчезнет. Программный интерфейс операционной системы Windows версии 3.1 имеет в своем составе функцию EnableScrollBar, позволяющую разрешать или запрещать работу полосы просмотра: BOOL WINAPI EnableScrollBar(HWND hwnd, int fnBar, UINT fuArrowFlag); Первые два парамера этой функции аналогичны параметрам функции ShowScrollBar. Параметр fuArrowFlag определяет, какие из кнопок полосы просмотра должны быть заблокированы или разблокированы:
Функция возвращает значение TRUE при успешном завершении или FALSE при ошибке (если, например, кнопки уже находятся в требуемом состоянии). Приложение SCROLLПриложение SCROLL представляет собой простейший пример использования горизонтальной полосы просмотра для изменения горизонтального размера (ширины) статического органа управления (рис. 2.8).
Рис. 2.8. Главное окно приложения SCROLL Главный файл приложения представлен в листинге 2.15. Листинг 2.15. Файл scroll\scroll.cpp // ---------------------------------------- // Работа с полосой просмотра // ---------------------------------------- #define STRICT #include <windows.h> #include <mem.h> // Идентификатор полосы просмотра #define ID_SCROLL 1 // Прототипы функций BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM); // Имя класса окна char const szClassName[] = "ScrollAppClass"; // Заголовок окна char const szWindowTitle[] = "Scroll Demo"; // Текущая позиция полосы просмотра int nPosition; // Идентификатор окна полосы просмотра HWND hScroll; // Идентификатор статического органа // управления HWND hStatic; // ===================================== // Функция WinMain // ===================================== #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения // Инициализируем приложение if(!InitApp(hInstance)) return FALSE; // После успешной инициализации приложения создаем // главное окно приложения hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем размеры и расположение CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, CW_USEDEFAULT, 0, // идентификатор родительского окна 0, // идентификатор меню hInstance, // идентификатор приложения NULL); // указатель на дополнительные // параметры // Если создать окно не удалось, завершаем приложение if(!hwnd) return FALSE; // Рисуем главное окно ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); // Создаем полосу просмотра hScroll = CreateWindow("scrollbar", NULL, WS_CHILD | WS_VISIBLE | SBS_HORZ, 20, 60, 200, 15, hwnd, (HMENU) ID_SCROLL, hInstance, NULL); // Устанавливаем текущую позицию nPosition = 100; // Устанавливаем минимальное и максимальное // значения для полосы просмотра SetScrollRange(hScroll, SB_CTL, 1, 200, TRUE); // Устанавливаем ползунок в середину // полосы просмотра SetScrollPos(hScroll, SB_CTL, nPosition, TRUE); // Создаем статический орган управления в виде // черного прямоугольника. Длина этого // прямоугольника будет определяться текущей // позицией полосы просмотра hStatic = CreateWindow("static", NULL, WS_CHILD | WS_VISIBLE | SS_BLACKRECT, 20, 40, nPosition, 15, hwnd, (HMENU) -1,hInstance, NULL); // Запускаем цикл обработки сообщений while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg); } return msg.wParam; } // ===================================== // Функция InitApp // Выполняет регистрацию класса окна // ===================================== BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна memset(&wc, 0, sizeof(wc)); wc.style = 0; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszMenuName = (LPSTR)NULL; wc.lpszClassName = (LPSTR)szClassName; // Регистрация класса aWndClass = RegisterClass(&wc); return (aWndClass != 0); } // ===================================== // Функция WndProc // ===================================== LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { // Сообщение от горизонтальной полосы просмотра case WM_HSCROLL: { // В зависимости от параметра сообщения // изменяем текущую позицию switch (wParam) { case SB_PAGEDOWN: { nPosition += 10; break; } case SB_LINEDOWN: { nPosition += 1; break; } case SB_PAGEUP: { nPosition -= 10; break; } case SB_LINEUP: { nPosition -= 1; break; } case SB_TOP: { nPosition = 0; break; } case SB_BOTTOM: { nPosition = 200; break; } case SB_THUMBPOSITION: { nPosition = LOWORD (lParam); break; } case SB_THUMBTRACK: { nPosition = LOWORD (lParam); break; } default: break; } // Ограничиваем пределы изменения текущей // позиции значениями от 1 до 200 if(nPosition > 200) nPosition = 200; if(nPosition < 1) nPosition = 1; // Устанавливаем ползунок полосы просмотра // в соответствии с новым значением // текущей позиции SetScrollPos(hScroll, SB_CTL, nPosition, TRUE); // Устанавливаем новый размер статического // органа управления MoveWindow(hStatic, 20, 40, nPosition, 15, TRUE); return 0; } // Обеспечиваем управление полосой просмотра // при помощи клавиатуры case WM_KEYDOWN: { // В зависимости от кода клавиши функция окна // посылает сама себе сообщения, которые // обычно генерируются полосой просмотра switch (wParam) { case VK_HOME: { SendMessage(hwnd, WM_HSCROLL, SB_TOP, 0L); break; } case VK_END: { SendMessage(hwnd, WM_HSCROLL, SB_BOTTOM, 0L); break; } case VK_LEFT: { SendMessage(hwnd, WM_HSCROLL, SB_LINEUP, 0L); break; } case VK_RIGHT: { SendMessage(hwnd, WM_HSCROLL, SB_LINEDOWN, 0L); break; } case VK_PRIOR: { SendMessage(hwnd, WM_HSCROLL, SB_PAGEUP, 0L); break; } case VK_NEXT: { SendMessage(hwnd, WM_HSCROLL, SB_PAGEDOWN, 0L); break; } } return 0; } case WM_DESTROY: { PostQuitMessage(0); return 0; } } return DefWindowProc(hwnd, msg, wParam, lParam); } После инициализации приложения и создания главного окна приложения функция WinMain создает на базе предопределенного класса "scrollbar" орган управления - полосу просмотра. Для этого она вызывает функцию CreateWindow: hScroll = CreateWindow("scrollbar", NULL, WS_CHILD | WS_VISIBLE | SBS_HORZ, 20, 60, 200, 15, hwnd, (HMENU) ID_SCROLL, hInstance, NULL); В глобальной переменной nPosition хранится значение, соответствующее текущему положению ползунка. Сразу после создания полосы просмотра в эту переменную записывается значение 100. Далее при помощи функции SetScrollRange функция WinMain задает диапазон полосы просмотра - от 1 до 200: SetScrollRange(hScroll, SB_CTL, 1, 200, TRUE); После этого ползунок устанавливается в позицию, соответствующую содержимому переменной nPosition: SetScrollPos(hScroll, SB_CTL, nPosition, TRUE); Так как ранее в эту переменную было записано значение 100, ползунок будет установлен в середину полосы просмотра. Затем функция WinMain создает статический орган управления в виде черного прямоугольника, ширина которого равна значению, записанному в переменную nPosition: hStatic = CreateWindow("static", NULL, WS_CHILD | WS_VISIBLE | SS_BLACKRECT, 20, 40, nPosition, 15, hwnd, (HMENU) -1,hInstance, NULL); Функция главного окна приложения получает от полосы просмотра сообщения с кодом WM_HSCROLL. Обработчик этого сообщения анализирует параметр wParam, определяя действие, послужившее причиной появления сообщения от полосы просмотра. В зависимости от значения параметра wParam обработчик увеличивает или уменьшает содержимое переменной nPosition. При этом он следит, чтобы это содержимое находилось в диапазоне от 1 до 200. После этого ползунок устанавливается в новое положение: SetScrollPos(hScroll, SB_CTL, nPosition, TRUE); Далее обработчик сообщения полосы просмотра устанавливает новую ширину статического органа управления, для чего вызывает функцию MoveWindow: MoveWindow(hStatic, 20, 40, nPosition, 15, TRUE); Для того чтобы шириной статического органа управления можно было управлять не только с помощью полосы просмотра, но и с помощью клавиатуры, функция главного окна обрабатывает сообщение WM_KEYDOWN. Это сообщение поступает в функцию окна, когда вы нажимаете любые клавиши на клавиатуре. Параметр wParam сообщения WM_KEYDOWN содержит код виртуальной клавиши. Этот код анализируется. Если вы нажали, например, клавишу <Home>, функция окна посылает сама себе сообщение с кодом WM_HSCROLL с параметром wParam, имеющим значение SB_TOP. С этой целью вызывается функция SendMessage: SendMessage(hwnd, WM_HSCROLL, SB_TOP, 0L); Обработчик этого сообщения устанавливает начальную позицию, равную нулю: case SB_TOP: { nPosition = 0; break; } При этом ползунок будет установлен в крайнее левое положение. Аналогично обрабатываются сообщения, попадающие в функцию окна, когда вы нажимаете другие клавиши. Для плавного изменения размера статического органа управления вы можете использовать клавиши перемещения курсора по горизонтали. Клавиши <PgUp> и <PgDn> обеспечивают скачкообразное изменение размера. И, наконец, для установки минимального и максимального размера вы можете использовать, соответственно, клавиши <Home> и <End>. Файл определения модуля для приложения SCROLL приведен в листинге 2.16. Листинг 2.16. Файл scroll\scroll.def ; ============================= ; Файл определения модуля ; ============================= NAME SCROLL DESCRIPTION 'Приложение SCROLL, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 5120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple Приложение SCRLMETНаше следующее приложение демонстрирует использование вертикальной полосы просмотра, определенной в стиле окна, для свертки окна. Оно выводит в окно метрики текста (рис. 2.9).
Рис. 2.9. Главное окно приложения SCRLMET Приложение сделано на базе приложения TMETRICS, описанного в предыдущем томе "Библиотеки системного программиста". Отличие заключается в том, что теперь главное окно приложения имеет вертикальную полосу просмотра, благодаря которой вы можете просмотреть все значения при почти любом вертикальном размере окна. Главный файл приложения SCRLMET приведен в листинге 2.17. Листинг 2.17. Файл scrlmet\scrlmet.cpp // ---------------------------------------- // Просмотр метрик шрифта // ---------------------------------------- #define STRICT #include <windows.h> #include <mem.h> BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM); char const szClassName[] = "ScrlMetAppClass"; char const szWindowTitle[] = "SCRLMET Application"; // ===================================== // Функция WinMain // ===================================== #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения if(!InitApp(hInstance)) return FALSE; hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW | WS_VSCROLL, // стиль окна CW_USEDEFAULT, // задаем размеры и расположение CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, CW_USEDEFAULT, 0, // идентификатор родительского окна 0, // идентификатор меню hInstance, // идентификатор приложения NULL); // указатель на дополнительные // параметры if(!hwnd) return FALSE; ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg); } return msg.wParam; } // ===================================== // Функция InitApp // ===================================== BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна memset(&wc, 0, sizeof(wc)); // Определяем стиль класса окна, при // использовании которого окно требует // перерисовки в том случае, если // изменилась его ширина или высота wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszMenuName = (LPSTR)NULL; wc.lpszClassName = (LPSTR)szClassName; aWndClass = RegisterClass(&wc); return (aWndClass != 0); } При создании главного окна в качестве третьего параметра функции CreateWindow передается значение WS_OVERLAPPEDWINDOW | WS_VSCROLL. Благодаря этому в правой части главного окна приложения появляется вертикальная полоса просмотра. Никаких других особенностей функция WinMain не имеет. Исходный текст функции главного окна представлен в листинге 2.18. Листинг 2.18. Файл scrlmet\wndproc.cpp // ===================================== // Функция WndProc // ===================================== #define STRICT #include <windows.h> #include <stdio.h> #include <string.h> void Print(HDC, int, char *); static int cxChar, cyChar; static int cxCurrentPosition; static int cyCurrentPosition; static int nScrollPos; LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { static WORD cxClient, cyClient; HDC hdc; // индекс контекста устройства PAINTSTRUCT ps; // структура для рисования static TEXTMETRIC tm; // структура для записи метрик // шрифта switch (msg) { case WM_CREATE: { // Получаем контекст отображения, // необходимый для определения метрик шрифта hdc = GetDC(hwnd); // Заполняем структуру информацией // о метрике шрифта, выбранного в // контекст отображения GetTextMetrics(hdc, &tm); // Запоминаем значение ширины для // самого широкого символа cxChar = tm.tmMaxCharWidth; // Запоминаем значение высоты букв с // учетом межстрочного интервала cyChar = tm.tmHeight + tm.tmExternalLeading; // Инициализируем текущую позицию // вывода текста cxCurrentPosition = cxChar; cyCurrentPosition = 0; // Освобождаем контекст ReleaseDC(hwnd, hdc); // Начальное значение позиции nScrollPos = 0; // Задаем диапазон изменения значений SetScrollRange(hwnd, SB_VERT, 0, 20, FALSE); // Устанавливаем ползунок в начальную позицию SetScrollPos(hwnd, SB_VERT, nScrollPos, TRUE); return 0; } // Определяем размеры внутренней области окна case WM_SIZE: { cxClient = LOWORD(lParam); cyClient = HIWORD(lParam); return 0; } // Сообщение от вертикальной полосы просмотра case WM_VSCROLL: { switch(wParam) { case SB_TOP: { nScrollPos = 0; break; } case SB_BOTTOM: { nScrollPos = 20; break; } case SB_LINEUP: { nScrollPos -= 1; break; } case SB_LINEDOWN: { nScrollPos += 1; break; } case SB_PAGEUP: { nScrollPos -= cyClient / cyChar; break; } case SB_PAGEDOWN: { nScrollPos += cyClient / cyChar; break; } case SB_THUMBPOSITION: { nScrollPos = LOWORD(lParam); break; } // Блокируем для того чтобы избежать // мерцания содержимого окна при // перемещении ползунка case SB_THUMBTRACK: { return 0; } default: break; } // Ограничиваем диапазон изменения значений if(nScrollPos > 20) nScrollPos = 20; if(nScrollPos < 0) nScrollPos = 0; // Устанавливаем ползунок в новое положение SetScrollPos(hwnd, SB_VERT, nScrollPos, TRUE); // Обновляем окно InvalidateRect(hwnd, NULL, TRUE); return 0; } case WM_PAINT: { // Инициализируем текущую позицию // вывода текста cxCurrentPosition = cxChar; cyCurrentPosition = 0; hdc = BeginPaint(hwnd, &ps); // Выводим параметры шрифта, полученные во // время создания окна при обработке // сообщения WM_CREATE Print(hdc, tm.tmHeight, "tmHeight"); Print(hdc, tm.tmAscent, "tmAscent"); Print(hdc, tm.tmDescent, "tmDescent"); Print(hdc, tm.tmInternalLeading, "tmInternalLeading"); Print(hdc, tm.tmExternalLeading, "tmExternalLeading"); Print(hdc, tm.tmAveCharWidth, "tmAveCharWidth"); Print(hdc, tm.tmMaxCharWidth, "tmMaxCharWidth"); Print(hdc, tm.tmWeight, "tmWeight"); Print(hdc, tm.tmItalic, "tmItalic"); Print(hdc, tm.tmUnderlined, "tmUnderlined"); Print(hdc, tm.tmStruckOut, "tmStruckOut"); Print(hdc, tm.tmFirstChar, "tmFirstChar"); Print(hdc, tm.tmLastChar, "tmLastChar"); Print(hdc, tm.tmDefaultChar, "tmDefaultChar"); Print(hdc, tm.tmBreakChar, "tmBreakChar"); Print(hdc, tm.tmPitchAndFamily, "tmPitchAndFamily"); Print(hdc, tm.tmCharSet, "tmCharSet"); Print(hdc, tm.tmOverhang, "tmOverhang"); Print(hdc, tm.tmDigitizedAspectX,"tmDigitizedAspectX"); Print(hdc, tm.tmDigitizedAspectY,"tmDigitizedAspectY"); EndPaint(hwnd, &ps); return 0; } // Обеспечиваем управление полосой просмотра // при помощи клавиатуры case WM_KEYDOWN: { // В зависимости от кода клавиши функция окна // посылает сама себе сообщения, которые // обычно генерируются полосой просмотра switch (wParam) { case VK_HOME: { SendMessage(hwnd, WM_VSCROLL, SB_TOP, 0L); break; } case VK_END: { SendMessage(hwnd, WM_VSCROLL, SB_BOTTOM, 0L); break; } case VK_UP: { SendMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0L); break; } case VK_DOWN: { SendMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0L); break; } case VK_PRIOR: { SendMessage(hwnd, WM_VSCROLL, SB_PAGEUP, 0L); break; } case VK_NEXT: { SendMessage(hwnd, WM_VSCROLL, SB_PAGEDOWN, 0L); break; } } return 0; } case WM_DESTROY: { PostQuitMessage(0); return 0; } } return DefWindowProc(hwnd, msg, wParam, lParam); } // ========================================== // Функция для вывода параметров шрифта // в окно // ========================================== void Print(HDC hdc, int tmValue, char *str) { char buf[80]; int i, y; // Вычисляем начальную позицию для вывода y = cyCurrentPosition + cyChar * (1 - nScrollPos); // Подготавливаем в рабочем буфере // и выводим в окно начиная с текущей // позиции название параметра sprintf(buf, "%s", str); i = strlen(str); TextOut(hdc, cxCurrentPosition, y, buf, i); // Подготавливаем в рабочем буфере // и выводим в текущей строке окна // со смещением значение параметра sprintf(buf, "= %d", tmValue); i = strlen(buf); TextOut(hdc, cxCurrentPosition + 12 * cxChar, y, buf, i); // Увеличиваем текущую позицию по // вертикали на высоту символа cyCurrentPosition += cyChar; } При создании главного окна обработчик сообщения WM_CREATE получает информацию о метрике шрифта и сохраняет ее в структуре tm. Затем обработчик этого сообщения запоминает высоту и ширину букв системного шрифта и инициализирует текущую позицию для вывода текста. Далее устанавливается начальное значение позиции полосы просмотра, устанавливается диапазон изменения значений и устанавливается ползунок полосы просмотра: nScrollPos = 0; SetScrollRange(hwnd, SB_VERT, 0, 20, FALSE); SetScrollPos(hwnd, SB_VERT, nScrollPos, TRUE); Так как всего выводится 20 параметров, окно содержит 20 строк. Поэтому устанавливается диапазон полосы просмотра (0, 20). При изменении размера окна (а также в процессе создания окна) функция окна получает сообщение WM_SIZE, которое используется для определения размеров внутренней области окна: case WM_SIZE: { cxClient = LOWORD(lParam); cyClient = HIWORD(lParam); return 0; } Функция главного окна приложения содержит также обработчик сообщения WM_VSCROLL, поступающего от вертикальной полосы просмотра. Этот обработчик устанавливает новое значение для позиции полосы просмотра в переменной nScrollPos. Небольшое замечание относительно сообщения WM_VSCROLL, для которого значение wParam равно SB_THUMBTRACK. Напомним, что такое сообщение поступает в окно при передвижении ползунка по полосе просмотра. Мы обрабатывали это сообщение в предыдущем приложении, изменяя ширину статического органа управления синхронно с передвижениями ползунка. Теперь же мы игнорируем это сообщение: case SB_THUMBTRACK: { return 0; } Зачем мы так поступаем? Дело в том, что вслед за передвижением ползунка нам надо перерисовать главное окно приложения. А это длительный процесс. В тех случаях, когда невозможно обеспечить большую скорость перерисовки окна, нет смысла обрабатывать сообщение полосы просмотра с кодом SB_THUMBTRACK. Вы можете ограничиться обработкой сообщения с кодом SB_THUMBPOSITION, перерисовывая окно только после окончания процесса перемещения ползунка. После обновления содержимого переменной nScrollPos функция окна устанавливает ползунок в новое положение и объявляет все окно требующим перерисовки: SetScrollPos(hwnd, SB_VERT, nScrollPos, TRUE); InvalidateRect(hwnd, NULL, TRUE); Это приведет к тому, что функции главного окна приложения будет передано сообщение WM_PAINT. Обработчик этого сообщения мало отличается от аналогичного обработчика приложения TMETRICS, рассмотренного нами в предыдущем томе. Он выводит параметры шрифта с помощью функции Print, определенной в приложении. Функция Print вычисляет начальную позицию по вертикали для вывода текста на основании текущей позиции полосы просмотра: y = cyCurrentPosition + cyChar * (1 - nScrollPos); Если значение переменной nScrollPos таково, что начальная позиция по вертикали меньше нуля, текст будет "выводиться" выше внутренней области окна. Строки, выведенные вне внутренней области окна, будут обрезаны. Файл определения модуля для приложения SCRLMET приведен в листинге 2.19. Листинг 2.19. Файл scrlmet\scrlmet.def ; ============================= ; Файл определения модуля ; ============================= NAME SCRLMET DESCRIPTION 'Приложение SCRLMET, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 5120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple Приложение SIZEBOXНаше следующее приложение создает полосу просмотра, имеющую стиль SBS_SIZEBOX (рис. 2.10).
Рис. 2.10. Главное окно приложения SIZEBOX Как нетрудно заметить, такая полоса просмотра мало напоминает то, что мы видели раньше. Мы получили полосу просмотра в виде маленького квадратика серого цвета, расположенного в главном окне приложения. С помощью созданной нами полосы просмотра мы можем изменять размеры окна, не имеющего ни толстой рамки (которая обычно используется для изменения размера окна), ни кнопок минимизации или максимизации окна. Для изменения размера окна установите курсор мыши на квадратик и нажмите левую кнопку мыши. Далее, не отпуская кнопки, перемещайте мышь до тех пор, пока окно не примет нужную форму. Затем отпустите клавишу мыши. Исходный текст приложения SIZEBOX приведен в листинге 2.20. Листинг 2.20. Файл sizebox\sizebox.cpp // ---------------------------------------- // Работа с полосой просмотра, // имеющей стиль SBS_SIZEBOX // ---------------------------------------- #define STRICT #include <windows.h> #include <mem.h> // Идентификатор полосы просмотра #define ID_SCROLL 1 // Прототипы функций BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM); // Имя класса окна char const szClassName[] = "ScrollSizeBoxAppClass"; // Заголовок окна char const szWindowTitle[] = "SizeBox Demo"; // Идентификатор окна полосы просмотра HWND hScroll; // ===================================== // Функция WinMain // ===================================== #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения // Инициализируем приложение if(!InitApp(hInstance)) return FALSE; // После успешной инициализации приложения создаем // главное окно приложения hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна // стиль окна WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU, CW_USEDEFAULT, // задаем расположение CW_USEDEFAULT, // окна, принятое по умолчанию 200, 200, 0, // идентификатор родительского окна 0, // идентификатор меню hInstance, // идентификатор приложения NULL); // указатель на дополнительные // параметры // Если создать окно не удалось, завершаем приложение if(!hwnd) return FALSE; // Рисуем главное окно ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); // Создаем полосу просмотра hScroll = CreateWindow("scrollbar", NULL, WS_CHILD | WS_VISIBLE | SBS_SIZEBOX | SBS_SIZEBOXTOPLEFTALIGN, 30, 30, 0, 0, hwnd, (HMENU) ID_SCROLL, hInstance, NULL); // Запускаем цикл обработки сообщений while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg); } return msg.wParam; } // ===================================== // Функция InitApp // Выполняет регистрацию класса окна // ===================================== BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна memset(&wc, 0, sizeof(wc)); wc.style = 0; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszMenuName = (LPSTR)NULL; wc.lpszClassName = (LPSTR)szClassName; // Регистрация класса aWndClass = RegisterClass(&wc); return (aWndClass != 0); } // ===================================== // Функция WndProc // ===================================== LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_DESTROY: { PostQuitMessage(0); return 0; } } return DefWindowProc(hwnd, msg, wParam, lParam); } Для главного окна приложения мы указываем стиль окна следующим образом: WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU При этом создается перекрывающееся окно с заголовком и системным меню, но без толстой рамки для изменения размера, а также без кнопок максимизации и минимизации. Эти элементы окна нам не нужны, так как размер окна будет изменяться с помощью полосы просмотра. Далее после инициализации главного окна мы создаем орган управления - полосу просмотра: hScroll = CreateWindow("scrollbar", NULL, WS_CHILD | WS_VISIBLE | SBS_SIZEBOX | SBS_SIZEBOXTOPLEFTALIGN, 30, 30, 0, 0, hwnd, (HMENU) ID_SCROLL, hInstance, NULL); Этот орган управления создается в главном окне приложения в точке с координатами (30, 30). Размер органа управления не указан, так как стиль SBS_SIZEBOXTOPLEFTALIGN предполагает использование предопределенных размеров. Особенность функции окна для данного приложения заключается в том, что она не обрабатывает никаких сообщений, поступающих от созданной нами полосы просмотра. Это, однако, не означает, что наша полоса просмотра не посылает в окно никакие сообщения. Полоса просмотра, имеющая стиль SBS_SIZEBOX, посылает в функцию окна те же самые сообщения, что и толстая рамка, предназначенная для изменения размера окна. Поэтому наш орган управления можно использовать для изменения размеров окна без дополнительных усилий со стороны программиста. Файл определения модуля приложения SIZEBOX приведен в листинге 2.21. Листинг 2.21. Файл sizebox\sizebox.def ; ============================= ; Файл определения модуля ; ============================= NAME SIZEBOX DESCRIPTION 'Приложение SIZEBOX, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 5120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple |