ПРОГРАММИРОВАНИЕ НА VISUAL C++

Выпуск No. 19 от 15 октября 2000 г.

Приветствую всех!

/ / / / WinAPI & MFC / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /

Тонкая настройка панели инструментов
Задание внешнего вида кнопок и размещение отличных от кнопок элементов

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

Но что делать, если внешний вид приложений MS Office или VC IDE не дает вам спокойно спать? Если вам просто необходимо идти в ногу с конкурирующими программами, где с этим как раз все в порядке? Если вы решили, что размещение дополнительных элементов управления на панели инструментов сразу сделает интерфейс гораздо понятнее и удобнее ?..

Тогда нужно просто взять и сделать это. Да, для этого придется приложить некоторые усилия. Так что любители "рисовать приложения" могут спокойно об этом забыть, а еще лучше - перейти на С++Builder или Delphi, где их способности к рисованию смогут реализоваться в полной мере. (Ладно, не обижайтесь.) Самое большое преимущество Visual C++ в том, что при желании с его помощью можно сделать практически ВСЕ, ЧТО УГОДНО. Главное, знать как. Не пугайтесь - от вас потребуется совсем немного.

Но для начала давайте выясним, можно ли просто изменить стандартный вид тулбара (так для краткости я буду именовать панель инструментов). Например, сделать тулбар а'ля WinZip - большие иконки, с подписями...

Итак, какие настройки нам доступны практически сразу же? - Конечно же, стили. Оперируя стилями, мы тоже можем повлиять на внешний вид панели. Вот самые интересные из них:

ССS_TOP и ССS_BOTTOM - задают размещение тулбара - вверху или внизу окна. (CСS_TOP используется по умолчанию.)
TBSTYLE_FLAT - Делает кнопки плоскими. (Используется по умолчанию.)
TBSTYLE_LIST - Используется для вывода текста справа от кнопки. (Помните пункт "выборочно текст справа" в IE4 и Outlook?)

Остальные стили вы можете, в принципе, посмотреть сами - они задают некоторые другие параметры. Но нам сейчас нужно другое: сделать кнопки большими и с подписями. Выполнить это можно с помощью членов классов CToolBar и CToolBarCtrl, а если вы работаете не в MFC, то придется работать с сообщениями.
Вышеупомянутые классы MFC располагают богатым набором функций для всевозможной настройки тулбара. Функции CToolBarCtrl::SetButtonSize(), CToolBarCtrl::SetBitmapSize(), CToolBarCtrl::SetButtonWidth(), CToolBar::SetButtonText(), CToolBar::SetHeight() говорят сами за себя. Именно они понадобятся нам для реализации такого тулбара, как в WinZip. Без MFC это можно сделать, послав тулбару сообщения TB_SETBUTTONSIZE, TB_SETBITMAPSIZE, TB_SETBUTTONWIDTH, TB_ADDSTRING и др. Кстати, многие функции классов MFC делают фактически то же самое - просто посылают соответствующие сообщения.

Можете сами поиграться с различными настройками. Если бы я взялся здесь подробно описывать ВСЕ возможности, и как каждую реализовать, то эта статья растянулась бы минимум на  дюжину выпусков. Цель данной публикации - дать вам общее представление, направление, так сказать. Я не думаю, что обязательно нужно все разжевывать и, как это часто происходит, сводить мысль к "нажмите туда, выберите то". Я хочу, чтобы вы учились думать сами.
Психологи приводят следующую статистику: когда человек просто прочитывает информацию, то запоминает не более 50% из прочитанного. Если еще и записывает - не более 70%.
Но если человеку информация досталась с некоторым трудом, если он ее нашел сам - то запоминается более 90%! Сделайте выводы.

Теперь пришла пора перейти к самому интересному вопросу, который я рассмотрю гораздо подробнее, а именно - как на тулбаре разместить что-либо, не являющееся кнопкой.
Я буду рассказывать как это делается в MFC, хотя понять, как это сделать в API после ознакомления с текстом не составит никакого труда.

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

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

CONTROL_INDEX - порядковый номер кнопки-содержателя контрола;
CONTROL_WIDTH - ширина контрола;
CONTROL_HEIGHT - высота контрола. 

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

class CAdvBar : public CToolBar
{
   ...
   protected :
     CComboBox m_ComboBox;  // наш контрол

     void Initialize ( );  // ф-ция инициализации
  
...
}

Функция инициализации превращает нашу кнопку в сепаратор и устанавливает ему нужную ширину, после чего создает и позиционирует контрол на его место. Пример для комбобокса:

void CAdvBar::Initialize()
{
  CRect rc; 

  SetHeight (CONTROL_HEIGHT + 8); // устанавливаем нужную толщину тулбара

  // превращаем кнопку в сепаратор нужных размеров
  // (IDC_COMBO - ID кнопки)
  SetButtonInfo (CONTROL_INDEX, IDC_COMBO, TBBS_SEPARATOR, CONTROL_WIDTH);

  GetItemRect (CONTROL_INDEX, rc);  // получаем координаты сепаратора

  // теперь создаем комбобокс.
  // не забывайте, что для этого контрола при создании указывается
  // его высота В РАЗВЕРНУТОМ ВИДЕ, именно поэтому
  // мы к ней прибавляем еще 100 пикселов.

  rc.bottom += 100;  < BR>
  m_ComboBox.Create( WS_CHILD | WS_VISIBLE | WS_TABSTOP |
                     WS_VSCROLL | CBS_AUTOHSCROLL | CBS_DROPDOWN, 
                     rc, this, IDC_COMBO);

  // настраиваем контрол
  m_ComboBox.AddString( "Строка 1" );
  m_ComboBox.AddString( "Строка 2" );
  m_ComboBox.SetCurSel(0);
}

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

