Dnes nebude řeč o uživatelsky kreslených prvcích, jako ve většině předchozích článků. Ale myslím, že v tomto seriálu můžeme rozšířit záběr i na témata typu: „Jak na to, Tipy a Triky…“ s tím, že samozřejmě zůstaneme v oblasti programování v C/C++ s použitím vývojového prostředí Microsoft Visual C++.
Tolik na úvod a nyní již k dnešnímu tématu. Pokud jde o průhlednost oken, ve všech verzích Windows lze využít tzv. regiony (HRGN), jak jsme se s tím setkali v jednom z minulých článků, kde jsme oknu přiřadili region vytvořený na základě bitmapy a tím jsme dosáhli toho, že okno nebylo obdélníkové, tedy dalo by se říci, že část obdélníka ležící mimo region okna, byla průhledná, a to i ve smyslu příjmu „myších“ zpráv. Použití regionů, zvláště složitějších, kombinovaných však poměrně výrazně zatěžuje systém, hlavně grafické rozhraní GDI. Ve Windows 2000 a vyšších je implementována podpora průhlednosti jiným způsobem. Každé okno, které má v rozšířeném styly (ExStyle) atribut WS_EX_LAYERED může mít definován jeden ze dvou režimů průhlednosti (resp. průsvitnosti):
kódem barvy – všechny části okna, které mají barvu (COLORREF) rovnou definované hodnotě jsou 100% průhledné a to stejně jako u zmíněných regionů včetně toho, že v této části (nebo částech) nereagují na zprávy myši a jsou tedy „průklepná“, tedy klepnutím myši do této části aktivujeme okno ležící pod naším oknem v této části našeho okna. Ukázku vidíte na následujícím obrázku:
alfa hodnotou – okno jako celek (tedy bez ohledu na barvy) je řekněme průsvitné, s tím, že míra průsvitnosti je určována tzv. alfa hodnotou, které může být v rozsahu 0 – 255 (tedy BYTE), kde hodnota 0 znamená 100% průhlednost celého okna a hodnota 255 naopak žádnou průsvitnost. Výsledek vypadá takto:
Ukázkový projekt jsem vytvořil jako aplikaci typu „single document“, ze které jsem odstranil dokument i pohled, tedy jako hlavní a jediné okno aplikace je okno typu CMainFrame, na které budeme průhlednost aplikovat.
Nejprve musíme oknu nastavit v rozšířeném stylu atribut WS_EX_LAYERED, pak teprve lze aplikovat zmíněné režimy průhlednosti. Nastavení rozšířeného stylu (ExStyle) lze samozřejmě realizovat kdykoli pomocí funkce SetWindowLong, resp. SetWindowLongPtr, nebo přímo při vytváření okna. V aplikaci psané v „čistém API“ bychom tento atribut uvedli ve funkci CreateWindowEx, v případě aplikace s použitím MFC je obvyklým způsobem definovat vlastnosti (které určuje struktura CREATESTRUCT) přepsáním členské funkce třídy CWnd PreCreateWindow. V našem případě může vypadat takto:
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
if( !CFrameWnd::PreCreateWindow(cs) )
return FALSE;
cs.dwExStyle = WS_EX_LAYERED;
cs.style = WS_OVERLAPPED | WS_SYSMENU;
cs.cx = 400;
cs.cy = 300;
cs.x = 100;
cs.y = 100;
return TRUE;
}
Když má okno nastaven uvedený styl můžeme jeho průhlednost nastavovat pomocí funkce
BOOL SetLayeredWindowAttributes(
HWND hwnd, // handle okna
COLORREF crKey, // hodnota průhledné barvy
BYTE bAlpha, // alfa hodnota
DWORD dwFlags //
);
Když začneme odzadu tak parametr dwFlags určuje, který ze zmíněných režimů průhlednosti bude použit. Může tedy nabývat následujících hodnot:
- LWA_COLORKEY – hodnota určená parametrem crKey určuje barvu (COLORREF), která bude 100% průhledná.
- LWA_ALPHA – stupeň průhlednosti je určen parametrem bAlpha, který může nabývat hodnot v rozsahu 0 – 255, kde 0 znamená, že celé okno je 100 % průhledné a naopak 255 znamená žádnou průhlednost.
Nyní konkrétně k naší aplikaci. Do třídy okna (CMainFrame) si přidáme 2 členské proměnné:
…..
protected:
BYTE m_AlphaValue;
BOOL m_UseColorKey;
…..
do kterých, jak názvy napovídají, si budeme ukládat parametry průhlednosti, které budou nastavitelné za běhu programu. Výchozí hodnoty si nastavíme v konstruktoru a při vytvoření okna:
CMainFrame::CMainFrame()
{
LoadFrame(IDR_MAINFRAME);
m_UseColorKey = TRUE;
m_AlphaValue = 125; // cca 50%
}
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;
SetClassLongPtr(m_hWnd, CLP_HBRBACKGROUND,
(LONG_PTR)(HBRUSH)(COLOR_BTNFACE+1));
SetLayeredWindowAttributes(m_hWnd, 0x0000FF00, 0, LWA_COLORKEY);
return 0;
}
což je případ použití klíče barvy. Jako kód průhledné barvy použijeme čistě zelenou barvu (0x0000FF00), která je tak nepříjemná na oči, že se většinou v této čisté podobě nepoužije. Po spuštění programu dostaneme výsledek, který vidíte na prvním screen-shotu (zvolené pozadí je použito pro lepší zvýraznění efektu a nemá samozřejmě s naším programem nic společného).
Přepínání do „režimu alfa hodnoty“ a zpět realizujeme přes 2 položky menu s následujícími handlery:
void CMainFrame::OnColorKey()
{
SetLayeredWindowAttributes(m_hWnd, 0x0000FF00, 0, LWA_COLORKEY);
m_UseColorKey = TRUE;
RedrawWindow();
}
void CMainFrame::OnAlphaValue()
{
SetLayeredWindowAttributes(m_hWnd, 0, m_AlphaValue, LWA_ALPHA);
m_UseColorKey = FALSE;
RedrawWindow();
}
Pro aplikaci zvoleného režimu okno překreslíme, a handler WM_PAINT vypadá takto:
void CMainFrame::OnPaint()
{
CPaintDC dc(this); // device context for painting
RECT rect;
GetClientRect(&rect);
DrawIcon(dc.m_hDC, 5,5,
LoadIcon(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDR_MAINFRAME)));
TCHAR chAlphaValue[8];
TCHAR chText[120];
if ( m_UseColorKey )
{
InflateRect(&rect, -10, -10);
HBRUSH hbrush = CreateSolidBrush(0x0000FF00);
HPEN hpen = CreatePen(PS_SOLID, 1, 0x0000FF00);
dc.SelectObject(hbrush);
dc.SelectObject(hpen);
dc.Ellipse(&rect);
}
else
{
itoa(m_AlphaValue, chAlphaValue, 10);
wsprintf(chText, "Hodnota alfa: %d ( %d procent )",
m_AlphaValue, (100*m_AlphaValue)/255);
dc.SetBkMode(TRANSPARENT);
dc.SetTextColor(0x00FF0000);
DrawText(dc.m_hDC, chText, -1, &rect,
DT_SINGLELINE | DT_VCENTER | DT_CENTER);
}
}
Vidíte, že v případě použití kódu barvy vykreslíme touto barvou elipsu, která pak bude průhledná, ve druhém případě pouze vypíšeme na plochu okna alfa-hodnotu průhlednosti.
Jako poslední si přidáme možnost měnit za běhu programu právě tuto alfa-hodnotu. V tomto případě ji realizujeme tím způsobem, že kliknutím myší v titulkovém pruhu okna, za současného držení klávesy <Shift> určíme tuto hodnotu tak, že titulek nám bude představovat jakousi stupnici. Většinou asi máte titulek zobrazený barevným přechodem, takže čím více kliknete vlevo, tím bude okno méně průhledné a naopak. Jak na to? Především musíme v proceduře okna odchytit zprávu WM_NCLBUTTONDOWN, která znamená že došlo ke stlačení levého tlačítka myši v neklientské oblasti (což je oblast okna tvořená rámečkem okna, titulkovým pruhem, pruhem nabídky - menu). Parametr wParam této zprávy určuje ve které části této oblasti k události došlo – podrobný popis možných hodnot (tzv. hit-test value) naleznete v popisu zprávy WM_NCHITTEST. Dále pomocí funkce GetAsyncKeyState zjistíme, zda v tomto okamžiku je stlačena klávesa <Shift>.
LRESULT CMainFrame::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
switch ( message )
{
case WM_NCLBUTTONDOWN:
if ( m_UseColorKey )
break;
if ( wParam != HTCAPTION ) // chceme pouze titulek
break;
// Pokud není držena klávesa <Shift>, jdeme pryč
if ( GetAsyncKeyState(VK_SHIFT ) >= 0)
break;
OnTitleClick(lParam);
break;
}
return CFrameWnd::WindowProc(message, wParam, lParam);
}
Podmínka stlačení <shift> je zde proto, abychom se nepletli do zpracování událostí, jako například tažení okna, maximalizace dvojklikem v titulku apod.
Souřadnice myši v okamžiku události WM_NCLBUTTONDOWN dostaneme ve lParam této zprávy, který si předáme do vlastní funkce, ve které pak realizujeme vlastní výpočet a nastavení alfa-hodnoty:
void CMainFrame::OnTitleClick(LPARAM lParam)
{
static POINTS pts;
static RECT rect;
int BtnWidth = GetSystemMetrics(SM_CXSMICON);
int BorderWidth = GetSystemMetrics(SM_CXBORDER);
pts = MAKEPOINTS(lParam);
GetWindowRect(&rect);
rect.left = rect.left + BtnWidth+BorderWidth;
rect.right = rect.right - BtnWidth;
int width = rect.right - rect.left;
int alphaValue = (255 * (rect.right - pts.x))/width;
if ( alphaValue >= 0 && alphaValue <= 255 )
{
m_AlphaValue = alphaValue;
SetLayeredWindowAttributes(m_hWnd, 0, (BYTE)m_AlphaValue, LWA_ALPHA);
RedrawWindow();
}
}
Jak vidíte, tato funkce je již v podstatě pouze o počítání s využitím zjištění rozměrů systémových „ikonek“ a šířky rámečku okna, které zjistíme pomocí funkce GetSystemMetrics.
Vzhledem k tomu, že takováto průhlednost je realizována systémem na základě atributů okna, nastává „zajímavý“ efekt při opisu obrazovky pomocí <Print Screen>. Výše uvedené screen-shoty jsou výřezy z bitmapy získané opisem celé obrazovky. Pokud použijete opis aktuálního (našeho) okna klávesami <Alt>+<Print Screen>, získáte obrázek našeho okna bez průhlednosti, tedy to co my kreslíme ve funkci OnPaint. V prvním případě (kód barvy) do očí bijící zelenou elipsu, ve druhém případě (alfa-hodnota) okno vždy neprůhledné.
Pozor na to, když někde v programu nastavíte alfa-hodnotu na 100% průhlednost, okno zcela zmizí z obrazovky a ani myši ho nijak nezachytíte. Pak musíte aplikaci zavřít třeba pravým tlačítkem v pruhu úloh apod.
Ukázkový projekt si můžete stáhnout zde: LayeredWindows.zip