Pull to refresh

Пишем скринсейвер на wxWidgets

Reading time10 min
Views3.5K

Предисловие


Этот материал никоим образом не призывает читателя к написанию скринсейверов, а предназначается, прежде всего, для обзора некоторых возможностей библиотеки wxWidgets. Тем, кто еще не знаком с wxWidgets, но хочет узнать больше, можно почитать статьи на CodeProject.com (эту и эту)

Мозг – это то, что нам нужно


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

Основным объектам, который отображается на экране, у нас является символ. Каждый символ характеризуется следующими параметрами:
  • Координаты
  • Отображаемое значение
  • Скорость перемещения
  • Цвет

Для хранения информации о символе создадим такой класс:
  1. class MatrixSymbol
  2. {
  3.   wxPoint m_Position;
  4.   wxChar m_Symbol;
  5.   int m_Speed;
  6.   wxColour m_Colour;
  7. public:
  8.   MatrixSymbol()
  9.     : m_Position(wxDefaultPosition), m_Symbol(wxT('0')),
  10.     m_Speed(1), m_Colour(*wxGREEN) {}
  11.   MatrixSymbol(const wxPoint & position, wxChar symbol,
  12.     int speed, const wxColour & colour)
  13.     : m_Position(position), m_Symbol(symbol),
  14.     m_Speed(speed), m_Colour(colour) {}
  15.  
  16.   const wxPoint & GetPosition() {return m_Position;}
  17.   void SetPosition(const wxPoint & value) {m_Position = value;}
  18.   wxChar GetSymbol() {return m_Symbol;}
  19.   void SetSymbol(wxChar value) {m_Symbol = value;}
  20.   int GetSpeed() {return m_Speed;}
  21.   void SetSpeed(int value) {m_Speed = value;}
  22.   const wxColour & GetColour() {return m_Colour;}
  23.   void SetColour(const wxColour & value) {m_Colour = value;}
  24. };
* This source code was highlighted with Source Code Highlighter.

Таких символов у нас на экране должно быть много и, соответственно, для хранения всего этого добра нам необходим массив:
  1. #include <wx/dynarray.h>
  2. ...
  3. WX_DECLARE_OBJARRAY(MatrixSymbol, MatrixSymbolArray);
  4. ...
  5. #include <wx/arrimpl.cpp>
  6.  
  7. WX_DEFINE_OBJARRAY(MatrixSymbolArray);
* This source code was highlighted with Source Code Highlighter.

Лепим графический интерфейс


Отлично, подготовительный этап мы закончили, теперь можно приступать к реализации графического интерфейса.
Создадим новый компонент, унаследованный от wxWindow, и добавим в него в виде переменной-члена класса массив объектов MatrixSymbol, а также метод, инициализирующий этот массив значениями:
  1. class MatrixCanvas: public wxWindow
  2. {  
  3.   ...
  4.   void InitMatrix();
  5.   ...
  6.   MatrixSymbolArray m_Symbols;
  7.   ...
  8. };
  9. void MatrixCanvas::InitMatrix()
  10. {
  11.   int width(0), height(0);
  12.   int sw(0), sh(0);
  13.   GetTextExtent(wxT("0"), &sw, &sh);
  14.   GetClientSize(&width, &height);
  15.   m_Symbols.Clear();
  16.   for(int x = 0; x < width; x += sw+2)
  17.   {
  18.     m_Symbols.Add(MatrixSymbol(
  19.       wxPoint(x, 0),
  20.       rand()%2 ? wxT('0') : wxT('1'),
  21.       3+rand()%5,
  22.       wxColour(0, rand()%200+56, 0)));
  23.   }
  24. }
* This source code was highlighted with Source Code Highlighter.

