Глава 11.
Вводим элементы пользовательского интерфейса
Начиная с этой главы, мы с вами начнем разработку нового приложения — простейшего графического редактора, которое позволит нам познакомиться со следующими вопросами:
1. Создание панелей инструментов и работа с ними.
2. Создание строки состояния и работа с ней.
3. Динамическое изменение курсора.
4. Вычерчивание геометрических фигур с помощью мыши.
5. Работа с меню.
Итак, прежде всего необходим каркас приложения. Те из вас, кто работает с Visual C++ 6.0, должны создать стандартное MFC-приложение, отказавшись от поддержки архитектуры "документ/представление". Для тех же читателей, которые используют Visual C++ 5.0, я подготовил специального мастера — MyWizard4, чтобы не начинать все практически с нуля.
Примечание
Внешний вид приложений, созданных стандартным мастером Visual C++ 6.0 и моим, несколько отличается друг от друга, но это никак не повлияет на работу приложения
Прежде всего посмотрите на состав проекта (рис. 11.1) и убедитесь, что основные изменения, по сравнению с нашим предыдущим приложением, коснулись ресурсов. Добавилось меню и панель инструментов. Вопросы, связанные с меню, мы частично (на необходимом на данный момент уровне) уже рассматривали и будем еще развивать эту тему в дальнейшем. Сейчас же займемся панелями инструментов.
Работа с панелями инструментов
Любое современное профессионально написанное приложение имеет в своем главном окне как минимум два дополнительных инструментальных средства, значительно облегчающих работу — это панель инструментов (Toolbar) и строка состояния (Statusbar). И хотя в библиотеке MFC для этих, а также некоторых 'других, элементов давно реализованы соответствующие классы, только 32-разрядные операционные системы Windows 95/98 и Windows NT обеспечивают со своей стороны поддержку этих элементов пользовательского интерфейса. На самом деле создать панель управления не сложнее, чем обыкновенное окно, тем более используя классы MFC. Давайте подробнее познакомимся с этими удобными и полезными компонентами приложения.
Рис. 11.1. В приложении появились новые ресурсы
Для того чтобы можно было работать с панелью инструментов, ее необходимо прежде всего создать. Сделать это очень просто (особенно, если вы умеете рисовать). Щелкните правой кнопкой мыши на папке Toolbar в Resource View, а затем выберите из контекстного меню Insert Toolbar (Вставить панель инструментов), и Visual C++ автоматически создаст новую панель инструментов. Выведите на экран окно свойств панели инструментов и в поле ID введите какой-либо содержательный идентификатор (рис. 11.2).
Рис. 11.2. Свойства панели инструментов
Теперь осталось заполнить панель кнопками. Сделать это несложно — выделите "пустую" кнопку и творите. Вот что получилось у меня (рис. 11.3). После того как кнопка "нарисована", необходимо присвоить ей идентификатор. Для этого дважды щелкните на кнопке, чтобы вывести окно свойств, и в нем введите необходимую информацию, например, так, как это показано на рис. 11.4.
Вот и все. Новая панель инструментов создана, и каждой кнопке присвоен идентификатор, интерпретируемый как команда — совершенно аналогично команде меню.
Рис. 11.3. Пример панели инструментов рисования
Рис. 11.4. Свойства кнопки панели инструментов
Теперь необходимо добавить код, который создаст объект; Windows для панели инструментов. Как вы помните, панель инструментов Лучше всего ассоциировать с классом CMainFrame. Библиотека MFC для панелей инструментов предлагает два класса, являющихся производными от класса CControlBar (рис. 11.5). Мы кратко рассмотрим их в следующем разделе.
Рис. 11.5. Иерархия объектов инструментов
Этот класс определяет поведение панели инструментов (toolbar) — панели управления, отображающей полосу растровых кнопок (все они должны быть одного размера — стандартно 23x22 пиксела) и разделителей. Кнопки активизируют команды, т. е. нажатие на кнопку панели инструментов подобно выбору элемента меню. Так же, как и элементы меню, кнопки панели инструментов могут действовать аналогично обычным кнопкам, переключателям и флажкам. Как и любая другая, панель инструментов либо привязана к какой-либо стороне родительского фрейма, либо является плавающей. В последнем случае пользователь может изменять ее размер. Класс CToolBar поддерживает и работу с всплывающей подсказкой — маленьким окном, содержащим текстовую строку.
Для изображения всех кнопок панели инструментов создается один битовый массив, который должен содержать один и только один образ для каждой кнопки. Обычно порядок расположения образов совпадает с тем, как они отображаются на экране. Все образы должны иметь один и тот же размер (обычно 16x15 пикселов) и располагаться рядом друг с другом без промежутков. Изменение, изображения кнопок панели инструментов в зависимости от их состояния и стиля (нажатая, заблокированная, неопределенная, отжатая и т. п.) формируется автоматически благодаря механизму ON_UPDATE_COMMAND_UI: во время цикла простоя (idle time) панель инструментов вызывает обработчик команды ON_UPDATE_COMMAND_UI с идентификаторами команд для всех своих кнопок (исключая разделители). Для панелей инструментов класс CCmdUI, обеспечивающий правильное функционирование обработчика, поддерживает следующие функции: CCmdUI::Enable, CCmdUI::SetCheck и CCmdUI::SetRadio.
Теперь, когда у вас сложилось общее представление о панели инструментов, мы, как обычно, рассмотрим основные функциональные возможности, предоставляемые классом CToolBar.
Поскольку панель инструментов — это окно, то для ее создания необходимо проделать стандартные шаги:
1. Создать объект класса (в классе CMainFrame)
CToolBar m_wndPaneBar;
Конструктор класса
CToolBar::CToolBar ()
создает объект класса и устанавливает размеры по умолчанию: размер кнопок — 23x22, размер рисунков на них (образов) — 16x15, высота верхней и нижней рамки — 3 пиксела.
2. На базе предопределенного оконного класса TOOLBARCLASSNAME создать окно Windows. Сделать это лучше всего в обработчике OnCreate сообщения WM_CREATE класса CMainFrame:
if (!m_wndPaneBar.Create(this,
WS_CHILD | WS_VISIBLE | CBRS_SIZE_FIXED |
CBRSJTOP | CBRSJTOOLTIPS) | |
!m_wndPaneBar.LoadToolBar(IDR_PANEBAR))
TRACED("Ошибка при создании панели инструментов рисования\n"));
return -1; // при создании произошла ошибка
Примечание
Пусть вас не пугают слова "... создать на базе предопределенного оконного класса TOOLBARCLASSNAME...". Они приведены только для информации — ничего специально делать не надо. Фактически мы создаем новое окно, а о том, как это делается, мы говорили уже достаточно.
Для создания окна Windows и присоединения его к объекту предназначены две функции класса CToolBar.
BOOL CToolBar::Create (
CWnd *pParentWnd,
DWORD dwStyle = WS_CHILD | WS_VISIBLE I CBRSJTOP,
UINT nID = AFX_IDW_TOOLBAR)
В качестве аргументов передаются параметры: pParentWnd— указатель на родительское окно панели инструментов, nID— идентификатор дочернего окна (напомню, что панель инструментов является дочерним окном) и dwStyle — стиль панели инструментов, который может принимать следующие значения:
CBRS_TOP
Расположить панель вверху рабочей области родительского окна
CBRS_BOTTOM
Расположить панель внизу рабочей области родительского окна
CBRS_NOALIGN
He переопределять размеры панели в случае, если изменились родительские
CBRS_TOOLTIPS
Выводить краткое описание кнопок
CBRS_SIZE_DYNAMIC
Разрешает пользователю при помощи мыши менять размер панели инструментов, если она находится в плавающем состоянии
CBRS_SIZE_FIXED
Запрещает менять размер панели инструментов
CBRS_FLOATING
Панель инструментов плавающая
CBRS_FLYBY
В строке состояния отображать информацию о кнопке
CBRS_HIDE_INPLACE
Сделать панель невидимой для пользователя
Помимо вышеперечисленных можно задавать также стили окон Windows и общие стили для всех панелей управления:
CBRS_ALIGN_TOP
Панель управления привязывается к верхней стороне рабочей области фрейма
CBRS_ALIGN_BOTTOM
Панель управления привязывается к нижней стороне рабочей области фрейма
CBRS_ALIGN_LEFT
Панель управления привязывается к левой стороне рабочей области фрейма
CBRS_ALIGN_RIGHT
Панель управления привязывается к правой стороне рабочей области фрейма
CBRS_ALIGN_ANY
Панель управления привязывается к какой-либо стороне рабочей области фрейма
CBRS_BORDER_TOP
Рамка очерчивает верхний край панели управления, когда она видима
CBRS_BORDER_BOTTOM
Рамка очерчивает нижний край панели управления, когда она видима
CBRS_ BORDER _LEFT
Рамка очерчивает левый край панели управления, когда она видима
CBRS_BORDER_RIGHT
Рамка очерчивает правый край панели управления, когда она видима
CBRS_FLOAT_MULTI
В единственном окне мини-фрейма могут располагаться несколько панелей управления
CCS_ADJUSTABLE
Пользователь может изменять конфигурацию элемента управления
CCS_BOTTOM
Элемент управления должен быть расположен в нижней части рабочей области окна
CCS_TOP
Элемент управления должен быть расположен в верхней части рабочей области окна
CCS_NODIVIDER
В верхней части элемента управления не надо рисовать разделительную линию шириной 2 пиксела
CCS_NOHILITE
В верхней части элемента управления не надо рисовать разделительную линию шириной 1 пиксел
CCS_NOMOVEY
В ответ на сообщение WM_SIZE элемент управления может изменять свои размеры и перемещаться только горизонтально, его вертикальная координата при этом остается постоянной. Не используйте этот стиль совместно со стилем CCS_NOSIZE
CCS_NOPARENTALIGN
Элемент управления не будет автоматически перемещаться в верхнюю или нижнюю часть родительского окна
CCS_NORESIZE
Приложение должно задать размеры элемента управления явным образом, т. к. размеры, заданные по умолчанию, не будут использоваться
BOOL CToolBar::CreateEx(
CWnd* pParentWnd,
DWORD dwCtrlStyle = TBSTYLE_FLAT,
DWORD dwStyle = WS_CHILD i WS_VISIBLE I CBRS_ALIGN_TOP,
CRect rcBorders = CRecttO, 0, 0, 0),
UINT nID = AFX_IDW_TOOLBAR)
В качестве аргументов в наряду с теми же параметрами, что и у предыдущей функции, передаются также два дополнительных: первый— rcBorders— задает прямоугольник, определяющий положение и размер панели инструментов (если используются нулевые значения, то размер устанавливается автоматически), второй — dwCtrlStyle — позволяет дополнительно задать стили кнопок элемента управления Toolbar, на котором базируется панель инструментов. Этот параметр может иметь следующие значения:
TBSTYLE_BUTTON
Стандартная кнопка
TBSTYLE_SEP
Разделитель между группами кнопок, представляющий собой маленький промежуток. "Кнопка" с таким стилем не может взаимодействовать с пользователем
TBSTYLE_CHECK
Кнопка с фиксацией. Эта кнопка "залипает" при нажатии ее пользователем. Для возврата в исходное состояние необходимо нажать ее еще раз
TBSTYLE_GROUP
Отмечает начало группы стандартных кнопок
TBSTYLE_CHECKGROUP
Отмечает начало группы кнопок с фиксацией. Она остается нажатой до тех пор, пока не нажата другая кнопка из этой же группы . (TBSTYLE_GROUP I TBSTYLE_GHECK)
TBSTYLE_DROPDOWN
He документирован #if (_WIN32_IE >= 0x0300)
TBSTYLE_AUTOSIZE
He документирован // automatically calculate the ex of the button#if (_WIN32 IE >= 0x0400)
TBSTYLE_NOPREFIX
He документирован // if this button should not have accel prefix(_WIN32_IE >= 0x0400)
TBSTYLE_TOOLTIPS
Панель инструментов может создавать и управлять элементом управления ToolTip (всплывающая подсказка). При использовании этого стиля необходимо самому обрабатывать извещения от этого элемента управления.
TBSTYLE_WRAPABLE
Панель инструментов может располагаться в несколько линий. Кнопки переносятся на следующую строку, если им не хватило места в текущей
TBSTYLE_ALTDRAG
He документирован
TBSTYLE_FLAT
He документирован #if (_WIN32_IE >= 0x0300)
TBSTYLE_LIST
He документирован
TBSTYLE_CUSTOMERASE
He документирован
TBSTYLE_REGISTERDROP
He документирован #if (_WIN32_IE >= 0x0400)
TBSTYLE_TRANSPARENT
He документирован #if (_WIN32_IE >= 0x0400)
TBSTYLE_EX_DRAWDDARROWS
He документирован #if (_WIN32_IE >= 0x0400)
3. Теперь осталось только загрузить из ресурсов образы кнопок. Для этого в классе CToolBar предусмотрены две возможности: через вызов функции LoadToolBar и путем загрузки образов кнопок при помощи функции LoadBitmap с привязкой их к соответствующим командам вызовом функции SetButtons. Рассмотрим эти функции более подробно.
Примечание
Visual C++, начиная с версии 4.x, поддерживает ресурс типа TOOLBAR, где задаются не только вид кнопок, но также осуществляется автоматическая привязка к ним команд. Поэтому функция LoadBitmap приводится скорее для справки, чем для реального использования.
BOOL CToolBar::LoadBitmap (LPCTSTR IpszResourceName)
и
BOOL CToolBar: :LoadBitmap (UINT nIDResource)
Загружает битовый массив, определяемый параметром IpszResourceName или nIDResource, который должен содержать один образ для каждой кнопки панели инструментов. Если образы имеют нестандартный размер, то необходимо вызвать SetSizes для установки правильных размеров кнопок и их образов.
BOOL CToolBar::SetButtons (
const UINT *lpIDArray,
int nIDCount)
Устанавливает идентификатор команды каждой кнопки панели инструментов в значение, определяемое соответствующим элементом массива IplDArray, число элементов которого задается параметром nIDCount. Если элемент массива имеет предопределенное значение ID_SEPARATOR, то в соответствующей позиции панели инструментов создается разделитель. Если значение IplDArray равно NULL, то функция просто отводит место для nIDCount элементов, и для установки атрибутов каждого элемента следует вызвать функцию SetButtonlnfo. Эта функция устанавливает также для каждой кнопки стиль TBBS_BUTTON, a для каждого разделителя — стиль TBBS_SEPARATOR и определяет каждой кнопке индекс образа. Индекс образа задает его позицию внутри битового образа массива.
BOOL CToolBar::LoadToolBar (LPCTSTR IpszResourceName)
и
BOOL CToolBar::LoadToolBar (UINT nIDResource)
Загружает изображение панели инструментов и ресурса, заданного либо именем — IpszResourceName, либо идентификатором — nIDResource. При этом сопоставление кнопок с образами и командами осуществляется автоматически.
Пример использования этих функций вы найдете в приведенном фрагменте кода. Теперь посмотрим, как к изображениям на кнопках можно добавить текстовые строки:
int CMainFrame::OnCreate(LPCREATESTRUCT IpCreateStruct)
{
...
// Создаем панель инструментов рисования
...
// Добавляем к изображениям на кнопках текстовые строки
// Прежде всего узнаем и запоминаем размер изображений
CRect imageRect;
m_wndPaneBar.GetIternRect (0, simageRect);
// Добавляем по строке на каждую кнопку,
// если строк будет меньше — все оставшиеся кнопки
// будут осчастливлены ПЕРВОЙ ЗАДАННОЙ СТРОКОЙ !!!
for (int index = ID_PANEERASE; index <= ID_PANEOVAL; index++)
{
CString szPane; szPane.LoadString(index);
// Строки хранятся в следующем виде:
// -сообщение для строки состояния о возможном выборе,
// — символ '\n',
// — краткое наименование для всплывающих подсказок.
// Именно последнее мы и используем
m__wndPaneBar.SetButtonText(index - ID_PANEERASE,
szPane.Mid(szPane.Find('\n')+!));
}
// После каждой вставки текста автоматически происходило
// изменение размеров, но об этом знает Windows,
// объект класса CToolBar об этом знать не знает -
// сообщим ему об этом, помятуя, что размер изображений
// остался без изменений
// константы 7 — требования Microsoft
// Точнее, Microsoft требует значений 6 и 7,
//но внешний вид при этом оставляет желать лучшего
CRect buttonRect;
m_wndPaneBar.GetItemRect(0, SbuttonRect);
m_wndPaneBar.SetSizes(CSize(buttonRect.Width(),
buttonRect.Height()),
CSize(imageRect.Width() — 7,
imageRect.Height () — 7));
...
return 0;
}
Рассмотрим функции, использованные в этом фрагменте.
void CToolBar::SetSizes (
SIZE sizeButton,
SIZE sizelmage)
Устанавливает размер (в пикселах) кнопок панели инструментов, задаваемый параметром sizeButton, при этом параметр sizelmage должен содержать размер образов, зафиксированный в соответствующем битовом массиве. Размеры кнопок задаются, исходя из размеров образов, плюс 7 пикселов для ширины и 6 — для высоты. Размеры самой панели инструментов функция устанавливает автоматически. Microsoft настаивает (это отражено в документе An Application Design Guide) на том, чтобы приложения содержали кнопки стандартных размеров, но любезно предоставляет возможность их изменять.
virtual void CToolBar::GetltemRect (
int nIndex,
LPRECT IpRect)
Заполняет структуру IpRect координатами кнопки или разделителя, задаваемых параметром nIndex. Координаты отсчитываются относительно левого верхнего угла панели инструментов. Эту функцию можно использовать для определения координат разделителя, который вы хотите заменить на комбинированный список или другой элемент управления.
BOOL CToolBar::SetButtonText (
int nlndex,
LPCTSRT IpszText)
Позволяет сопоставить кнопку (задается идентификатором nlndex) панели инструментов и текстовую строку (параметр IpszTexfj, которая появится на ней.
После того как панель инструментов создана, необходимо ее где-то расположить. Рассмотрим фрагмент кода:
int CMainFrame::OnCreate(LPCREATESTRUCT IpCreateStruct)
{
...
// Создаем панель инструментов рисования
...
// Заголовок для будущего окна,
// если панель будет в плавающем режиме.
// Для этой задачи используется функция класса CWnd
m_wndPaneBar.SetWindowText(_Т("Инструменты"));
EnableDocking(CBRS_ALIGN_ANY);
// Сразу настраиваем количество столбцов -
// эта вспомогательная функция приведена ниже
SetColumns(m_nPaneCol);
m_wndPaneBar.EnableDocking(CBRS_ALIGN_ANY);
DockControlBar{&m_wndPaneBar, AFX_IDW_DOCKBAR_RIGHT);
// Размещаем панель инструментов в задаваемом месте экрана
CPoint pt (: :GetSysteinMetrics (SM_CXSCREEN) - 300,
::GetSystemMetrics(SM_CYSCREEN) / 3);
FloatControlBar(&m_wndPaneBar, pt);
...
return 0;
}
// Разбивка панели на столбцы — находим
// конечные кнопки каждой строки
void CMainFrame::SetColumns(UINT nColumns)
// Прежде всего определяем число кнопок панели инструментов
int nCount = m_wndPaneBar.GetToolBarCtrl().GetButtonCount();
for (int i = 0; i < nCount; i++)
{
// Мы собираемся изменять стиль,
// поэтому запоминаем тот, который был установлен
UINT nStyle = m_wndPaneBar.GetButtonStyle(i);
BOOL bWrap = (((i + 1) % nColuinns) = 0) ;
if (bWrap)
nStyle |= TBBS__WRAPPED;
else'
nStyle &= ~TBBS_WRAPPED;
// Устанавливаем новый стиль кнопок
m_wndPaneBar.SetButtonStyle(i, nStyle);
}
// Сообщаем, что панель инструментов требует перерисовки
m_wndPaneBar.Invalidate();
// Перепозиционируем панель инструментов
m_wndPaneBar.GetParentFrame()->RecalcLayout ();
}
Как видите, код небольшой и достаточно простой. Поэтому мне осталось привести только некоторые пояснения по использованным в нем функциям. Начну с метода, который класс CToolBar унаследовал от своего базового класса CControlBar.
void CControlBar::EnableDocking (DWORD dwStyle)
Разрешает привязать панель управления к одной из сторон родительского фрейма, которая должна быть доступна для привязки. Если сторона, передаваемая в качестве параметра dwStyle, и сторона, разрешенная для привязки, у фрейма не совпадают, то панель управления не может быть привязана к родительскому фрейму. В качестве этого параметра можно использовать следующие значения: CBRS_ALIGN_TOP, CBRS_ALIGN_RIGHT, CBRS_ALIGN_LEFT, CBRS_ALIGN_BOTTOM, CBRS_ALIGN_ANY и CBRS_ALIGN_MULTI.
Поскольку возможность быть привязанной к родительскому фрейму зависит не только от "желания" самой панели управления, но и от ее родителя — чаще всего фрейма главного окна, здесь же рассмотрим функции класса CFrameWnd, которые отвечают за работу с панелями управления.
void CFrameWnd::EnableDocking (DWORD dwDockStyle)
Разрешает привязку панели управления к стороне(ам) фрейма, задаваемой(ых) в параметре dwDockStyle с возможными значениями CBRS_ALIGN_TOP, CBRS_ ALIGN_BOTTOM, CBRS_ALIGN_RIGHT, CBRS_ALIGN_LEFT, CBRS_ALIGN_ANY. По умолчанию панели управления привязываются к сторонам фрейма в следующем порядке: верхняя, нижняя, левая и правая.
void CFrameWnd::DockControlBar (
CControlBar *pBar,
UINT nDockBarlD = 0,
LPCRECT IpRect = NULL)
Привязывает панель управления рВаг к одной из сторон текущего фрейма, которая задается параметром nDockBarlD и может быть нулем или комбинацией следующих значений:
AFX_IDW_DOCKBAR_TOP
Привязка к верхней стороне фрейма
AFX_IDW_DOCKBAR_BOTTOM
Привязка к нижней стороне фрейма
AFX_IDW_DOCKBAR_LEFT
Привязка к левой стороне фрейма
AFX_IDW_DOCKBAR_RIGHT
Привязка к правой стороне фрейма
Если параметр nDockBarlD равен нулю, то сторона может быть любой из числа предварительно определенных при помощи двух функций: CControlBar: EnableDocking и CFrameWnd::EnableDocking. Параметр IpRect определяет в координатах экрана, где вне рабочей области фрейма будет привязано окно, содержащее плавающую панель элементов управления.
"FrameWnd* CFrameWnd::FloatControlBar (
CControlBar *pBar,
CPoint point,
DWORD dwStyle = CBRS_ALIGN_TOP)
Переводит панель управления рBar в плавающее (floating) состояние. Обычно она вызывается при запуске приложения для восстановления предыдущих установок. Кроме того, эта функция вызывается фреймом, когда пользователь после перемещения панели выполняет операцию отпускания (drop), не достигнув места, доступного для привязывания. Параметр point задает координаты точки, в которой будет находиться левый верхний угол панели управления, a dwStyle определяет стиль ее выравнивания и ориентации внутри нового фрейма: CBRS_ALIGN_BOTTOM и CBRS_ALIGN_TOP - вертикальная ориентация, а CBRS_ALIGN_RIGHT и CBRS_ALIGN_ LEFT — горизонтальная. Если указаны оба типа ориентации, то панель будет ориентирована горизонтально.
virtual void CFrameWnd::RecalcLayout (BOOL bNotify = TRUE)
Перепозиционирует панели управления текущего объекта. Параметр bNotify определяет, получит ли активизированный по месту элемент извещение (notify) при изменении размещения. Эта функция вызывается фреймом, когда переключаются стандартные панели управления или когда изменяется размер фрейма. Для того чтобы иметь возможность управлять отображением и поведением панелей управления после изменения компоновки фрейма, необходимо переопределить эту функцию в своем классе. Ее реализация, определенная по умолчанию, вызывает функцию CWnd::RepositionBars, чтобы перепозиционировать все панели управления во фрейме.
Еще две функции, которые мы использовали для изменения стиля панели инструментов, выглядят следующим образом.
VINT CToolBar::GetButtonStyle (int nlndex)
Возвращает стиль кнопки или разделителя, заданных на панели инструментов номером позиции nIndex.
oid CToolBar::SetButtonStyle (
int nlndex,
UINT nStyle)
Позволяет установить стиль кнопки, заданной номером позиции nIndex. Стиль кнопки определяет ее реакцию на действия пользователя. Доступными являются следующие стили:
TBBS_BUTTON
Стандартная кнопка
TBBS_SEPARATOR
Разделитель между группами кнопок
TBBS_CHECKBOX
Кнопка с фиксацией; эта кнопка "залипает" при нажатии, для возврата в исходное состояние необходимо нажать ее еще раз
TBBS_GROUP
Отмечает начало группы стандартных кнопок
TBBS_CHECKGROUP
Отмечает начало группы кнопок с фиксацией, остается нажатой до тех пор, пока не нажата другая кнопка из этой же группы
TBBS_WRAP
Кнопка, находящаяся в этом состоянии, сообщает о том, что все следующие кнопки будут отображаться на новой строке. Этот стиль не документирован
Как уже говорилось, класс CToolBar реализует основные функциональные возможности для всех панелей управления. Более специфические и интересные возможности реализованы в его производных классах. Но прежде чем переходить к их рассмотрению, отметим еще один важный момент: в том случае, если кнопка панели управления не имеет обработчиков для UPDATE_COMMAND_UI или COMMAND, то она автоматически блокируется фреймом и выводится серым цветом.
Последняя функция класса CToolBar, которую мы рассмотрим, позволяет получить ссылку на объект CToolBarCtrl:
CToolBar& CToolBar::GetToolBarCtrl ()
Позволяет воспользоваться функциональными возможностями общего элемента управления Toolbar, появившегося в Win32. С помощью этого элемента управления можно, в частности, осуществлять более тонкую настройку панели инструментов.
Для использования преимуществ общего элемента управления Toolbar можно пойти двумя путями: либо непосредственно создать окно CToolBarCtrl, либо создать панель инструментов на основе класса CToolBar, а затем просто получить доступ к элементам объекта CToolBarCtrl (рис. 11.6), вызвав функцию этого класса CToolBar::CToolBarCtrl. Чаще всего используют именно второй способ.
Рис. 11.6. Место класса CToolbarCtrl в иерархии библиотеки MFC
Любая кнопка панели инструментов имеет ассоциированную с ней структуру TBBUTTON, которая содержит полную информацию об этой кнопке. Определение структуры находится в файле <commctrl.h>:
typedef struct _TBBUTTON {
int iBitmap;
int idCommanci;
BYTE fsState;
BYTE fsStyle;
#ifdef _WIN32
BYTE bReserved[2];
#endif
DWORD dwData;
int iString;
} TBBUTTON, NEAR* PTBBUTTON, FAR* LPTBBUTTON;
В поле iBitmap содержится номер кнопки (нумерация начинается с нуля). Для разделителя здесь должно быть записано нулевое значение. В поле idCommand находится идентификатор команды, который будет передаваться окну-владельцу вместе с сообщением WM_COMMAND, когда пользователь выберет эту кнопку. Для разделителя здесь должно быть записано нулевое значение. Поле fsState содержит комбинацию одного или нескольких флагов, определяющих состояние кнопки:
TBSTATE_CHECKED
Кнопка имеет стиль
TBSTYLE_CHECKED
"кнопка с фиксацией" и изображается в нажатом состоянии
TBSTATE_ENABLED
Кнопка доступна для действий пользователя. Если это состояние не установлено, то кнопка заблокирована и изображается серым цветом
TBSTATE_HIDDEN
Кнопка не видима
TBSTATE_IDETERMINATE
Кнопка отображается серым цветом
TBSTATE_PRESSED
Кнопка изображается в нажатом состоянии
TBSTATE_WRAP
Кнопка, находящаяся в этом состоянии, сообщает о том, что все следующие кнопки будут отображаться на новой строке. Использование данного флага позволяет создавать многострочные панели инструментов. Этот флаг можно указывать только вместе с флагом TBSTATE_ENABLED
TBSTATE_ELLIPSES
He документирован. Может использоваться, если определено _WIN32_IE >= 0x0300
TBSTATE_MARKED
He документирован. Может использоваться, если определено _WIN32_IE >= 0x0400
В поле fsStyle записывается стиль кнопки. Можно использовать комбинацию стилей, приведенных при описании метода CToolBar::CreateEx. Поле dwData предназначено для хранения дополнительных данных, связанных с конкретной кнопкой. Если это поле не используется, то лучше записать туда нулевое значение. Если вы хотите написать на поверхности кнопки какой-либо текст, то в поле iString следует занести ее номер во внутреннем списке элемента управления. В противном случае оно должно быть обнулено. Поле bReserved зарезервировано для внутреннего использования.
Полную информацию о любой кнопке панели инструментов можно получить при помощи функции GetButton.
Достаточно большая группа функций позволяет установить новое или узнать текущее состояние определенной кнопки данного элемента управления. Их действие настолько очевидно, что мы ограничимся только их перечислением: IsButtonEnabled, IsButtonChecked, IsButtonPressed, Is Button Hidden, Is Button Indeterminate, GetState, SetState, EnableButton, CheckButton, PressButton, HideButton, Indeterminate.
Мы использовали доступ к объекту класса CToolBarCtrl для определения числа кнопок панели инструментов:
int CToolBarCtrl::GetButtonCount ()
Функция позволяет узнать общее число кнопок панели инструментов.
Теперь необходимо добавить код, который будет отображать панель инструментов на экране. Для этого создадим элемент меню, как мы это делали в главе 8. Но сначала нужно решить, кто будет отвечать за отображение панели инструментов. Можно возложить эту задачу на дочернее окно, а можно на фрейм главного окна. Для ответа на этот вопрос вспомним, что мы решили каждому компоненту программы поручить свою задачу. Поэтому включим соответствующий пункт в меню, ассоциированное именно с фреймом главного окна.
1. Для добавления кода обработчика команды меню выведем на экран окно мастера ClassWizard (рис. 11.7), выполнив команду View | ClassWizard...
Рис. 11.7. Окно мастера ClassWizard
2. В комбинированном списке Class name выберите класс CMainFrame, с которым мы будем связывать созданную панель инструментов рисования.
3. В списке ObjectID мастера ClassWizard выделите идентификатор созданного элемента меню IDM_VIEWPANE, при этом изменится содержимое списка Messages. Выделите в этом списке COMMAND и нажмите кнопку Add Function.
Мастер автоматически создаст для вас пустой каркас обработчика сообщения, в который мы добавим код, позволяющий отображать и скрывать панель инструментов:
void CMainFrame : : OnViewPaneBar ()
{
// Проверяем "видимость" панели инструментов
BOOL bVisible = ;(m_wndPaneBar.GetStyle() & WS_VISIBLE) != 0);
// Меняем видимость на экране
ShowControlBar(&m_wndPaneBar, IbVisible, FALSE);
// Перепозиционируем панель инструментов
RecalcLayout();
// Здесь в будущем мы добавим некоторый код
...
}
Единственная неизвестная нам функция выглядит следующим образом:
void CFramewnd: : Show-ConeroiBar
CControlBar *pBar,
BOOL bShow,
BOOL bDelay)
Параметр bShow определяет видимость панели на экране: TRUE — показать, FALSE — скрыть панель управления рВаг. Значение параметра bDelay, равное TRUE, определяет, что перед выводом на экран нужна задержка.
Остальной код не должен вызвать дополнительных вопросов.
Теперь нужно отметить состояние, для чего необходимо определить обработчик сообщения:
void CMainFrame::OnUpdateViewPaneBar(CCmdUI* pCmdUI)
{
// Проверяем "видимость" панели инструментов
BOOL bVisible = ((m_wndPaneBar.GetStyle() & WS_VISIBLE) != 0);
// Устанавливаем или сбрасываем маркер у пункта меню
pCmdUI->SetCheck(bVisible);
}
Ну вот, теперь можно поиграть с созданной панелью инструментов — отображать ее и прятать, переводить в плавающее состояние и привязывать
к любому краю главного фрейма. Мы не можем только получить доступа к ее кнопкам, т. к. они заблокированы. Чтобы изменить такое положение, необходимо добавить код, выполняемый при нажатии на кнопку. К сожалению, ClassWizard нам помочь не может, поскольку вставлять обработчик для каждой кнопки слишком расточительно. Придется реализовывать это вручную. Добавим в карту сообщений класса CMainFrame следующие строки:
BEGIN_MESSAGE__MAP(CMainFrame, CFrameWnd)
// {{AFX_MSG__MAP (CMainFrame)
...
// Этот фрагмент нас не интересует,
// поскольку добавляется мастером ClassWizard
//}}AFX_MSG_MAP
ON_COMMAND_RANGE(ID_PANEERASE, ID_PANEOVAL, OnPane)
ON_UTOATE_COI*1AND_OI_RAN6E (
ID_PANEERASE, ID_PANEOVAL, OnOpdatePane)
END_MESSAGE_MAP()
Добавим строки также в класс CMainFrame:
class CMainFrame : public CFrameWnd
{
...
// Generated message map functions protected:
//{{AFX_MSG(CMainFrame)
...
// Этот фрагмент нас не интересует,
// поскольку добавляется мастером ClassWizard
//}}AFX_MSG
afx_msg void OnPane(UINT nID);
afx_msg void OnUpdatePane(CCmdUI* pCmdUI);
DECLARE_MESSAGE_MAP()
};
Теперь осталось только реализовать, собственно, обработчик команд, ассоциированных с каждой кнопкой панели инструментов:
void CMainFrame::OnPane(UINT nID)
{
// nID — идентификатор команды, ассоциированной с кнопкой
m_nlndex = nID — ID_PANEERASE;
// Порядковый номер кнопки
// Запоминаем идентификатор нажатой кнопки
m_wndChild.m_nToolNum = nID;
// Сбрасываем все кнопки
for(int i = ID_PANEERASE; i <= ID_PANEOVAL; i++)
m_vmdPaneBar.GetTociBarCtrlt).CheckButton(i, FALSE);
// Определяем нажатую кнопку
BOOL bCheck = m_wndPaneBar.GetToolBarCtrl().IsButtonChecked(nlD);
// "Нажимаем" ее
m_wndPaneBar.GetTooIBarCtri ().CheckButton(nID, IbCheck);
...
}
void CMainFrame: :OnrJpdate?ane ;ССжШ1* pCmdUI)
// Нажимаем или отпускаем кнопку
pCmdUI->SetChecki(UINTi (ID_?ANE3RASE + nlndex) = pCmdUI->m_nID);
}
Что еще сказать по поводу работы с кнопками панели инструментов? Вроде бы все понятно и так. Поэтому коснусь только изменения формы курсора мыши при нажатии на некоторую кнопку, поскольку мы планируем разработать графический редактор.
Прежде всего создадим необходимые курсоры. Рассмотрим этот процесс на одном примере:
1. Раскройте вкладку Resource View и создайте новый пустой курсор.
2. Нарисуйте внешний вид этого курсора (рис. 11.8).
Рис. 11.8. Создаем курсор
3. Нажмите кнопку Hot, а затем на ту точку образа курсора, которая будет определять его нулевые, т. е. точку (0, 0), координаты.
4. Раскройте окно свойств курсора и в поле ID введите содержательный идентификатор, например, IDC_CUR_PAINT (рис. 11.9).
5. Аналогичную операцию проделайте для всех остальных курсоров.
Примечание
Идентификаторы курсоров необходимо располагать в том же порядке, что и кнопки панели инструментов. Это не требование, а всего лишь пожелание. Если это условие не выполнить, то в приводимые фрагменты кода приложения необходимо будет внести определенные изменения.
Рис. 11.9. Свойства курсоров
Теперь, когда необходимые курсоры созданы, внесем некоторые добавления в рассмотренный ранее код. Но начнем с описания функции API.
DWORD::SetClassLong (
HWND hWnd,
int nlndex,
LONG dwNewLong)
Устанавливает в структуре WNDCLASS, описывающей оконный класс для hWnd, новое значение dwNewLong в поле, определяемое параметром nlndex, который может принимать одно из следующих значений:
GCL_CBCLSEXTRA
Устанавливает размер (в байтах) дополнительной памяти, предназначенной для использования всеми окнами, создаваемыми на базе класса окна; размер памяти, уже ассоциированной с классом, при этом не изменяется
GCL_CBWNDEXTRA
Устанавливает размер (в байтах) дополнительной памяти, предназначенной для использования отдельно каждым окном, создаваемым на базе класса окна; размер памяти, уже ассоциированной с окном, при этом не изменяется
GCL_HBRBACKGROUND
Устанавливает дескриптор кисти для фона, ассоциированного классом окна
GCL_HCURSOR
Устанавливает дескриптор курсора, ассоциированного классом окна
GCL_HICON
Устанавливает дескриптор пиктограммы, ассоциированной с классом окна; данное значение используется только в Windows 95/98 и не определено в Windows NT
GCL_HMODULE
Устанавливает дескриптор модуля, который зарегистрировал класс окна
GCL_MENUNAME
Устанавливает адрес строки меню, которая идентифицирует ресурс меню, ассоциированного с классом окна
GCL_STYLE
Устанавливает биты стиля оконного класса
GCL_WNDPROC
Устанавливает адрес оконной процедуры, ассоциированной с классом
При успешном выполнении функция возвращает значение указанного слова из структуры класса окна или нуль — при ошибке. Дополнительную информацию об ошибке можно получить, вызвав функцию API GetLastError.
Обратную задачу — получение информации из структуры класса окна — выполняет функция
DWORD : :GetClassLong (
HWND hWnd,
int nlndex).
Примечание
Получить значение дескриптора hWnd для требуемого окна можно с помощью функции CWnd::GetSafeWnd.
Мне осталось только привести код:
static UINT BASED__CODE cursors!] =
{
IDC_CUR_ERASE,
IDC_CUR_PEN,
IDC_CUR_SELECT,
IDC_CUR_BRUSH,
IDC_CUR_SPRAY,
IDC_CUR_PAINT,
IDC_CUR_LINE,
IDC_CUR_EYEDROP,
IDC_CUR_MAG,
IDC_CUR_RECT,
IDC__CUR_ROUND,
IDC_CUR_OVAL
};
...
void CMainFrame::OnViewPaneBar()
{
// Проверяем "видимость" панели инструментов
BOOL bVisibl.e = ( (m_wndPaneBar.GetStyle () & WS_VISIBLE) != 0) ;
// Проверяем "видимость" панели инструментов и устанавливаем либо
// текущий для выбранной кнопки курсор, либо "стандартную стрелку"
if(Invisible)
::SetClassLong(m_wndChiid.GetSafeHwnd(),
GCL_HCURSOR,
(LONG)AfxGetApp()->LoadCursor(cursors[m_nlndex]));
else
::SetClassLong(m_wndChild.GetSafeHwnd(),
GCL_HCURSOR,
(LONG)AfxGetApp()->LoadStandardCursor(IDC_ARROW));
}
void CMainFrame::OnPane(UINT nID)
{
// nID — идентификатор команды, ассоциированной с кнопкой
m_nlndex = nID — ID_PANEERASE;
// Порядковый номер кнопки
// Устанавливаем соответствующий кнопке курсор
::SetClassLong(m_wndChild.GetSafeHwnd(),
GCL_HCURSOR,
(LONG)AfxGetApp()->LoadCursor(cursors[mjilndex])) ;
}
Примечание
В окончательном варианте приложения в основную панель инструментов управления я добавил раскрывающийся список (элемент Combobox). Но эту часть мы рассмотрим, когда познакомимся с элементами управления.
Чтобы закончить с панелями управления, рассмотрим, каким образом можно работать со строкой состояния. Для нее библиотека MFC также содержит два класса: CStatusBar и CStatusBarCtrl.
Строка состояния (statusbar) — это панель управления, отображающая полосу, которую можно разделить на несколько областей для раздельного вывода в них текста или графической информации. Наиболее часто области используются как строка сообщений (например, для вывода расширенной информации об элементах меню или кнопках панели инструментов) и для отображения индикаторов состояния (например, индикаторов CAPS LOCK, NUM LOCK и INSERT). В качестве стандартного шрифта для строки состояния используется шрифт "MS Sans Serif высотой 10 пунктов. Изменение отображения строки состояния осуществляется автоматически благодаря механизму ON_UPDATE_COMMAND_UI: во время цикла простоя (idle time) она вызывает обработчик команды ON_UPDATE_COMMAND_UI с идентификаторами строк областей индикации. Для строки состояния класс CCmdUI, обеспечивающий правильное функционирование обработчика, поддерживает следующие функции: CCmdUI::Enable и CCmdUI::SetText.
Строка состояния может работать в двух режимах — упрощенном и стандартном. В первом режиме разделение этого окна на несколько областей невозможно и выводить в него можно только текстовую информацию. В стандартном режиме приложение может разбить окно строки состояния на несколько областей для раздельного вывода в них текстовой или графической информации. По умолчанию принимается, что первая область — "эластичная": ее длина определяется длиной области, не используемой всеми другими областями, которые к тому же выравнены по правому краю. Переход из одного режима в другой осуществляется при помощи сообщения SB_SIMPLE. Все настройки обычно проводятся перед первым отображением строки состояния на экране.
Класс CStatusBar отвечает в библиотеке MFC за реализацию строки состояния, для создания которой, как обычно, необходимы все те же два шага: создать объект класса и создать окно Windows. Рассмотрим их:
1. В нашем примере объект создается в классе CMainFrame:
CStatusBar::CStatusBar ()
Конструктор объекта, который определяет шрифт строки состояния и устанавливает его характеристики в значения, используемые по умолчанию.
class CMainFrame : public CFrameWnd
{
// Никто другой, кроме самого фрейма,
// не должен иметь доступа к строке состояния
protected:
CStatusBar m_wndStatusBar;
};
2. Теперь необходимо создать массив идентификаторов областей строки состояния:
// Для строки состояния необходимо определить
// массив идентификаторов ее областей
static UINT indicators[] =
{
ID_SEPARATOR,
ID_INDICATOR_NUM,
ID_INDICATOR_IDLE,
ID_INDICATOR_TIME
};
3. Само окно (строки состояния) создается главным фреймом в ответ на сообщение WM_CREATE:
int CMainFrame::OnCreate(LPCREATESTRUCT IpCreateStruct)
{
if(!m_wndStatusBar.Create(this) [[
!m_wndStatusBar.Setlndicators(indicators,
sizeof(indicators)/sizeof(UINT)))
{
TRACEO("Ошибка при создании строки состояния\n");
return -1;
}
...
}
Для создания строки состояния используется функция:
BOOL CStatusBar::Create (
CWnd *pParentWnd,
DWORD dwStyle = WS_CHILD | WS_VISIBLE | CBRS_BOTTOM,
UINT nID = AFX_IDW_STATUS_BAR)
Создает дочернее окно "Строка состояния", ассоциирует его с объектом Windows CStatusBar, инициализирует шрифт и устанавливает его размер в значение, заданное по умолчанию. Для строки состояния помимо стандартных стилей окна доступны еще следующие: CBRS_TOP, CBRS_BOTTOM, CBRS_NOALIGN и SBARS_SIZEGRIP. Обратите внимание на последний стиль, при котором правый нижний угол строки состояния принимает вид, показанный на рис. 11.10.
По умолчанию строка состояния располагается в нижней части родительского окна и имеет стиль SBARS_SIZEGRIP. Однако при желании вы можете расположить его и в верхней части, использовав стиль CBRS ТОР. Очевидно, что этот стиль нельзя комбинировать со стилем SBARS_SIZEGRIP, т. к., во-первых, такой элемент управления будет выглядеть очень странно, а во-вторых, он не будет работать так, как ожидает пользователь.
Рис. 11.10. Строка состояния, созданная со стилем SBARS_SIZEGRIP
Помимо функции Create, в Visual C++ 6.0 реализована еще одна, которая аналогична такой же функции класса CToolBar.
BOOL CStatusBar::CreateEx(
CWnd* pParentWnd,
DWORD dwCtrlStyle = 0,
DWORD dwStyle = WS_CHILD I WS_VISIBLE | CBRS_BOTTOM,
UINT nID = AFX_IDW_STATUS_BAR).
После того, как объект Windows "Строка состояния" создан, необходимо разбить ее на области. Используем для этого функцию
BOOL CStatusBar::Setlndicators (
const UINT *lpIDArray,
int nIDCount)
Устанавливает идентификатор каждой области в значение, заданное соответствующим элементом массива IplDArray, загружает для них строки ресурса и записывает соответствующие текстовые строки в эти области. Параметр nIDCount определяет число элементов в массиве идентификаторов.
Массив идентификаторов мы уже определили, но, кроме этого, нужны еще соответствующие строковые ресурсы (с целью экономии места привожу фрагмент из файла ресурсов, однако задавать их проще с помощью редактора ресурсов, выбрав создание String Table):
STRINGTABLE DISCARDABLE
BEGIN
ID_INDICATOR_TIME "00:00:00"
ID_INDICATOR_IDLE "•"
END
Естественно, существуют и предопределенные идентификаторы. Вот они:
ID_INDICATOR_EXT Расширенный индикатор выбора
ID_INDICATOR_CAPS Индикатор Caps Lock
ID_INDICATOR_NUM Индикатор Num Lock
ID_INDICATOR_SCRL Индикатор Scroll Lock
ID_INDICATOR_OVR Индикатор режима вставки
ID_INDICATOR_REC Индикатор режима записи
ID_INDICATOR_KANA Индикатор Капа Lock
ID_SEPARATOR Разделитель
Поскольку мы решили отображать в строке состояния текущее время и индикатор времени простоя, то необходимо каким-нибудь способом передать строке состояния эту информацию. Сделать это можно с помощью функции
BOOL CStatusBar::SetPaneText (
int nlndex,
LPCTSTR IpszNewText,
BOOL bOpdate = TRUE)
Записывает текст, на который указывает IpszNewText, в область строки состояния, заданную номером nlndex. Если параметр bUpdate равен TRUE, то после записи текста область помечается как поврежденная для ее немедленного обновления.
Осталось только получить индекс по известному идентификатору, что можно сделать с помощью метода
int CStatusBar::CommandToIndex( UINT nIDFind)
Индекс первого идентификатора равен 0.
Последняя функция класса CStatusBar, которую мы рассмотрим, служит для получения ссылки на объект CStatusBarCtrl:
CStatusBarCtrlS CStatusBar::GetStatusBarCtrl ().
Это позволит вам воспользоваться функциональными возможностями общего элемента управления Statusbar, включающим, в частности, настройку строки состояния.
В заключение осталось сказать, каким образом строка состояния обрабатывает команду ON_UPDATE_COMMAND_UI. Как обычно, обработчик этой команды вызывается во время цикла простоя последовательно для всех областей строки состояния. Доступными командами класса CCrndUI, отвечающего за обработку, являются Enable и SetText. Первая из них блокирует или разблокирует соответствующую область, при этом у заблокированных областей выключается отображение текста. Вторая функция позволяет изменить текст, который выводится в текущей области. Следует иметь в виду, что при выводе текста не происходит автоматического изменения размеров области.
// Каждый раз меняем символ в области строки состояния.
// Эта функция при отсутствии каких-либо действий
// вызывается примерно раз в секунду, а при манипуляциях мышью -
// практически постоянно
void CMainFrame::OnUpdateIndicatorIdle(CCmdUI* pCmdUI)
{
// Обратите внимание, что переменная bldle — статическая
static BOOL bldle = FALSE;
// Выводим текст
pCmdUI->SetText(bldle ? " ": "\x95");
bldle = !bldle;
}
Примечание
Идентификаторы строковых ресурсов не отображаются в окне мастера ClassWizard. Поэтому все необходимые действия следует производить вручную. Как это сделать, я уже показывал.
Практический пример вывода информации в строку состояния мы рассмотрим после знакомства с таймером. А мы на этом заканчиваем рассмотрение наиболее часто используемых панелей управления — панели инструментов и строки состояния — и переходим к достаточно полезному устройству — таймеру.
Таймер в Windows является устройством, которое периодически извещает приложение об истечении заданного интервала времени. Происходит это путем посылки сообщения WM_TIMER. Таймер присоединяется к приложению при помощи специальной функции основного оконного класса CWnd:
UINT CWnd::SetTimer{
UINT nIDEvent,
UINT nElapse,
void (CALLBACK EXPORT* IpfnTimer)(HWND, UINT, UINT, DWORD))
Первый параметр определяет идентификатор таймера, которым может быть любое целое положительное число. Второй параметр определяет 32:разрядное целое без знака, задающее интервал в миллисекундах, т. е. значение 7000 задает генерацию сообщений WM_TIMER один раз в 7 секунд. И, наконец, последний параметр позволяет определить функцию, которая будет получать сообщения таймера из вашего приложения.
Установить таймер лучше всего в обработчике сообщения WM_CREATE:
int CMainFrame::OnCreate(LPCREATESTRUCT IpCreateStruct)
{
...
// Создаем таймер для смены содержимого области строки состояния
// Таймер будет срабатывать каждую секунду
SetTimer(0, 1000, NULL);
return 0;
}
Остановить поток сообщений WM_TIMER можно в любой момент, вызвав функцию
BOOL CWnd::KillTimer( UINT nIDEvent)
Единственным параметром является идентификатор таймера, который необходимо "отключить".
В любом случае перед завершением работы приложения необходимо уничтожить все активные таймеры. И если вы не сделали этого раньше, то используйте сообщение WM_DESTROY, предварительно создав соответствующий обработчик:
void CMainFrame::OnDestroy()
{
// Пусть сначала библиотека сделает все необходимое
CMDIFrameWnd::OnDestroy();
//За собой всегда необходимо убирать — удаляем таймер
KillTimer(0);
}
Теперь приведу некоторые пояснения. Начнем с первого параметра функции SetTimer. Как вы уже поняли, можно определить несколько таймеров, каждый из которых будет "срабатывать" в "свое" время. Так, например, если необходимо определить два таймера, то это можно сделать следующим образом:
// Первый таймер срабатывает один раз в секунду
SetTimer(1, 1000, NULL);
// Второй таймер срабатывает один раз в минуту
SetTimer(2, 60000, NULL);
Система Windows не хранит в очереди сообщений несколько сообщений WM_TIMER, а объединяет все такие сообщения в одно. Для того чтобы их разделить, требуется соответствующим образом организовать обработчик сообщения. Создать его очень просто с помощью уже знакомого мастера ClassWizard, в поле Messages которого необходимо выделить сообщение WM_TIMER и нажать кнопку Add Function (рис. 11.11).
Код, помещаемый в этот обработчик, может быть примерно таким:
void CMainFrame::OnTimer(UINT nIDEvent)
{
switch(nIDEvent) i
case 1: ... // Обработка сообщения один раз в секунду
break;
case 2: ... // Обработка сообщения один раз в минуту
break;
}
...
}
Рис. 11.11. Определяем обработчик системного сообщения WM_TIMER
Примечание
Если необходимо установить новый интервал времени для существующего таймера, то необходимо сначала уничтожить его, а потом определить снова.
Теперь то, что касается второго параметра. Таймер в Windows имеет ту же разрешающую способность — 54,925 миллисекунды, что и встроенный таймер PC. Поэтому существуют следующие ограничения:
Учитывайте эти обстоятельства при работе с таймером.
При том способе, который использовали мы, сообщения WM_TIMER посылаются в обычную оконную процедуру, где его можно обработать. Третий параметр функции SetTimer позволяет переадресовать такие сообщения в специальную функцию, которая называется функцией "обратного вызова" и вызывается Windows. Она должна иметь определенные параметры:
void CALLBACK EXPORT TimerProc(
HWND hWnd, // Дескриптор окна, для которого вызывается SetTimer
UINT nMsg, // Сообщение - WM_TIMER
UINT nIDEvent // Идентификатор таймера
DWORD dwTime // Системное время
);
На этом мы закончим рассмотрение таймера — полученных сведений достаточно, чтобы отобразить в строке состояния текущее время.
void CMainFrame: :OnTimer (UINT nIDEvent}
{
// Отображаем текущее время в строке состояния"
m_wndStatusBar.SetPaneText(
m_wndStatusBar.CommandToIndex(ID_INDICATOR_TIME), //Получаем индекс
CTime::GetCurrentTime().Format("%H:%M:%S")); // Получаем текущее
// время с помощью
// класса CTime
// Передаем дальнейшую обработку в библиотеку
CMDIFrameWnd::OnTimer(nIDEvent);
}
Итак, что мы имеем на данный момент? Прежде всего дочернее окно, в которое можно организовать вывод. Затем панель инструментов рисования, позволяющая выбрать необходимый инструмент, что сигнализируется нажатой кнопкой и изменением формы курсора (рис. 11.12).
Рис. 11.12. Мы ввели панели инструментов и строку состояния
Осталось воспользоваться созданными элементами и научиться рисовать различные фигуры. Для этого нам понадобится организовать графический вывод и научиться работать с мышью. Определенный навык по рисованию в Windows мы уже приобрели, поэтому познакомимся с еще одним устройством — мышью.
Прежде всего поставим задачу: в зависимости от нажатой кнопки на панели инструментов рисования вычертить соответствующую фигуру в дочернем окне. Всю подготовительную работу для этого мы уже проделали. Поэтому сейчас рассмотрим основные шаги, необходимые для динамического рисования:
1. Когда пользователь щелкает левой кнопкой мыши, приложение должно запомнить координаты х и у из сообщения WM_LBUTTONDOWN.
2. Когда пользователь двигает мышь по экрану, не отпуская левую кнопку, приложение рисует соответствующую фигуру всякий раз, когда получает сообщение WM_MOUSEMOVE. Причем перед тем, как изобразить новую фигуру, необходимо стереть предыдущую. Размеры новой фигуры вычисляются посредством комбинирования запомненных координат, полученных в сообщении WM_LBUTTONDOWN, с текущими, передающимися в сообщении WM_MOUSEMOVE.
3. Когда пользователь отпускает левую кнопку мыши, генерируя тем самым сообщение WM_LBUTTONUP, приложение должно сформировать окончательное изображение соответствующей фигуры.
Очевидно, что описанный процесс довольно сильно упрощен, однако главное пока — это прочувствовать основные шаги. Еще раз внимательно прочитайте все необходимые этапы и переходите к деталям. Начнем с кода обработчика сообщения WM_LBUTTONDOWN.
Примечание
Надеюсь, что нет необходимости пояснять, где именно мы собираемся обрабатывать сообщения от мыши.
void CChildWnd::OnLButtonDown(UINT riFlags, CPoint point)
{
// Создаем контекст устройства для клиентской области окна
CClientDC dc(this);
// Определяем вспомогательные переменные
COLORREF color(RGB(255, 0, 0 ));
CRect IpRect(point.х, point.у, point.х + 12, point.у + 12);
CBrush brNew(m_color), *oldBrush;
// Устанавливаем текущий режим рисования
int oldMode = dc.SetROP2(R2_COPYPEN);
// Создаем новый карандаш
CPen newPen(PS_SOLID, 1, RGB(0, О, О)),//DOT
//и выбираем его в контекст устройства,
*oldPen = dc.SelectObject(SnewPen);
//а также выбираем кисть
oldBrush = dc.SelectObject(SbrNew);
// Стираем старое изображение
dc.Rectangle (m_rcSel);
//В зависимости от выбранного инструмента запоминаем координаты
// нажатия кнопки мыши в соответствующей переменной,
// если это необходимо
switch(m_nToolNum)
{
case ID_PANEEYEDROP:
clr = dc.GetPixel(point.x, point.y);
break; case ID_PANEERASE:
dc.FillSolidRect(SlpRect, clr);
break; case ID_PANESELECT:
m_rcSel.left = m_rcSel.right = point.x;
m_rcSel.top = mjrcSel.bottom = point.y;
break;
case ID_PANEOVAL:
case ID_PANERECT:
case ID_PANELINE:
m_rcSel = CRect(0, 0, 0, 0);
m_rcCur.left = m_rcCur.right = point.x;
m_rcCur.top = m_rcCur.bottom = point.y;
break;
case ID_PANEPEN:
m_pnDot.x = point.x;
m_pnDot.у = point.y;
dc.MoveTo(point.x, point.y);
dc.LineTo(point.x, point.y);
break;
case ID_PANEPAINT:
dc.SelectObject(SbrNew);
dc.ExtFloodFill(point.x, point.y,
dc.GetPixel(point.x, point.y),
FLOODFILLSURFACE);
break;
}
// Восстанавливаем старые параметры в контексте устройства
dc.SelectObject(oldPen) ;
dc.SelectObject(oidBrush);
dc.SetROP2(oldMode);
// Обязательно (!) организуем "захват" мыши
SetCapture();
// Устанавливаем переключатель начала рисования
redrawing = TRUE;
// Передаем управление библиотеке для корректной работы
CWnd::OnLButtonDown(nFlags, point);
}
Основным здесь является запоминание места события и "захват" мыши. С координатами все просто — они передаются в обработчик в качестве параметра point. Обращение к функции CWnd::SetCapture необходимо, поскольку в процессе рисования фигуры пользователь может вывести мышь за пределы окна, что в данном случае недопустимо.
После того как нажата левая кнопка мыши, приложение начинает улавливать все направляемые сообщения WM_MOUSEMOVE:
void CChildWnd::OnMouseMove(UINT nFlags, CPoint point)
{
// Проверяем, что нажата левая кнопка мыши
if(m_drawing == TRUE)
{
// Получаем контекст устройства
CClientDC dc(this);
// Устанавливаем текущий режим рисования
int oldMode = dc.SetROP2(R2_NOTXORPEN);
// Также определяем текущие переменные
CPen newPen(PS_SOLID, I, RGB(0, 0, 0))
, *oldPen = dc.SelectObject(SnewPen);
CPen pen(PSJDOT, 1, RGB(0, 0, 0));
CRect IpRect(point.x, point.y, point.x + 12, point.у + 12);
// Выбираем в контекст устройства предопределенную кисть
CBrush *oldBrush = (CBrush*)dc.SelectStockObject(NULL_BRUSH);
// Определяем выбранный инструмент рисования
switch(m_nToolNum)
{
case ID_PANEERASE:
dc.FillSolidRect(SlpRect, clr);
break;
case ID_PANEOVAL:
/'/ Стираем старое изображение
dc.Ellipse(mrcCur);
// Фиксируем новые координаты
m_rcCur.right = point.x;
m_rcCur.bottom = point.у;
// Рисуем эллипс
dc.Ellipse(m_rcCur);
break;
case ID_PANELINE:
// Стираем старое изображение
dc.MoveTo(m_rcCur.left, m_rcCur.top);
dc.LineTo(m_rcCur.right, m_rcCur.bottom);
m_rcCur.right = point.x;
m_rcCur.bottom = point.y;
// Рисуем новую линию
dc.MoveTo(m_rcCur.left, m_rcCur.top);
dc.LineTo(m_rcCur.right, m_rcCur.bottom);
break;
case ID_PANESELECT:
// В режиме выбора прямоугольник будем рисовать пунктирной
// линией, поэтому переопределяем текущий карандаш
dc.SelectObject(&pen);
// Стираем старое изображение
dc.Rectangle(m_rcSel);
m_rcSel.right = point.x;
m_rcSel.bottom = point.у;
// Рисуем новое изображение
dc.Rectangle(m_rcSel);
break;
case ID_PANERECT:
// Стираем старое изображение
dc.Rectangle(m_rcCur);
m_rcCur.right = point.x;
m_rcCur.bottom = point.y;
// Рисуем новый прямоугольник
dc.Rectangle(m_rcCur);
break;
case ID_PANEPEN:
dc.SetROP2(R2_COPYPEN);
dc.MoveTo(m_pnDot.x, m_pnDot.y);
m_pnDot.x = point.x;
m_pnDot.y = point.y;
dc.LineTo(point.x, point.y); break;
}
// Восстанавливаем карандаш, кисть и режим рисования
dc.SelectObject(oldPen);
dc.SelectObject(oldBrush);
dc.SetROP2(oldMode);
}
// Передаем управление библиотеке для корректной работы
CWnd::OnMouseMove(nFiags, point);
}
В первой строке этого обработчика используется один из возможных способов проверки, нажата ли левая кнопка мыши. Если кнопка не нажата — сообщение игнорируется. В противном случае обработчик получает контекст устройства, устанавливает параметры карандаша, кисти и режима рисования, определяет выбранный инструмент, стирает предыдущее изображение, запоминает текущие размеры фигуры, рисует ее и освобождает контекст устройства.
Новым здесь является установка текущего режима рисования R2_NOTXORPEN с помощью функции CDC::SetROP2, которую мы рассмотрели в главе 9.
Используемая при этом логическая операция выбрана потому, что в этом случае, рисуя старую фигуру непосредственно по исходному изображению, мы тем самым эффективно стираем ее. Затем фигура рисуется еще раз, но уже с новыми размерами. Весь этот процесс повторяется снова и снова (с невероятной скоростью), пока пользователь не отпустит левую кнопку мыши:
void CChildWnd::OnLButtonUp(UINT nFiags, CPoint point)
{
CClientDC dc(this);
int oldMode = dc.SetROP2(R2_COPYPEN);
CPen, newPen(PS_SOLID, I, RGB(0, 0, 0)),
*oldPen = dc.SelectObject(snewPen);
// Выбираем в контекст устройства предопределенную кисть
CBrush *oldBrush = (CBrush*)dc.SelectStockObject(NULL_BRUSH);
// Определяем выбранный инструмент рисования
switch(m_nToolNum)
{
case ID_PANEOVAL:
// Стираем старое изображение
dc.Ellipse (m_rcCur) ;
// Фиксируем новые координаты
m_rcCur.right = point.x;
m_rcCur.bottom = point.у;
// Рисуем эллипс
dc.Ellipse(m_rcCur);
break;
case ID_PANEPEN:
// Здесь нам ничего делать не надо, т. к. в момент отпускания
// левой кнопки мыши новая точка не появляется
break;
case ID_PANELINE:
// Стираем старое изображение
dc.MoveTo(m_rcCur.left, m_rcCur.top);
dc.LineTo(m_rcCur.right, m_rcCur.bottom);
// Фиксируем новые координаты
m_rcCur.right = point.x;
m_rcCur.bottom = point.у;
// Рисуем линию
dc.MoveTo(m_rcCur.left, m_rcCur.top);
dc.LineTo(m_rcCur.right, m_rcCur.bottom) ;
break/case ID_PANERECT:
// Аналогичная последовательность действий для прямоугольника:
dc.Rectangle(m_rcCur);
m_rcCur.right = point.x;
m_rcCur.bottom = point.у;
// Рисуем пунктирный прямоугольник
dc.SelectObject(SnewPen);
dc,Rectangle(m_rcCur);
break;
}
// Восстанавливаем карандаш, кисть и режим рисования
dc.SelectObject(oldPen) ;
dc.SelectObject(oldBrush);
dc.SetROP2(oldMode);
// Сбрасываем флаг рисования
m_drawing = FALSE;
// Освобождаем мышь
ReleaseCapture();
// Передаем управление библиотеке для корректной работы
CWnd::OnLButtonUp(nFlags, point);
}
Этот код проверяет выбранный инструмент рисования и рисует окончательное изображение, после чего сбрасывает флажок, говоря о том, что пользователь решил прекратить рисование, и освобождает "захваченную" мышь. Как видите, если рассматривать процесс последовательно, то он не кажется сложным. Поэтому просто еще раз повторю все шаги, чтобы вы четко запомнили их:
Очевидно, что рассмотренный пример далеко не закончен, фактически это только небольшая заготовка для дальнейшей работы, и я предлагаю вам в качестве упражнения довести ее до конца самостоятельно.