Рисование в окне при изменении размера оставляет неокрашенную границу - PullRequest
2 голосов
/ 20 марта 2012

Проблема, которая у меня есть, кажется тривиальной, но я не могу найти способ ее решить.Вот.У меня есть окно с графикой.

Для простоты, скажем, это сплошной зеленый прямоугольник, который заполняет всю клиентскую область окна.Я хочу, чтобы этот прямоугольник перерисовывался и заполнял все окно каждый раз, когда окно меняет свой размер.Первоначально я сделал это.Я отправил WM_PAINT сообщение от обработчика WM_SIZE .

Это работает, но если я быстро перемещаю мышь, я вижу немного неокрашенной (белой) области вокруг зеленого прямоугольника (на самом деле только одна или две стороны, рядом с тем местом, где находится мышь).Мое понимание проблемы состоит в том, что системный поток, который обрабатывает пользовательский ввод (мышь), работает быстрее, чем мой обработчик сообщения WM_PAINT.Это означает, что к тому времени, когда я начинаю рисовать обновленный прямоугольник (его размер взят из WM_SIZE), мышь фактически немного перемещается, и система рисует новую рамку окна, которая отличается от того, что я пытаюсь заполнить зеленым цветом.Это создает незаполненные области рядом с границами, которые перемещаются во время изменения размера.

Когда я прекращаю изменение размера, зеленый цвет в конечном итоге заполняет все окно, но во время изменения размера возникает небольшое мерцание вблизи границ, что раздражает.Чтобы решить проблему, я попробовал следующее.

bool finishedPainting;
RECT windowRect;

case WM_PAINT :
    // .....  painting here

  finishedPainting = TRUE;
  break;

case WM_SIZE :
    // .... some actions

    // posting WM_PAINT
  InvalidateRect(hWnd, NULL, FALSE);
  PostMessage(hWnd, WM_PAINT, 0, 0);
  break;

case WM_SIZING :
    // this supposedly should prevent the system from passing
    // new window size to WM_SIZE
  if (!finishedPainting) memcpy((void*)lParam, &windowRect, sizeof(windowRect));
  else {
      // remember current window size for later use
    memcpy(&windowRect, (void*)lParam, sizeof(windowRect));
    finishedPainting = FALSE;
  }
  return TRUE;

Это не работает.В качестве небольшого изменения я тоже попробовал это.

bool  finishedPainting;
POINT cursorPos;

case WM_PAINT :
    // .....  painting here

  finishedPainting = TRUE;
  break;

case WM_SIZE :
  if (!finishedPainting) SetCursorPos(cursorPos.x, cursorPos.y);
  else {
    finishedPainting = FALSE;
    GetCursorPos(&cursorPos);

      // .... some actions

    InvalidateRect(hWnd, NULL, FALSE);
    PostMessage(hWnd, WM_PAINT, 0, 0);
  }
  break;

Это тоже не работает.Насколько я понимаю, решение проблемы заключается в том, чтобы каким-то образом замедлить работу мыши, чтобы она переместилась в следующую позицию на экране (перетаскивая за нее угол или сторону окна) только после завершения рисования.

Есть идеи, как этого добиться?Или, может быть, что-то в корне неверно в том, как я вижу проблему, и решение лежит где-то еще?

// ====================================================

Обновление

Я провел несколько экспериментов и здесьэто то, что я нашел

1) При изменении размера, последовательность сообщений WM_SIZING - WM_NCPAINT - WM_SIZE - WM_PAINT.Это выглядит немного странно для меня.Я ожидал бы, что WM_SIZE будет следовать за WM_SIZING без прерывания WM_NCPAINT

2) В каждом обработчике сообщений я проверял ширину окна во время изменения размера (для простоты я только изменял ширину).Удивительно, но ширина, измеренная в WM_SIZE, оказалась отличной от ширины в WM_SIZING, но такой же, как в WM_NCPAINT и WM_PAINT.Это не проблема как таковая, просто странный факт.

