Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Visual C++ - расширенное программирование Создание файлового менеджера (часть 3). Панели инструментов.


Информационный Канал Subscribe.Ru


Visual C++ - расширенное программирование

Выпуск № 8
Cайт : SoftMaker.fatal.ru
Архив рассылки : SoftMaker.fatal.ru
Количество подписчиков : 45

В этом выпуске

От ведущего
MFC - простое и сложное
 
Создание файлового менеджера (часть 3)
  Панели инструментов (часть 1).
   Общая информация.
   
Плавающее и причаленное состояния.
    
Возможные проблемы.
   
Панели инструментов для файлового менеджера.
    
Главная панель инструментов.
    
Панель команд.
Подписчикам
Вопросы
Ответы

От ведущего


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

  Как всегда, Вы можете отправить свои пожелания, кликнув по этой ссылке.

Искренне Ваш. С уважением, Вахтуров Виктор.

MFC - простое и сложное [Создание файлового менеджера (часть 3)].

Часть 3.
Панели инструментов (часть 1).

  Итак, сегодня мы снова продолжим создание нашего файлового менеджера. На этот раз мы рассмотрим такую немаловажную в создании пользовательского интерфейса тему как панели инструментов. Им, собственно, и посвящена эта статья.

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

 Итак, сегодня вы узнаете :

  1. Несколько недокументированных классов MFC
  2. Несколько недокументированных функций MFC
  3. Принцип работы механизма перетаскивания панели инструментов.
  4. Неочевидные аспекты и возможные проблемы при использовании панелей инструментов.

  Прежде чем приступить к основной теме, как обычно, немного теории.

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

  Думаю, что для вас не будет новостью то, что в Windows нет стандартных окон, реализующих функциональность панелей инструментов в полной мере. Например, очень часто панели инструментов имеют возможность находиться в двух состояниях : причаленном (docking) и плавающем (float). Среди стандартных элементов управления и окон Windows нет ни одного, поддерживающего возможность перехода в плавающее состояние и обратно. Все это реализуется обычно механизмами библиотек классов.

  В данной статье мы рассмотрим принципы реализации панелей инструментов в библиотеке MFC.

Общая информация.

  Под панелью инструментов обычно понимают элемент пользовательского интерфейса так или иначе обладающий функциональностью стандартного элемента управления Windows - панели инструментов (ToolBar).

  Стандартный элемент управления ToolBar обладает возможностью автоматического выравнивания вдоль границ родительского окна, но он (как это было замечено выше) не имеет возможности перехода в плавающее состояние. Данная функциональность в MFC реализуется самой библиотекой.

  Если внимательно рассмотреть иерархию классов MFC, то можно выделить несколько из них, имеющих один и тот же базовый класс и обладающих одинаковой функциональностью в плане возможности быть 'приклеенными' к какому либо краю родительского окна-рамки и динамически изменять размеры при изменении размеров окна-контейнера (рамки). Это классы :

CToolBar, CReBar, CStatusBar, CDialogBar, COleResizeBar.

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

  Давайте вкратце рассмотрим принципы функционирования системы панелей (в данном случае под термином 'панели', понимаются элементы пользовательского интерфейса, созданные непосредственно как объекты классов, указанных выше, либо как объекты классов, унаследованных от вышеописсанных), реализуемой библиотекой MFC.

  Как уже было сказано, класс CControlBar реализует основную функциональность панелей, связанную с перетаскиванием. Но как же удается использовать один и тот же класс 'в роли' строки состояния, элемента ToolBar, элемента ReBar, и даже DialogBar - окна, созданного на основе шаблона диалога ?

Как известно, в Windows есть стандартные окна, обеспечивающие функции, например, ToolBаr-а, строки состояния. И они действительно используются здесь по своему прямому назначению. Весь фокус состоит в том, что класс CControlBar сам по себе не создает окна. Окна соответствующих оконных классов создаются в классах MFC, наследованных от CControlBar. Например, тот же самый CToolBar создает окно класса "ToolbarWindow32" (в MFC как "ToolbarWindow32" определен идентификатор TOOLBARCLASSNAME).

Таким образом, сам класс CControlBar - просто обертка, обеспечивающая нужную функциноальность.

