56 votes

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

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

Le problème est que la fenêtre ne se repeint pas lorsque le contenu de la fenêtre change, de sorte que les zones transparentes du PNG sont "coincées" avec ce qui se trouvait sous la fenêtre lors du démarrage initial de l'application.

Voici la ligne où je configure 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 défini ceci comme arrière-plan :

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 lorsque l'utilisateur ouvre, ferme ou déplace Windows sous l'application.

Lorsque l'application démarre, elle semble parfaite. Les parties transparentes (et quasi-transparentes) du PNG apparaissent parfaitement. MAIS, lorsque l'arrière-plan sous l'application change, l'arrière-plan ne se met pas à jour, il reste le même qu'au premier démarrage de l'application. En fait, WM_PAINT (ou WM_ERASEBKGND ne sont pas appelés lorsque l'arrière-plan change).

Je joue avec ce problème depuis un certain temps et j'ai presque réussi à le résoudre à 100%, mais je n'y suis pas arrivé. 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 se repeigne lorsque le contenu qu'elle contient change ?

46voto

adoss Points 266

J'ai pu faire exactement ce que je voulais en utilisant le code de la partie 1 et de la partie 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 de l'affichage d'un écran d'accueil en Win32 C++, mais c'était presque identique à ce que je devais faire. Je pense que la partie qui me manquait était qu'au lieu de simplement peindre le PNG sur la fenêtre en utilisant GDI+, je devais utiliser la fonction UpdateLayeredWindow avec le paramètre BLENDFUNCTION approprié. Je vais coller la méthode SetSplashImage ci-dessous, qui se trouve 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);
}

25voto

Simon Steele Points 8344

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

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

Vous devrez également configurer votre fenêtre avec le drapeau "layered", par ex.

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

Après cela, 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 fondre dans l'arrière-plan, cela devient plus compliqué. Vous pouvez essayer de regarder l'approche dans cet article de CodeProject :

Dialogues cool, semi-transparents et façonnés avec des contrôles standard pour Windows 2000 et plus

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X