Что у нас делает метод InitMatrix()? В зависимости от размеров клиентской области компонента в массив добавляется некоторое количество объектов MatrixSymbol со случайными координатами, отображаемым значением «0» или «1», и различными цветами (выбирается случайная градация зеленого).
Теперь нам нужно обеспечить отображение символов на экране. Для этого создадим обработчики событий wxEVT_PAINT и wxEVT_ERASE_BACKGROUND.
  1. BEGIN_EVENT_TABLE( MatrixCanvas, wxWindow )
  2.   EVT_PAINT( MatrixCanvas::OnPaint )
  3.   EVT_ERASE_BACKGROUND( MatrixCanvas::OnEraseBackground )
  4. END_EVENT_TABLE()
  5.  
  6. void MatrixCanvas::OnPaint( wxPaintEvent& event )
  7. {
  8.   wxBufferedPaintDC dc(this);
  9.   dc.SetBackground(wxBrush(GetBackgroundColour()));
  10.   dc.Clear();
  11.   wxFont font = GetFont();
  12. #if defined(__WXWINCE__)
  13.   int fontSize = 14;
  14. #else
  15.   int fontSize = 48;
  16. #endif
  17.   font.SetPointSize(fontSize);
  18.   dc.SetFont(font);
  19.   dc.SetTextForeground(wxColour(00, 20, 00));
  20.   dc.DrawLabel(wxT("http://wxwidgets.info"),
  21.     wxRect(0, 0, dc.GetSize().GetWidth(), dc.GetSize().GetHeight()),
  22.     wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL);
  23.   dc.SetFont(GetFont());
  24.   for(size_t i = 0; i < m_Symbols.Count(); i++)
  25.   {
  26.     dc.SetTextForeground(m_Symbols[i].GetColour());
  27.     dc.DrawText(wxString::Format(wxT("%c"), m_Symbols[i].GetSymbol()),
  28.       m_Symbols[i].GetPosition());
  29.   }
  30. }
  31.  
  32. void MatrixCanvas::OnEraseBackground( wxEraseEvent& event )
  33. {
  34. }
* This source code was highlighted with Source Code Highlighter.

Обработчик события wxEVT_ERASE_BACKGROUND пустой (без вызова event.Skip()). Это обеспечит нам перерисовку компонента без мерцания.
В обработчике события wxEVT_PAINT у нас создается контекст устройства, устанавливается цвет фона равный цвету фона нашего компонента, затем происходит очистка (это равноценно заливке цветом). После этого в центре компонента отрисовывается надпись и затем в цикле происходит отрисовка всех символов из массива.
Далее нам необходимо добавить обработчик события wxEVT_SIZE для того чтобы при изменении размера компонента символы отображались по всей ширине. В обработчике мы просто будем вызывать метод InitMatrix(), который заполняет массив символами.
  1. BEGIN_EVENT_TABLE( MatrixCanvas, wxWindow )
  2.   ...
  3.   EVT_SIZE( MatrixCanvas::OnSize )
  4. END_EVENT_TABLE()
  5. ...
  6. void MatrixCanvas::OnSize( wxSizeEvent& event )
  7. {
  8.   InitMatrix();
  9.   Refresh();
  10. }
* This source code was highlighted with Source Code Highlighter.

Так, отображения символов мы добились, но нам еще необходимо сделать так чтобы символы перемещались по экрану. Код, обеспечивающий перемещение символов по экрану, лучше всего выполнять в обработчике события таймера.
  1. class MatrixCanvas: public wxWindow
  2. {  
  3.   ...
  4.   wxTimer * m_MovementTimer;
  5. };
  6. MatrixCanvas::MatrixCanvas(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style)
  7. {
  8.   Init();
  9.   Create(parent, id, pos, size, style);
  10. }
  11.  
  12. bool MatrixCanvas::Create(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style)
  13. {
  14.   wxWindow::Create(parent, id, pos, size, style);
  15.   CreateControls();
  16.   return true;
  17. }
  18.  
  19. MatrixCanvas::~MatrixCanvas()
  20. {
  21.   wxDELETE(m_MovementTimer);
  22. }
  23.  
  24. void MatrixCanvas::Init()
  25. {
  26.   m_PreviewMode = false;
  27. }
  28.  
  29. void MatrixCanvas::CreateControls()
  30. {  
  31.   this->SetForegroundColour(wxColour(0, 255, 0));
  32.   this->SetBackgroundColour(wxColour(0, 0, 0));
  33.   int timerID = wxNewId();
  34.   m_MovementTimer = new wxTimer(this, timerID);
  35.   Connect(timerID, wxEVT_TIMER,
  36.     wxTimerEventHandler(MatrixCanvas::OnMovementTimer));
  37.   InitMatrix();
  38.   Refresh();
  39.   m_MovementTimer->Start(30);
  40. }
  41.  
  42. void MatrixCanvas::OnMovementTimer( wxTimerEvent & event )
  43. {
  44.   for(size_t i = 0; i < m_Symbols.Count(); i++)
  45.   {
  46.     int y = m_Symbols[i].GetPosition().y + m_Symbols[i].GetSpeed();
  47.     if(y > GetClientSize().GetHeight())
  48.     {
  49.       y = -20;
  50.       m_Symbols[i].SetSpeed(3+rand()%5);
  51.       m_Symbols[i].SetColour(wxColour(0, rand()%200+56, 0));
  52.       m_Symbols[i].SetSymbol(rand()%2 ? wxT('0') : wxT('1'));
  53.     }
  54.     m_Symbols[i].SetPosition(wxPoint(
  55.       m_Symbols[i].GetPosition().x, y));
  56.   }
  57.   Refresh();
  58. }
