У
каждого из нас бывают ситуации, когда кажется что того набора компонент,
которые доступны в стандартной поставке, недостаточно для комфортной работы.
Иногда написание своих компонентов является фатальной необходимостью. Так,
например, в моей практике часто возникали ситуации, когда необходимо было
разрабатывать новые элементы управления, которые по своей функциональности
заменяли бы несколько стандартных элементов. Зачастую к созданию новых
элементов управления нас подталкивает мода на всякого рода красивости,
которые так любят обычные пользователи.
Но так ли часто мы задумываемся о том, правильно ли выбрана реализация того
или иного компонента, на сколько быстро и эффективно работает наш компонент
и не будет ли его использование замедлять работу нашей программы? Конечно,
сегодня аппаратное обеспечение позволяет всё реже и реже задумываться о
таких вещах, но в случаях, когда недоработок слишком много, быстродействие
может снижаться очень заметно.
Сегодня я хочу рассказать о том, как же все-таки избавить себя от
головной боли при разработке элементов управления и обеспечить максимальное
быстродействие при отрисовке графики.
Представим себе ситуацию, что мы решили создать свой компонент и уже
определились с его функционалом (у нас это будут аналоговые часы). Прежде
чем начинать работу над компонентом, неплохо было бы создать небольшое
тестовое приложение, которое позволило бы проверить его работоспособность.
Ничего сложного мы делать не будем, и создадим обычное GUI-приложение с
одной формой:
| PaintingTestMainFrame.h |
#ifndef _PAINTING_TEST_MAINFRAME_H #define _PAINTING_TEST_MAINFRAME_H #include
|
| PaintingTestMainFrame.cpp |
#include "PaintingTestMainFrame.h"
PaintingTestMainFrame::PaintingTestMainFrame(wxWindow * parent, wxWindowID id, const wxString & title)
{
Create(parent, id, title);
}
bool PaintingTestMainFrame::Create(wxWindow * parent, wxWindowID id, const wxString & title,
const wxPoint & pos, const wxSize & size, long style)
{
bool res = wxFrame::Create(parent, id, title, pos, size, style);
if(res)
{
SetMinSize(wxSize(450, 300));
CreateControls();
}
return res;
}
void PaintingTestMainFrame::CreateControls()
{
}
|
| PaintingTestApp.h |
#ifndef _PAINTING_TEST_APP_H #define _PAINTING_TEST_APP_H #include
|
Теперь можно приступать к написанию класса компонента:
| wxPaintingTestCtrl.h |
#ifndef _WX_PAINTING_TEST_CTRL_H #define _WX_PAINTING_TEST_CTRL_H #include
|
Класс компонента содержит два объекта класса wxBitmap – первый хранит себе фоновое изображение (m_BackgroundBitmap), а второй – изображение циферблата (m_CenterBitmap).
Метод DoGetBestSize() позволяет переопределить размер компонента по умолчанию (у нас это будет wxSize(100,100), см. ниже)
Get/SetBackgroundBitmap и Get/SetCenterBitmap – это, соответственно, aceessor-методы для фонового изображения и для изображения циферблата
OnPaint – Обработчик события wxEVT_PAINT (отвечает за отрисовку графики)
| wxPaintingTestCtrl.cpp |
#include "wxPaintingTestCtrl.h" #include
|
Теперь нам нужно добавить наш компонент на форму
| PaintingTestMainFrame.h |
...
class PaintingTestMainFrame : public wxFrame
{
wxPaintingTestCtrl * m_TestCtrl1;
...
public:
...
};
...
PaintingTestMainFrame.cpp
...
void PaintingTestMainFrame::CreateControls()
{
wxBoxSizer * sizer = new wxBoxSizer(wxVERTICAL);
SetSizer(sizer);
wxBitmap background(wxT("background.png"), wxBITMAP_TYPE_PNG);
wxBitmap center(wxT("center.png"), wxBITMAP_TYPE_PNG);
m_TestCtrl1 = new wxPaintingTestCtrl(this, ID_TEST_CTRL1, background);
m_TestCtrl1->SetCenterBitmap(center);
sizer->Add(m_TestCtrl1, 1, wxGROW|wxALL, 5);
}
...
|
Запускаем наше приложение и смотрим
При изменении размеров формы наблюдаем артефакты отрисовки
Для того чтобы убрать эти артефакты, нам необходимо переопределить обработчик события изменения размеров нашего компонента
| wxPaintingTestCtrl.h |
...
class wxPaintingTestCtrl : public wxControl
{
...
DECLARE_EVENT_TABLE()
...
void OnSize(wxSizeEvent & event);
};
...
|
| wxPaintingTestCtrl.cpp |
...
BEGIN_EVENT_TABLE(wxPaintingTestCtrl, wxControl)
...
EVT_SIZE(wxPaintingTestCtrl::OnSize)
END_EVENT_TABLE()
...
void wxPaintingTestCtrl::OnSize(wxSizeEvent & event)
{
Refresh();
}
...
|
Отлично. Собираем наше приложение, запускаем. И что мы видим: сильное мерцание при изменении размеров. Но с этой проблемой мы тоже можем довольно легко справиться, сделав небольшие изменения в исходном коде
| wxPaintingTestCtrl.h |
...
class wxPaintingTestCtrl : public wxControl
{
...
DECLARE_EVENT_TABLE()
...
void OnEraseBackground(wxEraseEvent & event);
};
...
|
| wxPaintingTestCtrl.cpp |
... #include
|
Класс wxBufferedPaintDC обеспечивает doublebuffering при отрисовке, что
позволяет избавиться от мерцания.
Пустой обработчик события wxEVT_ERASE_BACKGROUND также позволяет немного
ускорить отрисовку.
Собираем приложение, запускаем…. отлично, мерцание исчезло. Но стрелки наших аналоговых часов изменяют положение только при перерисовке содержимого компонента. Это значит, что нам не обходимо добавить таймер, который будет инициировать отрисовку через определенный промежуток времени
| wxPaintingTestCtrl.h |
...
class wxPaintingTestCtrl : public wxControl
{
protected:
...
wxTimer * m_Timer;
...
DECLARE_EVENT_TABLE()
void OnRefreshTimer(wxTimerEvent & event);
};
...
|
| wxPaintingTestCtrl.cpp |
...
enum
{
ID_PAINTING_TEST_CTRL_TIMER = 10001
};
...
BEGIN_EVENT_TABLE(wxPaintingTestCtrl, wxControl)
...
EVT_TIMER(ID_PAINTING_TEST_CTRL_TIMER, wxPaintingTestCtrl::OnRefreshTimer)
END_EVENT_TABLE()
...
void wxPaintingTestCtrl::OnRefreshTimer(wxTimerEvent & event)
{
Refresh();
}
|
Собираем, запускаем.
Отлично. Видно что часы идут. Но на этом работа не заканчивается.
Разворачиваем окно программы на весь экран, запускаем Task Manager и
начинаем перемещать окно Task Manager над компонентом
В результате получаем загрузку процессора на 100%. Это происходит потому
что при каждой перерисовке объект класса wxBufferedPaintDC создает
изображение размером с наш компонент, отрисовка производится на это
изображение в памяти, а потом это изображение отрисовывается на компонент.
Попробуем сделать так, чтобы наш компонент работал быстрее и не потреблял
такое огромное количество ресурсов. Для этого мы попробуем реализовать
doublebuffering вручную.
Добавим новуые переменные в класс компонента
| wxPaintingTestCtrl.h |
...
class wxPaintingTestCtrl : public wxControl
{
protected:
wxBitmap m_DoubleBuffer;
wxMemoryDC m_DoubleBufferDC;
...
};
...
|
| wxPaintingTestCtrl.cpp |
...
void wxPaintingTestCtrl::OnPaint(wxPaintEvent & event)
{
wxPaintDC dc(this);
dc.Blit(0,0, dc.GetSize().GetWidth(), dc.GetSize().GetHeight(), &m_DoubleBufferDC, 0, 0);
}
...
void wxPaintingTestCtrl::OnSize(wxSizeEvent & event)
{
m_DoubleBufferDC.SelectObject(wxNullBitmap);
m_DoubleBuffer = wxBitmap(event.GetSize().GetWidth(), event.GetSize().GetHeight());
m_DoubleBufferDC.SelectObject(m_DoubleBuffer);
DoDraw(m_DoubleBufferDC);
Refresh();
}
void wxPaintingTestCtrl::OnRefreshTimer(wxTimerEvent & event)
{
DoDraw(m_DoubleBufferDC);
Refresh();
}
|
Как видно, при изменении размеров мы пересоздаем изображение, используемое для doublebuffering’а, ассоциируем с ним контекст устройства m_DoubleBuffer и производим отрисовку на изображение в памяти. А в обработчике OnPaint производим копирование из контекста устройства изображения на контекст устройства компонента. В результате загрузка процессора значительно снизилась
Ну и результатом всей нашей работы будет вот такой компонент – аналоговые часы
Хотелось бы заметить, что реализация doublebuffering'а вручную не всегда является самым удачным выбором. Для компонентов, которые будут иметь небольшой размер на форме, можно использовать wxBufferedPaintDC (или даже wxPaintDC если не требуется отрисовка большого количества графических объектов)
Проект для VisualStudio 2005 и wxDev-CPP можно скачать на странице автора.
Последние комментарии