3) Я пришел к выводу, что есть две основные причины мерцания, возникающие у границ окна.Во-первых, WM_NCPAINT предшествует WM_PAINT.Представь, что ты растягиваешь свое окно.Сначала появится новый кадр (первым идет WM_NCPAINT), затем WM_PAINT заполняет клиентскую область.Человеческий глаз ловит тот короткий промежуток времени, когда новый кадр уже находится на экране, но он пуст.Даже если вы укажете, что не хотите, чтобы фон окна удалялся перед перекрашиванием, вновь добавленная область пуста, и вы можете увидеть ее за доли секунды.Эту причину мерцания лучше всего продемонстрировать, когда вы берете правый край окна и быстро перемещаете его вправо.Другая причина мерцающего эффекта менее очевидна и лучше всего видна, когда вы берете левый край окна и перемещаете его влево.Во время этого движения вы увидите незаполненные области по правому краю.Насколько я понимаю, эффект вызван этим.Когда пользователь выполняет изменение размера, Windows делает следующее: A) отправляет WM_NCPAINT, чтобы нарисовать новый фрейм, B) копирует содержимое старой клиентской области в новый левый верхний угол окна (в нашем случае он перемещается влево),В) он отправляет WM_PAINT для заполнения новой клиентской области.Однако на этапе B по какой-то причине Windows создает эти незаполненные области по правому краю, хотя кажется, что этого не должно быть, потому что старый контент должен просто оставаться там, где он есть, пока он не будет перекрашен во время WM_PAINT.

OkОстается вопрос - как избавиться от этих артефактов при изменении размеров.Насколько я сейчас вижу, это невозможно сделать, используя стандартные методы и функции, потому что они вызваны последовательностью шагов, которые Windows выполняет во время изменения размера.Поменять местами WM_NCPAINT и WM_PAINT, вероятно, было бы невозможно, но, похоже, это вне нашего контроля (если нет простого способа сделать это, о котором я просто не знаю).

Ответы [ 3 ]

4 голосов
/ 20 марта 2012

Вы не должны отправлять или отправлять сообщения WM_PAINT самостоятельно.Вместо этого используйте :: InvalidateRect для аннулирования частей вашего окна и позвольте Windows решить, когда отправлять сообщения WM_PAINT.

2 голосов
/ 20 марта 2012

Windows работает таким образом специально.Обычно считается более важным реагировать на действия пользователя (то есть мыши), чем иметь полностью обновленное окрашенное окно.

Если вы всегда рисуете все окно в своем обработчике WM_PAINT, выможно устранить много мерцания, переопределив обработчик WM_ERASEBKGND и вернувшись, ничего не делая.

Если вы действительно настаиваете на предпочтении обновлений окна над отзывчивостью мыши, замените вызов InvalidateRect вызовом RedrawWindow, используя флаг RDW_UPDATENOW.

Редактировать: Ваши наблюдения имеют смысл.WM_SIZING приходит до изменения размера окна, чтобы дать вам возможность изменить размер и / или положение.Вы можете попробовать нарисовать клиентскую область в обработчике WM_NCPAINT даже до того, как будет нарисована граница.После того, как вы нарисуете его, вы можете проверить клиентскую область, чтобы он не рисовался снова.

1 голос
/ 20 марта 2012

Плохо писать вручную WM_PAINT или WM_SIZE . Однако, одна странная хакерская сумасшедшая вещь, которую вы можете сделать, - записать новые координаты с измененным размером в RECT в WM_SIZE , используйте MoveWindow, чтобы изменить размер окна назад к предыдущему, затем вручную измените его размер снова , используя MoveWindow в WM_PAINT сообщении после после того, как вы сделали рисование ,

Другое возможное решение - постоянно заполнять окно цветом, независимо от того, изменен размер экрана или нет. * 1024 то есть *

// in the WinMain function
if (GetMessage(&msg,NULL,NULL,0))
{
    TranslateMessage(&msg,NULL,NULL);
    DispatchMessage(&msg,NULL,NULL);
}
else
{
     // fill your window with the color in here
}

Или, конечно, вы можете просто установить зеленый цвет фона вашего окна вместо того, чтобы рисовать самостоятельно:

// Before RegisterClass or RegisterClassEx
// wincl is a WNDCLASS or WNDCLASSEX
wincl.hbrBackground = CreateSolidBrush(RGB(50, 238, 50));
...