* This source code was highlighted with Source Code Highlighter.

Как видно, в методе CreateControls() создается таймер и с помощью метода Connect() ему назначается обработчик события. В деструкторе таймер удаляется.
Отлично. Скринсейвер может работать в обычном режиме и в режиме предварительного просмотра. В обычном режиме нам необходимо обеспечить реакцию на действия пользователя, а именно на нажатия клавиш, а также на клик мышкой. Для этого мы создадим переменную-член класса bool m_PreviewMode и, в зависимости от ее значения, в обработчиках событий нажатия клавиш и нажатия кнопок мыши будем закрывать главную форму приложения.
  1. void MatrixCanvas::OnMouse( wxMouseEvent& event )
  2. {
  3.   if(event.LeftDown() || event.MiddleDown() || event.RightDown())
  4.   {
  5.     if(!m_PreviewMode)
  6.     {
  7.       wxFrame * frame = wxDynamicCast(wxTheApp->GetTopWindow(), wxFrame);
  8.       if(frame) frame->Close();
  9.     }
  10.   }
  11. }
  12.  
  13. void MatrixCanvas::OnChar( wxKeyEvent& event )
  14. {
  15.   if(!m_PreviewMode)
  16.   {
  17.     wxFrame * frame = wxDynamicCast(wxTheApp->GetTopWindow(), wxFrame);
  18.     if(frame) frame->Close();
  19.   }
  20. }
* This source code was highlighted with Source Code Highlighter.

Собственно, на этом работа над компонентом завершена. Теперь надо поместить его на главную форму:
  1. void MatrixEffectMainFrame::CreateControls()
  2. {  
  3.   MatrixEffectMainFrame* itemFrame1 = this;
  4.  
  5.   wxBoxSizer* itemBoxSizer2 = new wxBoxSizer(wxVERTICAL);
  6.   itemFrame1->SetSizer(itemBoxSizer2);
  7.  
  8.   m_Canvas = new MatrixCanvas( itemFrame1, ID_CANVAS, wxDefaultPosition, wxSize(100, 100), wxNO_BORDER );
  9.   itemBoxSizer2->Add(m_Canvas, 1, wxGROW, 0);
  10. }
* This source code was highlighted with Source Code Highlighter.

Вообще супер. На этом работу над графическим интерфейсом мы закончим.


Обработка параметров командной строки


