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, Zelené tlačítko, Okno, Alfa, Byte, Message, Shift, Print Screen

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

Hledáte účinný lék na nespavost? Pořiďte si psa!

Hledáte účinný lék na nespavost? Pořiďte si psa!

** „Živý lék“ proti nespavosti předepisují lékaři z věhlasné Mayo Clinic ** Lidé, kteří sdílejí ložnici se psem, spí kvalitněji ** Studie však varuje: Neberte si psa do postele!

18.  9.  2017 | Jaroslav Petr | 4


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