КАК сделать текст на экране прокручиваемым?

До сих пор мы как-то обходили эту тему. Вроде бы выводим графику, текст в рабочую область окна и всё гладко. Но так только до поры до времени. Представьте, что вы решили создать программу управляющую работой парового котла. Термодатчик каждую секунду снимает данные с термопары и отправляет на порт вашего компьютера. Программа считывает данные с порта и?... Что с ними делать дальше? Можно, конечно, записывать их в файл, дописывая в конец всё новые данные показания. Так обычно и делается. А считывать их оттуда кто будет? Если пользователь должен будет открывать потом этот файл через "Блокнот" или "Wordpad", он будет долго ругаться. Гораздо лучше сделать всё в одной программе. На экране будет длинная таблица со значениями, прокрутив которую, вы сможете просмотреть всю историю. Да что там все эти специализированные программы. Все текстовые редакторы от Блокнота до Word имеют полосу прокрутки. Вы пользуетесь ими каждый день, совсем этого не замечая.

Для того, чтобы экран можно было прокручивать, надо добавить к окну полосу прокрутки или Scroll Bar. Не важно, будете вы выводить текст в главное окно или в диалоговое. Полосу прокрутки можно приделать даже к кнопке!

Разбёрём полосу прокрутки главного окна. Важно понимать, что когда пользователь перемещается по полосе вниз, его текст тем временем ползёт вверх. Кроме того, стрелки полосы прокрутки можно перемещать кнопками мыши, а можно стрелками клавиатуры и клавишами Page Up и Page Down. Можно ещё нажать левую кнопку мыши сверху или снизу движка, и он проскочит сразу несколько линий.

Все эти сообщения полосы Scroll Bar мы научимся обрабатывать сами. Вы сами сможете задавать переместиться тексту по нажатию кнопки PageDown до конца или на три строчки. Диапазон полосы полосы прокрутки и положение её движка тоже можно изменить в любой момент времени.

Полосы прокрутки бывают вертикальными и горизонтальными. В зависимости от типа, полоса посылает сообщения: WM_HSCROLL (горизонтальная) и WM_VSCROLL (вертикальная).

Как не странно, почему-то у всех пользователей полоса прокрутки вызывает дрожь в коленях и зубную боль. Не скрою, и я долго обходил эту тему, а потом долго нед ней бился. Поэтому в этой главе будет целых три примера. Они помогут разобраться с полосой

Начнём с очень полезной программы, которая, возможно будет вам ещё долго хорошим другом. И это не метафора. В любом языке программирования: C++, Java и др, где работают с палитрой 256 цветов, цвет получают сложением красного, зелёного и синего. Так называемая модель RGB. Занимаясь WEB-дизайном и задавая цвет гиперссылок, текста и фона, вы тоже сталкиваетесь с этой моделью, только цвет кодируется его 16-ричным эквивалентом. Так белый цвет RGB(0,0,0) будет в 16-ричной системе FFFFFF. А синий RGB(57,0,255) будет представлен как 390FF. Часто, чтобы подобрать нужный цвет в тех же программах на API или в WEB-дизайне вы долго подбирали цифры, чтобы получить более приятный оттенок. Вам никогда не хотелось этот процесс подбора автоматизировать?
Мне да, поэтому я написал следующую программу. Она сложна тем, что в ней всего много (как и подобает нормально работающей программе, которая приносит пользу). Но мы постараемся во всём разобраться.
Выглядеть это будет так:


Как видите, три полосы прокрутки, которые меняют значения трёх цветов. Эти значения моментально выводятся в три поля ввода. Кроме того, в правой части окна размещён цветной эллипс на белом фоне. Он будет менять свой цвет в зависимости от выбранных вами значений красного, синего и зелёного. Но и это ещё не всё. Под эллипсом размешёно окошечко, в котором будут меняться значения цвета в 16-ричном коде, откуда их всегда можно скопировать и вставить в исходник HTML-документа.

Но прежде, чем знакомиться с такой сложной программой, давайте познакомимся с полосой прокрутки. Что с ней можно делать?

1. Создание полосы прокрутки.
Поскольку это обычный элемент управления, создаётся Scroll Bar точно также, функцией CreateWindow. Вот пример создания одной горизонтальной полосы из этого примера:

hGreenScroll=CreateWindow("scrollbar", NULL,
WS_CHILD|WS_VISIBLE|SBS_HORZ, 20, 160, 200, 25,
hWnd,(HMENU)-1, hInstance, NULL);

