2012-03-20 2 views
2

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

Для простоты можно сказать, что это сплошной зеленый прямоугольник, который заполняет всю клиентскую область окна. Я хочу, чтобы этот прямоугольник перерисовывался и заполнял все окно каждый раз, когда окно меняет свой размер. Первоначально я это делал. Я отправил 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; 

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

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

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

Update

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

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) он копирует содержимое старой клиентской области в новый верхний угол окна (в нашем случае он перемещается влево), C) он отправляет WM_PAINT для заполнения новой клиентской области. Однако на этапе B по какой-то причине Windows создает те незаполненные области вдоль правого края, хотя кажется, что это не должно быть, потому что старый контент должен оставаться там, где он есть, пока он не будет перерисовываться во время WM_PAINT.

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

+0

Вы копаете себе довольно глубокое отверстие здесь. Запретить мерцание путем двойной буферизации или путем написания обработчика сообщений для WM_ERASEBKGND, который ничего не делает. –

+2

Ханс, я делаю двойную буферизацию, обрабатываю WM_ERASEBKGND и делаю другие трюки, чтобы уменьшить мерцание. Дело в том, что в этом случае мерцание НЕ вызвано неправильной техникой окраски. На самом деле у меня вообще нет мерцания во время обычных операций. Картина при изменении размера - это единственное, что я не могу победить. И снова, даже в этом случае мои вещи не мерцают. Это новая добавленная область окна, которая мерцает, потому что я не могу контролировать ее добавление. Кажется, что проблема на одном или двух уровнях ниже - это то, что делает Windows при изменении размеров окон. – wladp

+0

Вам не нужно будет обрабатывать WM_SIZE/SIZING - если вы хотите, чтобы все окно было перерисовано при изменении размера, просто укажите [CS_HREDRAW и CS_VREDRAW биты стиля класса в WNDCLASS] (http://msdn.microsoft.com). /en-us/library/windows/desktop/ff729176(v=vs.85).aspx), и Windows позаботится об этом для вас. Кроме того, некоторый уровень мерцания рядом с краем границы кажется неизбежным (в ответе Марка) ниже - вы можете сравнить с такими приложениями, как Explorer, Chrome, IE и т. Д., Чтобы узнать, что такое «базовый уровень», и проверить, что вы находитесь на по крайней мере, не хуже. – BrendanMcK

ответ

4

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

+0

MitchelRemoving PostMessage (hWnd, WM_PAINT, 0, 0) из обработчика WM_SIZE ничего не меняет (все еще мерцает возле границ). – wladp

+0

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

+0

Пол, это не работает. Все еще мерцает. – wladp

1

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

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

// 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)); 
+0

Это не о заполнении окна цветным или красящим прямоугольником. Это всего лишь простые вещи, чтобы проиллюстрировать проблему. На самом деле у меня есть график, показывающий данные в реальном времени. Несмотря на то, что при изменении размера это не критично, это определенно раздражает. Что касается вашей первой идеи (игра с MoveWindow() в WN_SIZE), это на самом деле еще одна вариация того, что я пытался сделать. И это также не работает. – wladp

+0

Причина, насколько я понимаю, такая же - после записи размера окна в WM_SIZE и к моменту поступления WM_PAINT мышь уже находится в немного другом месте. Мышь перетаскивает угол или сторону окна в это место, и результат заключается в том, что новый оконный кадр отличается от прямоугольника, который я собираюсь сделать. И это по очереди ведет к незаполненным районам вдоль границ. – wladp

2

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

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

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

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

+1

RedrawWindow() - хорошая идея. Интересно, как я мог забыть об этой функции. Однако, когда я это пробовал, результат один и тот же - какая-то новая область добавляется в окно (которое изменяется) до завершения рисования. Это противоречит тому, что должна делать эта функция (закончить живопись до возвращения) и вызывает разрывный эффект вдоль границ. – wladp

+0

Обновление: я ошибся, RedrawWindow() работает точно так, как описано. Однако флаг RDW_UPDATENOW не влияет на основное окно, он влияет на его дочерние элементы, заставляя их получать WM_PAINT (и, надеюсь, их перерисовывать) до возвращения функции. В моем случае у меня нет дочерних окон, из-за которых RedrawWindow() ничего не делает, т. Е. Не имеет значения, если я использую RedrawWindow() или InvalidateRect(). – wladp

+0

@wladp, я думаю, вы неправильно поняли RedrawWindow. Он * всегда * обновляет окно, чей дескриптор вы передаете ему, а дети включены или исключены на основе флагов. См. Документацию: http://msdn.microsoft.com/en-us/library/dd162911.aspx Вы можете попробовать включить флаг RDW_INVALIDATE. –

Смежные вопросы