Est-il possible d’éliminer complètement le scintillement lors du redimensionnement d’une fenêtre?

Normalement, même en cas de double tampon, lors du redimensionnement d’une fenêtre, le scintillement semble inévitable.

Étape 1, la fenêtre d’origine.

Étape 1

Étape 2, la fenêtre est redimensionnée, mais la zone supplémentaire n’a pas été peinte.

Étape 2

Étape 3, la fenêtre est redimensionnée et la zone supplémentaire a été peinte.

Étape 3

Est-il possible en quelque sorte de cacher la setp 2? Puis-je suspendre le processus de redimensionnement jusqu’à ce que l’action de peinture soit terminée?

Voici un exemple:

#include  #include  #include  #pragma comment(lib, "Uxtheme.lib") LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); BOOL MainWindow_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct); void MainWindow_OnDestroy(HWND hWnd); void MainWindow_OnSize(HWND hWnd, UINT state, int cx, int cy); void MainWindow_OnPaint(HWND hWnd); int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow) { WNDCLASSEX wcex = { 0 }; HWND hWnd; MSG msg; BOOL ret; wcex.cbSize = sizeof(wcex); wcex.lpfnWndProc = WindowProc; wcex.hInstance = hInstance; wcex.hIcon = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED); wcex.hCursor = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED); wcex.lpszClassName = TEXT("MainWindow"); wcex.hIconSm = wcex.hIcon; if (!RegisterClassEx(&wcex)) { return 1; } hWnd = CreateWindow(wcex.lpszClassName, TEXT("CWin32"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, HWND_DESKTOP, NULL, hInstance, NULL); if (!hWnd) { return 1; } ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); while ((ret = GetMessage(&msg, NULL, 0, 0)) != 0) { if (ret == -1) { return 1; } TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { HANDLE_MSG(hWnd, WM_CREATE, MainWindow_OnCreate); HANDLE_MSG(hWnd, WM_DESTROY, MainWindow_OnDestroy); HANDLE_MSG(hWnd, WM_SIZE, MainWindow_OnSize); HANDLE_MSG(hWnd, WM_PAINT, MainWindow_OnPaint); default: return DefWindowProc(hWnd, uMsg, wParam, lParam); } } BOOL MainWindow_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct) { BufferedPaintInit(); return TRUE; } void MainWindow_OnDestroy(HWND hWnd) { BufferedPaintUnInit(); PostQuitMessage(0); } void MainWindow_OnSize(HWND hWnd, UINT state, int cx, int cy) { InvalidateRect(hWnd, NULL, FALSE); } void MainWindow_OnPaint(HWND hWnd) { PAINTSTRUCT ps; HPAINTBUFFER hpb; HDC hdc; BeginPaint(hWnd, &ps); hpb = BeginBufferedPaint(ps.hdc, &ps.rcPaint, BPBF_COMPATIBLEBITMAP, NULL, &hdc); FillRect(hdc, &ps.rcPaint, GetStockBrush(DKGRAY_BRUSH)); Sleep(320); // This simulates some slow drawing actions. EndBufferedPaint(hpb, TRUE); EndPaint(hWnd, &ps); } 

Est-il possible d’éliminer le scintillement?

Lorsque la fenêtre est mise à jour pendant une opération de glissement, le système d’exploitation doit afficher quelque chose dans la région de la fenêtre étendue. Si vous ne pouvez rien fournir, alors l’arrière-plan sera affiché jusqu’à ce que vous le fassiez. Puisque vous n’avez pas spécifié d’arrière-plan, vous obtenez la noirceur. Vous devriez sûrement spécifier un pinceau d’arrière-plan? Ajouter simplement ce qui suit à votre code rend le comportement plus acceptable:

 wcex.hbrBackground = GetStockBrush(DKGRAY_BRUSH); 

Toutefois, si vous prenez jusqu’à 320 ms pour répondre à un WM_PAINT vous ruinez l’interface utilisateur de redimensionnement de l’utilisateur. Il devient saccadé et ne répond plus. Le système est conçu autour de l’hypothèse que vous pouvez peindre la fenêtre assez rapidement pour que le glissement soit lisse. La bonne façon de résoudre votre problème consiste à faire fonctionner WM_PAINT dans un délai raisonnable.

Si vous ne pouvez vraiment pas peindre assez rapidement pour un glissement en douceur, je vous propose deux solutions:

  1. Désactiver les mises à jour de la fenêtre lors du déplacement. Je suis sûr que cela peut être fait pour des fenêtres individuelles, mais je ne me souviens pas comment le faire par coeur.
  2. Peindre quelque chose de faux lorsqu’un redimensionnement / glisser est actif et reporter la peinture réelle jusqu’à la fin du redimensionnement / glisser. L’écoute de WM_ENTERSIZEMOVE et WM_EXITSIZEMOVE est la clé. Cet article MSDN explique comment procéder: http://support.microsoft.com/kb/121541