Вот, теперь все, что осталось сделать - в CMainFrame::OnCreate() после создания тулбара вызвать Initialize().

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

/ / / / ВОПРОС-ОТВЕТ / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /

Из ответов, содержащих одинаковое решение, я выбрал лучшие (с моей точки зрения). Большая просьба: не нужно присылать мне целые проекты и большие куски кода. Лучше объясните ваше решение словами.

Q| Как просканировать LAN на предмет создания поименного списка машин, чтобы затем можно было изпользовать результат в ListBox'e? Пробовал использовать для этой цели SHBrowseForFolder( ) и связанные ф-ции с установленным флагом CIDL_NETWORK, но открывающееся окно для выбора узла и необходимость "раскрывать плюсики" в локальных группах меня не устраивает. Если можно, в API без MFC. - DevXarT

|A1 Необходимо подключить заголовочные файлы
#include <lmcons.h>
#include <lmserver.h>
#include <lmerr.h>
и библиотеку NetAPI
в диалоге "Project Settings" на странице "Link" в поле "Object/library modules:" вписать netapi32.lib

Далее, например так:


void CTestDlg::RefreshHostList()
{
   LPSERVER_INFO_100  pServerEnum;
   DWORD   dwResult, dwRead, dwTotal;

   dwResult = ::NetServerEnum( NULL, 100, 
                         (BYTE**) &pServerEnum,
                          -1, &dwRead, &dwTotal,
                         SV_TYPE_ALL, NULL, 0 );
                         
   if ( dwResult == NERR_Success ){
      for ( DWORD i=0; i<dwRead; i++ )
         m_wndListBox.AddString( CString( (LPCWSTR) pServerEnum[ i ].sv100_name ) );
   }
}

Причем, используя SERVER_INFO_101 можно получить более подробную информацию (например тип и версию операционной системы), а комбинируя различные флаги в седьмом параметре NetServerEnum можно выбирать компьютеры по определенному признаку (например, только SQL-серверы или Terminal Server).
Недостаток такого способа в том, что он получает список хостов от мастер-браузера. Таким образом в этом списке присутствуют только хосты, в настоящий момент присутствующие в сети. А поскольку мастер-браузер обновляет эту информацию с периодичностью около 15 минут, список может быть не актуален на данный момент. Кроме того в нем отсутствуют "скрытые" хосты (например командой net config server /hidden:yes ).

А вот мой вопрос... Многие утилиты Windows NT Server (regedt32, Windows NT Diagnostics, Event Viewer, Perfomance Monitor, Shutdown Manager) имеют диалог "Select Computer". Наверняка он в системе "стандартный". Что-то типа SHBrowseForFolder. Может кто знает, где его найти, как вызвать?

- Андрей

|A2 Ответ кроется в группе функций с префиксом ::WNetХХХ:
WNetOpenEnum(RESOURCE_CONTEXT, RESOURCETYPE_ANY, 0, NULL, &handleEnum) - открыть нумерацию локальных доменов верхнего уровня (включая узел Entire Network, эквиалентно выбору Network Neighbourhoods в Explorer), четвертый параметр имеет тип LPNETRESOURCE, где NETRESOURCE - структура, описывающая узел;
WNetOpenEnum(RESOURCEUSAGE_CONTAINER, RESOURCETYPE_ANY, 0, pNetCurrent, &handleEnum) - открыть нумерацию ресурсов узла (шаринги, локальные домены следующего уровня, принтеры, см. флажки в МСДН);
WNetEnumResource(handleEnum, &dwCounter, pNetResource, &dwBufferSize) - получить список ресурсов узла, handleEnum получается предыдущей ф-цией.

... я бы не советовал заполнять листбокс всеми именами машин за раз, процесс этот может быть довольно длительным во времени (порядка минуты); если сеть достаточно велика (от 30-50 машин), лучше использовать дерево.

- James Nicolas Borodco

|A3 Список машин, их имена, имена провайдера, тип подключения и т.д. имеется в реестре. Смотри ключи:
HKEY_CURRENT_USER\Network
HKEY_CURRENT_USER\Network\Recent
Функции для работы с реестром имеются, ищи в MSDN Library, например, RegOpenKeyEx, RegQueryInfoKey. Там же в MSDN Library имеются и примеры работы с реестром (в обзорах, конечно).

- Виктор Никитенко

К сведению: Не во всех системах есть такие ключи реестра. В Windows NT/2000, например, их нет.

Хочу поблагодарить всех, кто откликнулся и прислал ответ на вопрос. Если хотите чтобы ваш ответ был опубликован - постарайтесь, чтобы он был лучше других! Напоминаю, что только вопросы от авторов опубликованных ответов и материалов имеют в дальнейшем приоритет. (Вопросы, естественно, должны быть по программированию).

/ / / В ПОИСКАХ ИСТИНЫ / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /

Q| Как создать такое окно (или это диалогбар?) как Workspace в Visual Studio? То есть, я представляю, что если это диалогбар со списком, то какие стили применить в Create, чтобы его можно было "переносить" и изменять размер? - СашА

   Ответить на вопрос

/ / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /

Начинающим программистам рекомендую подписаться на дружественную рассылку:

Практикум программирования на C++ под Windows
Учебный курс по программированию на языке C++ под Windows. Предназначена для тех, кто уже (немного) умеет программировать, но не знает языка C++ или идеологии написания программ под Windows. В планах - изучение Win32 API, MFC, терминологии Windows и технологий, связанных с ней. Ответы на вопросы, глоссарий, приложения.

Будьте здоровы!

Алекс Jenter   jenter@mail.ru
Красноярск, 2000.

Предыдущие выпуски     Статистика рассылки