Итак, теперь нам осталась самая интересная часть работы, а именно обработка параметров командной строки.
Скринсейвер в Windows может запускаться с тремя различными аргументами командной строки:
  • “/s” или “/S” – непосредственно для запуска скринсейвера
  • “/c:” – для вызова окна настройки скринсейвера, где — численное представление дескриптора родительского окна.
    “/p:” – для запуска скринсейвера в режиме предварительного просмотра, где — численное представление дескриптора родительского окна.

    Для обработки аргументов командной строки в wxWidgets существует класс wxCmdLineParser. Его мы и будем использовать.
    1. bool wxMatrixEffectApp::OnInit()
    2. {
    3. #if defined(__WXMSW__) && !defined(__WXWINCE__)
    4.   wxCmdLineParser parser(argc, argv);
    5.   parser.AddSwitch(wxT("S"), wxEmptyString,
    6.     wxEmptyString, wxCMD_LINE_PARAM_OPTIONAL);
    7.   parser.AddSwitch(wxT("s"), wxEmptyString,
    8.     wxEmptyString, wxCMD_LINE_PARAM_OPTIONAL);
    9.   parser.AddOption(wxT("c"), wxEmptyString,
    10.     wxEmptyString, wxCMD_LINE_VAL_NUMBER, wxCMD_LINE_PARAM_OPTIONAL);
    11.   parser.AddOption(wxT("p"), wxEmptyString,
    12.     wxEmptyString, wxCMD_LINE_VAL_NUMBER, wxCMD_LINE_PARAM_OPTIONAL);
    13.   if(parser.Parse(false) == 0)
    14.   {
    15.     long parentHWND(0);
    16.     if(parser.Found(wxT("S")) || parser.Found(wxT("s")))
    17.     {
    18.       MatrixEffectMainFrame* mainWindow =
    19.         new MatrixEffectMainFrame( NULL );
    20.       mainWindow->ShowFullScreen(true);
    21.       return true;
    22.     }
    23.     else if(parser.Found(wxT("c")))
    24.     {
    25.       wxMessageBox(
    26.         _("No settings for this screensaver. For more information visit wxwidgets.info"));
    27.       return false;
    28.     }
    29.     else if(parser.Found(wxT("p"), &parentHWND))
    30.     {
    31.       wxWindow * parent = new wxWindow;
    32.       parent->SetHWND((HWND)parentHWND);
    33.       RECT r;
    34.       GetWindowRect((HWND)parent->GetHWND(), &r);
    35.       MatrixCanvas* mainWindow = new MatrixCanvas(
    36.         parent, ID_MATRIXEFFECTMAINFRAME,
    37.         wxPoint(0,0), wxSize(r.right-r.left, r.bottom-r.top),
    38.         wxNO_BORDER);
    39.       mainWindow->SetPreviewMode(true);
    40.       SetTopWindow(mainWindow);
    41.       mainWindow->Show(true);
    42.       return true;
    43.     }
    44.   }
    45.   return false;
    46. #else
    47.   MatrixEffectMainFrame* mainWindow =
    48.     new MatrixEffectMainFrame( NULL );
    49.   SetTopWindow(mainWindow);
    50. #if defined(__WXWINCE__)
    51.   mainWindow->Show(true);
    52. #else
    53.   mainWindow->ShowFullScreen(true);
    54. #endif
    55.   return true;
    56. #endif
    57. }
    * This source code was highlighted with Source Code Highlighter.

    Как видно из кода, в методе OnInit() класса приложения мы создаем объект wxCmdLineParser, добавляем описания всех возможных параметров:
    • Метод AddSwitch() указывает что командная строка может содержать аргумент вида /<argument> или --<argument> без дополнительных параметров.
    • Метод AddOption() указывает, что командная строка может содержать аргумент вида /<argument>=<value> или --= или /<argument>:<value>, т.е. параметр командной строки, содержащий значение (строковое, численное, дату).

      После настройки объекта wxCmdLineParser происходит вызов метода Parse(), который проверяет соответствие командной строки указанным настройкам.
      Затем происходит проверка наличия параметров.
      • Если программа запущена с параметром “/s” или “/S”, то отображается главное окно приложения.
      • Если программа запущена с параметром “/c”, то отображается окно сообщения о том, что наш скринсейвер не требует настройки.
      • Если программа запущена с параметром “/p”, то извлекается дескриптор родительского окна, из него создается объект wxWindow и вместо главного окна приложения создается компонент MatrixCanvas, который размещается на родительском окне.

      Еще раз посмотрим на код, создающий wxWindow из дескриптора окна:
      wxWindow * parent = new wxWindow;
      parent->SetHWND((HWND)parentHWND);

      На самом деле это очень полезный прием и может быть использован для взаимодействия wxWidgets-приложений с приложениями, написанными с помощью других библиотек.
      Ну вот, собственно и все. В результате мы получим что-то вроде этого:


      PDF-версия статьи
      Исходный код примера, проект для Win32 и Windows Mobile, исполняемый файл для Win32.
Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
+10
Comments4

Articles