Тип элемента "scrollbar", расположение указывается в стиле SBS_HORZ или SBS_VERT. Дальше идут координаты левого верхнего угла, ширина и длина.

2. Задание диапазона Scroll Bar. Для этого существует специальная API функция SetScrollRange. Зачем надо задавать диапазон. Не лучше ли увеличивать значения, пока пользователю не надоест?
Совершенно очевидно, что для тех же цветов, их значения не могут принимать значения больше 255 и меньше 0. Если мы управляем сложным процессом, дорогое оборудование может выйти из строя или "зависнуть", если мы зададим скажем номер порта, который не существует или отрицательным.
Итак, вот как задаётся диапазон:

SetScrollRange(hRedScroll, SB_CTL, nxMin, nxMax, TRUE);
Здесь hRedScroll - идентификатор окна, созданного CreateWindow, SB_CTL - дескриптор полосы прокрутки, если она используется, как элемент управления; nxMin и nxMax - минимальное и максимальное значения для данной полосы. Последнее значение - булевское. Будет ли происходить перерисовка при установке нового диапазона.

3. Установка положения движка. После создания полос прокрутки и задания их диапазона, мы хотим установить движки на них в центральное положение. Для этого надо задать команду движку: "Встань в положение 127". Эти слова произносит API-функция SetScrollPos. Вот как это она делает:

SetScrollPos(hRedScroll, SB_CTL, nxPos, TRUE);

hRedScroll - это то окну, которому мы это говорим, SB_CTL - дескриптор полосы, nxPos - числовое значение, в данном случае, 127. Значение последнего аргумента - то же, что и в предыдущей функции.

4. Обработка сообщений
В зависимости от действий пользователя, полоса прокрутки передаёт следующие сообщения:

Сообщение Действие
SB_LINEUP Полоса передвинута на линию вверх (вертикальная)
SB_LINEDOWN Полоса передвинута на линию вниз
SB_LINELEFT Сдвиг на линию влево
SB_LINERIGHT Сдвиг на линию вправо
SB_PAGEUP Сдвиг на страницу вверх
SB_PAGEDOWN Сдвиг на страницу вниз
SB_PAGELEFT Сдвиг на влево
SB_PAGERIGHT Сдвиг на вправо
SB_THUMBPOSITION Позиция движка была изменена
SB_THUMBTRACK Позиция движка была перемещена (эти два события обрабатывают вместе)

Вот как выглядит обработка сообщений полосы:

//была передвинута горизонтальная полоса прокрутки
case WM_HSCROLL:


switch (LOWORD(wParam)) {

//сдвиг на страницу вправо
case SB_PAGERIGHT:
nxPos+=10;
break;

//сдвиг на линию вправо
case SB_LINERIGHT:
nxPos++;
break;

case SB_PAGELEFT:
nxPos-=10;
break;

...
}


//Передвинем движок на изменённую позицию
SetScrollPos(hRedScroll, SB_CTL, nxPos, TRUE);
break;

В каждой ветви этого условия должна отмечаться какая-то переменная (nxPos), и в конце обработки, мы сами устанавливаем движок на эту изменившуюся позицию. Без нашей команды движок двигаться не будет.

5. КАК связать полосу с какими-то событиями?
Ну вот мы и добрались до главного. Мы уже умеем двигать движок, если пользователь перемещает его мышкой. Но как связать с полосой какое-то событие?
Точно так же! У нас есть какая-то переменная nxPos, на неё и опираемся. Если мы выводим таблицу, то надо задать вывод от значений массива, начиная с элемента nxPos, допустим. Чтобы вывести в поле ввода значение nxPos, как мы это делаем в данной программе, достаточно использовать функцию SetDlgItemInt, которая задаёт число в элементе управления.

6. КАК узнать, какую полосу передвинул пользователь?
Ну, с главным окном всё ясно. Прокручиваем его по вертикали - WM_VSCROLL, по горизонатли - WM_HSCROLL. Всё предельно просто. Ну, а если у нас три полосы и все горизонтальные?
Тогда обрабатывая WM_HSCROLL, мы должны спросить какую полосу предвинул пользователь?
У меня в программе это делается так:

case WM_HSCROLL:

if(hRedScroll==(HWND)lParam) i=0;
if(hGreenScroll==(HWND)lParam) i=1;
if(hBlueScroll==(HWND)lParam) i=2;


