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

Určitě si přečtěte

Tesla chce změnit nákladní dopravu. Její elektrický náklaďák má ohromující parametry

Tesla chce změnit nákladní dopravu. Její elektrický náklaďák má ohromující parametry

** Tesla představila elektrický kamion ** Má obdivuhodný výkon i dojezd ** Prodávat by se měl už za dva roky

17.  11.  2017 | Vojtěch Malý | 158

30 počítačových brzd, které vám zpomalí Windows

30 počítačových brzd, které vám zpomalí Windows

Na webu najdete hromadu rad, jak zrychlit počítač a Windows. My jsme na to šli opačně a naopak jsme hledali činnosti, které ho nejvíce zpomalují. Toto je třicítka těch základních.

12.  11.  2017 | Jakub Čížek | 90

Elektronika, která nepotřebuje kabel ani baterii. Živí se rádiovým šumem

Elektronika, která nepotřebuje kabel ani baterii. Živí se rádiovým šumem

** Každá elektrická krabička má konektor pro napájení nebo baterii ** Jenže pozor, jednou by to tak nemuselo být ** Drobná elektronika se může živit rádiovými vlnami

14.  11.  2017 | Jakub Čížek | 15

Nejlepší notebooky do 10 tisíc, které si teď můžete koupit

Nejlepší notebooky do 10 tisíc, které si teď můžete koupit

** I pod hranicí desíti tisíc korun existují dobře použitelné notebooky ** Mohou plnit roli pracovního stroje i zařízení pro zábavu ** Nejlevnější použitelný notebook koupíte za pět a půl tisíce

16.  11.  2017 | Stanislav Janů | 52

Do 20 let nebude nikdo vlastnit auta, říká zkušený šéf několika automobilek

Do 20 let nebude nikdo vlastnit auta, říká zkušený šéf několika automobilek

** Bývalý šéf a expert z několika velkých automobilek se vyjádřil k budoucnosti tohoto průmyslu ** Do 20 let „nikdo“ nebude vlastnit auta ** Veškerá doprava bude řešená pomocí velkých logistických platforem

15.  11.  2017 | Karel Javůrek | 74


Aktuální číslo časopisu Computer

Otestovali jsme 5 HDR 4K televizorů

Jak natáčet video zrcadlovkou

Vytvořte si chytrou domácnost

Radíme s koupí počítačového zdroje