Главная Архив версий Помощь Материалы по популярным тематикам Обсуждение различных вопросов Полезные ссылки
Обратная связь
Накрутка рейтинга сайта Rambler's Top100
 

ToolBar в стиле Office XP

Введение

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

  • добавить один или более элементов "combo box" на панель инструментов;
  • изменить размер шрифта "combo box";
  • выполнить обработку команд от "combo box";
  • создать "combo box" с историей, содержащий вводимые пользователем данные.

Векторный графический редактор с панелью инструментов в стиле Office XP

Встраивание элемента "combo box" на панель инструментов

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

Необходимо помнить, что при создании, например, окна диалога и добавлении элемента управления к нему мы определяем добавляемые элементы как "статические". В действительности же, мы можем создать любой тип элемента динамически, вызвав функцию CWnd::Create(…) в любое время. Этот метод поддерживается всеми классами, порожденными от CWnd. Мы можем использовать это для создания элемента управления и помещения его в любое место на панели инструментов. Динамически создаваемые окна редко используются в программировании, потому что в этом случае программисту необходимо вычислять размер и положение окна. Однако панель инструментов в виде ресурса не позволяет добавлять что-либо кроме кнопок, поэтому динамический метод - это единственный способ добиться желаемого результата. Возникает вопрос - куда мы можем поместить наш combo box, чтобы он занимал определенно большее место, чем занимают кнопки, которые расположены "плечо к плечу"? Нельзя допускать, чтобы одни элементы накладывались на другие. Первое, что мы должны сделать - это найти неиспользуемое пространство на панели инструментов, где мы можем создать свой combo box.

На панели инструментов неактивным, а значит незадействованным элементом является разделитель. Если мы щелкнем на нем мышкой, то никакого отклика на это действие не произойдет. Существует функция CToolBar::SetButtonInfo(…), используя которую можно превратить кнопку в разделитель. Также при вызове этой функции мы можем указать желаемую ширину разделителя с помощью параметра iImage (когда мы записываем TBBS_SEPARATOR в параемтр nStyle, значение параметра iImage становится шириной разделителя). То же самое можно сделать, если заранее в ресурсе панели инструментов создать разделитель, и затем просто изменить его ширину. Далее мы можем создать и поместить поверх разделителя любой элемент, используя динамический метод.

Чтобы поместить "combo box" на панель инструментов, необходимо добавить код к обработчику сообщений CMainFrame::OnCreate. Используем CComboBox для объявления переменной m_comboBox в классе CMainFrame:

class CMainFrame : public CFrameWnd
{
...
protected:
   CToolBar   m_wndToolBar;
   CStatusBar m_wndStatusBar;
   CComboBox  m_comboBox;
...
};

Чтобы избежать добавления низкоуровневых деталей панели инструментов в метод OnCreate, можно инкапсулировать их в отдельный метод CreateComboBox, который добавляется к обычному классу на основе CToolBar:

class CToolBarWithCombo : public CToolBar
{
public:
   CComboBox m_comboBox;

// Overrides
   // ClassWizard generated virtual function overrides
   //{{AFX_VIRTUAL(CToolBarWithCombo)
   //}}AFX_VIRTUAL

   BOOL CreateComboBox(class CComboBox& comboBox, 
                       UINT nIndex, 
                       UINT nID, 
                       int nWidth, 
                       int nDropHeight);

// Generated message map functions
protected:
   //{{AFX_MSG(CToolBarWithCombo)
   //}}AFX_MSG
   DECLARE_MESSAGE_MAP()
};

BOOL CToolBarWithCombo::CreateComboBox(CComboBox& comboBox, 
                                       UINT nIndex, 
                                       UINT nID, 
                                       int nWidth, 
                                       int nDropHeight)
{
   // создадим combo box
   SetButtonInfo(nIndex, nID, TBBS_SEPARATOR, nWidth);

   CRect rect;
   GetItemRect(nIndex, &rect);
   rect.top = 1;
   rect.bottom = rect.top + nDropHeight;
   if (!comboBox.Create(CBS_DROPDOWN|WS_VISIBLE|WS_TABSTOP|WS_VSCROLL,
                        rect, this, nID))
   {
       TRACE("Невозможно создать combo-box\n");
       return FALSE;
   }

   return TRUE;
}

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