switch (LOWORD(wParam)) {

case SB_PAGERIGHT:
...
}

switch(i)
{
case 1:

break;
...
}

break;

Берётся какая-то абстрактная переменная i, которая хранит 0, если была передвинута первая полоса, 1 - если вторая и 2 - если третья. Идентификатор окна, сделавшего перемещение хранится в lParam. По всем сообщениям изменяется одна и та же nxPos, а после всех сообщений, вспоминаем, чему была равна i и в зависимости от этого, перемещаем соответствующую полосу на nxPos.
Мой пример встречается во многих книжках и наборах типовых программ. И часто авторы для своего удобства создают массив окон, массив полос прокруток, полей ввода и других элементов управления. Я посчитал, что в такой программе вы точно не разберётесь и написал свою. Если уж я понял, то вы и подавно разберётесь.

А вот и текст программы:

//Идентификаторы элементов управления
#define ID_EDITRED 1
#define ID_EDITGREEN 2
#define ID_EDITBLUE 3
#define ID_EXIT 4
#define ID_EDITHEX 5

#include <windows.h>
#include <stdio.h>
#include <string.h>

BOOL RegClass(WNDPROC, LPCTSTR, UINT);
LRESULT CALLBACK WndProc( HWND, UINT, WPARAM, LPARAM);
HINSTANCE hInstance;

char szProgName[]="ProgName";
char szClassName[]="ScrollClass";

char *string="Установка цвета"; //текст в окне
char *string1="Ваш цвет: ";
char *string2="16-ричный код цвета:";

char Buf[]="AAAAAA"; //буфер для 16-ричных значений

int i=0;
int red=128, green=128, blue=128; //значения цветов

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR IpszCmdParam, int nCmdShow)
{
hInstance=hInstance;
HWND hWnd;
MSG Msg;

if(!RegClass(WndProc, szClassName, COLOR_WINDOW))
return FALSE;

//Создание главного окна приложения

if(!(hWnd=CreateWindow(szProgName, "RGB Viewer",
WS_OVERLAPPEDWINDOW,
100, 100, //координаты окна
530, 400,
NULL, NULL,
hInstance, NULL))) return 0;
ShowWindow(hWnd, SW_SHOWNORMAL);
UpdateWindow(hWnd);
while(GetMessage(&Msg, 0, 0,0))
DispatchMessage(&Msg);
return Msg.wParam;
}


BOOL RegClass(WNDPROC Proc, LPCTSTR szName, UINT brBackground)
{
WNDCLASS WndClass;

WndClass.style = CS_HREDRAW|CS_VREDRAW;
WndClass.lpfnWndProc = WndProc;
WndClass.cbClsExtra = 0;
WndClass.cbWndExtra = 0;
WndClass.hInstance = hInstance;
WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
WndClass.hbrBackground = (HBRUSH) CreateSolidBrush(RGB(58,128,211));
WndClass.lpszMenuName = NULL;
WndClass.lpszClassName = szProgName;
return(RegisterClass(&WndClass)!=0);
}