Плавающее и причаленное состояния.

  Теперь мы выясним как же панелям MFC удается удерживаться у краев окна- рамки в 'причаленном' (docked) состоянии, и переходить в плавающее состояние (float).

  Если посмотреть в заголовочный файл MFC AFXRES.H, то можно наткнуться на группу идентификаторов, определенных для использования в качестве идентификаторов окон панелей MFC. Вот они :

 
 
 #define AFX_IDW_CONTROLBAR_FIRST        0xE800 
 #define AFX_IDW_CONTROLBAR_LAST         0xE8FF 
 
 #define AFX_IDW_TOOLBAR                 0xE800  // main Toolbar for window 
 #define AFX_IDW_STATUS_BAR              0xE801  // Status bar window 
 #define AFX_IDW_PREVIEW_BAR             0xE802  // PrintPreview Dialog Bar 
 #define AFX_IDW_RESIZE_BAR              0xE803  // OLE in-place resize bar 
 #define AFX_IDW_REBAR                   0xE804  // COMCTL32 "rebar" Bar 
 #define AFX_IDW_DIALOGBAR               0xE805  // CDialogBar 

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

В документации сказано просто - при изменении размеров окна-рамки, каркас приложения MFC шлет всем окнам, идентификаторы которых лежат в диапазоне от AFX_IDW_CONTROLBAR_FIRST до AFX_IDW_CONTROLBAR_LAST сообщение WM_SIZEPARENT, обрабатывая которое, панели (обычно их идентификаторы лежат в этом диапазоне) и изменяют свое местоположение, 'подстраиваясь' под новые размеры окна-рамки.

И действительно, в классе CControlBar можно увидеть функцию-член

LRESULT CControlBar::OnSizeParent(WPARAM, LPARAM lParam),
являющуюся обработчиком сообщения WM_SIZEPARENT, в которой и скрыт механизм изменения позиций панелей.

Но вернемся немного назад...

Тут у нас уже должны были зародиться первые небольшие подозрения... А действительно, почему здесь жестко определяются идентификаторы панелей, и почему только по одному для каждого вида ? Определен один идентификатор для панели ToolBar, один для DialogBar. Ведь панелей инструментов может быть много.

Подозрения не напрасны. В очередной раз мы видим, как MFC повернута к разработчику 'лицом'. Опишу возможные проблемы, которые могут возникнуть из-за незнания тонкостей внутренней реализации библиотеки в этом плане при разработке приложения.

Возможные проблемы.

  Проблема 1.

Выше было сказано, что каркас приложения MFC шлет всем окнам, идентификаторы которых лежат в диапазоне от AFX_IDW_CONTROLBAR_FIRST до AFX_IDW_CONTROLBAR_LAST сообщение WM_SIZEPARENT, обрабатывая которое, панели и изменяют свое местоположение и размеры. Так вот, догадайтесь, что будет, если вы создадите панель с идентификатором, лежащим вне данного диапазона. Правильно - ничего не будет. Панель просто не будет корректно работать. Это также накладывает ограничение на максимальное количество панелей в одной рамке.

  Проблема 2.

AFX_IDW_CONTROLBAR_FIRST и AFX_IDW_CONTROLBAR_LAST - разность между ними равна 256. Разве 256 панелей это мало ? Оказывается, помимо этого ограничения есть еще более жестокие правила, определяемые библиотекой. Вспомните, в приложениях, использующих архитектуру документ/облик обычно доступен режим предварительного просмотра документа перед печатью. При инициализации этого режима состояние видимости всех панелей, существующих в даный момент в пределах окна-рамки изменяется на невидимое. При этом состояние видимости/невидимости сохраняется библиотекой, а при выходе из режима предварительного просмотра восстанавливается. Так вот, сохраняются состояния не всех панелей, а только тех, идентификаторы которых лежат в диапазоне от AFX_IDW_CONTROLBAR_FIRST до AFX_IDW_CONTROLBAR_FIRST + 32. Это происходит из-за того, что состояния панелей хранятся как биты в переменной - двойном слове (похоже, разработчики Microsoft стали совсем жадны по отношению к расходу памяти, а, может, им просто было лень реализовать сохранение состояния всех панелей).
Это означает, что если вы создадите панель инструментов с идентификатором AFX_IDW_CONTROLBAR_LAST - 1, 'спрячите' ее, затем войдете и выйдете из режима preview, то панель отобразиться, хотя должна была бы быть скрыта.