Функция CComboBox::Create(…) имеет четыре параметра. Мы должны указать стиль элемента combo box, размер и положение, родительское окно, а также идентификатор этого элемента. Вот формат этой функции:

BOOL CComboBox::Create(DWORD dwStyle, const RECT& rect,
                       CWnd* pParentWnd, UINT nID);

Мы можем использовать первый параметр для задания стиля. Combo box может иметь разные стили, но пока что мы просто создадим обычный drop-down (выпадающий) combo box. Он состоит из двух частей: поле редактирования (edit box) и списка (list box). В нормальном состоянии список не показывается. Когда пользователь щелкает на стрелке, список появляется.

Функция CToolBar::GetItemRect(…) вызывается для получения размера и положения разделителя. После вызова этой функции информация сохраняется в переменной rect, которая объявлена с использованием класса CRect. Так как размер элемента combo box представляет собой суммарный размер при видимом списке, нам надо увеличить вертикальный размер разделителя перед его использованием и использовать этот размер для задания размера combo. В вышеприведенном коде только вертикальный размер увеличивается на 150, а горизонтальный остается таким же. Благодаря этому разделитель и combo box имеют одинаковую ширину.

Размеры элемента управления типа ComboBox

Сразу после того, как создана панель инструментов, можно вызывать метод CreateComboBox (внутри CMainFrame::OnCreate):

if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, 
                                WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER |
                                CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
	 !m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
{
    TRACE0("Невозможно создать панель инструментов\n");
    return -1;
}

// Элементу combo box присвоен индекс 3. Это значит, что он будет помещен
// сразу за третьей кнопкой на панели инструментов
if (!m_wndToolBar.CreateComboBox(m_wndToolBar.m_comboBox, 3, ID_COMBO, 150, 100))
{
    TRACE0("Невозможно встроить combo box на панель инструментов\n");
    return -1;
}

Альтернативным вариантом является переопределение стандартной функции CToolBarWithCombo::OnCreate и помещение вызова CreateComboBox(…) непосредственно там.

Изменение шрифта

По умолчанию combo box использует системный шрифт, который выглядит не очень красиво на панелях инструментов. В связи с этим следующим логическим шагом будет улучшение вида combo box. Можно добавить код выбора шрифта непосредственно к методу CreateComboBox, но так как мы собираемся добавить больше усовершенствований к нашему combo box, мы произведем новый класс от CComboBox:

class CSmartComboBox : public CComboBox
{
protected:
    CFont m_font;

public:
    CSmartComboBox();

    LONG    m_lfHeight;
    LONG    m_lfWeight;
    CString m_strFaceName;

    BOOL CreateFont(LONG lfHeight, LONG lfWeight, LPCTSTR lpszFaceName);

// Generated message map functions
protected:
    afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
    DECLARE_MESSAGE_MAP()
};

CSmartComboBox::CSmartComboBox()
{
    m_lfHeight = -10;
    m_lfWeight = FW_NORMAL;
    m_strFaceName = _T("MS Sans Serif");
    m_nMaxStrings = 10;
}

BEGIN_MESSAGE_MAP(CSmartComboBox, CComboBox)
    ON_WM_CREATE()
END_MESSAGE_MAP()

int CSmartComboBox::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CComboBox::OnCreate(lpCreateStruct) == -1)
        return -1;

    if( !CreateFont(m_lfHeight, m_lfWeight, m_strFaceName) )
    {
        TRACE0("Невозможно создать шрифт для combo box\n");
	return -1;
    }

    return 0;
}

BOOL CSmartComboBox::CreateFont(LONG lfHeight, LONG lfWeight, LPCTSTR lpszFaceName)
{
    //  создадим шрифт для combo box
    LOGFONT logFont;
    memset(&logFont, 0, sizeof(logFont));

    if (!::GetSystemMetrics(SM_DBCSENABLED))
    {
        logFont.lfHeight = lfHeight;
        logFont.lfWeight = lfWeight;
        CString strDefaultFont = lpszFaceName;
        lstrcpy(logFont.lfFaceName, strDefaultFont);
        if (!m_font.CreateFontIndirect(&logFont))
        {
            TRACE("Невозможно создать шрифт для combo\n");
            return FALSE;
        }
        SetFont(&m_font);
    }
    else
    {
        m_font.Attach(::GetStockObject(SYSTEM_FONT));
        SetFont(&m_font);
    }
    return TRUE;
}

