Dnes si ukážeme, jak vytvořit, přesněji řečeno upravit podle svého uživatelsky kreslený prvek tab-control, který budeme kreslit s vlastním pozadím, tvořeným bitmapovým rastrem, jiným pozadím budeme vyplňovat aktuálně vybranou „záložku“.
Dnes si ukážeme, jak vytvořit, přesněji řečeno upravit podle svého uživatelsky kreslený prvek tab-control, který budeme kreslit s vlastním pozadím, tvořeným bitmapovým rastrem, jiným pozadím budeme vyplňovat aktuálně vybranou „záložku“. Dále budeme umět na každé záložce zobrazit vlastní obrázek, v tomto konkrétním příkladě z ikony uložené v image-listu. Vytvoříme si také vlastní font pro zobrazení textu záložek (s ukázkou možnosti sklonu písma u vybrané záložky), jak vidíte na následujícím obrázku:
Ukázková aplikace je vytvořena jako MFC aplikace typu dialog. Do zdrojů je importováno 5 bitmap, které použijeme pro pozadí tab-control a pozadí vybrané záložky, dále bitmapy, které budeme přepínat podle aktuálně vybrané záložky. Na dialog tedy umístíme prvek „Tab Control“ a dále prvek „Picture“, který bude zobrazovat vždy jednu ze 3 bitmap.
Nastavení vlastnosti owner-draw
Abychom mohli tab-control kreslit uživatelsky, nastavíme mu v editoru zdrojů v „Tab Control Properties“ na záložce „Styles“ vlastnost „Owner draw fixed“.
Vše realizujeme v kódu třídy dialogu
Podobně jako u vícesloupcového list-boxu není v MFC (alespoň v mém případě – používám Visual C++ 6 Professional, Service Pack 4) zpráva WM_DRAWITEM korektně posílána třídě CTabCtrl (resp. jejímu potomku, který bychom si odvodili). Bylo by možné z dialogu tuto zprávu třídě posílat „ručně“, ale jednoduší a efektivnější bude zpracovat vše v kódu třídy dialogu, aniž bychom vůbec vytvářeli pro Tab Control členskou proměnnou.
Vytvořme si tedy následující členské proměnné třídy dialogu:
// bitmapy zobrazované na jednotlivých „stránkách“:
HBITMAP m_hb0;
HBITMAP m_hb1;
HBITMAP m_hb2;
// výška záložky:
int m_tabHeight;
// struktura pro vytvoření písma záložky:
LOGFONT m_logfont;
// štětce pro výplň pozadí a pozadí vybrané záložky:
HBRUSH m_SelectedTab;
HBRUSH m_BackTab;
// obrázky zobrazované na záložkách:
CImageList m_ImageList;
Inicializaci provedeme ve funkci InitDialog. Ukažme si nyní postupně části jejího kódu:
Nejdříve běžným způsobem načteme ze zdrojů bitmapy, první z nich hned zobrazíme pomocí prvku „Picture“, naplníme si výchozí vlastnosti struktury m_logfont vlastnostmi získanými z jednoho ze „systémových“ písem, které zvětšíme a nastavíme jako tučné, a nakonec vytvoříme dva štětce pro uvedené výplně pozadí:
m_hb0 = LoadBitmap(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDB_HONZA));
m_hb1 = LoadBitmap(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDB_FOTO1));
m_hb2 = LoadBitmap(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDB_FOTO2));
m_Picture.SetBitmap(m_hb0);
m_tabHeight = 48;
::GetObject(GetStockObject(DEFAULT_GUI_FONT), sizeof(LOGFONT),&m_logfont);
m_logfont.lfHeight =20;
m_logfont.lfWeight = FW_BOLD;
m_BackTab = CreatePatternBrush(
LoadBitmap(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDB_BITMAP1)));
m_SelectedTab = CreatePatternBrush(
LoadBitmap(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDB_BITMAP2)));
Dalším krokem je vytvoření image-listu:
m_ImageList.Create(32,32,ILC_COLOR, 0, 1);
m_ImageList.Add(AfxGetApp()->LoadIcon(IDR_MAINFRAME));
m_ImageList.Add(LoadIcon(NULL, IDI_ERROR));
m_ImageList.Add(LoadIcon(NULL, IDI_INFORMATION));
Nakonec přidáme do Tab Controlu 3 záložky pomocí zprávy TCM_INSERTITEM, nastavíme zvolenou výšku záložky a podle celkové šířky Tab Controlu nastavíme délku záložek tak, aby jejich celková délka byla „zarovnaná“ k délce Tab Controlu; nakonec nastavíme pozadí Tab Controlu jako vlastnost „background“ třídy jeho okna.
TCITEM ti;
ti.mask = TCIF_TEXT;
ti.pszText = "První";
SendDlgItemMessage(IDC_TAB, TCM_INSERTITEM, 0, (LPARAM)&ti);
ti.pszText = "Druhý";
SendDlgItemMessage(IDC_TAB, TCM_INSERTITEM, 1, (LPARAM)&ti);
ti.pszText = "Třetí";
SendDlgItemMessage(IDC_TAB, TCM_INSERTITEM, 2, (LPARAM)&ti);
RECT rect;
GetDlgItem(IDC_TAB)->GetWindowRect(&rect);
SendDlgItemMessage(IDC_TAB, TCM_SETITEMSIZE, 0,
(LPARAM)MAKELPARAM(m_tabHeight, m_tabHeight));
SendDlgItemMessage(IDC_TAB, TCM_SETMINTABWIDTH, 0,
(LPARAM)(rect.right - rect.left)/3 - 1);
SetClassLong(::GetDlgItem(m_hWnd, IDC_TAB), GCL_HBRBACKGROUND,
(LONG)m_BackTab);
Vykreslení záložek Tab Controlu realizujeme v handleru zprávy WM_DRAWITEM, kterou obdrží vlastník uživatelsky kresleného Tab Controlu, tedy náš dialog. V kódu handleru vidíte, jak nastavit sklon písma pomocí prvků lfOrientation a lfEscapement struktury LOGFONT, a dále jak získat text přiřazený příslušné záložce pomocí zprávy TCM_GETITEM. K dalším částem kódu handleru myslím netřeba dalšího komentáře:
void CUkazkaDlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)
{
HFONT hfont;
if ( lpDrawItemStruct->itemState & ODS_SELECTED )
{
FillRect(lpDrawItemStruct->hDC, &lpDrawItemStruct->rcItem,
(HBRUSH)m_SelectedTab);
SetTextColor(lpDrawItemStruct->hDC, 0x00A00000);
// vybraná položka bude mít sklon písma
m_logfont.lfOrientation = 350;
m_logfont.lfEscapement = 350;
}
else
{
FillRect(lpDrawItemStruct->hDC, &lpDrawItemStruct->rcItem,
(HBRUSH)m_BackTab);
SetTextColor(lpDrawItemStruct->hDC, 0x00A0FFFF);
m_logfont.lfOrientation = 0;
m_logfont.lfEscapement = 0;
}
hfont = CreateFontIndirect(&m_logfont);
SelectObject(lpDrawItemStruct->hDC, hfont);
SetBkMode(lpDrawItemStruct->hDC, TRANSPARENT);
TCITEM ti;
TCHAR chText[20];
ZeroMemory(&ti, sizeof(TCITEM));
ti.mask = TCIF_TEXT;
ti.cchTextMax = 20;
ti.pszText = chText;
// získáme text položky
SendDlgItemMessage(IDC_TAB, TCM_GETITEM,
lpDrawItemStruct->itemID,
(LPARAM)(LPTCITEM)&ti);
// nakreslíme obrázek
ImageList_Draw(m_ImageList.m_hImageList,
lpDrawItemStruct->itemID,
lpDrawItemStruct->hDC,
lpDrawItemStruct->rcItem.left,
(m_tabHeight - 32)/2,
ILD_TRANSPARENT);
// výpis textu
if ( lpDrawItemStruct->itemState & ODS_SELECTED )
TextOut(lpDrawItemStruct->hDC,
lpDrawItemStruct->rcItem.left + 34,
lpDrawItemStruct->rcItem.bottom - 20,
chText, strlen(chText));
else
DrawText(lpDrawItemStruct->hDC,
ti.pszText, -1,
&lpDrawItemStruct->rcItem,
DT_SINGLELINE | DT_CENTER | DT_VCENTER);
DeleteObject(hfont);
}
Nyní nám zbývá jediné: zachytit změnu výběru Tab Controlu a podle toho zobrazit příslušnou bitmapu, což je v ukázkovém příkladě realizováno velice „primitivním“ způsobem, neboť tématem článku je uživatelsky kreslený Tab Control a nikoli „pokročilé“ zobrazování bitmapy (jako centrování, zoom apod.). Při změně výběru tedy Tab Control posílá svému vlastníku notifikaci TCN_SELCHANGE ve zprávě WM_NOTIFY. Její zpracování vidíte v kódu procedury okna dialogu:
LRESULT CUkazkaDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
LPNMHDR lpnmhdr = (LPNMHDR)lParam;
switch ( message )
{
case WM_NOTIFY:
if ( (wParam == IDC_TAB) && (lpnmhdr->code == TCN_SELCHANGE))
{
switch ( SendDlgItemMessage(IDC_TAB, TCM_GETCURSEL, 0, 0) )
{
case 0:
m_Picture.SetBitmap(m_hb0);
break;
case 1:
m_Picture.SetBitmap(m_hb1);
break;
case 2:
m_Picture.SetBitmap(m_hb2);
break;
}
}
break;
}
return CDialog::WindowProc(message, wParam, lParam);
}
Uvedený příklad je velice jednoduchý. Cílem je ukázat princip a vlastní kreativní činnost je pak věcí programátora. Ukázkový příklad si můžete stáhnout zde
TabControl.zip (668 kB)