Вы можете встретить и другие неувязки, но продолжим тему далее...

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

Хочу обратить ваше внимание на одну вещь, которая привычна настолько, что совсем не бросается в глаза. Вспомните, обычно панели инструментов занимают не всю ширину или высоту окна-рамки. Обычно они гораздо меньше. При 'прилипании' панели к краю рамки оставшееся пространство заполняет...
Что ?
Если посмотреть свойства окна, заполняющего свободное пространство при помощи утилиты Spy из комплекта поставки Visual Studio, то можно обнаружить, что это окно является родительским для панели инструментов. Но самое удивительное то, что это тоже панель !

Это специальные панели, функциональность которых реализуется классом CDockBar. В документации этот класс упоминается мало. Изучить его можно по исходному коду MFC. Эти окна предназначены специально для того, чтобы быть контейнерами для других панелей.
Вызов функции CFrameWnd::EnableDocking служит, в частности для создания этих служебных окон в соответствии с параметром, переданным в качестве аргумента.



  А теперь вспомним про то, что панели инструментов могут переходить в 'плавающее' состояние. Снова поясню только основные принципы реализации этого механизма, так как подробно изучить его работу можно по исходному коду MFC.

В файле afxpriv.h содержится определение, а в файле bardock.cpp - реализация класса CMiniDockFrameWnd. Это класс окна-рамки, являющейся контейнером элемента управления, реализующего панель инструментов, в плавающем состоянии.

То есть, в 'прилипшем' состоянии панель инструментов имеет в качестве родительского окно специальной панели, а в 'плавающем' состоянии - окно-рамку.

Класс CMiniDockFrameWnd является оболочкой окна-рамки, имеющей расширенный стиль окна WS_EX_TOOLWINDOW (инструментальное окно - имеет узкий заголовок) и используется исключительно для вышеописанных целей. Этот класс, как и класс CDockBar недокументирован. Возможно, его реализация и интерфейс впоследствии будут меняться.

  Последним, что мы рассмотрим относительно перемещения панелей из пристыкованного в 'плавающее' состояние будет класс CDockContext. В моем MSDN нет даже намека на упоминания о нем.

Этот класс реализует, по-сути, само 'перетаскивание' панелей, точнее - их контуров. Чтобы не утомлять вас, скажу только, что идея перетаскивания панелей в MFC такая состоит в блокировании всего графического вывода на экран функцией LockWindowUpdate.
В функции CDockContext::InitLoop() происходит получение указателя на временный объект класса CWnd, содержащего дескриптор окна рабочего стола (сделать это можно при помощи функции CWnd::GetDesktopWindow). И затем для него вызывается функция LockWindowUpdate. Далее происходит захват мыши (SetCapture) и перемещение контура прямоугольника панели вслед за указателем мыши.

При отпускании кнопки мыши, происходит разблокирование вывода на экран, вызывается ReleaseCapture и панель инструментов располагается в новую позицию (в плавающее или пристыкованное состояние). На самом деле это только общее описание механизмов работы. Но, уверяю вас, сами механизмы не столь уж и сложны.

Панели инструментов для файлового менеджера.

  А теперь давайте перейдем к практическим изысканиям - добавим панели инструментов в наш файловый менеджер. Снова будем ориентироваться на классический пример - интерфейс программы WindowsCommander.

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

Внизу - тоже две панели. В самом низу - панель основных команд (View, Edit, Copy, и.т.д.). Над ней - панель с выпадающим списком истории команд и полем, содержащим путь к директории, которвя отображается на активной файловой панели.

Все остальное относится непосредственно к файловым панелям.

А теперь давайте быстренько это все реализуем.

Все панели мы сделаем без возможности установки в плавающее (float) состояние, так как для файлового менеджера плавающие панели - это излишество.

Для каждой панели мы создадим отдельный класс, инкапсулирующий всю функциональность панели.

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