Utilisez WM_SIZING au lieu de WM_SIZE et n’oubliez pas WM_ERASEBKGND .

Si vous accédez à l’applet du panneau de configuration des propriétés système , choisissez l’onglet Avancé , puis cliquez sur Paramètres … dans la zone de groupe Performances. Un paramètre de case à cocher appelé Afficher le contenu de la fenêtre tout en faisant glisser s’affiche . Si vous décochez cette option et essayez de redimensionner une fenêtre, vous constaterez que seul le cadre de la fenêtre se déplace jusqu’à la fin de l’opération de glissement, puis que la fenêtre est repeinte une fois à la nouvelle taille. C’est ainsi que le dimensionnement des fenêtres fonctionnait lorsque nous avions des ordinateurs lents et cruel.

Maintenant, nous ne voulons pas vraiment changer le paramètre globalement (ce que vous feriez en appelant SystemParametersInfo avec SPI_SETDRAGFULLWINDOWS, mais ne le faites pas vraiment car vos utilisateurs ne l’aimeront pas).

Ce qui se produit lorsque l’utilisateur saisit la bordure de redimensionnement, c’est que le fil entre dans une boucle modale contrôlée par le gestionnaire de fenêtres. Votre fenêtre recevra WM_ENTERSIZEMOVE au début de la boucle et WM_EXITSIZEMOVE à la fin de l’opération. À un moment donné, vous obtiendrez également un WM_GETMINMAXINFO, qui ne correspond probablement pas à ce que vous devez faire. Vous obtiendrez également des messages WM_SIZING , WM_SIZE rapidement lorsque l’utilisateur tire le cadre de dimensionnement (et les WM_SIZE rapides conduisent souvent à WM_PAINT s).

Le paramètre global Afficher le contenu de la fenêtre lors du glissement est chargé d’obtenir les messages rapides WM_SIZE. Si ce paramètre est désactivé, vous n’obtiendrez qu’un message WM_SIZE à la fin.

Si votre fenêtre est compliquée, vous avez probablement des tâches informatiques de code de présentation (et peut-être un déplacement de fenêtres enfants) dans le gestionnaire WM_SIZE et beaucoup de code de peinture dans le gestionnaire WM_PAINT. Si tout ce code est trop lent (comme le suggère votre délai d’échantillon de 320 ms), vous vivrez une expérience saccadée et instable.

Nous ne voulons vraiment pas changer le paramètre global, mais cela inspire une solution à votre problème:

Faites un dessin plus simple pendant l’opération de redimensionnement, puis ne faites votre dessin complexe (plus lent) qu’une seule fois, une fois l’opération terminée.

Solution:

  1. Définir un indicateur lorsque vous voyez le WM_ENTERSIZEMOVE.
  2. Modifiez votre gestionnaire WM_SIZE pour vérifier le drapeau et ne rien faire s’il est défini.
  3. Modifiez votre gestionnaire WM_PAINT pour vérifier le drapeau et effectuer un remplissage simple et rapide de la fenêtre dans une couleur unie, le cas échéant.
  4. Désactivez l’indicateur lorsque vous voyez WM_EXITSIZEMOVE, puis déclenchez votre code de présentation et invalide votre fenêtre pour que tout soit mis à jour en fonction de la taille finale.

Si votre fenêtre lente est un enfant plutôt que la fenêtre de niveau supérieur de votre application, vous devez signaler à la fenêtre enfant que la fenêtre de niveau supérieur obtient WM_ENTERSIZEMOVE et WM_EXITSIZEMOVE afin de mettre en œuvre les étapes 1 et 4.

Oui, vous pouvez supprimer complètement le scintillement 🙂

Vous pouvez gérer tous les messages de la fenêtre dans un thread et dessiner son contexte dans un autre. Votre fenêtre rest toujours sensible. Cela fonctionne très bien, vous ne pouvez pas comprendre pourquoi ce n’est pas la meilleure pratique.

Si vous liez un contexte Direct3D par exemple, il peut avoir une mise à l’échelle instantanée lors du redimensionnement, sans avoir à mettre le contexte à jour!

Mon code ressemble à ceci:

 int WINAPI wWinMain( HINSTANCE a_hInstance, HINSTANCE a_hPrevInstance, LPWSTR a_lpCmdLine, int a_nCmdShow ) { Win32WindowRunnable* runnableWindow=new Win32WindowRunnable(a_hInstance, a_nCmdShow); IThread* threadWindow=new Win32Thread(runnableWindow); threadWindow->start(); Scene1* scene=new Scene1(runnableWindow->waitForWindowHandle()); IThread* threadRender=new Win32Thread(scene); threadRender->start(); threadWindow->join(); threadRender->pause(); threadRender->kill(); delete runnableWindow; return 0; } 

Exemple de source complète ici: https://github.com/TheWhiteAmbit/TheWhiteAmbit/blob/master/Win32App/Win32Main.cpp