КАК сделать свою панель управления?

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

Вот пример приложения со стандартной панелью управления:



А вот приложение с более крупными значками:



К счастью, проектирование панелей управления в Visual C++ в значительной степени автоматизировано. Панель управления включается в проект, как новый ресурс. Есть даже средство для рисования картинок на кнопках. Кстати, многие, кто впервые сталкиваются с созданием панелей инструментов считают, что для каждой кнопки существует отдельный файл-картинка. На самом деле, файл создаётся один, соответствующий по размерам панели управления. Нарисовать его придётся нам самим.

Рассмотрим функции и структуры данных для работы с панелью управления.

Как всегда нам потребуются библиотека общих элементов управления commctrl.h, а также статическая библиотека comctl.lib, которую будет нужно включить в проект.

1. Создание панели управления
Для создания панели управления используется функция CreateTollbarEx().

HWND CreateToolbarEx(HWND hwnd, DWORD dwStyle, WORD ID, int Num, HINSTANCE hInst, WORD BPID, LPCTBBUTTON Buttons, int Num, int ButtonWidth, int ButtonHeight, int BMPWidth, int BMPHeight, UINT Size);

Параметр hwnd задаёт дескриптор родительского окна.
Стиль окна задаётся параметром dwStyle. Он обязательно должен включать в себя стили, обязательные для элементов управления: WS_CHILD, WS_VISIBLE и WS_BORDER. Кроме того, существуют специальные стили для работы с панелью управления: TBSTYLE_TOOLTIPS позволяющий включать всплывающие подсказки, а также TBSTYLE_WRAPABLE для панели, включающей большое количество кнопок, разрашеющий размещать их в несколько рядов.
Парметр ID это идентификатор панели управления. NumButtons - количество кнопок в панели. В примере, приведённом ниже их будет три. Параметр hInst - дескриптор текущей версии приложения. Идентификатор ресурса картинок на кнопки - аргумент BPID.
В параметр Buttons передаётся указатель на массив структурTBBUTTON, который содержит информацию о каждой кнопке. Количество кнопок задаётся переменной Num. Ширина и высота кнопок задаётся параметрами ButtonWidth и ButtonHeight. Ширина и высота картинок задаются параметрами BMPWidth и BMPHeight. Размер структуры TBBUTTON передаётся аргументу Size.
Несмотря на то, что аргументов у этой функции достаточно много, на практике её применение практически не отличается от уже привычной нам CreateWindow. В примере, который я приведу ниже мы рассмотрим как выглядит эта функция на практике.

2. Структура для описания кнопок.
Рассмотрим подробнее структуру TBBUTTON. Обычно по числу кнопок в панели управления создаётся массив этих структур, описывающий каждую из этих кнопок. Для чего это нужно? Дело в том, что кнопка описывается несколькими параметрами. Причём, у каждой из кнопок в панели управления эти параметры могут быть различны. Например, вы можете задать одну из кнопок нажатой по умолчанию, другую неактивной и т.д.

Структура TBBUTTON состоит из следующий полей:

typedef struct TBBUTTON {
int iBitmap;
int idCommand;
BYTE fsState;
BYTE fsStyle;
DWORD dwData;
int iString; }
TBBUTTON, NEAR* PTBBUTTON, FAR* LPTBBUTTON;
typedef const TBBUTTON FAR* LPCTBBUTTON;

Назначение полей структуры следующее:

iBitmap - номер картинки в созданном нами ресурсе панель управления. Картинки нумеруются с нуля, как и элементы любого массива.
idCommand - идентификатор, связанный с данной кнопкой, который будет обрабатываться сообщением WM_COMMAND. Его мы задаём сами директивой #define, как и для любой кнопки. Например ID_OPEN или ID_EXIT.
fsState - состояние кнопки. Именно это поле задаёт активной будет кнопка или нет.
dwData - содержит значение, определённое приложением. Обычно это 0L.
iString - индекс строки, связанной с кнопкой. В данном случае 0.

Теперь рассмотрим пример инициализации этой структуры.

TBBUTTON tbButtons[NUM]; //объявляется глобально