Главная панель инструментов.

  Создадим класс главной панели инструментов (им мы заменим панель, созданную по-умолчанию). Назовем этот класс CMainToolBar. Он должен быть наследником класса CToolBar.

Сделаем это.

Создадим ресурс панели инструментов (изображения иконок я позаимствовал из того же WindowsCommander).

Затем создадим сам класс панели.
Сразу создать визардом класс не удасться, так как в списке родительских классов класса CToolBar нет. Поэтому мы создадим класс, наследованный от класса CToolBarCtrl, а затем в файлах, сгенерированных инструментом ClassWizard проведем контекстную замену фразы CToolBarCtrl на CToolBar.

В этом классе мы добавим всего одну функцию-член класса :

BOOL CMainToolBar::CreateEx(CWnd *pParentWnd)

В ней будет производиться создание окна панели инструментов. Здесь все просто - мы лишь вызовем унаследованный метод с нужными парамеирами и установим размеры кнопок панели и ее высоту :

 
 
BOOL CMainToolBar::CreateEx(CWnd *pParentWnd) 
{ 
 if (!CToolBar::CreateEx(pParentWnd, TBSTYLE_FLAT, 
  WS_CHILD | WS_VISIBLE | CBRS_TOP | 
  CBRS_TOOLTIPS | CBRS_SIZE_DYNAMIC, 
  rcNullRect, AFX_IDW_TOOLBAR) || 
  !LoadToolBar(IDR_MAINFRAME)) 
  return FALSE; 
 
 SetSizes(CSize(26, 26), CSize(17, 19)); 
 
 SetHeight(32); 
 
 return TRUE; 
} 

Обратите внимание, что при создании панели не установлен стиль CBRS_GRIPPER - мы не хотим отображения зоны захвата на нашей панели.

Здесь используется константную переменную rcNullRect. В нашем проекте нам будут требоваться некоторые константы. Для них создадим файл объявления и реализации constants.h и constants.cpp. Файл constants.h подключим в stdafx.h

В описании класса CMainFrame и в функции CMainFrame::OnCreate произведем некоторые изменения :
Удалим вызовы функций, сгенерированных при создании проекта :

 
 
 m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY); 
 EnableDocking(CBRS_ALIGN_ANY); 
 DockControlBar(&m_wndToolBar); 

теперь панель не будет плавающей никогда.

На панель инструментов я пока поместил несколько кнопок и сделал для них функции - обработчики в классе CMainFrame. Обработчики пустые - не будем на этом останавливаться.

Панель команд.

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

У нас должна получиться панель, содержащая на кнопках только текст, и изменяющая размер этих кнопок в соответствии с шириной главного окна.

Снова начнем с того, что создадим ресурс панели инструментов. Идентификатор ресурса я задал IDR_COMMAND_TOOLBAR. Добавим в ресурс кнопки. Между всеми кнопками поставим разделители (SEPARATOR). На самих кнопках можно ничего не рисовать - далее вы поймете почему мы этого не делали.

Опять сделаем то же самое что и раньше - создадим новый класс, наследованный от класса CToolBarCtrl (назовем его CCommandToolBar), заменим CToolBarCtrl на CToolBar.

Добавим функцию-член класса CCommandToolBar::CreateEx(CWnd *pParentWnd) :

 
 
BOOL CCommandToolBar::CreateEx(CWnd *pParentWnd) 
{ 
 if (!CToolBar::CreateEx(pParentWnd, TBSTYLE_FLAT | TBSTYLE_LIST, 
  WS_CHILD | WS_VISIBLE | CBRS_ALIGN_BOTTOM | 
  CBRS_TOOLTIPS | CBRS_SIZE_DYNAMIC, 
  rcNullRect, AFX_IDW_TOOLBAR + 256 - 1) || 
  !LoadToolBar(IDR_COMMAND_TOOLBAR)) 
  return FALSE; 
 
 for(i = 0; i < GetCount(); i++) 
 { 
  TBBUTTON tbb; 
 
  GetToolBarCtrl().GetButton(i, &tbb); 
 
  if(!(tbb.fsStyle & TBBS_SEPARATOR) && tbb.idCommand) 
  { 
   CString strResourceText; 
 
   if(strResourceText.LoadString(tbb.idCommand)) 
   { 
    CString strMessageText; 
 
    if(AfxExtractSubString(strMessageText, (LPCTSTR) strResourceText, 0)) 
     SetButtonText(i, (LPCTSTR) strMessageText); 
   } 
  } 
 } 
 
 return TRUE; 
} 

