Průhledná okna ve Windows 2000

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:

Jak na to

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.

Pár poznámek na závěr

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

Témata článku: Software, Windows, Programování, Okna, Message, Okno, Shift, Print Screen, Alfa, Zelené tlačítko, Byte

4 komentáře

Nejnovější komentáře

  • ML 31. 10. 2004 23:25:41
    Dobry nemohli by ste mi niekto pomoct? Moje SDKcko nepozna konstantu...
  • Petr Vones 30. 8. 2001 16:00:24
    Ono záleží i na použitém hardware, ale obecně platí že všechny tyto...
  • BlackRider 30. 8. 2001 11:29:16
    Teda W2k uz nakej ten patek nepouzivam, ale zkusil sem pomoci WinBlinds...
Určitě si přečtěte

Země se jenom o vlásek vyhnula věčnému zmrznutí

Země se jenom o vlásek vyhnula věčnému zmrznutí

** Země po většinu doby své existence zmrzlá rozhodně nebyla ** Podle nového výzkumu tomu však unikla jen o vlásek ** Kdyby totiž byla jenom o 15 procent dále od Slunce, tak by prý kompletně celá zamrzla

20.  9.  2017 | Stanislav Mihulka | 9

CCleaner obsahuje softwarovou havěť! Tvůrcům se do kódu dostali hackeři

CCleaner obsahuje softwarovou havěť! Tvůrcům se do kódu dostali hackeři

** Masově oblíbený program pro softwarovou očistu Windows ovládli hackeři ** Narušení se podařilo zavčas odhalit, unikla jen data o počítačích uživatelů ** Je paradoxní, že CCleaner byl slabě zabezpečen, když jej letos koupil Avast

18.  9.  2017 | David Polesný | 45

Noční strana Venuše vydala další tajemství

Noční strana Venuše vydala další tajemství

18.  9.  2017 | Jiří Černý

Jak tankují bombardéry: Z létající benzinky šest kilometrů nad Českem

Jak tankují bombardéry: Z létající benzinky šest kilometrů nad Českem

** Bombardéry tankují z létající benzinky Boeing KC-135 Stratotanker ** Tu americké letectvo pro doplňování paliva jiných letounů ve vzduchu využívá už více jak půlstoletí ** Tankování probíhá přes speciální výsuvné čerpací rameno na zádi

17.  9.  2017 | Natoaktual.cz

Hacknutý CCleaner je mnohem zákeřnější, než se zdálo. Update na novou verzi nestačí, proveďte obnovu systému

Hacknutý CCleaner je mnohem zákeřnější, než se zdálo. Update na novou verzi nestačí, proveďte obnovu systému

** Chyba v CCleaneru je závažnější, než se zdálo ** Update na novou verzi nemusí stačit ** Přinášíme detaily

21.  9.  2017 | Stanislav Janů | 55


Aktuální číslo časopisu Computer

Vyplatí se ještě těžit kryptoměny?

Velký test studentských notebooků

Test pěti levných soundbarů

Nejlepší chytré hodinky