LRESULT CALLBACK WndProc(HWND hWnd, UINT Message,
WPARAM wParam, LPARAM lParam)
{
static HWND hRedScroll, hGreenScroll, hBlueScroll; //окна-прокрутки
static HWND hEditRed, hEditGreen, hEditBlue; //окна-со значениями цвета
static HWND hEditHex; //окно с 16-ричным кодом цвета
static HWND hExit; //кнопка выхода

static int nxPos, nxMin, nxMax, nyPos, nyMin, nyMax; //значения для всех полос

HDC hDC;
HBRUSH hBrush; //кисть для рисования эллипса
PAINTSTRUCT PaintStruct;
RECT Rect, Client; //области отрисовки

switch(Message)
{

case WM_CREATE:
{

nxPos=128; //Среднее положение движка
nxMin=0; //Минимальное положение движка
nxMax=255; //Максимальное положение движка

//Создание трёх полос для каждого цвета
hRedScroll=CreateWindow("scrollbar", NULL,
WS_CHILD|WS_VISIBLE|SBS_HORZ, 20, 60, 200, 25,
hWnd,(HMENU)-1, hInstance, NULL);


hGreenScroll=CreateWindow("scrollbar", NULL,
WS_CHILD|WS_VISIBLE|SBS_HORZ, 20, 160, 200, 25,
hWnd,(HMENU)-1, hInstance, NULL);


hBlueScroll=CreateWindow("scrollbar", NULL,
WS_CHILD|WS_VISIBLE|SBS_HORZ, 20, 260, 200, 25,
hWnd,(HMENU)-1, hInstance, NULL);


//Установка диапазона полосы прокрутки
//nxMin, nxMax - максимальные значения, TRUE - необходимость перерисовки
SetScrollRange(hRedScroll, SB_CTL, nxMin, nxMax, TRUE);
SetScrollRange(hGreenScroll, SB_CTL, nxMin, nxMax, TRUE);
SetScrollRange(hBlueScroll, SB_CTL, nxMin, nxMax, TRUE);

//Cледующая функция устанавливает ползунок в нужное положение
//nxPos - новое положение ползунка. TRUE - необходимость перерисовки
SetScrollPos(hRedScroll, SB_CTL, nxPos, TRUE);
SetScrollPos(hGreenScroll, SB_CTL, nxPos, TRUE);
SetScrollPos(hBlueScroll, SB_CTL, nxPos, TRUE);

//Создание полей ввода со значениями цветов
hEditRed=CreateWindowEx(WS_EX_STATICEDGE, "edit", "127",
WS_CHILD|WS_VISIBLE|WS_BORDER, 240, 60, 30, 25,
hWnd,(HMENU)ID_EDITRED, hInstance, NULL);

hEditGreen=CreateWindowEx(WS_EX_STATICEDGE, "edit", "127",
WS_CHILD|WS_VISIBLE|WS_BORDER, 240, 160, 30, 25,
hWnd,(HMENU)ID_EDITGREEN, hInstance, NULL);

hEditBlue=CreateWindowEx(WS_EX_STATICEDGE, "edit", "127",
WS_CHILD|WS_VISIBLE|WS_BORDER, 240, 260, 30, 25,
hWnd,(HMENU)ID_EDITBLUE, hInstance, NULL);

//Поле ввода с 16-ричным кодом цвета
hEditHex=CreateWindowEx(WS_EX_CLIENTEDGE, "edit", "7F7F7F",
WS_CHILD|WS_VISIBLE|WS_BORDER, 380, 260, 60, 25,
hWnd,(HMENU)ID_EDITHEX, hInstance, NULL);


//Кнопка выхода
hExit=CreateWindowEx(WS_EX_CLIENTEDGE, "button", "Выход",
WS_CHILD|WS_VISIBLE|WS_BORDER|BS_DEFPUSHBUTTON, 200, 315, 100, 25,
hWnd,(HMENU)ID_EXIT, hInstance, NULL);

//Обработка сообщения прокрутки по горизонтали
case WM_HSCROLL:

//Проверяем на какой полосе было перемещение
if(hRedScroll==(HWND)lParam) i=0;
if(hGreenScroll==(HWND)lParam) i=1;
if(hBlueScroll==(HWND)lParam) i=2;


switch (LOWORD(wParam)) {

case SB_PAGERIGHT: //На страницу вправо
nxPos+=10;
Mas[i]+=10;
break;

case SB_LINERIGHT: //На одну линию вправо
nxPos+=1;
Mas[i]+=1;
break;

case SB_PAGELEFT: //На страницу влево
nxPos-=10;
Mas[i]-=10;
break;

case SB_LINELEFT: //На линию влево
nxPos-=1;
Mas[i]-=1;
break;

case SB_TOP: //Максимальное значение
nxPos=nxMax;
Mas[i]=nxMax;
break;

case SB_BOTTOM: //Минимальное значение
nxPos=nxMax;
Mas[i]=nxMax;
break;

case SB_THUMBPOSITION: //Любое перемещение
case SB_THUMBTRACK:
nxPos=HIWORD(wParam);
break;

default: break;
}

//Если позиция больше максимальной
if(nxPos>nxMax)
{
nxPos=nxMax; //Устанавливаем на граничную максимальную
Mas[i]=nxMax;
}

//Если позиция меньше минимальной
if(nxPos<nxMin)
{
nxPos=nxMin; //Устанавливаем на граничную минимальную
Mas[i]=nxMin;

}

if(nxPos==nxMax)
//Если положение движка достигло максимума, блокировать его
switch(i) {
case 0: EnableScrollBar(hRedScroll, SB_CTL, ESB_DISABLE_RIGHT); break;
case 1: EnableScrollBar(hRedScroll, SB_CTL, ESB_DISABLE_RIGHT); break;
case 2: EnableScrollBar(hRedScroll, SB_CTL, ESB_DISABLE_RIGHT); break;
}

//Вспоминаем, какая полоса была сдвинута
switch(i) {
case 0:
//Перемещаем движок
SetScrollPos(hRedScroll, SB_CTL, nxPos, TRUE);
//Выводим в поле ввода новое значение
SetDlgItemInt(hWnd, ID_EDITRED, nxPos,0);
break;

case 1:
SetScrollPos(hGreenScroll, SB_CTL, nxPos, TRUE);
SetDlgItemInt(hWnd, ID_EDITGREEN,nxPos,0);
break;

case 2:
SetScrollPos(hBlueScroll, SB_CTL, nxPos, TRUE);
SetDlgItemInt(hWnd, ID_EDITBLUE,nxPos,0);
break;
}

//Новые значения переменных цвета получены из полей ввода
red=GetDlgItemInt(hWnd, ID_EDITRED, NULL, 0);
green=GetDlgItemInt(hWnd, ID_EDITGREEN, NULL, 0);
blue=GetDlgItemInt(hWnd, ID_EDITBLUE, NULL, 0);

//Задаём границы области перерисовки по эллипсу
Client.left=330;
Client.top=60;
Client.right=480;
Client.bottom=220;

//Вызываем сообщение WM_PAINT по перерисовке области
//эллипса


InvalidateRect(hWnd, &Client, 1);
//Записываем в буфер 16-ричные эквиваленты цветов
sprintf(Buf, "%X%X%X", red, green ,blue);
//Выводим буфер в поле 16-ричного вывода
SetDlgItemText(hWnd, ID_EDITHEX,Buf);


break;

//Сообщшение рисования
case WM_PAINT:
hDC = BeginPaint(hWnd, &PaintStruct);
GetClientRect(hWnd, &Rect);
//Прозрачный режим фона
SetBkMode(hDC, TRANSPARENT);
//Белый прямоугольник под эллипсом
Rectangle(hDC, 330, 60, 480, 220);
//Создаём кисть с новыми цветами
hBrush=CreateSolidBrush(RGB(red, green, blue));
//Делаем её активной
SelectObject(hDC, hBrush);
//Рисуем эллипс
Ellipse(hDC, 350,80,460,200);

//Выводим текст в окно
TextOut(hDC, 60,20,string, strlen(string));
TextOut(hDC, 370,20,string1, strlen(string1));
TextOut(hDC, 325, 234, string2, strlen(string2));

//Выводим текст над полями ввода разного цвета
SetTextColor(hDC, RGB(255,0,0));
TextOut(hDC, 240,42,"Red:",4);
SetTextColor(hDC, RGB(0,255,0));
TextOut(hDC, 234,140,"Green:", 6);
SetTextColor(hDC, RGB(0,0,255));
TextOut(hDC, 240,240,"Blue:", 5);


EndPaint(hWnd, &PaintStruct);

break;

case WM_COMMAND:
switch(LOWORD(wParam)){

//Завершение работы по кнопке "Выход"
case ID_EXIT:
DestroyWindow(hWnd);
return 0;
break;

}
break;


case WM_DESTROY:
DeleteObject(hBrush);
PostQuitMessage(0);
break;

default:
return DefWindowProc(hWnd,Message, wParam, lParam);

}
return 0;
}

}

