web-dev-qa-db-fra.com

Création d'une fenêtre transparente en C ++ Win32

Je crée ce qui devrait être une application Win32 C++ très simple qui a pour seul but d'afficher UNIQUEMENT un PNG semi-transparent. La fenêtre ne doit pas avoir de chrome et toute l'opacité doit être contrôlée dans le PNG lui-même.

Mon problème est que la fenêtre ne se repeint pas lorsque le contenu sous la fenêtre change, donc les zones transparentes du PNG sont "coincées" avec ce qui était sous la fenêtre lorsque l'application a été initialement lancée.

Voici la ligne où j'ai configuré la nouvelle fenêtre:

hWnd = CreateWindowEx(WS_EX_TOPMOST, szWindowClass, szTitle, WS_POPUP, 0, height/2 - 20, 40, 102, NULL, NULL, hInstance, 0);

Pour l'appel à RegisterClassEx, j'ai cet ensemble pour le fond:

wcex.hbrBackground = (HBRUSH)0;

Voici mon gestionnaire pour le message WM_Paint:

 case WM_Paint:
 {
   hdc = BeginPaint(hWnd, &ps);
   Gdiplus::Graphics graphics(hdc);
   graphics.DrawImage(*m_pBitmap, 0, 0);
   EndPaint(hWnd, &ps);
   break;
 }

Une chose à noter est que l'application est toujours ancrée à gauche de l'écran et ne bouge pas. Mais ce qui se trouve sous l'application peut changer à mesure que l'utilisateur ouvre, ferme ou déplace des fenêtres en dessous.

Lorsque l'application démarre pour la première fois, elle semble parfaite. Les parties transparentes (et simi-transparentes) du PNG transparaissent parfaitement. MAIS, lorsque l'arrière-plan sous l'application change, l'arrière-plan ne se met pas à jour, il reste juste le même qu'au démarrage de l'application. En fait, WM_Paint (ou WM_ERASEBKGND n'est pas appelé lorsque l'arrière-plan change).

Je joue avec ça depuis un bon moment et j'ai presque réussi à avoir 100% raison, mais pas tout à fait là. Par exemple, j'ai essayé de définir l'arrière-plan sur (HBRUSH) NULL_BRUSH et j'ai essayé de gérer WM_ERASEBKGND.

Que peut-on faire pour que la fenêtre soit repeinte lorsque son contenu change?

44
adoss

J'ai pu faire exactement ce que je voulais en utilisant le code des parties 1 et 2 de cette série: http://code.logos.com/blog/2008/09/displaying_a_splash_screen_with_c_introduction.html

Ces articles de blog parlent d'afficher un écran de démarrage dans Win32 C++, mais c'était presque identique à ce que je devais faire. Je crois que la partie qui me manquait était qu'au lieu de simplement peindre le PNG dans la fenêtre à l'aide de GDI +, je devais utiliser la fonction UpdateLayeredWindow avec le bon BLENDFUNCTION paramètre. Je vais coller la méthode SetSplashImage ci-dessous, qui peut être trouvée dans la partie 2 du lien ci-dessus:

void SetSplashImage(HWND hwndSplash, HBITMAP hbmpSplash)
{
  // get the size of the bitmap
  BITMAP bm;
  GetObject(hbmpSplash, sizeof(bm), &bm);
  SIZE sizeSplash = { bm.bmWidth, bm.bmHeight };

  // get the primary monitor's info
  POINT ptZero = { 0 };
  HMONITOR hmonPrimary = MonitorFromPoint(ptZero, MONITOR_DEFAULTTOPRIMARY);
  MONITORINFO monitorinfo = { 0 };
  monitorinfo.cbSize = sizeof(monitorinfo);
  GetMonitorInfo(hmonPrimary, &monitorinfo);

  // center the splash screen in the middle of the primary work area
  const RECT & rcWork = monitorinfo.rcWork;
  POINT ptOrigin;
  ptOrigin.x = 0;
  ptOrigin.y = rcWork.top + (rcWork.bottom - rcWork.top - sizeSplash.cy) / 2;

  // create a memory DC holding the splash bitmap
  HDC hdcScreen = GetDC(NULL);
  HDC hdcMem = CreateCompatibleDC(hdcScreen);
  HBITMAP hbmpOld = (HBITMAP) SelectObject(hdcMem, hbmpSplash);

  // use the source image's alpha channel for blending
  BLENDFUNCTION blend = { 0 };
  blend.BlendOp = AC_SRC_OVER;
  blend.SourceConstantAlpha = 255;
  blend.AlphaFormat = AC_SRC_ALPHA;

  // Paint the window (in the right location) with the alpha-blended bitmap
  UpdateLayeredWindow(hwndSplash, hdcScreen, &ptOrigin, &sizeSplash,
      hdcMem, &ptZero, RGB(0, 0, 0), &blend, ULW_ALPHA);

  // delete temporary objects
  SelectObject(hdcMem, hbmpOld);
  DeleteDC(hdcMem);
  ReleaseDC(NULL, hdcScreen);
}
38
adoss

Utilisez SetLayeredWindowAttributes, cela vous permet de définir une couleur de masque qui deviendra transparente, permettant ainsi à l'arrière-plan de passer.

http://msdn.Microsoft.com/en-us/library/ms633540 (VS.85) .aspx

Vous devrez également configurer votre fenêtre avec le drapeau en couches, par exemple.

SetWindowLong(hwnd, GWL_EXSTYLE, GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_LAYERED);

Après c'est assez simple:

// Make red pixels transparent:
SetLayeredWindowAttributes(hwnd, RGB(255,0,0), 0, LWA_COLORKEY);

Lorsque votre PNG contient des pixels semi-transparents que vous souhaitez fusionner avec l'arrière-plan, cela devient plus compliqué. Vous pouvez essayer de regarder l'approche dans cet article CodeProject:

Boîtes de dialogue sympas, semi-transparentes et façonnées avec des contrôles standard pour Windows 2000 et supérieur

18
Simon Steele