На этом шаге мы рассмотрим создание стационарных панелей инструментов.
В некоторых программах команды меню предлагают пользователю выбирать среди множества опций, таких как шрифты, цвета, стили линий, способы выравнивания текста и тип фигур. Обычно пользователь так часто пользуется ими, что удобно их иметь на экране в виде некой панели инструментов, а не "закапывать" в меню. Панели инструментов OWL, представленные классом TToolbox, позволяют вам легко встраивать такие панели в вашу программу.
В ObjectWindows панели инструментов являются не чем иным, как окнами специального типа, производными от класса TGadgetWindow, содержащего ряд OWL-приспособлений. OWL автоматически размещает эти приспособления - которые могут вмещать в себя растровые изображения, кнопки, тексты, управляющие элементы и разделители, - в некоторую симметричную матрицу со столбцами одинаковой ширины и строками одинаковой высоты. Панель инструментов OWL во многом похожа на панель управления OWL, причем разница в основном заключается в том, каким способом размещаются приспособления. Как следует из приведенного ниже примера, создание панели инструментов ничем не сложнее создания панели управления.
#include <owl\owlpch.h> #include <owl\applicat.h> #include <owl\decframe.h> #include <owl\toolbox.h> #include <owl\buttonga.h> #include "pr58_1.rc" // Класс приложения. class TApp: public TApplication { public: TApp():TApplication() {} void InitMainWindow(); }; // Класс обрамленного окна. class TWndw : public TDecoratedFrame { public: TWndw (TWindow *parent, const char far *title, TWindow *client); protected: TToolBox *toolBox; void CmLine (WPARAM Id); void CmColor (WPARAM Id); DECLARE_RESPONSE_TABLE(TWndw); }; DEFINE_RESPONSE_TABLE1 (TWndw, TDecoratedFrame) EV_COMMAND_AND_ID (CM_LINE1, CmLine), EV_COMMAND_AND_ID (CM_LINE2, CmLine), EV_COMMAND_AND_ID (CM_LINE3, CmLine), EV_COMMAND_AND_ID (CM_LINE4, CmLine), EV_COMMAND_AND_ID (CM_BLACK, CmColor), EV_COMMAND_AND_ID (CM_BLUE, CmColor), EV_COMMAND_AND_ID (CM_RED, CmColor), EV_COMMAND_AND_ID (CM_GREEN, CmColor), EV_COMMAND_AND_ID (CM_YELLOW,CmColor), EV_COMMAND_AND_ID (CM_PURPLE,CmColor), END_RESPONSE_TABLE; // Класс окна-клиента. class TCWndw : public TWindow { public: TCWndw (TWindow *parent, const char far *title); protected: int lineWidth, lineColor; BOOL button; TPen *pen; TClientDC *lineDC; void EvLButtonUp (UINT, TPoint &point); void EvLButtonDown (UINT, TPoint &point); void EvMouseMove (UINT, TPoint &point); LRESULT PmChangeColor (WPARAM color, LPARAM); LRESULT PmChangeLine (WPARAM width, LPARAM); DECLARE_RESPONSE_TABLE (TCWndw); }; DEFINE_RESPONSE_TABLE1 (TCWndw, TWindow) EV_WM_LBUTTONUP, EV_WM_LBUTTONDOWN, EV_WM_MOUSEMOVE, EV_MESSAGE (PM_CHANGELINE, PmChangeLine), EV_MESSAGE (PM_CHANGECOLOR, PmChangeColor), END_RESPONSE_TABLE; //*********************************** // Реализация класса TWndw. //*********************************** // TWndw::TWndw() // Это конструктор главного окна. TWndw::TWndw(TWindow *parent, const char far *title, TWindow *clienTWndw): TDecoratedFrame (parent, title, clienTWndw) { TButtonGadget *b; TSeparatorGadget *s; // Добавить меню к главному окну. AssignMenu (MENU_1); // Создать новый объект панели инструментов. toolBox = new TToolBox (this); // Добавить приспособления в панель инструментов. b = new TButtonGadget (BMP_LINE1, CM_LINE1, TButtonGadget::Exclusive, TRUE, TButtonGadget::Down); toolBox->Insert (*b); b = new TButtonGadget (BMP_LINE2, CM_LINE2, TButtonGadget::Exclusive, TRUE, TButtonGadget::Up); toolBox->Insert (*b); b = new TButtonGadget (BMP_LINE3, CM_LINE3, TButtonGadget::Exclusive, TRUE, TButtonGadget::Up); toolBox->Insert (*b); b = new TButtonGadget (BMP_LINE4, CM_LINE4, TButtonGadget::Exclusive, TRUE, TButtonGadget::Up); toolBox->Insert (*b); s = new TSeparatorGadget (10); toolBox->Insert (*s); s = new TSeparatorGadget (10); toolBox->Insert (*s); b = new TButtonGadget (BMP_BLACK, CM_BLACK, TButtonGadget::Exclusive, TRUE, TButtonGadget::Down); toolBox->Insert (*b); b = new TButtonGadget (BMP_BLUE, CM_BLUE, TButtonGadget::Exclusive, TRUE, TButtonGadget::Up); toolBox->Insert (*b); b = new TButtonGadget (BMP_RED, CM_RED, TButtonGadget::Exclusive, TRUE, TButtonGadget::Up); toolBox->Insert (*b); b = new TButtonGadget (BMP_GREEN, CM_GREEN, TButtonGadget::Exclusive, TRUE, TButtonGadget::Up); toolBox->Insert (*b); b = new TButtonGadget (BMP_YELLOW, CM_YELLOW, TButtonGadget::Exclusive, TRUE, TButtonGadget::Up); toolBox->Insert (*b); b = new TButtonGadget (BMP_PURPLE, CM_PURPLE, TButtonGadget::Exclusive, TRUE, TButtonGadget::Up); toolBox->Insert (*b); // Добавить к этому окну панель инструментов. Insert (*toolBox, TDecoratedFrame::Left); // Определить расположение и размеры этого окна. Attr.X = 50; Attr.Y = 50; Attr.W = GetSystemMetrics(SM_CXSCREEN)/1.5; Attr.H = GetSystemMetrics(SM_CXSCREEN)/1.5; } // TWndw::CmLine() // Эта функция реагирует на любую кнопку линии из панели // инструментов, посылая сообщение окну-клиенту. // Сообщение информирует окно-клиент о // том, что ширина пера должна быть изменена. void TWndw::CmLine(WPARAM Id) { ClientWnd->PostMessage(PM_CHANGELINE, Id - 100, 0); } // TWndw::CmColor() // Эта функция реагирует на любую кнопку цвета из панели // инструментов, посылая сообщение окну-клиенту. // Сообщение информирует окно-клиент о том, // что цвет пера должен быть изменен. void TWndw::CmColor (WPARAM Id) { ClientWnd->PostMessage(PM_CHANGECOLOR, Id - 200, 0); } //*********************************** // Реализация окна TCWndw. //*********************************** // TCWndw::TCWndw() // Это конструктор окна-клиента. TCWndw::TCWndw (TWindow *parent, const char far *title): TWindow (parent, title) { // Инициализировать переменные. lineWidth = 1; lineColor = 0; button = FALSE; pen = NULL; lineDC = NULL; } // TCWndw::EvLButtonDown() // Эта функция обрабатывает сообщения WM_LBUTTONDOWN, // которые Windows посылает приложению, когда // пользователь производит нажатие левой кнопки // мыши внутри окна пользователя. void TCWndw::EvLButtonDown (UINT, TPoint &point) { // Если это новое нажатие кнопки... if (!button) { // Получить контекст устройства и стандартное перо. lineDC = new TClientDC (HWindow); pen = new TPen (lineColor, lineWidth, PS_SOLID); lineDC->SelectObject (*pen); // Направить все вводимое мышью в наше окно. SetCapture(); // Привязать начало линии к координатам курсора мыши. lineDC->MoveTo (point); // Установить флаг кнопки мыши. button = TRUE; } } // TCWndw::EvLButtonUp() // Эта функция обрабатывает сообщения WM_LBUTTONUP, // которые Windows посылает приложению, когда // пользователь отпускает левую кнопку мыши. void TCWndw::EvLButtonUp (UINT, TPoint&) { // Освободить контекст устройства. delete lineDC; lineDC = NULL; // Удалить объект стандартного пера. delete pen; // Выключить флаг кнопки мыши. button = FALSE; // Освободить захват мыши для дальшейшей работы. ReleaseCapture(); } // TCWndw::EvMouseMove() // Эта функция обрабатывает сообщения WM_MOUSEMOVE, // которые Windows посыпает приложению, когда // пользователь перемещает мыщь. void TCWndw::EvMouseMove (UINT, TPoint &point) { // Если нажата левая кнопка мыши, то чертить // линию к новой точке. if (button) lineDC->LineTo (point); } // TCWndw::PmChangeColor() // Эта функция реагирует на определяемое пользователем // сообщение PM_CHANGECOLOR установкой нового цвета линии. LRESULT TCWndw::PmChangeColor (WPARAM color, LPARAM) { lineColor = color; return 1; } // TCWndw::PmChangeLine() // Эта функция реагирует на определяемое пользователем // сообщение PM_CHANGELINE установкой новой толщины линии. LRESULT TCWndw::PmChangeLine (WPARAM width, LPARAM) { lineWidth = width; return 1; } //*********************************** // Реализация класса TApp. //*********************************** // Эта функция создает новое окно приложения. void TApp::InitMainWindow() { // Построить окно-клиент. TWindow *client = new TCWndw (0, 0); // Построить обрамленное окно. TDecoratedFrame *frame = new TWndw (0,"Панель инструментов", client); // Установить главным окном обрамленное окно. SetMainWindow(frame); } int OwlMain(int,char *[]) { return TApp().Run(); }
Файл ресурсов:
#ifndef WORKSHOP_INVOKED #include "windows.h" #endif #define MENU_1 100 #define CM_EXIT 24310 #define BMP_LINE1 1 #define BMP_LINE2 2 #define BMP_LINE3 3 #define BMP_LINE4 4 #define BMP_BLACK 5 #define BMP_BLUE 6 #define BMP_RED 7 #define BMP_GREEN 8 #define BMP_YELLOW 9 #define BMP_PURPLE 10 #define CM_LINE1 101 #define CM_LINE2 102 #define CM_LINE3 103 #define CM_LINE4 105 #define CM_BLACK 200 #define CM_BLUE 204 #define CM_RED 201 #define CM_GREEN 202 #define CM_YELLOW 203 #define CM_PURPLE 205 #define PM_CHANGECOLOR WM_USER #define PM_CHANGELINE WM_USER+1 #ifdef RC_INVOKED MENU_1 MENU { POPUP "&File" { MENUITEM "E&xit", CM_EXIT } } BMP_LINE1 BITMAP "line1.bmp" BMP_LINE2 BITMAP "line2.bmp" BMP_LINE3 BITMAP "line3.bmp" BMP_LINE4 BITMAP "line4.bmp" BMP_BLUE BITMAP "blue.bmp" BMP_BLACK BITMAP "black.bmp" BMP_RED BITMAP "red.bmp" BMP_GREEN BITMAP "green.bmp" BMP_YELLOW BITMAP "yellow.bmp" BMP_PURPLE BITMAP "purple.bmp" #endif
Когда вы запустите эту программу, вы увидите окно, приведенное на рисунке 1.
Рис.1. Результат работы приложения
Это окно содержит обычную строку меню, а также панель инструментов, которая позволяет вам выбрать цвета и стили рисуемых линий. Зачем нужно выбирать цвета и стили линий? Их надо выбирать затем, так как это приложение позволяет вам рисовать в области пользователя окна. Эта программа не обладает возможностью хранить файлы. На самом деле, она даже не может сама себя перерисовать. Поэтому, если вы минимизируете окно, а затем снова его восстановите, то рисунок сотрется.
Эта программа использует три класса:
Главное окно, которое является производным OWL-класса TFrameWindow, включает как окно-клиент, так и панель инструментов. Класс TWndw управляет всеми взаимодействиями с панелью инструментов:
class TWndw : public TDecoratedFrame { public: TWndw (TWindow *parent, const char far *title, TWindow *client); protected: TToolBox *toolBox; void CmLine (WPARAM Id); void CmColor (WPARAM Id); DECLARE_RESPONSE_TABLE(TWndw); }; DEFINE_RESPONSE_TABLE1 (TWndw, TDecoratedFrame) EV_COMMAND_AND_ID (CM_LINE1, CmLine), EV_COMMAND_AND_ID (CM_LINE2, CmLine), EV_COMMAND_AND_ID (CM_LINE3, CmLine), EV_COMMAND_AND_ID (CM_LINE4, CmLine), EV_COMMAND_AND_ID (CM_BLACK, CmColor), EV_COMMAND_AND_ID (CM_BLUE, CmColor), EV_COMMAND_AND_ID (CM_RED, CmColor), EV_COMMAND_AND_ID (CM_GREEN, CmColor), EV_COMMAND_AND_ID (CM_YELLOW,CmColor), EV_COMMAND_AND_ID (CM_PURPLE,CmColor), END_RESPONSE_TABLE;
Когда пользователь щелкает мышью по одной из кнопок панели инструментов, главное окно получает сообщение-команду и обрабатывает его. Эти сообщения следующие: CM_LINE1, CM_LINE2, CM_LINE3 и CM_LINE4 - если пользователь изменяет толщину линии, или: CM_BLACK, CM_RED, CM_BLUE, CM_GREEN, CM_YELLOW или CM_PURPLE - если пользователь изменяет цвет. Из таблицы откликов видно, что OWL направляет все сообщения об изменении параметров линии функции CmLine(), а все сообщения об изменении пвета - функции CmColor().
Поскольку для связи этих сообщений с их функциями отклика в таблице используются макросы EV_COMMAND_AND_ID, то эти функции могут использовать посланный им индикатор для определения той толщины линии или того цвета, которые выбраны пользователем. При этом нет необходимости иметь для каждой команды отдельную функцию отклика на сообщение.
Окно-клиент является производным от OWL-класса TWindow:
class TCWndw : public TWindow { public: TCWndw (TWindow *parent, const char far *title); protected: int lineWidth, lineColor; BOOL button; TPen *pen; TClientDC *lineDC; void EvLButtonUp (UINT, TPoint &point); void EvLButtonDown (UINT, TPoint &point); void EvMouseMove (UINT, TPoint &point); LRESULT PmChangeColor (WPARAM color, LPARAM); LRESULT PmChangeLine (WPARAM width, LPARAM); DECLARE_RESPONSE_TABLE (TCWndw); }; DEFINE_RESPONSE_TABLE1 (TCWndw, TWindow) EV_WM_LBUTTONUP, EV_WM_LBUTTONDOWN, EV_WM_MOUSEMOVE, EV_MESSAGE (PM_CHANGELINE, PmChangeLine), EV_MESSAGE (PM_CHANGECOLOR, PmChangeColor), END_RESPONSE_TABLE;
Видно, что класс окна-клиента занят обработкой всех задач, связанных с рисованием. Он принимает сообщения мыши из Windows и отвечает на них, а также обрабатывает два определяемых пользователем сообщения, PM_CHANGECOLOR и PM_CHANGELINE, которые главное окно посылает окну-клиенту всякий раз, когда пользователь изменяет стиль линии или цвет.
Что собой представляют сообщения пользователя? До сих пор вы имели дело с сообщениями Windows, которые являются системными сообщениями, предопределенными Windows в виде констант с приставкой WМ_, и сообщениями-командами с приставкой СМ_, которую вы присоединяете к меню и управляющим элементам. (Сообщения-команды в действительности являются одной из форм определенных пользователем сообщений, так как программист задает их значения.) Хотя эти два типа сообщений могут управлять большей частью того, что происходит в вашей программе, однако существует и третий тип сообщений, который упрощает задачу организации связи между объектами-окнами. Это сообщения Windows, определяемые пользователем.
Чтобы добавить определяемые пользователем сообщения к таблице откликов вашего класса, используйте макрос EV_MESSAGE, который принимает в качестве двух своих параметров идентификатор сообщения и имя функции, которая будет обрабатывать это сообщение. Макрос EV_MESSAGE добавляется к вашей таблице откликов точно так же, как добавляются другие макросы, с которыми вы ознакомились, - набором имени макроса и его аргументов, заключенных в круглые скобки.
Почему программе требуются дополнительные сообщения, когда сотни их уже заданы в Windows? Приведенной программе требуется определенное пользователем Windows-сообщение для того, чтобы родительское окно могло известить окно-клиент, когда следует изменить толщину пера и цвета. Например, когда пользователь выбирает из панели инструментов новую толщину линии, OWL вызывает функцию-член CmLine() класса TWndw:
void TWndw::CmLine(WPARAM Id)
{
ClientWnd->PostMessage(PM_CHANGELINE, Id - 100, 0);
}
Эта функция посылает сообщение PM_CHANGELINE окну-клиенту. Первым параметром функции PostMessage() является тип сообщения, вторым и третьим параметрами являются значения сообщений wParam и lParam, соответственно. В данном случае функция CmLine() размещает в wParam толщину линии.
Параметр lParam не используется и потому полагается равным нулю. Как видите, идентификаторы кнопок линии в панели инструментов были заданы так, что Id-100 равен новой толщине линии.
Когда пользователь выбирает из панели инструментов кнопку выбора цвета, OWL вызывает функцию CmColor():
void TWndw::CmColor (WPARAM Id)
{
ClientWnd->PostMessage(PM_CHANGECOLOR, Id - 200, 0);
}
Эта функция действует аналогично функции CmLine(), за тем исключением, что'она посылает сообщение PM_CHANGECOLOR и значение Id-200.
Когда окно-клиент получает сообщение PM_CHANGELINE или PM_CHANGECOLOR, оно отвечает вызывом соответствующих функций PmChangeLine() и PmChangeColor().
LRESULT TCWndw::PmChangeColor (WPARAM color, LPARAM) { lineColor = color; return 1; } LRESULT TCWndw::PmChangeLine (WPARAM width, LPARAM) { lineWidth = width; return 1; }
Обе эти функции попросту устанавливают значения соответствующих членов-данных и возвращают 1. В этой программе не используются значения, возвращенные от этих функций, тем не менее вы можете использовать возвращенные значения в вашей собственной программе, чтобы указать, была ли способна функция выполнить действие, запрашиваемое сообщением, или возвратить любой другой вид ответа, который вам нужен.
Сама панель инструментов строится в конструкторе главного окна. После вызова AssignMenu() для установки меню окна конструктор TWndw строит пустую панель инструментов, вызывая OWL-конструктор класса TToolBox:
toolBox = new TToolBox (this);
Хотя в вышеприведенном вызове конструктора TToolBox содержится только один аргумент, указатель на родительское окно, полный прототип конструктора выглядит следующим образом:
TToolBox (TWindow *parent, int numColumns=2, int numRows=AS_MANY_AS_NEEDED, TTileDirection direction+RowMajor, TLibId libid=0);
Аргументами здесь являются указатель на окно, являющееся родительским по отношению к панели инструментов, количество столбцов в панели инструментов, количество строк в панели инструментов, направление, в котором "размещать" приспособления панели инструментов и TLibId. Так как все аргументы, кроме первого, имеют значения, принятые по умолчанию, то вы можете предоставить OWL задавать значения для остальных аргументов. Однако вы можете строить вашу панель инструментов любым способом, задавая соответствующие значения. После построения пустой панели инструментов программа должна снабдить ее кнопками соответствующих приспособлений. В данном окне имеется 10 кнопок, причем первые четыре кнопки отделены от остальных шести двумя разделителями. Кнопки приспособлений создаются следующим образом:
b = new TButtonGadget (BMP_LINE1, CM_LINE1, TButtonGadget::Exclusive,
TRUE, TButtonGadget::Down);
В этой программе нам требуются кнопки взаимоисключающего типа, а не командные; поэтому третьим аргументом в предыдущем фрагменте является TButtonGadget::Exclusive, то есть стиль, который допускает только одну активную кнопку в группе. Конструктор TButtonGadget содержит перечисляемый тип TType, который определяет типы Command, Exclusive и Nonexclusive.
Четвертый аргумент в вызове конструктора TButtonGadget представляет собой булево значение, указывающее, должно ли быть включено приспособление. Последний аргумент задает начальное состояние кнопки приспособления, которое является одним из перечисленных состояний; Up, Down или Indeterminate. Если взглянуть на полный конструктор класса TWndw, то можно заметить, что только одна кнопка в каждой группе имеет тип TButtonGadget::Down; другим кнопкам в группе присваивается тип TButtonGadget::Up.
Наконец, после создания каждой из кнопок инструментов и помещения их в панель инструментов с помощью функции Insert() программа вставляет эту готовую панель инструментов в окно, вызывая функцию Insert() данного класса:
Insert (*toolBox, TDecoratedFrame::Left);
Помимо создания панели инструментов, программа позволяет пользователю рисовать в окне простые линии. Функции рисования вызываются, когда пользователь нажимает на левую кнопку мыши в то время, как указатель находится в области окна-клиента; при этом генерируется сообщение WM_LBUTTONDOWN. Когда программа получает это сообщение, начинает работать функция отклика на сообщение EvLButtonDown():
void TCWndw::EvLButtonDown (UINT, TPoint &point) { // Если это новое нажатие кнопки... if (!button) { // Получить контекст устройства и стандартное перо. lineDC = new TClientDC (HWindow); pen = new TPen (lineColor, lineWidth, PS_SOLID); lineDC->SelectObject (*pen); // Направить все вводимое мышью в наше окно. SetCapture(); // Привязать начало линии к координатам курсора мыши. lineDC->MoveTo (point); // Установить флаг кнопки мыши. button = TRUE; } }
Здесь программа начинает процесс рисования путем создания контекста устройства. Затем создается новый объект ТРеn с использованием переменных lineColor и lineWidth, после чего программа вводит это перо в контекст устройства. Обращение к функции SetCapture() обеспечивает направление всего ввода мыши в окно, даже если курсор мыши не находится в области окна. Если программа этого не сделает, то могут иметь место странные побочные эффекты.
Например, если пользователь переместил курсор мыши за пределы окна и отпустил клавишу, то программа не будет "знать", что кнопка отпущена и, следовательно, не установит флаг button в соответствии с новым состоянием кнопки. Когда пользователь переместил курсор мыши обратно в область окна, рисование продолжится, хотя кнопка уже больше не нажата. Функция SetCapture() предотвращает этот эффект, так как отпускание кнопки мыши даже вне окна направляется в окно (функция SetCapture() является функцией-членом класса TWindow и представляет функцию Windows с таким же именем). Наконец, после установки захвата ввода от мыши программа вызывает функцию MoveTo(), чтобы связать начальную точку линии с текущими координатами курсора мыши, и устанавливает значение флага кнопки мыши в TRUE, что указывает на то, что кнопка нажата.
Когда пользователь перемещает курсор мыши по окну, Windows формирует длинную последовательность сообщений WM_MOUSEMOVE. Так как таблица откликов класса TCWndw указывает, что программа может обработать эти сообщения, перемещение мыши также заставляет OWL вызвать функцию отклика на сообщение EvMouseMove():
void TCWndw::EvMouseMove (UINT, TPoint &point) { // Если нажата левая кнопка мыши, то чертить // линию к новой точке. if (button) lineDC->LineTo (point); }
Здесь программа проверяет, нажата ли кнопка мыши. Это необходимо потому, что мышь генерирует сообщения WM_MOUSEMOVE, показывающие, нажата ли кнопка. Вы не хотите рисовать, когда кнопка не нажата, поэтому программа сначала проверяет содержимое переменной button. Если button - истинно, что указывает на нажатие кнопки, программа рисует линию от предыдущего положения курсора мыши (которое первоначально установлено вызовом функции MoveTo() в EvLButtonDown()), изменяемого при каждом обращении к LineTo(), до его текущего положения.
Когда пользователь отпускает кнопку мыши, Windows генерирует сообщение WM_LBUTTONUP, которое принимается функцией отклика EvLButtonUp():
void TCWndw::EvLButtonUp (UINT, TPoint&) { // Освободить контекст устройства. delete lineDC; lineDC = NULL; // Удалить объект стандартного пера. delete pen; // Выключить флаг кнопки мыши. button = FALSE; // Освободить захват мыши для дальшейшей работы. ReleaseCapture(); }
Эта функция удаляет контекст устройства стандартное перо, устанавливает флаг button в состояние FALSE (это указывает, что теперь клавиша отпущена) и вызывает функцию ReleaseCapture(), что позволяет направлять ввод мыши в другие окна.
На следующем шаге мы рассмотрим создание плавающих панелей инструментов.