tbButtons[0].iBitmap=0; //порядкоый номер картинки, связанной с кнопкой
tbButtons[0].idCommand=ID_OPEN; //идентификатор кнопки
tbButtons[0].fsState=TBSTATE_ENABLED; //состояние кнопки
tbButtons[0].fsStyle=TBSTYLE_BUTTON; //стиль кнопки
tbButtons[0].dwData=0L;
tbButtons[0].iString=0;

tbButtons[1].iBitmap=1;
tbButtons[1].idCommand=ID_CLOCK;
tbButtons[1].fsState=TBSTATE_ENABLED;
tbButtons[1].fsStyle=TBSTYLE_BUTTON;
tbButtons[1].dwData=0L;
tbButtons[1].iString=0;

tbButtons[2].iBitmap=2;
tbButtons[2].idCommand=ID_EXIT;
tbButtons[2].fsState=TBSTATE_ENABLED;
tbButtons[2].fsStyle=TBSTYLE_BUTTON;
tbButtons[2].dwData=0L;
tbButtons[2].iString=0;

Обычно иницилизацию панели управления размещают в отдельной функции InitToolBar(), чтобы она не отвлекала от основного кода.


Давайте же это сделаем!

1. Создайте новый проект типа Win32 под названием: "Panel". Выберите типа проекта "Typical Hello World Application"
2. Теперь, когда у нас есть минимальный каркас, добавим панель управления, как новый ресурс. Выберите пункт меню Insert->Resource или нажмите Ctrl+R. В появившемся окне Insert Resource ("Добавление ресурса") выберите пункт Toolbar ("Панель инструментов"). Нажмите New ("Новая").

3. Будущая панель управления сразу откроется в редакторе ресурсов.


4. Чтобы не осложнять ситуацию мы не будем создавать панель с большим количеством кнопок. Пусть их будет три: "Открыть файл", "Вывести дату" и "Выход". Нарисуем для них картинки. Ничего страшного, если вы не умеет рисовать. Пока главное, чтобы вы понимали, что означает каждая картинка. Поймут ли это другие - не ваша забота! Выберите пункт меню View->Properties. У вас появится окно свойств рисунка. По умолчанию в нём задан идентификатор из целой комбинации букв и цифр, сгенерированный программой. Вы должны будете заменить его своим.

В этом же окне вы можете задать размеры картинок для ваших кнопок. Если размеры кнопок можно менять программно, то размеры картинок надо задать уже сейчас.
Сразу встаёт вопрос - какой выбрать размер? Желательно, чтобы по вертикали и горизонтали он был одинаков. Других требований нет. Обычно этот размер кратен 16: 16Х16, 32Х32, 64Х64... Классическая панель управления - 16Х16, поэтому в данном примере пусть размер картинок будет 16Х16. Естественно, что большие кнопки нажимать будет удобнее, но они будут красть драгоценное место у клиентской области окна в которую можно будет вывести меньше информации.

Рассмотрим окно редактирования панели инструментов. В верхней его части вы видите панель такой, какой она будет размещена в окне. Пока она состоит только из одной кнопки. Внизу эта кнопка открыта в редакторе ресурсов в виде массива точек, цвет которых вы можете задавать. В этой кнопке уже сейчас можно рисовать. Справа - инструменты для рисования. Они почти не отличаются по смыслу от инструментов в программе Paint. В нижней части - нехитрый набор цветов.
В панели инструментов выберите инструмент "Карандаш" (Pencil) и нажмите кнопку с его изображением. Теперь перевидите мышь в окно редактирования и нажмите левой кнопкой мыши на серый квадрат. Как только вы нажмёте на него в верхней части окна добавится ещё одна кнопка. Для заливки контура используйте инструмент Fill ("Заливка"). Циферблат часов удобно рисовать инструментом Ellipse.
5. Чтобы перейте к рисованию следующей кнопки щёлкните по ней в верхней части окна, где находится проект панели управления. Если вы хотите отменить какое-то действие, нажмите Ctrl+Z для отмены.
Вот, что получилось у меня:

