V tomto článku si ukážeme, jak zakázat stránku v PropertySheet.
Jde nám o to, aby se uživatel nemohl na danou stránku dostat žádným způsobem a aby
záložka svázaná s touto stránkou byla vykreslena jiným způsobem.Například jako je
tomu na následujícím obrázku:

Abychom dosáhli požadovaného stavu musíme zajistit následující
akce:
1. Podědit potomka ze třídy CPropertySheet.
2. Rozšířit třídu o členskou proměnnou, která bude držet informace o zakázaných
stránkách.
3. Zajistit vykreslování zakázaných záložek
4. Zakázat přepnutí do zakázané záložky.
První bod asi nepotřebuje komentář, a proto se hned podíváme na
další bod. Ke každé stránce si budeme držet informace, zda je povolená, či ne. K
tomu nám poslouží pole bajtů, kde každé stránce bude odpovídat jeden záznam. Pro
přístup k poli zavedeme metody SetEnableState a GetEnableState.
Abychom si zjednodušili práci, budou se nenastavené položky chápat, jako by stránky
byly povolené. Metody tedy budou vypadat takto:
void PRO_PropertySheet::SetEnableState(int iPage, bool bEnable)
{
// Pokud je stránka mimo rozsah, přidáme informace o daších
// stránkách.
if (iPage >= m_oStatusArray.GetSize())
{
// Stav nastavujeme na povolen
while (iPage >= m_oStatusArray.GetSize())
m_oStatusArray.Add(1);
}
// Nastavíme konkrétní stránku
m_oStatusArray.SetAt(iPage, (BYTE)bEnable);
}
//-----------------------------------------------------------------
bool PRO_PropertySheet::GetEnableState(int iPage)
{
// Pokud jsme mimo nastavení, tak se tváříme, jako že je stránka
// povolena.
if (iPage>=m_oStatusArray.GetSize())
return true;
// Jinak poctivě získáme stav
return m_oStatusArray.GetAt(iPage) ? true : false;
} |
Snad je vše jasné z komentářů. Navržený algoritmus není
připraven na odstraňování stránek, ale protože to není příliš běžná akce,
bude nám to (zatím) takto vyhovovat.
Nyní můžeme přistoupit ke třetímu bodu - vykreslování. Abychom
mohli převzít vykreslování, je třeba nastavit prvek TabControl, který
třída CPropertySheet obsahuje, tak aby jsme se o vykreslování starali my,
třeba následovně:
BOOL PRO_PropertySheet::OnInitDialog()
{
// Voláme metodu předka
BOOL bResult = CPropertySheet::OnInitDialog();
// Získáme ukazatel na prvek TabControl
CTabCtrl *poTabCtrl = GetTabControl();
if (!poTabCtrl->GetSafeHwnd())
return FALSE;
// Získáme původní styl
DWORD dwStyle = GetWindowLong(poTabCtrl->GetSafeHwnd(), GWL_STYLE);
// Nastavíme styl, že se o vykreslování staráme my
dwStyle |= TCS_OWNERDRAWFIXED;
::SetWindowLong(poTabCtrl->GetSafeHwnd(), GWL_STYLE, dwStyle);
return bResult;
} |
V okamžiku, kdy nastavíme styl OwnerDraw, se musíme o vykreslení
postarat. Systém zajistí, že pro každou vykreslovanou záložku je volána metoda OnDrawItem,
kde zajistíme vykreslení textu. Pokud zjistíme, že záložka vede na zakázanou
stránku, zobrazíme text "zakázaně", tedy vmáčkle šedě. Toto se snadno
zajistí dvojitým vykreslením na posunutých souřadnicích, jak je vidět ze
zdrojového textu:
void PRO_PropertySheet::OnDrawItem(
int nIDCtl,
LPDRAWITEMSTRUCT lpDIS)
{
// Převedeme si HDC na CDC abychom mohli používat metody
CDC oDC;
oDC.Attach(lpDIS->hDC);
CRect rcItem = lpDIS->rcItem;
// Pokud zobrazujeme vybranou záložku, je třeba zrušit bílou
// oddělovací čáru. Toho docílíme třeba vymazáním celé záložky
if (lpDIS->itemState == ODS_SELECTED)
{
oDC.FillSolidRect(&rcItem, RGB(192,192,192));
rcItem.OffsetRect(1, 1);
}
else
rcItem.OffsetRect(2, 2);
// Získáme text záložky, který je uložen přímo v informacích
// prvku TabControl.
CString strText;
TC_ITEM oItem;
oItem.mask = TCIF_TEXT;
oItem.pszText = strText.GetBuffer(200);
oItem.cchTextMax = 200;
GetTabControl()->GetItem(lpDIS->itemID, &oItem);
strText.ReleaseBuffer();
// Pokud je stránka povolena, záložku vykreslíme jako běžný text
if (GetEnableState(lpDIS->itemID))
{
oDC.DrawText(strText, rcItem,
DT_SINGLELINE | DT_CENTER | DT_VCENTER);
}
else
{
// Pokud je stránka zakázána,
// tak vykreslíme nejprve text bíle
oDC.SetBkMode(TRANSPARENT);
COLORREF clrOld = oDC.GetTextColor();
oDC.SetTextColor(RGB(255,255,255));
oDC.DrawText(strText, rcItem,
DT_SINGLELINE | DT_CENTER | DT_VCENTER);
// a poté tmavě šedě na souřadnicích o bod vedle v obou osách
oDC.SetTextColor(RGB(128,128,128));
rcItem.OffsetRect(-1, -1);
oDC.DrawText(strText, rcItem,
DT_SINGLELINE | DT_CENTER | DT_VCENTER);
oDC.SetTextColor(clrOld);
}
// Oddělíme třídu CDC a HDC, protože jinak by došlo ke
// zrušení kontextu zařízení.
oDC.Detach();
} |
Poslední co musíme zajistit, je aby se uživatel nemohl přepnout do
zakázané stránky. Toto se řeší pomocí notifikačních zpráv. Bohužel tyto zprávy
chodí pouze v případě, že dojde k přepnutí myší, pomocí klávesnice (CTRL + TAB,
atp.) budeme muset reakci řešit jinak.
BOOL PRO_PropertySheet::OnNotify(
WPARAM wParam,
LPARAM lParam,
LRESULT* pResult)
{
NMHDR* pnmh = (NMHDR*)lParam;
// Tato notifikace určuje, že dojde k přepnutí stránek. Uložíme si
// posledně vybranou stránku, abychom se měli kam přepnout v
// případě, že se nepodaří přepnout na cílovou stránku.
if (pnmh->code == TCN_SELCHANGING)
{
int iPage = GetTabControl()->GetCurSel();
m_nLastSelPage = iPage;
*pResult = TRUE;
}
// Tato notifikace určuje, že došlo k přepnutí stránky.
if (pnmh->code == TCN_SELCHANGE)
{
// Pokud přepnutá stránka je zakázaná,
int iPage = GetTabControl()->GetCurSel();
// tak se přepneme na tu, která byla aktivní před přepnutím.
if (!GetEnableState(iPage))
SetActivePage(m_nLastSelPage);
}
// Dáme šanci předkovi
return CPropertySheet::OnNotify(wParam, lParam, pResult);
} |
Poslední problém, který musíme vyřešit, se týká přepnutí
pomocí klávesnice. Tato akce je vytvářena v metodě PreTranslateMessage
předka. Tuto metodu kompletně překryjeme a napíšeme vlastní správu přepínání
stránek, kde zakážeme přepnutí do nepovolených stránek, například takto:
BOOL PRO_PropertySheet::PreTranslateMessage(MSG* pMsg)
{
ASSERT_VALID(this);
// allow tooltip messages to be filtered
if (CWnd::PreTranslateMessage(pMsg))
return TRUE;
// Pokud je to "naše" kombinace...
if (pMsg->message == WM_KEYDOWN &&
GetAsyncKeyState(VK_CONTROL) <0 &&
(pMsg->wParam == VK_TAB || pMsg->wParam == VK_PRIOR
|| pMsg->wParam == VK_NEXT)
)
{
// Zjistíme vybranou stránku
int iPage = GetTabControl()->GetCurSel();
int nDirect; // Proměnná představuje směr posunu
// Zjistíme směr posunu
nDirect = +1;
if (pMsg->wParam == VK_TAB && GetAsyncKeyState(VK_SHIFT) < 0)
nDirect = -1;
if (pMsg->wParam == VK_PRIOR)
nDirect = -1;
// Posun na další (předchozí) stránku
iPage += nDirect;
// Pokud jsme příliš daleko (blízko) upravíme na správná čísla
if (iPage >= GetPageCount())
iPage = 0;
if (iPage < 0)
iPage = GetPageCount()-1;
// Test, zda je stránka povolena
if (!GetEnableState(iPage))
{
// Pokud ne, tak nalezneme nejbližší povolenou v
// požadavaném směru.
int iIndex = iPage;
while (!GetEnableState(iPage))
{
iPage += nDirect;
if (iPage >= GetPageCount())
iPage = 0;
if (iPage < 0)
iPage = GetPageCount()-1;
}
}
// Nastavíme stránku
SetActivePage(iPage);
// TRUE, jako že jsme zprávu obsloužili
return TRUE;
}
// handle rest with IsDialogMessage
return PreTranslateInput(pMsg);
} |
Tímto jsme splnili poslední krok. Třída má ještě pár
nedostatků, jejichž řešení ale není nic náročného, to jistě každý zvládne:
1. Není ošetřeno odebírání stránek a jejich synchronizace s
naším polem nastavení.
2. V záložkách se vykresluje jenom text.
3. Zakázaná stránka lze vybrat programově.
Kompletní zdrojový text a příklad použití lze nalézt v sekci
Download tohoto článku. |