Как вы заметили, панель будет внизу (стиль CBRS_BOTTOM). Так как в нашей программе не предусмотрен режим preview, то смело можно создать панель с идентификатором AFX_IDW_TOOLBAR + 256 - 1. Для помещения на кнопки текста, мы загружаем его из таблицы строк и ищем нужную подстроку с помощью функции, описанной в одном из прошлых номеров рассылки (AfxExtractSubString).

Продекларируем переменную m_wndCommandToolBar класса CCommandToolBar как переменную-компоненту класса CMainFrame, в CMainFrame::OnCreate напишем код для создания окна панели (он прост, его сдесь я не привожу).

Скомпилируем проект и посмотрим что получилось.

На кнопках видны пустые изображения.

И вот тут сделаем небольшой финт - нам не нужны картинки на кнопках, а значит, не нужен и ресурс изображения для панели инструментов. Давайте просто удалим ресурс. Найдем в файле Resource.h строку наподобие :

 
 
 IDR_COMMAND_TOOLBAR     BITMAP  DISCARDABLE     "res\\command_.bmp" 

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

Снова скомпилируем проект и запустим приложение.

Возникает ошибка !!! Это происходит из-за того, что вызов LoadToolBar(IDR_COMMAND_TOOLBAR) терпит неудачу.

Да, все дело в том, что если ресурс изображения для панели инструментов не найден в исполняемом файле, то функция LoadToolBar возвращает FALSE.

Дорогие разработчики MFC в очередной раз позаботились о гибкости своего программного продукта. Они, наверно не подозревали, что тулбар может быть без изображений на кнопках.

Хорошо - позаботимся об этом сами...

Посмотрим код функции CToolBar::LoadToolbar, возьмем оттуда необходимый код и обойдемся без вызова этой функции.

Теперь имеем реализацию функции :

 
 
BOOL CCommandToolBar::CreateEx(CWnd *pParentWnd) 
{ 
 if (!CToolBar::CreateEx(pParentWnd, TBSTYLE_FLAT | TBSTYLE_LIST, 
  WS_CHILD | WS_VISIBLE | CBRS_ALIGN_BOTTOM | 
  CBRS_TOOLTIPS | CBRS_SIZE_DYNAMIC, 
  rcNullRect, AFX_IDW_TOOLBAR + 256 - 1)) 
  return FALSE; 
 
 LPCTSTR lpszResourceName = MAKEINTRESOURCE(IDR_COMMAND_TOOLBAR); 
 
 HINSTANCE hInst = AfxFindResourceHandle(lpszResourceName, RT_TOOLBAR); 
  
 HRSRC hRsrc = ::FindResource(hInst, lpszResourceName, RT_TOOLBAR); 
  
 if(hRsrc == NULL) 
  return FALSE; 
 
 HGLOBAL hGlobal = LoadResource(hInst, hRsrc); 
 
 if(hGlobal == NULL) 
  return FALSE; 
 
 CToolBarData* pData = (CToolBarData*) LockResource(hGlobal); 
 
 if(pData == NULL) 
  return FALSE; 
 
 ASSERT(pData->wVersion == 1); 
 
 UINT* pItems = new UINT[pData->wItemCount]; 
 
 for (int i = 0; i < pData->wItemCount; i++) 
  pItems[i] = pData->items() [i]; 
 
 BOOL bResult = SetButtons(pItems, pData->wItemCount); 
 
 delete[] pItems; 
 
 if(!bResult) 
  return FALSE; 
 
 m_sizeImage = CSize(0, 0); 
 
 VERIFY(SendMessage(TB_SETBITMAPSIZE, 0, 0)); 
 
 GetToolBarCtrl().SetDrawTextFlags(DT_CENTER | DT_VCENTER | DT_END_ELLIPSIS, DT_CENTER | DT_VCENTER | DT_END_ELLIPSIS); 
 
 for(i = 0; i < GetCount(); i++) 
 { 
  TBBUTTON tbb; 
 
  GetToolBarCtrl().GetButton(i, &tbb); 
 
  if(!(tbb.fsStyle & TBBS_SEPARATOR) && tbb.idCommand) 
  { 
   CString strResourceText; 
 
   if(strResourceText.LoadString(tbb.idCommand)) 
   { 
    CString strMessageText; 
 
    if(AfxExtractSubString(strMessageText, (LPCTSTR) strResourceText, 0)) 
     SetButtonText(i, (LPCTSTR) strMessageText); 
   } 
  } 
 } 
 
 CRect rcItem; 
 
 GetItemRect(0, &rcItem); 
 
 m_sizeButton.cx = 0; 
 m_sizeButton.cy = rcItem.Height(); 
 
 SetHeight(rcItem.Height() + 6); 
 
 SetBorders(0, 3, 0, 0); 
 
 return TRUE; 
} 