Теперь combo box может быть легко настроен на отображение стиля Вашего приложения.

Добавление отклика на команды пользователя

Когда combo box встроен на панель инструментов, мы можем использовать его идентификатор для доступа к нему и его свойствам традиционным способом. Но чтобы обеспечить немедленную реакцию на команды пользователя, встроенные элементы управления должны взаимодействовать с панелью инструментов, на которой они находятся. После этого пользователь сможет набрать некоторый тест в поле редактирования, нажать "Enter", и соответствующая команда будет послана приложению.

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

class CToolBarWithCombo : public CToolBar
{
public:

    CSmartComboBox	m_comboBox1;
    CSmartComboBox	m_comboBox2;

// Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CToolBarWithCombo)
    virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam);
    //}}AFX_VIRTUAL
...
}

BOOL CToolBarWithCombo::OnCommand(WPARAM wParam, LPARAM lParam) 
{
    if( wParam == IDOK && lParam == 0 )
    {
        CString strText;
	CString strCommand;
	CComboBox* rgComboBox[] = {&m_comboBox1, &m_comboBox2};
	for( int index = 0; 
             index < sizeof(rgComboBox) / sizeof(rgComboBox[0]); 
             ++index )
        {
            if( rgComboBox[index]->GetEditSel() != 0 )
            {
                rgComboBox[index]->GetWindowText(strText);
                strCommand.Format(_T("Команда от ComboBox[%d]: %s"), 
                                  index+1, 
                                  (LPCTSTR)strText);
                AfxMessageBox(strCommand);
		rgComboBox[index]->AddString(strText);
		rgComboBox[index]->SetWindowText(_T(""));
            }
        }
    }
	
    return CToolBar::OnCommand(wParam, lParam);
}

Как видите, для определения того, какой combo box в данный момент имеет фокус ввода, применяется вызов функции GetEditSel. Пока курсор находится вне combo box, функция будет возвращать 0.

Управление записями combo box

Последнее из наших улучшений - создать combo box, который будет управлять введенными ранее записями пользователя. Такие combo популярны для хранения истории наиболее часто используемых шаблонов поиска, набранных телефонных номеров и т.д. Они все имеют следующие свойства:

  • максимальное количество элементов ограничено - при достижении этого количества новые элементы заменяют старые;
  • элементы добавляются в начало списка;
  • список не содержит повторяющихся элементов.

Ниже приведен код, который необходимо добавить к CSmartComboBox:

class CSmartComboBox : public CComboBox
{
    // Показан только новый код
public:

    int m_nMaxStrings;

    int	AddString(LPCTSTR str);
    int	InsertString(int index, LPCTSTR str);
};

int CSmartComboBox::AddString(LPCTSTR str)
{
    if( _tcslen(str) == 0 )
        return -1;

    int oldIndex = FindStringExact(-1, str);
    if( oldIndex >= 0 )
        DeleteString(oldIndex);

    if( GetCount() == m_nMaxStrings )
        DeleteString(m_nMaxStrings-1);

    return CComboBox::InsertString(0, str);
}

int CSmartComboBox::InsertString(int index, LPCTSTR str)
{
    if( _tcslen(str) == 0 )
        return -1;

    int oldIndex = FindStringExact(-1, str);
    if( oldIndex >= 0 )
    {
        DeleteString(oldIndex);
	if( index >= oldIndex )
	    --index;
    }

    if( GetCount() == m_nMaxStrings )
        DeleteString(m_nMaxStrings-1);

    return CComboBox::InsertString(index, str);
}

11.05.2002

  1  
Copyright © 2005 Gizmo Research Labs. E-mail: gizmo@proton-sss.ru ::
Дизайн сайта: Алексей Дружиненко. E-mail: lexus_ttn@hotmail.com ::