Рис.9. Демонстрация элементов TitleTip
Вы, наверное, поинтересуетесь, почему я не использовал возможность пользовательской отрисовки подсказок (появившейся в IE 4.0 Common Controls DLL) для реализации TitleTips. Дело в том, что ширина окна подсказки рассчитывается исходя из ширины показанной части строки в списке. Другими словами, у вас нет прямого контроля над шириной элемента ToolTip. Это мешает реализации подсказок для элементов "список" с пользовательской отрисовкой, потому что вам может понадобиться вывести на экран не только текст. Кроме того, я думаю, нужно уметь создавать подсказки с нуля, потому что всегда может оказаться, что стандартная реализация подсказок не обеспечивает нужной функциональности. Допустим, вы захотите создать анимированную или говорящую подсказку.
На рис.10 показана диаграмма классов, которая показывает отношения между классами нашего примера. Класс CListBox – это стандартный класс MFC, который инкапсулирует функциональность стандартного элемента управления "список". Класс CTitleTipListBox унаследован от класса CListBox и ответственен за создание и управление подсказками для списка. CTitleTipListBox может использоваться напрямую, если вы реализуете обычный элемент "список". Класс CTitleTip унаследован от CWnd и представляет элемент ToolTip. Класс CODListBox – это элемент "список" с пользовательской отрисовкой, он унаследован от CTitleTipListBox. Для создания элемента "список" с пользовательской отрисовкой нужно унаследовать класс от CTitleTipListBox и переопределить функцию CTitleTipListBox::GetIdealItemRect. Мы обсудим детали реализации CTitleTipListBox::GetIdealItemRect позже.
Рис.10. Диаграмма классов для примера использования элементов ToolTip
Класс CTitleTip представляет окно подсказки (см. рис.11). В статической переменной CTitleTip::m_pszWndClass хранится зарегистрированное имя класса окна. Имя хранится в статической переменной, потому что класс окна нужно зарегистрировать только один раз для всех экземпляров CTitleTip. CTitleTip::m_nItemIndex – это индекс строки в списке, для которой в данный момент выводится подсказка. Эта переменная может принимать значение константы CTitleTip::m_nNoIndex, если подсказка не выводится ни для одной из строк. CTitleTip::m_pListBox хранит указатель на родительское окно элемента TitleTip. Родительское окно должно быть элементом "список", чтобы я смог взять оттуда информацию для подсказки.
Рис.11. CTitleTip
/////////////////////////////////////////////////////////////////////////////
// CTitleTip window
class CTitleTip : public CWnd {
public:
CTitleTip();
virtual BOOL Create(CListBox* pParentWnd);
virtual void Show(CRect DisplayRect, int nItemIndex);
virtual void Hide();
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CTitleTip)
//}}AFX_VIRTUAL
// Implementation
public:
virtual ~CTitleTip();
protected:
const int m_nNoIndex; // Пустой индекс
static LPCSTR m_pszWndClass; // Имя зарегистрированного класса
int m_nItemIndex; // Индекс строки, для которой показывается подсказка
CListBox* m_pListBox; // Родительское окно
BOOL IsListBoxOwnerDraw();
// Generated message map functions
protected:
//{{AFX_MSG(CTitleTip)
afx_msg void OnPaint();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
/////////////////////////////////////////////////////////////////////////////
// TitleTip.cpp : implementation file //
#include "stdafx.h"
#include "TitleTip.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
// CTitleTip
LPCSTR CTitleTip::m_pszWndClass = NULL;
CTitleTip::CTitleTip() : m_nNoIndex(-1) {
// Зарегистрировать класс окна, если он еще не зарегистрирован
// другим экземпляром CTitleTip.
if (m_pszWndClass == NULL) {
m_pszWndClass = AfxRegisterWndClass(CS_SAVEBITS | CS_HREDRAW | CS_VREDRAW);
}
m_nItemIndex = m_nNoIndex;
m_pListBox = NULL;
}
CTitleTip::~CTitleTip() { }
BOOL CTitleTip::Create(CListBox* pParentWnd) {
ASSERT_VALID(pParentWnd);
m_pListBox = pParentWnd;
// Не рисовать рамку для обычных элементов "список", так как
// строки с пользовательской отрисовкой добавляют рамку автоматически.
DWORD dwStyle = WS_POPUP;
if (!IsListBoxOwnerDraw()) {
dwStyle |= WS_BORDER;
}
return CreateEx(0, m_pszWndClass, NULL, dwStyle, 0, 0, 0, 0, pParentWnd->GetSafeHwnd(), NULL, NULL);
}
BOOL CTitleTip::IsListBoxOwnerDraw() {
ASSERT_VALID(m_pListBox);
DWORD dwStyle = m_pListBox->GetStyle();
return (dwStyle & LBS_OWNERDRAWFIXED) || (dwStyle & LBS_OWNERDRAWVARIABLE);
}
void CTitleTip::Show(CRect DisplayRect, int nItemIndex) {
ASSERT_VALID(m_pListBox);
ASSERT(nItemIndex < m_pListBox->GetCount());
ASSERT(nItemIndex >= 0);
ASSERT(::IsWindow(m_hWnd));
ASSERT(!DisplayRect.IsRectEmpty());
// Пометить для обновления, если новая строка.
if (m_nItemIndex != nItemIndex) {
m_nItemIndex = nItemIndex;
InvalidateRect(NULL);
}
// Установить позицию и видимость окна.
CRect WindowRect;
GetWindowRect(WindowRect);
int nSWPFlags = SWP_SHOWWINDOW | SWP_NOACTIVATE;
if (WindowRect == DisplayRect) {
nSWPFlags |= SWP_NOMOVE | SWP_NOSIZE;
}
VERIFY(SetWindowPos(&wndTopMost, DisplayRect.left, DisplayRect.top, DisplayRect.Width(), DisplayRect.Height(), nSWPFlags));