Заметьте !
Здесь используется один неизвестный, быть может, вам тип данных - структура CToolBarData.

Что же она собой представляет ?

 
 
struct CToolBarData 
{ 
 WORD wVersion; 
 WORD wWidth; 
 WORD wHeight; 
 WORD wItemCount; 
 
 // WORD aItems[wItemCount] 
 
 WORD* items() 
  { return (WORD*)(this+1); } 
}; 

Самое обидное обстоятельство заключается в том, что эта структура определена только в файле bartool.cpp, а это значит что для того, чтобы ее использовать, придется перенести объявление этой структуры в наш проект. И здесь мы видим искреннюю заботу со сторомы Microsoft о своих коллегах - программистах.

Для объявления глобальных типов данных заведем файл globdef.h И там объявим эту структуру. Файл globdef.h подключим в stdafx.h

  Все здорово, кроме одного обстоятельства - кнопки на панели имеют всегда одну и ту же ширину, и размеров не меняют. А нам хотелось бы сделать так, чтобы при изменении размеров главного окна, кнопки располагались во сю ширину панели.

Напрашивается простое решение - просто выставлять кнопкам нужную ширину. Но, как это ни прискорбно, для стандартного элемента управления Windows ToolBar (который и лежит в основе нашей панели инструментов) можно установить ширину всех кнопок сразу. Он не допускает установки ширины для отдельной кнопки.

Как мы помним, между всеми кнопками нашей панели присутспвуют разделители. Это, по сути, тоже кнопки, но их нельзя нажать. К тому же для них уже можно установить ширину. Вот за счет этой особенности мы и будем 'подгонять' положение кнопок командной панели.

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

По этой причине мы реализуем эту функцию сами.

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

 
 
struct V_DLLVERSIONINFO 
{ 
 DWORD cbSize; 
 DWORD dwMajorVersion;                   // Major version 
 DWORD dwMinorVersion;                   // Minor version 
 DWORD dwBuildNumber;                    // Build number 
 DWORD dwPlatformID;                     // DLLVER_PLATFORM_* 
}; 

 
 
typedef HRESULT (CALLBACK* V_DLLGETVERSIONPROC) (V_DLLVERSIONINFO *); 

 Сама функция выглядит так :

 
 
static int _vComCtlVersion = -1; // Эта переменная нужна для кэширования результата. 
 
DWORD AFXAPI _VGetComCtlVersion() 
{ 
 if (_vComCtlVersion != -1) 
  return _vComCtlVersion; 
 
 HINSTANCE hInst = ::GetModuleHandleA("COMCTL32.DLL"); 
 
 ASSERT(hInst != NULL); 
 
 V_DLLGETVERSIONPROC pfn; 
 
 pfn = (V_DLLGETVERSIONPROC) GetProcAddress(hInst, "DllGetVersion"); 
 
 DWORD dwVersion = VERSION_WIN4; 
 
 if(pfn != NULL) 
 { 
  V_DLLVERSIONINFO dvi; 
 
  memset(&dvi, 0, sizeof(dvi)); 
  dvi.cbSize = sizeof(dvi); 
   
  HRESULT hr = (*pfn)(&dvi); 
   
  if (SUCCEEDED(hr)) 
  { 
   ASSERT(dvi.dwMajorVersion <= 0xFFFF); 
   ASSERT(dvi.dwMinorVersion <= 0xFFFF); 
   
   dwVersion = MAKELONG(dvi.dwMinorVersion, dvi.dwMajorVersion); 
  } 
 } 
 
 _vComCtlVersion = dwVersion; 
  
 return dwVersion; 
} 