6. Три кнопки, по рисунку которых можно догадаться об их назначении. Не обязательно, чтобы ваши кнопки были похожи на мои. Но смысл в них вложен должен быть. Современного пользователя мало устроят кнопки, состоящие из 16 цветов и нарисованные грубо. В новейших программах, какие бы незначительные функции они бы не выполняли, всё чаще встретишь красивые рисунки на кнопках - с тенью и состоящие не менее,чем из 256 цветов. Чтобы создавать кнопки более красивые, чем это может предложить Visual C++, используйте внешние редакторы, чтобы потом вставить картинку, как ресурс в ваш проект.
Нажмите Ctrl+S, чтобы сохранить наши с вами художества. Окно с редактором можно свернуть или закрыть.
7. Добавьте в программу ещё один заголовочный файл:

#include <commctrl.h>

Он нужен для того, чтобы программа могла работать с общими элементами управления.
8. Добавьте в проект файл comctl.lib, как в предыдущем примере. В нём описаны тела нужных нам функций.
9. Объявите в начале исходного текста несколько макроопределений:

#define NUM 3 //количество кнопок

#define ID_OPEN 1 //идентификатор кнопки "Открыть"
#define ID_CLOCK 2 //Идентификатор кнопки "Часы"
#define ID_EXIT 3 //Идентификатор кнопки "Выход"

10. Объявите прототип функции InitToolbar() перед объявлением функции окна. Определение функции можно перенести в конец программы. С ним я познакомлю вас позже

//Функция инициализации панели инструментов
void InitToolbar();

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)

11. Объявите следующие глобальные переменные:

TBBUTTON tbButtons[NUM]; //массив структур описания кнопок tbButtons
HWND hPanel; //идентификатор окна панели

void InitToolbar();

Как мы уже говорили в массиве структур TBBUTTON хранятся состояния кнопок. Создавая этот массив, мы последовательно описываем все кнопки. Поля этих структур будут заполнены в теле InitToolbar.

12. Добавьте вызовы функции InitToolbar, InitCommonControls и CreateToolbar в вашу программу до вызова ShowWindow и UpdateWindow.

InitToolbar();
InitCommonControls();
hPanel=CreateToolbarEx(hWnd, WS_VISIBLE|WS_CHILD|WS_BORDER,
IDR_TOOLBAR1, NUM, hInstance, IDR_TOOLBAR1, tbButtons, NUM,0,0,16,16, sizeof(TBBUTTON));

ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);

13. В конце программы добавьте определение функции InitToolbar.

void InitToolbar()
{
tbButtons[0].iBitmap=0;
tbButtons[0].idCommand=ID_OPEN;
tbButtons[0].fsState=TBSTATE_ENABLED;
tbButtons[0].fsStyle=TBSTYLE_BUTTON;
tbButtons[0].dwData=0L;
tbButtons[0].iString=0;

tbButtons[1].iBitmap=1;
tbButtons[1].idCommand=ID_CLOCK;
tbButtons[1].fsState=TBSTATE_ENABLED;
tbButtons[1].fsStyle=TBSTYLE_BUTTON;
tbButtons[1].dwData=0L;
tbButtons[1].iString=0;

tbButtons[2].iBitmap=2;
tbButtons[2].idCommand=ID_EXIT;
tbButtons[2].fsState=TBSTATE_ENABLED;
tbButtons[2].fsStyle=TBSTYLE_BUTTON;
tbButtons[2].dwData=0L;
tbButtons[2].iString=0;

}

14. Запустите программу. Если вы всё сделали правильно, она будет выглядеть примерно так:

Наши кнопочки с картинками уже есть, но они не работают. Обработку нажатия на кнопки панели управления располагают в обработчике сообщения WM_COMMAND. Также, как и обработку нажатия пунктов меню.