Заключение. Мы вроде бы всё так подробно обсудили до, что после как бы и говорить-то не о чем. Однако, есть некоторые моменты, которые здесь встречаются впервые.

Здесь я специально часть переменных, которые будут выведены TextOut объявил в начале, в виде переменных типа char*, это позволяет менять текст переменной, не меняя функции TextOut и соответственно длины строки. Она будет вычисляться автомтически.

Также ещё раз вспоминим, что для перерисовки главного окна, то есть принудительного вызова WM_PAINT, можно применять InvalidateRect. Существенным может показаться и то, что во избежания мерцания при каждом перемещении движка (особенно это заметно, когда движок ведёшь), я перерисовываю только область эллипса. Для этого я специально создал экземпляр структуры RECT под названием Client. Заполнив его поля параметрами эллипса, я, тем самым, задал лишь небольшой квадратик, который стоит перерисовывать. Всё же окно остаётся неизменным.

И самое интересное - вывод 16-ричного кода. Здесь я применил старый верный трюк языка Си, функцию sprintf. Вобще-то в Windows ей не место, это вам любой скажет, но здесь она уж очень хорошо смотрится. Да и как без неё - разве что применять алгоритм преобразования десятичных чисел в 16-ричные... В библиотеке классов MFC есть класс CString, который позволяет представить строку не как набор сиволов, а вроде бы даже, как самостоятельный тип данных. Спасибо ему за это!