И вот теперь мы, наконец, можем реализовать алгоритм размещения кнопок на панели. В обработчике сообщения WM_WINDOWPOSCHANGING до вызова обработчика базового класса сделаем всю работу :

 
 
void CCommandToolBar::OnWindowPosChanging(WINDOWPOS FAR* lpwndpos)  
{ 
 if(!(lpwndpos->flags & SWP_NOSIZE)) 
 { 
  DWORD dwSepWidth; 
 
  if((GetStyle() & TBSTYLE_FLAT) || _VGetComCtlVersion() == VERSION_IE4) 
   dwSepWidth = 6; 
  else 
   dwSepWidth = 8; 
 
  CRect rcItem; 
 
  GetItemRect(0, &rcItem); 
 
  int nBtn = (lpwndpos->cx - dwSepWidth * 6) / 7; 
 
  if(nBtn > 0) 
  { 
   int nDelta = lpwndpos->cx - dwSepWidth * 6 - nBtn * 7; 
 
   TBBUTTON tbb; 
 
   for(int i = 0; i < 13; i++) 
   { 
    if(GetToolBarCtrl().GetButton(i, &tbb)) 
    { 
     if(tbb.fsStyle & TBBS_SEPARATOR) 
     { 
      if(nDelta > 0) 
      { 
       tbb.iBitmap = dwSepWidth + 1; 
       nDelta -= 1; 
      } 
      else 
       tbb.iBitmap = dwSepWidth; 
 
      SetButtonInfo(i, tbb.idCommand, TBBS_SEPARATOR, tbb.iBitmap); 
     } 
    } 
   } 
 
   VERIFY(SendMessage(TB_SETBUTTONSIZE, 0, MAKELONG(nBtn, rcItem.Height()))); 
  } 
 } 
 
 CToolBar::OnWindowPosChanging(lpwndpos); 
} 

Алгоритм размещения прост и интуитивно понятен.

А вот то, что в итоге получилось :

http://SoftMaker.fatal.ru/projects/vcmd/shots.htm

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

    На сегодня Все.

Автор статьи : Вахтуров Виктор.  

Исходный код проекта, рассматриваемого в статье вы можете найти на сайте рассылки SoftMaker.fatal.ru на главной странице проекта.

Подписчикам

  Дабы заранее разрешить возможные недоразумения, прошу Вас помнить, что Вопросы публикуются в рассылке только один раз. Поэтому, если Вам не ответили в этой рассылке, или ваш вопрос не был опубликован, пришлите его еще раз. Не стоит отвечать на вопрос, который был задан в предыдущей рассылке (за исключением случая, когда он снова опубликован в этой).
Для того, чтобы задать свой вопрос, пришлите письмо по этой ссылке.
Для того, чтобы ответить на вопрос, надо кликнуть по ссылке "ответить", расположенной под текстом вопроса.
Обо всех ошибках, замеченных в данной рассылке, а также своих предложениях и пожеланиях пишите сюда.

Вопросы

  Для того, чтобы задать свой вопрос, кликните этой ссылке.
Вы можете задавать любые вопросы, касающиеся программирования на языке C и C++. Это могут быть вопросы касающиеся как конструкций языка, применения библиотек классов, шаблонов (таких как MFC или STL), использования компиляторов, так и самой философии программирования на C или C++. Здесь нет ограничений - спрашивайте и получайте ответы.

  К сожалению, вопросов сегодня нет.

Ответы

  В данной рассылке нет ответов, так как не было задано вопросов в предыдущей.
Задавайте свои вопросы, и Вы обязятельно получите ответ.

Всего доброго. До встречи в следующей рассылке.

Любой из материалов, опубликованных в этой рассылке, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.


http://subscribe.ru/
E-mail: ask@subscribe.ru
Отписаться
Убрать рекламу

В избранное