?

Log in

No account? Create an account

November 11th, 2009 - Excelsior — LiveJournal

Nov. 11th, 2009

11:35 am - Уроки рисования для винформс

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

Вот и пришел мой момент озарения! Оказывается моргание контролов возникает из-за того, что отрисовка происходит в два этапа: PaintBackground и Paint. Первый из них по умолчанию забивает весь канвас цветом фона, а второй обычно перекрывается и рисует все, что надо. Однако винформы недостаточно смартовые, чтобы буферизовать эти этапы, и рисуют все сразу на экран. Поэтому, едва будучи отрисованным, бэкграунд стирается нафиг в он-пейнте, а это вызывает моргание.

Во-первых, можно перекрыть OnPaintBackground (или даже OnPaint) пустым методом. Такого же эффекта можно добиться, установив бит ControlStyles.Opaque. Тогда отрисовка будет происходить только один раз за инвалидейт. Ура?

Еще не ура. По умолчанию отрисовщик все свои действия сразу же повторяет на экране, так что если рисование идет в несколько слоев (например, стирает старый кадр и рисует новый), то вполне вероятны моргания. Хороший вариант здесь - рендерить в битмаповский буфер, хотя можно поступить проще: заюзать BeginUpdate/EndUpdate, которые сами создадут буфер рендеринга, или же (что эквивалентно) установить бит ControlStyles.DoubleBuffer.

Остался последний штрих. Отключив OnPaintBackground, мы отказываемся от гибкости многослойного рендеринга, что неприятно, хоть и имеет под собой хорошую причину. Оказывается, можно устранить эту неприятность, не вызвав моргание. Флажок ControlStyles.AllPaintingInWmPaint объединяет два этапа рендеринга под эгидой OnPaint, а это значит, что если мы к тому же еще и включим DoubleBuffer, то и PaintBackground и Paint будут отрисованы в один и тот же буфер. Вот тут-то ура!

Резюмируя: вот так правильно юзать контролы с кастомной отрисовкой:

Copy Source | Copy HTML
this.SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | ControlStyles.DoubleBuffer, true);
 
protected override void OnPaintBackground(PaintEventArgs e)
{
   // background rendering logic goes here
}
 
protected override void OnPaint(PaintEventArgs e)
{
   // main rendering logic goes here
}
 
 

Previous day (Calendar) Next day