Пример 2 "Таблица значений".

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

Красиво, не правда ли?

Поэтому сейчас я научу вас делать такую таблицу, которую никто больше не научит. Вывод таблицы в диалоговое окно! А? Каково! В этой программе я намеренно ввёл много элементов украшения, чтобы вы видели не только учебные программы, а реально работающие законченные окна. С такими программами работать приятнее, пусть даже мы на них только учимся.

Итак, о чём пойдёт речь? В этой таблице будет некоторая полезная информация - значения чисел от 0 до 255, эти же значения в квадрате, двойка в степени этого числа, 1 делённая на это число и экспоненциальная функция этого числа. Каждому значению будет отведён свой столбец в таблице. Итак, столбцов пять, строк 255. Представляем себе массив 255Х5. Поскольку числа будут дробные, тип данных будет double, но не float, поскольку для функции exp() нужны данные типа double. Кроме того, длины типа float может просто не хватить. К концу списка значения будут огромные!
Итак, подключаем в начале программы библиотеку math.h, объявляем массв:

double Mas[5][256];

Теперь надо спроектировать диалоговое окно, нарисовав его в редакторе ресурсов. Добавляем к программе новое окно и оформляем его следующим образом:



В свойствах диалогового окна, меняем заголовок на "Таблица", а идентификатор на IDD_TABLE. Кнопка так и будет IDOK. Перетащим в окно вертикальную полосу прокрутки, которую назовём IDC_SCROLL. В отличие от прошлого примера, полоса будет вертикальная и создана не динамически, а в виде ресурса.
Создадим под полосой прокрутки статический текст по имени IDC_STATIC. В его свойствах поставьте флажки на Border во вкладке Styles и Static Edge, и флажки: Modal Frame во вкладке Extended Styles.

Вот описание этого окна из файла ресурсов:

IDD_TABLE DIALOGEX 0, 0, 342, 201
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Таблица"
FONT 8, "MS Sans Serif"
BEGIN
DEFPUSHBUTTON "OK",IDOK,145,180,50,14
SCROLLBAR IDC_SCROLL,310,10,20,155,SBS_VERT //полоса прокрутки и её координаты
LTEXT "1",IDC_STATIC,310,170,20,15,WS_BORDER,
WS_EX_DLGMODALFRAME | WS_EX_STATICEDGE
END


Создадим для этого окна функцию окна TableProc(), объявив её в начале программы:

LRESULT CALLBACK TableProc(HWND, UINT, WPARAM, LPARAM);

Сделаем вызов нашего диалогового окна из меню. Для этого в главное меню добавим пункт IDM_TABLE, который будет называться "Таблица".

case IDM_TABLE:
DialogBox(hInst, (LPCTSTR)IDD_TABLE, hWnd, (DLGPROC)TableProc);
break;

Мы создали массив Mas. Теперь надо заполнить его числами. Лучше всего это сделать по сообщению WM_INITDIALOG в функции TableProc. На этом этапе инициализируются все переменные, с которыми будет работать диалоговое окно IDD_TABLE.

int i,j;
...
case WM_INITDIALOG:
for(i=0; i<255; i++)
{
Mas[0][i]=(double)i; //само число
Mas[1][i]=i*i; //квадрат этого числа

Mas[2][i]=1; //Возведение 2 в степень данного числа
for(j=0; j<i; j++)
Mas[2][i]=Mas[2][i]*2;

Mas[3][i]=(double)1/i; // 1 делённая на число
Mas[4][i]=exp(i); //Экспонента
}

break;

При желании всегда будет можно заменить exp(i) на sqrt(i), если вам нужна не экспонента, а корень. В данном случае можно использовать те функции math.h, которые вам больше нужны.

К моменту вывода на экран диалогового окна, массив уже будет заполнен значениями.

Теперь, чтобы программа получилась красивой, займёмся графикой. Я включил в программу целый ряд кистей - синюю для фона, жёлтую для таблицы и чёрную для тени.

hBlue=CreateSolidBrush(RGB(0,130,255));
hYellow=CreateSolidBrush(RGB(255,255,170));
hBlack=CreateSolidBrush(RGB(0,0,0));

Нарисуем синий прямоугольник во весь фон, жёлтый - там, где будет таблица, а чёрный - её тень.

