Прокручиваемые списки родились,
как логическое продолжение обычных списков. Когда список становится безнадёжно
длинным, его лучше спрятать в одну строку и вызывать только, когда он действительно
нужен. В таких списках удобно хранить числа, длинные перечисления, даты, названия,
списки языков и цветовых режимов. Широко используются прокручивемые списки в
окнах настройки, где надо выбрать шрифт, его размер и толщину, в окнах выбора
стандартных цветов, файловых менеджерах - для выбора текущего диска и много
где ещё. Наверное, не мне говорить вам о пользе прокручиваемых списков.
У Combo Box есть одна особенность. Когда вы сделаете его активным, щёлкнув по
нему один раз мышью, а потом нажмёте какую-нибудь клавишу на клавиатуре, то
активным станет тот пункт, который начинается на введённую вами букву. Так как
в списке может быть много значений, это позволяет быстро получать доступ к нужному
пункту.
Также, вы наверное не знаете о том, что Combo Box бывает трёх видом. Первый
- простой. Он не имеет кнопки со стрелкой. Первая его строка - обычное поле
ввода, которое позволяет редактировать содержимое выбранного пункта. Стиль такого
списка обозначается, как CBS_SIMPLE - простой Combo Box.
Второй тип - тоже позволяет редактировать содержимое выделенной строки, но справа
от списка имеется кнопочка со стрелкой, нажав которую, вы увидите весь выпадающий
список. Такой стиль называется CBS_DROPDOWN.
И наконец, самый первый по частоте использования список - без возможности редактирования
содержимого. Он не имеет ничего общего с полями ввода, а являет собой нечто
среднее между обычным списком, типа List Box и нередактируемой строкой. Его
представляет стиль CBS_DROPDOWNLIST.
Также, вы можете разрешить сортировку списка, добавив стиль CBS_SORT, и позаботиться
о том, чтобы всё строки были в нижнем регистре, дописав CBS_LOWERCASE (соответственно
для верхнего регистра CBS_UPPERCASE). Если вы хотите, чтобы в списке отображалась
полоса прокрутки (значений может быть столько, что они все не уместятся), то
для этого существует стиль CBS_DISABLENOSCROLL. Для автоматической свёртки текста
по горизонтали применяют стиль CBS_AUTOHSCROLL (действительно, строки могут
быть длиннее, чем указанная в CreateWindow длина списка).
Лучший способ познакомиться с этими стилями поближе - это попробовать их все.
Стили CBS_SIMPLE, CBS_DROPDOWN и CBS_DROPDOWNLIST конечно нужно применять по
отдельности.
Несколько слов о примере.
![]() |
В следующем примере
мы закрепим работу с обычными списками, динамическими элементами управления,
перерисовкой динамических окон и узнаем много нового о том, как применяются
прокручиваемые списки. |
Итак, по порядку!
Представьте, что нам надо написать функцию, которая преобразует три числа: день,
месяц и год (13,6,2005) в приятную фразу: "13-е июня 2005-го года".
Как вы уже догадались, для этого нам потребуется конструкция switch/case
и знание строк. Вот как будет выглядеть тело такой функции:
//Функция возвращает выбранную дату
LPSTR ReturnDate(int day, int month, int year)
{
//Переменные-буферы для хранения строковых значений
char myDay[80], myYear[4];
//Преобразуем число
day в строку myDay
_itoa(day+1, myDay, 10);
lstrcat(myDay, "-e "); //добавляем в конец строки -е
//Преобразуем число
месяца в название месяца
//и добавляем в нашу строку
switch(month+1){
case 1: lstrcat(myDay, "Января "); break;
case 2: lstrcat(myDay, "Февраля "); break;
case 3: lstrcat(myDay, "Марта "); break;
case 4: lstrcat(myDay, "Апреля"); break;
case 5: lstrcat(myDay,"Мая "); break;
case 6: lstrcat(myDay,"Июня "); break;
case 7: lstrcat(myDay,"Июля "); break;
case 8: lstrcat(myDay,"Августа "); break;
case 9: lstrcat(myDay,"Сентября "); break;
case 10: lstrcat(myDay,"Октября "); break;
case 11: lstrcat(myDay,"Ноября "); break;
case 12: lstrcat(myDay,"Декабря "); break;
default: lstrcat(myDay,"Июня "); break;
}
//Конкатенируем
с годом
lstrcat(myDay, _itoa(year, myYear, 10));
lstrcat(myDay, " г.");
//Возвращаем готовую дату
return myDay;
}
Поскольку функция возвращает строку, она имеет тип LPSTR, который эквивалентен типу char *. Функция формирует строку содержащую текст даты в зависимости от введённых чисел-аргументов. Для того, чтобы более детально понять её, мы должны знать...
КАК работать со строками в Win 32 API?
Функций для строк в API
безумно много. И связано это в том числе и с тем, что существует несколько стандартов:
ANSI, Unicide, ASCII... Функции конвертирования из одного формата в другой стоят
на первом месте в справочниках в разделе "строки". Между тем, они
гораздо менее насущны, по сравнению с теми, что мы разберём сейчас.
1. Длина строки.
Длину строки можно узнать с помощью _lstrlen(). Эта функция аналогичная
strlen в DOS и используется там, где нужно указать число символов в строке.
Например, в функции TextOut:
TextOut(hdc, x, y, Buf, _lstrlen(Buf));
2. Строку в число:
Если мы вводим в текстовое поле целое число, можно воспользоваться GetDlgItemInt
- получение целого числа из поля ввода. Но если число дробное, проще получить
сначала текст поля ввода функцией GetDlgItemText, а потом полученную
строку превратить в вещественное число. Это позволяет сделать atof(char *),
которая конвертирует строку в скобках в число типа double. (Для типа int она
называется соответственно atoi).
double Val;
Val=atof(Buf);
c=sqrt(Val);
3. Число в строку.
Когда мы хотим выводить числовые значения в статический элемент (метку), используя
SetDlgItemText, на помощь приходит _itoa(int, char *, int). Первый
аргумент конвертируемое число, второе - строка текста, третий по умолчанию =10
- система счёта. Пример:
int a=10;
char Buf[2];
_itoa(a, Buf, 10);
....
SetDlgItemText(hWnd,
IDC_STATIC, (LPSTR)Buf);
4.
Поместить в строку значение.
Иногда вместо того, чтобы приравнивать две строки (что очень не любит Visual
C++), можно скопировать значение одной строки в другую.
lstrcpy(Buf, ReturnDate(date,month,atoi(Buf1)));
В переменную Buf мы записываем строку, которую возвращает наша функция ReturnDate.
5. Сравнение строк.
Если вы решили сделать контроль доступа к вашей программе, то лучшей функции
вам не найти.
int lstrcmp(LPSTR str1, LPSTR str2);
Возвраемое значение >0, если str1>str2, <0, если str1<str2 и равно
0, если строки равны. Напомню, что строки сравниваются посимвольно, и строка
"аав" < "бав".
6. Конкатенция или сцепление строк.
Как я уже говорил, во многих языках программирования сцепление двух строк
осуществляется знаком "+". Это очень удобно. В C++ нет такой операции,
поэтому приходится вызывать специальную функцию. Она называется: lstrcat();
Синтаксис у неё такой:
LPSTR lstrcat(LPSTR str1, LPSTR str2);
Возвращает она сцеплённую строку, где в конец str1 добавлена str2.
Пример:
lstrcat(Buf,"Ещё одна строка");
Итак, снова задаёмся вопросом:
КАК работает Combo Box?
В этом примере у нас опять не будет ресурсов. Часто не требуется создавать главное
окно приложения, из которого вызываются через меню диалоговые окна. Если наша
программа выполняет какие-то небольшие конкретные действия, она может быть сразу
диалоговым окном. Это используется чаще, поэтому в компиляторах Delphi и C++
Builder по умолчанию вам предлагается работать именно с диалоговым окном, из
которого при желании всегда можно сделать главное окно с меню и панелью инструментов.
Итак, разместим динамически в окне элементы управления - ComboBox дней, ListBox
месяцев, ComboBox лет. Статический текст я решил сделать не стандартного чёрного
цвета, а цветной. Как, вы увидите ниже.
Итак, текст программы:
// Combo.cpp
#include<windows.h>
#include "combo.h"
//Создаём прототип функции
окна
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
//Функция формата даты
LPSTR ReturnDate(int day, int month, int year);
//объявляем имя программы
char szProgName[]="Имя программы";
HINSTANCE hInst;
HBRUSH hbrush; //создаём объект-кисть
//Идентификаторы окон-элементов
управления
HWND hComboBox, hListBox, hComboYear, hExit, hTake;
char Buf1[80],Buf[80];
HDC hdc1,memdc; //создаём
контекст устройства
PAINTSTRUCT ps; //структура рисования
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpszCmdLine, int nCmdShow)
{
HWND hWnd; //идентификатор главного окна
MSG lpMsg;
hInst=hInstance;
WNDCLASS w; //создаём
экземпляр структуры WNDCLASS
//И начинаем её заполнять
w.lpszClassName=szProgName;
w.hInstance=hInstance;
w.lpfnWndProc=WndProc;
w.hCursor=LoadCursor(NULL, IDC_ARROW);
w.hIcon=LoadIcon(NULL, IDI_APPLICATION); //иконка
w.lpszMenuName=0;
w.hbrBackground=(HBRUSH)CreateSolidBrush(RGB(0,0,0)); //цвет фона окна
w.style=CS_HREDRAW|CS_VREDRAW; //стиль - перерисовываемое по х и по у
w.cbClsExtra=0;
w.cbWndExtra=0;
//Если не удалось зарегистрировать
класс окна - выходим
if(!RegisterClass(&w))
return 0;
//Создадим окно в памяти,
заполнив аргументы CreateWindow
hWnd=CreateWindow(szProgName,
"Демонстрация списков",
WS_OVERLAPPEDWINDOW,
100, //Координаты окна
100,
300,
530,
(HWND)NULL,
(HMENU)NULL,
hInstance,
(LPSTR)NULL);
//Выводим окно из памяти
на экран
ShowWindow(hWnd, nCmdShow);
//Обновим содержимое окна
UpdateWindow(hWnd);
while(GetMessage(&lpMsg,
NULL, 0, 0)) {
TranslateMessage(&lpMsg);
DispatchMessage(&lpMsg);
}
return((int)lpMsg.wParam);
}
//Функция окна
LRESULT CALLBACK WndProc(HWND hWnd, UINT messg,
WPARAM wParam, LPARAM lParam)
{
int i; //переменная цикла
int date, month, year; //переменные пользовательской даты
int maxX=0, maxY=0; //максимальные координаты по х и по у
char day[2]; //Число
HBITMAP hbit; //объект изображение окна
//Массив месяцев года
LPCSTR masMonth[12]={
"Январь","Февраль","Март","Апрель","Май","Июнь",
"Июль","Август","Сентябрь","Октябрь","Ноябрь","Декабрь"
};
//Цикл обработки сообщений
switch(messg)
{
case WM_CREATE:
hdc1=GetDC(hWnd); //получаем контекст изображения
maxX=GetSystemMetrics(SM_CXSCREEN); //узнаём максимальные координаты по х и
по у
maxY=GetSystemMetrics(SM_CYSCREEN);
//Создаём окно-список чисел
hComboBox=CreateWindow("Combobox", NULL, WS_CHILD|WS_VISIBLE|WS_VSCROLL|CBS_DROPDOWNLIST|CBS_HASSTRINGS,
90,80,100,200, hWnd, (HMENU)ID_COMBODAY, hInst, NULL);
//Создаём окно-список месяцев.
hListBox=CreateWindow("Listbox", NULL,
WS_VISIBLE|WS_CHILD| WS_VSCROLL | WS_TABSTOP |WS_BORDER ,
90,140,100,200, hWnd, (HMENU)ID_LISTMONTH, hInst, NULL);
//Создаём список лет:
hComboYear=CreateWindow("Combobox",NULL, WS_CHILD|WS_VISIBLE|WS_VSCROLL|CBS_DROPDOWNLIST,
90,360,100,200, hWnd, (HMENU)ID_COMBOYEAR, hInst, NULL);
//Кнопка "Выбрать"
hTake=CreateWindow("button","Выбрать", WS_CHILD|WS_VISIBLE,
90,415,100,27, hWnd, (HMENU)ID_TAKE, hInst, NULL);
//Кнопка "Выход"
hExit=CreateWindow("button","Выход", WS_CHILD|WS_VISIBLE,
90,450,100,27, hWnd, (HMENU)ID_EXIT, hInst, NULL);
//Заполняем список дней значениями чисел от 1 до 31
for(i=1; i<32; i++)
{
//Преобразуем номер дня в строку
_itoa(i, day, 10);
//Запись строки в список
SendMessage(hComboBox, CB_ADDSTRING, 1, (LPARAM)day);
}
//Делаем текущей первую строку списка дней
SendMessage(hComboBox, CB_SETCURSEL, 0, 0L);
//Заносим значения месяцев в список
for(i=0; i<12; i++)
{
SendMessage(hListBox, LB_ADDSTRING, 1, (LPARAM)masMonth[i]);
}
//Делаем текущий строку "Июнь"
SendMessage(hListBox, LB_SETCURSEL, (WPARAM)5, 0L);
//Заносим значения лет:
SendMessage(hComboYear, CB_ADDSTRING, 1, (LPARAM)"2003");
SendMessage(hComboYear, CB_ADDSTRING, 1, (LPARAM)"2004");
SendMessage(hComboYear, CB_ADDSTRING, 1, (LPARAM)"2005");
//Делаем текущим первую
строчку
SendMessage(hComboYear, CB_SETCURSEL, 0, 0L);
memdc=CreateCompatibleDC(hdc1);
//создём в памяти контекст, совместимый с нашим
hbit=CreateCompatibleBitmap(hdc1, maxX, maxY); //создаём картинку нашего окна
SelectObject(memdc, hbit); //делаем её активной в области контекста памяти
hbrush=CreateSolidBrush(RGB(255,255,255)); //создаём кисть
SelectObject(memdc, hbrush); //делаем её активной
PatBlt(memdc, 0,0,maxX, maxY, PATCOPY); //копируем картинку из памяти в наше
окно, растянув её на всё окно
ReleaseDC(hWnd, hdc1); //освобождаем контекст
break;
case WM_SIZE: //при изменении размеров окна, взывается
InvalidateRect(hWnd, NULL, 1); //сообщение WM_PAINT
break;
case WM_PAINT: //которое
hdc1=BeginPaint(hWnd, &ps);
//Заголовки над элементами управления
SetTextColor(hdc1, RGB(255,255,67));
SetBkMode(hdc1, TRANSPARENT);
TextOut(hdc1, 85,60,"Выберите число: ", 16);
TextOut(hdc1, 85,120,"Выберите месяц: ", 16);
TextOut(hdc1, 85,340,"Выберите год: ", 16);
//Перерисовка окна
memdc=CreateCompatibleDC(hdc1);
BitBlt(hdc1, 0, 0, maxX, maxY, memdc, 0, 0, SRCCOPY); //копирует в окно сохранённую
картинку
EndPaint(hWnd, &ps);
break;
case WM_COMMAND:
switch(LOWORD(wParam)) {
//Нажатие кнопки выход
case ID_EXIT:
DestroyWindow(hWnd);
break;
//Кнопка "Выбрать"
case ID_TAKE:
//Получаем номер выделенной строки числа
date=SendMessage(hComboBox, CB_GETCURSEL,0, 0L);
//Получаем номер выделенной строки месяца
month=SendMessage(hListBox, LB_GETCURSEL, 0, 0L);
//Получаем номер выделенной строки года
year=SendMessage(hComboYear, CB_GETCURSEL, 0, 0L);
SendMessage(hComboYear, CB_GETLBTEXT, (WPARAM)year, (LPARAM)Buf1);
//Выводим на экран дату в удобоваримом формате
lstrcpy(Buf,ReturnDate(date, month,atoi(Buf1)));
MessageBox(NULL, Buf, "", MB_OK);
break;
}
break;
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return(DefWindowProc(hWnd, messg, wParam, lParam));
}
return 0;
}
//Функция возвращает
выбранную дату
LPSTR ReturnDate(int day, int month, int year)
{
//Переменные-буферы для хранения строковых значений
char myDay[80]="", myYear[8]="";
//Преобразуем число в
строку
_itoa(day+1, myDay, 10);
//Добавляем окончание
lstrcat(myDay, "-e ");
//Преобразуем число в название месяца
switch(month+1){
case 1: lstrcat(myDay, "Января "); break;
case 2: lstrcat(myDay, "Февраля "); break;
case 3: lstrcat(myDay, "Марта "); break;
case 4: lstrcat(myDay, "Апреля"); break;
case 5: lstrcat(myDay,"Мая "); break;
case 6: lstrcat(myDay,"Июня "); break;
case 7: lstrcat(myDay,"Июля "); break;
case 8: lstrcat(myDay,"Августа "); break;
case 9: lstrcat(myDay,"Сентября "); break;
case 10: lstrcat(myDay,"Октября "); break;
case 11: lstrcat(myDay,"Ноября "); break;
case 12: lstrcat(myDay,"Декабря "); break;
default: lstrcat(myDay,"Июня "); break;
}
//Преобразуем год в строку
_itoa(year, myYear, 10);
lstrcat(myYear, " г.");
//Конкатенируем с годом
lstrcat(myDay, myYear);
return myDay;
}
//Файл заголовков Combo.h
выглядит так:
// Combo.h
// Идентификаторы элементов управления
#define ID_BUTTON 1
#define ID_COMBODAY 2
#define ID_LISTMONTH 3
#define ID_COMBOYEAR 4
#define ID_TAKE 5
#define ID_EXIT 6
Итак, разбор полётов. Потому
что пока мы познакомились с ComboBox только на практике. Итак, вот основные
операции, которые мы сделали:
1. Создание элемента управления:
HWND hComboBox;
hComboBox=CreateWindow("Combobox", NULL, WS_CHILD|WS_VISIBLE|WS_VSCROLL|CBS_DROPDOWNLIST|CBS_HASSTRINGS,
90,80,100,200, hWnd, (HMENU)ID_COMBODAY, hInst, NULL);
Функция как функция. Стиль нашего списка CBS_DROPDOWNLIST.
2. Занесение данных:
for(i=1; i<32; i++)
{
//Преобразуем номер дня в строку
_itoa(i, day, 10);
//Запись строки в список
SendMessage(hComboBox, CB_ADDSTRING, 1, (LPARAM)day);
}
Переменная цикла считает от 1 до 31. Мы конвертируем эти значения в строки
с помощью _itoa и заносим в список, посылая ему сообщение CB_ADDSTRING.
3. Сделать текущей строку:
После создания списка вид у него неприглядный. Он начинается пустой строкой.
Чтобы сделать активной первую строку, вызовите функцию:
SendMessage(hComboBox, CB_SETCURSEL, 0, 0L);
Сообщение CB_SETCURSEL требует сделать текущей строку, указанную в wParam. В
данном случае, 0-ую.
4. Получить из списка номер выделенной строки:
int date;
date=SendMessage(hComboBox, CB_GETCURSEL,0, 0L);
Сообщение CB_GETCURSEL возвращает номер выделенной строки. В wParam и lParam
0.
5. Получить из списка текст выделенной строки:
int year;
LPSTR Buf1[80];
SendMessage(hComboYear, CB_GETLBTEXT, (WPARAM)year, (LPARAM)Buf1);
Сообщение CB_GETLBTEXT (Get ListBox Text) записывает
в строку Buf1 текст строки с номером year. Для этого она даже не должна быть
выделена.
6. Удалить строку из ComboBox. В нашем примере этого
нет.
SendMessage(hComboYear,
CB_DELETESTRING, (WPARAM)num, (LPARAM)0L);
Удаление строки с номером num.
7. Очистить список
SendMessage(hComboYear,
CB_RESETCONTENT, 0, 0L);
8. Вставить строку по номеру.
В отличие от CB_ADDSTRING добавляет строку не в конец, а туда, куда мы укажем.
int num;
LPSTR Buf1[80];
SendMessage(hComboYear, CB_GETLBTEXT, (WPARAM)num, (LPARAM)Buf1);
Вот основные приёмы работы с прокручиваемым списком.