switch (message)
{
case WM_COMMAND:

switch (wmId)
{
//Пункт меню "О программе..."
case IDM_ABOUT:
DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About);
break;

//Пункт меню "Выход"
case IDM_EXIT:
DestroyWindow(hWnd);
break;

//Обработка кнопки "Выход" панели управления

case ID_EXIT:
DestroyWindow(hWnd);
break;

default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;

Добавьте обработку кнопки "Выход" в вашу программу и запустите её на выполнение. Как видите, всё заработало!

15. Один обработчик уже готов! Теперь осталось написать два других. Один будет выводить в окно текущее время, другой - считывать из файла первые 256 байт и выводить в окно. Иногда для вывода длинных текстов используются статические элементы. Их легко использовать, как многострочные текстовые поля, если нам не нужно редактировать этот текст.
Для этого динамически создадим элемент static text в главном окне. Добавим в цикл обработки сообщений обработчик WM_CREATE - сообщение начальной инициализации окна.

switch (message)
{

case WM_CREATE:
CreateWindow("static", "Текст: ", WS_CHILD|WS_VISIBLE|SS_LEFT,
10,40,600,300, hWnd, (HMENU)ID_STATIC, hInst, NULL);

break;

case WM_COMMAND:
...
break;
}


16. Для работы со временем нам потребуется структура SYSTEMTIME, с которой мы уже работали. Экземпляр её будет создаваться в начале функции окна, а заполняться она может непосредственно в обработчике.

char Buf[256]; //строковый буфер
SYSTEMTIME st; //экземпляр структуры системного времени


case ID_CLOCK:
GetSystemTime(&st); //заполняем структуру текущей датой и временем
sprintf(Buf, "%02d:%02d:%02d", st.wHour, st.wMinute, st.wSecond); //записываем их в буфер
SetDlgItemText(hWnd, ID_STATIC, Buf); //выводим текст в статический элемент
break;

17. Аналогичная ситуация и с открытием файлов. Мы уже работали со стандартными окнами открытия файлов, поэтому структура OPENFILENAME нам также знакома. Заполненная структура будет возвращать имя файла, который мы выберем. API функция _lread будет считывать из файла первые 256 байт и выводить их в статический элемент. Когда это заработает, вы увидите, что соблюдены все переносы строк и пробелы.

static OPENFILENAME of; //идентификатор файловой структуры
HFILE hFile; //идентификатор файла

//Обработчик нажатия кнопки "Открыть файл" в панели инструментов
case ID_OPEN:

of.lStructSize=sizeof(OPENFILENAME); //размер структуры
of.hwndOwner=NULL;
of.lpstrFilter="txt\000 *.txt\000\000"; //фильтр
of.lpstrFile=Buf; //текстовая строка, в которую будет записано имя открываемого файла
of.nMaxFile=MAX_PATH; //максимальный путь - константа
of.lpstrFileTitle=NULL;
of.lpstrInitialDir="c:\\"; //папка, с которой начинается обзор
of.lpstrTitle="Открытие файла"; //заголовок окна - его вы придумываете сами
of.Flags=OFN_HIDEREADONLY; //флаги для открытия файла


if(!GetOpenFileName(&of))
MessageBox(0,"Ошибка открытия файла", "Error", MB_OK|MB_ICONSTOP);
else {

hFile=_lopen(Buf, OF_READ);
_lread(hFile, Buf, sizeof(Buf));
SetDlgItemText(hWnd, ID_STATIC, Buf);
_lclose(hFile);

}

break;

Я поставил фильтр TXT, чтобы открывались только текстовые файлы. Если вы поменяете строчку, заполняющую поле lpszFilter на:

of.lpstrFilter= "ALL files!\000 *.**\000\000";

то у вас будут открываться все файлы. Другой вопрос, что будет на экране. В любом случае, _lread здесь открывает файлы только для чтения, поэтому содержимое файла никак не пострадает.

16. Программа готова!

После всех этих мытарств вы конечно же скажете, что овчинка выделки не стоит, и что App Wizard создаёт всё это за считанные секунды. Да, нельзя с вами не согласиться. Мало кто сейчас будет писать панель инструментов на API. Тем не менее, я надеюсь, что после этой главы к вам придёт понимание принципов работы панели инструментов.

Здесь можно было бы и раскланяться, но... мы совсем не сказали о такой замечательной возможности панелей инструментов, как сопровождать свои кнопки всплывающими подсказкми (tooltips, hints).

Назад Главная Далее

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