//Заливаем фон синим
SelectObject(hDC, hBlue);
Rectangle(hDC, 0,0, 640,480);

//Делаем тень таблицы
SelectObject(hDC, hBlack);
Rectangle(hDC, 20,20,420,270);

//Заливаем пространство таблицы жёлтым
SelectObject(hDC, hYellow);
Rectangle(hDC, 10,10,400,250);


Чтобы у таблицы были чёткие границы, я создал перо, толщиной 3 линии.
hPen=CreatePen(0,3,RGB(0,0,0));
SelectObject(hDC, hPen);

Место, где будет "шапка" таблицы отгорожено линией.
MoveToEx(hDC, 10,50, NULL);
LineTo(hDC, 400,50);

Выведем в неё названия заголовков. Чтобы не было белого фона под текстом, зададим режим отображения прозрачным.

//Фон прозрачный
SetBkMode(hDC, TRANSPARENT);

//Меняем цвет текста
SetTextColor(hDC, RGB(136,65,55));

//Выводим заголовки столбцов
TextOut(hDC, 26,20,"Num", 3);
TextOut(hDC, 100,20,"Num^2", 5);
TextOut(hDC, 180,20,"2^Num", 5);
TextOut(hDC, 255,20,"1/Num", 5);
TextOut(hDC, 330,20,"Num", 3);

Теперь окно готово. Остаётся только выводить в него строки таблицы Mas в зависимости от положения полосы прокрутки. Что-то мы забыли сделать... Ах да, обработать сообщения от самой полосы! Без этого ничего в этом красивом окне не будет.
Так как на этот раз мы работаем с вертикальной полосой, то сообщение будет WM_VSCROLL.
Переменная, которую мы будем менять, будет называться yPos. Её значение будет постоянно выводиться в статическом окне IDC_STATIC, а также в зависимости от него, будут выводиться значения массива.

j=50;
for(i=yPos; i<yPos+10; i++)
{
sprintf(Buf, "%8.2e %8.2e %8.2e %8.2e %8.2e", Mas[0][i], Mas[1][i], Mas[2][i], Mas[3][i], Mas[4][i]);
TextOut(hDC, 15, j, Buf, strlen(Buf));
j+=20;
}

Понятно, что у нас уже есть массив из 256 элементов. Но на экране мы сможем увидеть из него только десять строк. Поэтому выводить эти 10 строк мы будем с элемента yPos. Получая из строки массива пять вещественных чисел, мы копируем их в строку, располагая друг за другом с помощью sprintf (не забудьте подключить для неё stdio.h!). Затем выводим этот текст на экран. Координата j меняет положение выводимого текста по вертикали. Она подставлется в TextOut.
Не могу не сказать про то, что для вывода значений в таблицу часто используется так называемый инженерный формат числа. В sprintf он обозначается как %e. Число 2,5 в таком формате будет выглядеть, как 2.50e+000, а число 16, как 1.60е+001. Зачем это надо и что это значит. В инженерном формате всё измеряется степенью 10. Например 2,5 это 2,5 умноженное на 10 в нулевой степени, то есть на 1. Десять в нулевой степени выглядит, как +000 после буквы e. Соответственно 16 = 1,6*10^1, или 1.60e+001. Эта единица называется порядком числа. Это число первого порядка.
Если бы мы выводили числа в обычном формате, как %d, %f или %lf, то очень скоро колонки таблицы потеряли бы свою стройность. Число увеличивались бы очень быстро и скоро вышли бы за пределы таблицы. Тогда как в инженерном формате, ширина столбцов сохраняется. Ещё одним выходом из положения является импортирование в окно таблицы типа Excel, в которой ширина столбцов постоянная, а если число не помещается, оно уходит за рамки.

Мы могли бы обойтись без массива вообще, вычисляя всё "на ходу". Но это усложнило бы программу, а самое главное, этот массив мы сможем использовать другими диалоговыми окнами для того же графика, гистрограммы или диаграммы.

Остаётся рассмотреть, как обрабатывается сообщение полосы прокрутки. Но здесь затруднений быть совсем не должно.

#define MAX 255
#define MIN 0

case WM_VSCROLL:
//Устанавливаем диапазон полосы прокрутки от 0 до 255 (MAX=255)
SetScrollRange((HWND)lParam, SB_CTL, MIN, MAX,0);

switch(LOWORD(wParam))
{
//На линию вниз
case SB_LINEDOWN:
yPos++;
//Если положение движка больше 255, установить его на 255
if(yPos>MAX) yPos=MAX;
//Установить движок на выбранную позицию
SetScrollPos((HWND)lParam, SB_CTL, yPos, 1);
//Вывести текст в статический элемент
SetDlgItemInt(hDlg, IDC_STATIC, yPos, 0);
//Перерисовка
InvalidateRect(hDlg, &Client, 1);
InvalidateRect(hDlg, &Client1, 1);
break;

//На линию вверх
case SB_LINEUP:
yPos--;
//Если положение движка меньше 0, установить на 0
if(yPos<MIN) yPos=MIN;
SetScrollPos((HWND)lParam, SB_CTL, yPos, 1);
SetDlgItemInt(hDlg, IDC_STATIC, yPos, 0);
InvalidateRect(hDlg, &Client, 1);
InvalidateRect(hDlg, &Client1, 1);

break;

//На страницу вверх
case SB_PAGEUP:
yPos-=10;
if(yPos<0) yPos=0;
SetScrollPos((HWND)lParam, SB_CTL, yPos, 1);
SetDlgItemInt(hDlg, IDC_STATIC, yPos, 0);
InvalidateRect(hDlg, &Client, 1);
InvalidateRect(hDlg, &Client1, 1);

break;

//На страницу вниз
case SB_PAGEDOWN:
//Перемещаемся на 10 позиций
yPos+=10;
if(yPos>MAX) yPos=MAX;
SetScrollPos((HWND)lParam, SB_CTL, yPos, 1);
SetDlgItemInt(hDlg, IDC_STATIC, yPos, 0);
InvalidateRect(hDlg, &Client, 1);
InvalidateRect(hDlg, &Client1, 1);

break;


//Сообщение при перетаскивании
case SB_THUMBPOSITION:
case SB_THUMBTRACK:
yPos=HIWORD(wParam); //получаем текущую позицию прокрутки
SetScrollPos((HWND)lParam, SB_CTL, yPos, 1);
SetDlgItemInt(hDlg, IDC_STATIC, yPos, 0);
InvalidateRect(hDlg, &Client, 1);
InvalidateRect(hDlg, &Client1, 1);

break;

}

break;

Здесь есть несколько хитростей, в которые я вас посвящу.
Во-первых, посольку мы не создавали эту полосу динамически, идентификатора окна типа HWND у неё нет. Однако, для SetScrollPos он нужен. Как быть? Мы знаем, что идентификатор окна можно получить при обработке сообщения, как старшее слово lParam. Так мы и сделаем:

SetScrollPos((HWND)lParam, SB_CTL, yPos, 1);

Даже если бы у нас было несколько вертикальных полос прокрутки, окно бы "поняло" от которой исходит сообщение в данный момент. Состояние движка, а значит переменной yPos мы выводим функцией SetDlgItemInt. И всё работает. Остаётся только маленькое но.... Перерисовка. Как только мы начнём нажимать на прокрутку, всё окно перерисуется и мы вообще потеряем таблицу. Чтобы этого не случилось, мы можем перерисовывать только прокрутку со статическим окном, да кнопку "Ok". Но посольку они в разных частях окна, придётся перерисовывать сразу две области - прокрутки и кнопки.
Создадим два экземпляра структуры Rect и заполним их соответствующими координатами:

RECT Client, Client1;

Client.left=464; //прокрутка
Client.top=16;
Client.right=496;
Client.bottom=300;

Client1.left=219; //кнопка
Client1.top=293;
Client1.right=293;
Client1.bottom=316;

При прокрутке окна, придётся два раза вызывать функцию InvalidateRect для разных областей.

case SB_LINEDOWN:
yPos++;
if(yPos>MAX) yPos=MAX;
SetScrollPos((HWND)lParam, SB_CTL, yPos, 1);
SetDlgItemInt(hDlg, IDC_STATIC, yPos, 0);

InvalidateRect(hDlg, &Client, 1); //Здесь перерисовка запускается сразу для двух
InvalidateRect(hDlg, &Client1, 1); //областей. Так придётся делать каждый раз

break;

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

Задание.

Сделайте, пожалуйста, чтобы после каждой строки, выведенной в окно, проводилась бы горизонтальная линия для удобства чтения. Таким образом таблица будет разбита на строки. Линия эта должна быть чёрного цвета.


Назад Главная Вперёд

Сайт управляется системой uCoz