Иногда кажется, что изучать что-то новое и потом это новое использовать в работе очень сложно. Но довольно часто новые, ранее незнакомые инструменты могут быть очень полезными.

Сегодня мы поговорим о том, как собрать библиотеку wxWidgets, а точнее ее порт wxWinCE, который используется для разработки приложений, работающих под управлением Windows Mobile и значительно упрощает процесс разработки GUI-приложений для карманных компьютеров и смартфонов под упралвением этой ОС. Для того чтобы собрать wxWinCE мы будем использовать Visual Studio 2008.

Итак, для начала нам необходимо загрузить исходный код wxWidgets с официального сайта. Сделать это можно здесь. На странице доступны различные версии дистрибутива wxWidgets, а также пакеты для различных ОС. На данный момент последней версией является 2.8.8. Лучше всего будет загрузить пакет wxALL (пакет, содержащий исходный код wxWidgets для всех поддерживаемых платформ и операционных систем).

После того как исходный код библиотеки загружен, распаковываем его и переходим в папку wxWidgets-2.8.8/build/wince. Здесь находятся файлы проектов для wxWinCE.

Открываем файл проекта wx_mono.vcp. При открытии Visual Studio предложит преобразовать файл проекта к новому формату.

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

В этом посте для примера будет использована платформа Pocket PC 2003 и Windows Mobile 6 Professional. Почему выбраны именно эти платформы? Pocket PC 2003 выбрана как самая старая. Приложения, собранные под эту платформу будут также работать под управлением более поздних версий Windows Mobile. Windows Mobile 6 Professional выбрана как самая новая.

Итак, открываем окно свойств проекта и переходим в раздел Configuration Properties -> General. В этом разделе в свойстве Output Directory устанавливаем значения:

  • Для платформы Pocket PC 2003 – “..\..\lib\evc_armv4_lib”
  • Для платформы Windows Mobile 6 Professional – “..\..\lib\evc_armv4t_lib”

Сделать это нужно для обеих конфигураций, Debug и Release.

Переходим в раздел Librarian -> General и в свойстве Output File устанавливаем значение $(OutDir)\$(ProjectName).lib для Release конфигурации и $(OutDir)\$(ProjectName)d.lib для Debug конфигурации.

Для платформы Windows Mobile 6 Professional переходим в раздел C/C++ -> Advanced и в свойстве Compile for Architecture устанавливаем значение ARM4T (QRarch4t).

Идем в раздел C/C++ -> Code Generation и в свойстве Enable C++ Exceptions устанавливаем значение No. Необходимо помнить, что сделать это нужно для обеих конфигураций и для обеих платформ.

Пытаемся собрать библиотеку, жмем Build. И что мы видим? Ошибки!!! Первое с чем прийдется столкнуться, это ошибка вида

—— Build started: Project: wx_mono, Configuration: Debug Pocket PC 2003 (ARMV4) ——
Creating ..\..\lib\evc_armv4_lib\winced\wx\msw\rcdefs.h
“clarm.exe” не является внутренней или внешней
командой, исполняемой программой или пакетным файлом.
Project : error PRJ0019: A tool returned an error code from “Creating ..\..\lib\evc_armv4_lib\winced\wx\msw\rcdefs.h”
Build log was saved at “file://e:\wxWidgets-2.8.8\build\wince\Pocket PC 2003 (ARMV4)\Debug\BuildLog.htm”
wx_mono – 1 error(s), 0 warning(s)
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

Лечится она просто, необходимо повторно запустить сборку проекта.

Смотрим дальше, ошибка вида

e:\wxWidgets-2.8.8\include\wx/aui/floatpane.h(33) : error C2504: ‘wxMiniFrame’ : base class undefined
..\..\src\aui\framemanager.cpp(2453) : error C2039: ‘SetTransparent’ : is not a member of ‘wxAuiFloatingFrame’
e:\wxWidgets-2.8.8\include\wx/aui/floatpane.h(32) : see declaration of ‘wxAuiFloatingFrame’

Лечится она тоже довольно просто, открываем файл floatpane.h и меняем декларацию макроса wxAuiFloatingFrameBaseClass чтобы оно выглядел так:

#if defined( __WXMSW__ ) || defined( __WXMAC__ ) ||  defined( __WXGTK__ )
#include "wx/minifram.h"
#define wxAuiFloatingFrameBaseClass wxFrame
#else
#define wxAuiFloatingFrameBaseClass wxFrame
#endif

Следующее, ошибка вида

..\..\src\msw\window.cpp(5965) : error C3861: ‘VkKeyScan’: identifier not found

лечится добавлением функции-заглушки


unsigned int VkKeyScan(int)
{
return 0;
}

Хотелось бы отметить что в версии 2.8.7 библиотеки этой ошибки нет и если уж совсем не хочется что-то править в исходниках wxWidgets, то можно воспользоваться версией 2.8.7.

Ну вот, теперь можно собирать библиотеку. Собираем Debug и Release конфигурацию для обеих платформ.

Дальше, для удобства использования wxWinCE в проектах, нам необходимо добавить переменную окружения WXWIN и присвоить ей в качестве значения путь папке с исходным кодом wxWidgets.

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

Previous ArticleNext Article
Технический директор IT-Dimension, компании-разработчика кросс-платформенного программного обеспечения

This post has 40 Comments

40
  1. Радуешь, спасибо:)

    А почему полная студия, а не экспресс? для большинства было бы куда более политически правильным:)

  2. А Windows Mobile SDK не поддерживает работу с Express-студией, оно даже не ставится если у тебя установлена только Express. Такова политика Microsoft.

  3. щет. это значит что бесплатно и лицензионно чисто не получится писать под винмобайл или я ошибаюсь?

  4. Ну.. где-то так.. без студии оно ставиться не хочет.

  5. T-Rex:
    У меня пара вопросов. Наткнулся собственно на этот блог, так как появилась необходимость писать под WM (и при этом, чтоб под 6й работало).
    Скачал собственно wxWidgets 2.8.9 (.8.8 небыло, погуглил – нашел несколько мертвых ссылок на китайском)
    В самом начале настройки появились проблемы – платформы Windows Mobile 6 Professional вообще нет. Пока плюнул, полез в настройки – полное отсутствие раздела Configuration Properties > General, соответственно ничего не компилит.
    Не подскажете советом? Мыло я указал.

  6. Для того чтобы полатформа Windows Mobile 6 появилась в списке доступных в Visual Studio надо скачать Windows Mobile 6 SDK и установить его.
    ЗЫ: Там кстати для 2.8.9 немного по-другому надо сорцы править при сборке. Если возникнут проблемы, пиши сюда.

  7. [quote]Там кстати для 2.8.9 немного по-другому надо сорцы править при сборке. Если возникнут проблемы, пиши сюда.[/quote]

    Собсна вот 🙂

    1>..\..\src\aui\auibar.cpp(745) : error C2039: ‘SetBitmap’ : is not a member of ‘wxMenuItem’
    1> ..\..\include\wx/msw/menuitem.h(27) : see declaration of ‘wxMenuItem’

  8. обернуть в #if defined(__WXWINCE__) && (CEVER < 0x500)

    #endif
    вроде для более старших версий чем 2003 есть иконка. если вдруг нету (я не помню), то просто оставить про __WXWINCE__

  9. Спасибо, помогло. На пути к wxWidget под ВыньМоб встал следующий эррор:
    1>..\..\src\msw\filedlg.cpp(441) : error C2065: ‘FNERR_INVALIDFILENAME’ : undeclared identifier

    Пофиксил вот так:
    #define FNERR_INVALIDFILENAME 0x3002
    Правильно?

  10. я просто #ifdef’ами заэкранировал. но если с редефайном пашет то гут.

  11. Статья очень полезная, давно искал такое.

    вопрос по поводу ошибки
    1>..\..\src\aui\auibar.cpp(745) : error C2039: ‘SetBitmap’ : is not a member of ‘wxMenuItem’

    Что именно там надо обернуть в #if … #endif ?

  12. т.к. при любой попытке обернуть код выводит такой еррор:

    Error 1 fatal error C1012: unmatched parenthesis : missing ‘)’ c:\wxWidgets-2.8.9\src\aui\auibar.cpp 743

  13. В auibar.cpp в методе ShowDropDown

    wxMenuItem* m = new wxMenuItem(&menuPopup, item.GetId(), text, item.GetShortHelp());
    #ifndef __WXWINCE__
    m->SetBitmap(item.GetBitmap());
    #endif

  14. хм… пересоберу потом, т.к. недождавшись ответа просто закоментировал
    //m->SetBitmap(item.GetBitmap());
    и компилятор съел(прога тоже сумела запуститься кстати)

  15. хммм… а эта инструкция только для 2.8.8?

    А то 2.8.10 примерно по инструкции вроде собралась, но глючит (сообщения в wm5,wm6 не доставляются в функции-обработчики, ни через Connect, ни через карты сообщений а в эмуляторе PPC2003 всё нормально)

  16. 2.8.9 нормально собралась. 2.8.10 не пробовал, пока не было необходимости.
    А какие у вас евенты не работают? UpdateUI вроде вобще на WM не отрабатывает

  17. А все евенты не доходят. Может, я называю это евентами неправильно?

    Вот скопировал минимальное приложение, там в меню файл пункт exit. выбираю его, а он не работает. даже отладчиком проверил:внутрь OnExit не заходит.Даже если я карту евентов макросами делаю, а не коннектом – один хрен. Так под wm5,а под PPC2003 всё вроде работает.

    А кстати собрался 2.8.10 прекрасно, исходники не требовалось править вообще. Вот только работает фигово:)

  18. етитская сила! собрал 2.8.9, всё работает как надо!!!

    Собрал 2.8.10 на 100%-свежей машине – тот же баг под wm5.

    Вывод: мерзкий глюк!!! А так как такой мерзкий глюк до сих пор никто не поймал и интернет молчит, значит wxWinCE 2.8.10 мало кто использует для программирования winCE. Жаль:(

    Интересно кстати, есть ли wx под айфон и под нокия s60.

  19. Для iPhone пока нет, но из того что вычитал в wxBlog’е, работа над этим ведется, а точне работа над портом wxOSX, который долен собираться и под десктоп и под iPhone.

  20. По-моему на прошлом GSoC был в кандидатах такой проект, вроде никто не взялся. Вобще все порты можно посмотреть в репозитории SVN. Там на офф. сайте есть адрес репозитория.

  21. Собрал свое приложение для WM 2003 с WX 2.8.10. Нажатие кнопок в тулбаре не вызывает событий, те же обработчики “привязанные” к опциям меню работают ОК 🙁

  22. Ммм? Какие это “те же самые”?
    Для меню надо прописывать макрос
    EVT_MENU() а для кнопки EVT_BUTTON()

  23. Для кнопок в тулбаре и меню события одинаковые – EVT_MENU.

    Да баг это, баг в 2.8.10. Понаменяли в frame.cpp и не проверили для wince.

  24. Вот тут описано лекарство:

    групс точка гугл точка ком/group/wx-dev/browse_thread/thread/dbaabd7260e51faa

    Там про четыре строчки. Сам бы не додумался. Работает.

  25. +1, сегодня пересобрал свое добро все под 2.8.10, очень огорчился что менюшки не работают. Даже странно как-то.
    За лекарство спасибо, посмотрю тоже.

  26. hello t-rex
    what values should be in General-> Output Directory for WinCE 5.0 and Mobile 5.0 ?
    and I´ve got this error after fix floatpane.h and window.cpp: fatal error LNK1104: cannot open file ‘$ (OutDir) \ $ (ProjectName) d.lib’ mae for PocketPC2003, do you know where I´m wrong?

  27. Привет. Делал все как ты описал в статье, только у меня wxWidgets-2.9.0. Выдает следующие ошибки:
    Warning 1 warning C4530: C++ exception handler used, but unwind semantics are not enabled. Specify /EHsc

    Error 2 error C3861: ‘strnlen’: identifier not found

    Подскажи как быть

  28. Доброго времени суток. У меня вопрос на счет менюшек для версии 2.8.10. Они видимо как у многих не работают. Читал про лекарство описанное выше, добавил четыре строчки в файле frame.cpp, но менюшки так и не ожили. Кто нибудь разобрался с этой проблемой? Как оживить меню?

  29. Спасибо за совет, я сделал как там написано, и не помогло. Кто нибудь пробовал добавлять четыре строчки? Заработало? Лично у меня нет.

  30. а тупо на 2.8.9 перейти не? она стабильная и надёжная.

  31. “четыре строчки” работают! Я же писал выше. Попробуйте еще повнимательнее. Это “запарка обыкновенная” 🙂
    То есть в состоянии неуверенности из-за наложения нескольких багов можно подумать что не работает нечто вполне работоспособное 🙂
    Потом разберешься спокойно и все станет OK. 🙂

  32. Что то я не догоняю, скачал wxWidgets 2.9.0, распаковал, открыл папку /build/wince, а там нет проектов! Только папка missing. Ладно, скачал транк из репозитория – тоже нету проектов? Что делать, куда бечь?

  33. Теоретически, там есть CMakeLists.txt и из него с помощью CMake можно погенерить все проекты для WM. Но я непробовал. Но оно должно работать.

  34. Собрал библиотеку, сделал тестовое приложение с одной формой на wxFormBuilder, но русский язык не хочет отображать, хотя в свойствах проекта программы и библиотеки включен юникод, и при компиляции библиотеки в файле setup.h значение директивы wxUSE_UNICODE поменял на 1 (0 тоже пробовал).

    Есть ли какие-то идеи, как с этим справиться?

  35. Давайте кусок кода с русскими буквами.
    Проверяли кодировку файлов в исходниках?

Leave a Reply

Your email address will not be published. Required fields are marked *

П.

Пишем мобильную игру на wxWidgets

Введение

В этот раз речь пойдет о разработке мобильных приложений, а если быть точным, то мобильных игр, с библиотекой wxWidgets (порт wxWinCE).
О том, как собрать wxWidgets для разработки приложений для Windows Mobile я уже писал ранее здесь. Как создать простейшее приложение с wxWinCE, рассказано в этой статье.
Здесь и далее по тексту подразумевается, что читатель уже может самостоятельно создать простейшее приложение с wxWinCE, а также настроить параметры сборки для PocketPC и Smartphone.

Каркас приложения

Так случилось, что я решил попробовать себя в написании мобильных игр. После небольшого исследования пришел к выводу, что в простейшем случае для этой задачи вполне может подойти архитектура Документ/Представление (Document/View).

О создании приложений, использующих архитектуру Документ/Представление на wxWidgets, я писал ранее (первая, вторая, третья часть). Т.е. для начала нам необходимо получить: приложение с графическим интерфейсом, главная форма которого содержит канву, на которой, собственно, происходит отрисовка сцены. Кажданя новая игра представляет собой пару документ/представление. Класс документа содержит информацию о текущем состоянии игры, класс представления обеспечивает игровую логику. Канва кроме, непосредственно, отображения сцены обеспечивает поддержку double-buffering’а.
HabraSnakeDocument.h

#ifndef _HABRA_SNAKE_DOCUMENT_H
#define _HABRA_SNAKE_DOCUMENT_H

#include <wx/wx.h>
#include <wx/docview.h>

class HabraSnakeDocument : public wxDocument
{
	DECLARE_DYNAMIC_CLASS(HabraSnakeDocument)
public:
	HabraSnakeDocument();
};

#endif

HabraSnakeDocument.cpp

#include "HabraSnakeDocument.h"

IMPLEMENT_DYNAMIC_CLASS(HabraSnakeDocument, wxDocument)

HabraSnakeDocument::HabraSnakeDocument()
{
}

HabraSnakeView.h

#ifndef _HABRA_SNAKE_VIEW_H
#define _HABRA_SNAKE_VIEW_H

#include <wx/wx.h>
#include <wx/docview.h>

class HabraSnakeView : public wxView
{
	DECLARE_DYNAMIC_CLASS(HabraSnakeView)
public:
	HabraSnakeView();
	virtual void OnDraw(wxDC* dc);	
	virtual void OnUpdate(wxView *sender, wxObject *hint = (wxObject *) NULL);
	virtual bool OnClose(bool deleteWindow = true);
};

#endif

HabraSnakeView.cpp

#include "HabraSnakeView.h"
#include "HabraSnakeMainFrame.h"
#include "HabraSnakeCanvas.h"

IMPLEMENT_DYNAMIC_CLASS(HabraSnakeView, wxView)

HabraSnakeView::HabraSnakeView()
{
	do 
	{
		HabraSnakeMainFrame * mainFrame = 
			wxDynamicCast(wxTheApp->GetTopWindow(), HabraSnakeMainFrame);
		if(!mainFrame) break;
		HabraSnakeCanvas * canvas = mainFrame->m_Canvas;
		SetFrame(canvas);
		canvas->SetView(this);
		canvas->Refresh();
	} 
	while (false);
}

void HabraSnakeView::OnDraw(wxDC* dc)
{
}

void HabraSnakeView::OnUpdate(wxView *sender, wxObject *hint)
{
	GetFrame()->Refresh();
}

bool HabraSnakeView::OnClose(bool deleteWindow)
{
	if (!GetDocument()->Close())
	{
		return false;
	}
	HabraSnakeCanvas * frame = wxDynamicCast(GetFrame(), HabraSnakeCanvas);
	if(frame)
	{
		frame->SetView(NULL);
		frame->Refresh();
	}
	SetFrame(NULL);
	Activate(false);
	return true;
}

HabraSnakeMainFrame.h

#ifndef _HABRASNAKEMAINFRAME_H_
#define _HABRASNAKEMAINFRAME_H_

#include "wx/docview.h"

class HabraSnakeCanvas;

#define ID_HABRASNAKEMAINFRAME 10000
#define ID_FOREIGN 10007
#define SYMBOL_HABRASNAKEMAINFRAME_STYLE wxCAPTION|wxRESIZE_BORDER|wxSYSTEM_MENU|wxCLOSE_BOX
#define SYMBOL_HABRASNAKEMAINFRAME_TITLE _("HabraSnake")
#define SYMBOL_HABRASNAKEMAINFRAME_IDNAME ID_HABRASNAKEMAINFRAME
#define SYMBOL_HABRASNAKEMAINFRAME_SIZE wxSize(400, 300)
#define SYMBOL_HABRASNAKEMAINFRAME_POSITION wxDefaultPosition

class HabraSnakeMainFrame: public wxDocParentFrame
{    
    DECLARE_CLASS( HabraSnakeMainFrame )
    DECLARE_EVENT_TABLE()

public:
    HabraSnakeMainFrame( wxDocManager *manager, wxFrame *parent, 
        wxWindowID id = SYMBOL_HABRASNAKEMAINFRAME_IDNAME, 
        const wxString& caption = SYMBOL_HABRASNAKEMAINFRAME_TITLE, 
        const wxPoint& pos = SYMBOL_HABRASNAKEMAINFRAME_POSITION, 
        const wxSize& size = SYMBOL_HABRASNAKEMAINFRAME_SIZE, 
        long style = SYMBOL_HABRASNAKEMAINFRAME_STYLE );
    bool Create( wxDocManager *manager, wxFrame *parent, 
        wxWindowID id = SYMBOL_HABRASNAKEMAINFRAME_IDNAME, 
        const wxString& caption = SYMBOL_HABRASNAKEMAINFRAME_TITLE, 
        const wxPoint& pos = SYMBOL_HABRASNAKEMAINFRAME_POSITION, 
        const wxSize& size = SYMBOL_HABRASNAKEMAINFRAME_SIZE, 
        long style = SYMBOL_HABRASNAKEMAINFRAME_STYLE );
    ~HabraSnakeMainFrame();
    void Init();
    void CreateControls();

    void OnABOUTClick( wxCommandEvent& event );
    void OnEXITClick( wxCommandEvent& event );
    wxBitmap GetBitmapResource( const wxString& name );
    wxIcon GetIconResource( const wxString& name );
    HabraSnakeCanvas* m_Canvas;
};

#endif

HabraSnakeMainFrame.cpp

#include "wx/wxprec.h"

#ifdef __BORLANDC__
#pragma hdrstop
#endif

#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif

#include "HabraSnakeMainFrame.h"
#include "HabraSnakeCanvas.h"
// #include "AboutDialog.h"

IMPLEMENT_CLASS( HabraSnakeMainFrame, wxDocParentFrame )

BEGIN_EVENT_TABLE( HabraSnakeMainFrame, wxDocParentFrame )
    EVT_MENU( wxID_ABOUT, HabraSnakeMainFrame::OnABOUTClick )
    EVT_MENU( wxID_EXIT, HabraSnakeMainFrame::OnEXITClick )
END_EVENT_TABLE()

HabraSnakeMainFrame::HabraSnakeMainFrame( wxDocManager *manager, 
        wxFrame *parent, wxWindowID id, const wxString& caption, 
        const wxPoint& pos, const wxSize& size, long style )
    : wxDocParentFrame( manager, parent, id, caption, pos, size, style )
{
    Init();
    Create( manager, parent, id, caption, pos, size, style );
}

bool HabraSnakeMainFrame::Create( wxDocManager *manager, wxFrame *parent, 
        wxWindowID id, const wxString& caption, const wxPoint& pos, 
        const wxSize& size, long style )
{
    SetParent(parent);
    CreateControls();
    Centre();
    return true;
}

HabraSnakeMainFrame::~HabraSnakeMainFrame()
{
}

void HabraSnakeMainFrame::Init()
{
    m_Canvas = NULL;
}

void HabraSnakeMainFrame::CreateControls()
{
    HabraSnakeMainFrame* itemDocParentFrame1 = this;

    wxMenuBar* menuBar = new wxMenuBar;
    wxMenu* itemMenu3 = new wxMenu;
    itemMenu3->Append(wxID_NEW, _("New\tCtrl+N"), _T(""), wxITEM_NORMAL);
    itemMenu3->AppendSeparator();
    itemMenu3->Append(wxID_ABOUT, _("About..."), _T(""), wxITEM_NORMAL);
    itemMenu3->AppendSeparator();
    itemMenu3->Append(wxID_EXIT, _("Exit\tAlt+F4"), _T(""), wxITEM_NORMAL);
    menuBar->Append(itemMenu3, _("File"));
    itemDocParentFrame1->SetMenuBar(menuBar);

    m_Canvas = new HabraSnakeCanvas( itemDocParentFrame1, ID_FOREIGN, 
        wxDefaultPosition, wxSize(100, 100), wxNO_BORDER );

#if defined(__WXMSW__)
	SetIcon(wxIcon(wxT("wxICON_AAA")));
#endif
#if !defined(__WXWINCE__)
	SetSize(350, 450);
	SetMinSize(GetSize());
#endif
	m_Canvas->RefreshScene();
}

wxBitmap HabraSnakeMainFrame::GetBitmapResource( const wxString& name )
{
    wxUnusedVar(name);
    return wxNullBitmap;
}

wxIcon HabraSnakeMainFrame::GetIconResource( const wxString& name )
{
    wxUnusedVar(name);
    return wxNullIcon;
}

void HabraSnakeMainFrame::OnABOUTClick( wxCommandEvent& event )
{
    // AboutDialog * dlg = new AboutDialog(this);
    // dlg->ShowModal();
    // dlg->Destroy();
}

void HabraSnakeMainFrame::OnEXITClick( wxCommandEvent& event )
{
    Close();
}

HabraSnakeApp.h

#ifndef _HABRASNAKEAPP_H_
#define _HABRASNAKEAPP_H_

#include "wx/image.h"
#include "HabraSnakeMainFrame.h"

class wxDocManager;

class HabraSnakeApp: public wxApp
{    
    DECLARE_CLASS( HabraSnakeApp )
    DECLARE_EVENT_TABLE()
	wxDocManager * m_DocManager;
public:
    HabraSnakeApp();
    void Init();
    virtual bool OnInit();
    virtual int OnExit();
};

DECLARE_APP(HabraSnakeApp)

#endif

HabraSnakeApp.cpp

#include "wx/wxprec.h"

#ifdef __BORLANDC__
#pragma hdrstop
#endif

#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif

#include "HabraSnakeApp.h"
#include "HabraSnakeDocument.h"
#include "HabraSnakeView.h"

IMPLEMENT_APP( HabraSnakeApp )

IMPLEMENT_CLASS( HabraSnakeApp, wxApp )

BEGIN_EVENT_TABLE( HabraSnakeApp, wxApp )
END_EVENT_TABLE()

HabraSnakeApp::HabraSnakeApp()
{
    Init();
}

void HabraSnakeApp::Init()
{
	m_DocManager = new wxDocManager;
	m_DocManager->SetMaxDocsOpen(1);
	new wxDocTemplate(m_DocManager, _("HabraSnake Game"), 
		wxEmptyString, wxEmptyString,
		wxEmptyString, wxT("HabraSnakeDoc"), wxT("HabraSnakeView"), 
		CLASSINFO(HabraSnakeDocument), CLASSINFO(HabraSnakeView));
}

bool HabraSnakeApp::OnInit()
{
#if wxUSE_LIBJPEG
	wxImage::AddHandler(new wxJPEGHandler);
#endif
#if wxUSE_XPM
	wxImage::AddHandler(new wxXPMHandler);
#endif
	HabraSnakeMainFrame* mainWindow = 
		new HabraSnakeMainFrame( m_DocManager,  NULL);
	SetTopWindow(mainWindow);
	mainWindow->Show(true);
    return true;
}

int HabraSnakeApp::OnExit()
{
	wxDELETE(m_DocManager);
	return wxApp::OnExit();
}

Такое большое количество кода в классе главной формы и в классе приложения не должно пугать. 90% этого кода сгенерировано редактором DialogBlocks. Руками пришлось дописать всего несколько строк. В классе главной формы это следующие строки в методе CreateControls():

HabraSnakeMainFrame.cpp

#if defined(__WXMSW__)
	SetIcon(wxIcon(wxT("wxICON_AAA")));
#endif
#if !defined(__WXWINCE__)
	SetSize(350, 450);
	SetMinSize(GetSize());
#endif
	m_Canvas->RefreshScene();
	wxCommandEvent event(wxEVT_COMMAND_MENU_SELECTED, wxID_NEW);
	ProcessEvent(event);
	m_Canvas->SetFocus();
}

В классе приложения это код методов Init() и OnExit().
Кроме, собственно, исходного кода класса главной формы и класса приложения, редактор DialogBlocks генерирует файл ресурсов, который обязательно необходимо добавить в проект. Без него приложение для Windows Mobile не будет работать корректно. Иконка wxICON_AAA описана в указанном выше файле ресурсов.

Немного больше внимания необходимо уделить компоненту, на котором будет происходить отрисовка сцены:
HabraSnakeCanvas.h

#ifndef _HABRASNAKECANVAS_H_
#define _HABRASNAKECANVAS_H_

#include <wx/docview.h>

class HabraSnakeCanvas;

#define ID_HABRASNAKECANVAS 10001
#define SYMBOL_HABRASNAKECANVAS_STYLE wxSIMPLE_BORDER
#define SYMBOL_HABRASNAKECANVAS_IDNAME ID_HABRASNAKECANVAS
#define SYMBOL_HABRASNAKECANVAS_SIZE wxSize(100, 100)
#define SYMBOL_HABRASNAKECANVAS_POSITION wxDefaultPosition

class HabraSnakeCanvas: public wxWindow
{    
    DECLARE_DYNAMIC_CLASS( HabraSnakeCanvas )
    DECLARE_EVENT_TABLE()
public:
    HabraSnakeCanvas();
    HabraSnakeCanvas(wxWindow* parent, wxWindowID id = ID_HABRASNAKECANVAS, 
	const wxPoint& pos = wxDefaultPosition, 
	const wxSize& size = wxSize(100, 100), long style = wxSIMPLE_BORDER);
    bool Create(wxWindow* parent, wxWindowID id = ID_HABRASNAKECANVAS, 
	const wxPoint& pos = wxDefaultPosition, 
	const wxSize& size = wxSize(100, 100), long style = wxSIMPLE_BORDER);
    ~HabraSnakeCanvas();
    void Init();
    void CreateControls();

    void OnSize( wxSizeEvent& event );
    void OnPaint( wxPaintEvent& event );
    void OnEraseBackground( wxEraseEvent& event );
    void OnLeftDown( wxMouseEvent& event );

    wxView * GetView() const { return m_View ; }
    void SetView(wxView * value) { m_View = value ; }

    wxBitmap GetBitmapResource( const wxString& name );
    wxIcon GetIconResource( const wxString& name );
	void DrawBackground( wxDC &dc );
	void RefreshScene();
private:
	wxView * m_View;
	wxBitmap m_BackgroundBitmap;
	wxBitmap m_DoubleBufferBitmap;
	wxMemoryDC m_DoubleBufferDC;
};

#endif

HanraSnakeCanvas.cpp

#include "wx/wxprec.h"

#ifdef __BORLANDC__
#pragma hdrstop
#endif

#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif

#include "HabraSnakeCanvas.h"
#include <wx/mstream.h>

#include "background_jpg.h"

IMPLEMENT_DYNAMIC_CLASS( HabraSnakeCanvas, wxWindow )

BEGIN_EVENT_TABLE( HabraSnakeCanvas, wxWindow )
    EVT_SIZE( HabraSnakeCanvas::OnSize )
    EVT_PAINT( HabraSnakeCanvas::OnPaint )
    EVT_ERASE_BACKGROUND( HabraSnakeCanvas::OnEraseBackground )
    EVT_LEFT_DOWN( HabraSnakeCanvas::OnLeftDown )
END_EVENT_TABLE()

HabraSnakeCanvas::HabraSnakeCanvas()
{
    Init();
}

HabraSnakeCanvas::HabraSnakeCanvas(wxWindow* parent, wxWindowID id, 
	const wxPoint& pos, const wxSize& size, long style)
{
    Init();
    Create(parent, id, pos, size, style);
}

bool HabraSnakeCanvas::Create(wxWindow* parent, wxWindowID id, 
	const wxPoint& pos, const wxSize& size, long style)
{
    wxWindow::Create(parent, id, pos, size, style);
    CreateControls();
    return true;
}

HabraSnakeCanvas::~HabraSnakeCanvas()
{
}

void HabraSnakeCanvas::Init()
{
    m_View = NULL;
}

void HabraSnakeCanvas::CreateControls()
{    
	wxMemoryInputStream in(background_jpg, sizeof(background_jpg));
	m_BackgroundBitmap = wxBitmap(wxImage(in));
#if defined(__WXWINCE__)
	wxFont font = GetFont();
	font.SetPointSize(8);
	SetFont(font);
#endif
}

void HabraSnakeCanvas::OnPaint( wxPaintEvent& event )
{
    wxPaintDC dc(this);
	if(m_DoubleBufferDC.IsOk())
	{
		dc.Blit(0, 0, m_DoubleBufferDC.GetSize().GetWidth(), 
			m_DoubleBufferDC.GetSize().GetHeight(), &m_DoubleBufferDC, 0, 0);
	}
	else
	{
		dc.SetBackground(wxBrush(GetBackgroundColour()));
		dc.Clear();
	}
}

void HabraSnakeCanvas::DrawBackground( wxDC &dc )
{
	if(m_BackgroundBitmap.IsOk())
	{
		int w(0), h(0);
		dc.GetSize(&w, &h);
		for(int x = 0; x < w; x += m_BackgroundBitmap.GetWidth())
		{
			for(int y = 0; y < h; y += m_BackgroundBitmap.GetHeight())
			{
				dc.DrawBitmap(m_BackgroundBitmap, x, y);
			}
		}
	}
}

void HabraSnakeCanvas::OnEraseBackground( wxEraseEvent& event )
{
}

wxBitmap HabraSnakeCanvas::GetBitmapResource( const wxString& name )
{
    wxUnusedVar(name);
    return wxNullBitmap;
}

wxIcon HabraSnakeCanvas::GetIconResource( const wxString& name )
{
    wxUnusedVar(name);
    return wxNullIcon;
}

void HabraSnakeCanvas::OnLeftDown( wxMouseEvent& event )
{
	SetFocus();
}

void HabraSnakeCanvas::RefreshScene()
{
	if(m_View)
	{
		DrawBackground(m_DoubleBufferDC);
		m_DoubleBufferDC.SetFont(GetFont());
		m_DoubleBufferDC.SetBrush(wxBrush(GetBackgroundColour()));
		m_DoubleBufferDC.SetPen(*wxBLACK_PEN);
		m_View->OnDraw(&m_DoubleBufferDC);
	}
}

void HabraSnakeCanvas::OnSize( wxSizeEvent& event )
{
	m_DoubleBufferDC.SelectObject(wxNullBitmap);
	m_DoubleBufferBitmap = wxBitmap(event.GetSize().GetWidth(), 
		event.GetSize().GetHeight());
	m_DoubleBufferDC.SelectObject(m_DoubleBufferBitmap);
	RefreshScene();
	Refresh();
}

Рассмотрим базовый функционал компонента:

  • В компоненте вручную реализован double-buffering. Такой подход обусловлен тем, что при использовании класса wxPaintDC для отрисовки сцены будет заметно мерцание. Использование wxBufferedPaintDC не очень приемлемо для мобильных устройств т.к. при каждом вызове обработчика события wxEVT_PAINT будет заново создаваться объект wxBufferedPaintDC, а он, в свою очередь, каждый раз будет заново создавать изображение в памяти, размером равным размеру компонента. Это очень негативно сказывается на ресурсоемкости приложения. Более подробную информацию о реализации double-buffering’а в wxWidgets вручную можно почитать здесь.
  • При изменении размеров компонента (метод OnSize()) заново пересоздается изображение для double-buffering’а и вызывается метод RefreshScene().
  • Метод RefreshScene() отрисовывает фоновое изображение и вызывает метод OnDraw() класса представления.
  • Класс компонента не привязан к какому-то особому классу представления и может быть использован для работы с различными типами представлений (это значит, что с таким подходом этот компонент можно использовать для отображения сцен из различных игр).
  • Обработчик события wxEVT_KEY_DOWN (нажатия клавиш) передает объект wxKeyEvent классу представления. Это значит, что обработкой нажатия клавиш должен заниматься класс представления.
  • Обработчик события нажатия левой кнопки мыши (метод OnLeftDown()) устанавливает фокус на наш компонент (это необходимо для того чтобы компонент смог получать события нажатия клавиш, т.к. только окно, имеющее фокус ввода может получать сообщения о нажатии клавиш).
  • Пустой обработчик события wxEVT_ERASE_BACKGROUND нам необходим для того чтобы избежать мерцания.
  • В обработчике события wxEVT_PAINT происходит копирование изображения из double-buffer’а на wxPaintDC.
  • Таким образом, мы получили универсальный класс канвы, который может использоваться для отрисовки сцен различных игр с подобной архитектурой приложения.

В приведенном выше коде есть ссылка на файл background_jpg.h. Это файл, содержащий jpeg-изображение, преобразованное в c-style массив байт с помощью утилиты Bin2C:
background_jpg.h

#ifndef _BACKGROUND_JPG_H
#define _BACKGROUND_JPG_H

unsigned char background_jpg[] ={
	0xFF,0xD8,0xFF,0xE0,0x00,0x10,0x4A,0x46,0x49,0x46,0x00,0x01,0x01,0x00,
	...
	0x94,0xFF,0xD9
};

#endif

Добавим немного логики

Пора добавить логику в наше приложение. Начнем с изменения класса документа. Класс документа у нас должен хранить состояние игры (текущее положение змейки, положение “цели”, признак окончания игры, количество набранных очков).
HabraSnakeDocument.h

#ifndef _HABRA_SNAKE_DOCUMENT_H
#define _HABRA_SNAKE_DOCUMENT_H

#include <wx/wx.h>
#include <wx/docview.h>
#include <wx/list.h>

/// Список точек
WX_DECLARE_LIST(wxPoint, wxPointList);

class HabraSnakeDocument : public wxDocument
{
	/// Размер игрового поля
	static wxSize GameFieldSize;
	DECLARE_DYNAMIC_CLASS(HabraSnakeDocument)
	/// "Сегменты" змейки
	wxPointList m_Snake;
	/// Координаты "цели" для змейки
	wxPoint m_Target;
	/// Признак окончания игры
	bool m_GameOver;
	/// Количество очков
	unsigned int m_Score;
	/// Инициализировать змейку
	void InitSnake();
	/// Пересоздать "цель"
	void CreateNewTarget();
	void ProcessNewPoint(wxPoint * newPoint, bool & targetFound);
	/// Проверка, можно ли переместиться в указанную точку
	bool CanMoveTo(wxPoint & pos);
	/// Проверка, не умерла ли змейка 
	/// Если змейка зохавала кусок себя, то все, каюк
	bool IsSnakeDead();
public:
	HabraSnakeDocument();
	/// Возвращает список сегментов змейки
	const wxPointList & GetSnake() const;
	/// Возвращает координаты "цели"
	const wxPoint & GetTarget() const;
	/// Выполнить шаг влево
	bool MoveLeft(bool & targetFound);
	/// Выполнить шаг вверх
	bool MoveUp(bool & targetFound);
	/// Выполнить шаг вправо
	bool MoveRight(bool & targetFound);
	/// Выполнить шаг вниз
	bool MoveDown(bool & targetFound);
	/// Возвращает признак завершения игры
	bool IsGameOver();
	/// Возвращает количество очков
	unsigned int GetScore();
	/// Возвращает размер игрового поля
	static wxSize GetGameFieldSize();
};

#endif

HabraSnakeDocument.cpp

#include "HabraSnakeDocument.h"
#include <wx/listimpl.cpp>

WX_DEFINE_LIST(wxPointList)

IMPLEMENT_DYNAMIC_CLASS(HabraSnakeDocument, wxDocument)

wxSize HabraSnakeDocument::GameFieldSize = wxSize(25, 50);

HabraSnakeDocument::HabraSnakeDocument()
{
	srand(time(NULL));
	m_GameOver = false;
	m_Score = 0;
	CreateNewTarget();
	InitSnake();
}

void HabraSnakeDocument::InitSnake()
{
	m_Snake.DeleteContents(true);
	m_Snake.Append(new wxPoint(15,25));
	m_Snake.Append(new wxPoint(16,25));
	m_Snake.Append(new wxPoint(17,25));
}

wxSize HabraSnakeDocument::GetGameFieldSize()
{
	return HabraSnakeDocument::GameFieldSize;
}

const wxPointList & HabraSnakeDocument::GetSnake() const
{
	return m_Snake;
}

bool HabraSnakeDocument::IsGameOver()
{
	return m_GameOver;
}

unsigned int HabraSnakeDocument::GetScore()
{
	return m_Score;
}

const wxPoint & HabraSnakeDocument::GetTarget() const
{
	return m_Target;
}

void HabraSnakeDocument::CreateNewTarget()
{
	m_Target.x = rand() % GameFieldSize.GetWidth();
	m_Target.y = rand() % GameFieldSize.GetHeight();
}

bool HabraSnakeDocument::IsSnakeDead()
{
	do 
	{
		wxPointList::Node * firstNode = m_Snake.GetFirst();
		if(!firstNode) break;
		wxPoint * head = firstNode->GetData();
		if(!head) break;
		for(wxPointList::Node * node = firstNode->GetNext(); 
			node; node = node->GetNext())
		{
			wxPoint * point = node->GetData();
			if(*head == *point) return true;
		}
	} 
	while (false);
	return false;
}

bool HabraSnakeDocument::CanMoveTo(wxPoint & pos)
{
	do 
	{
		wxPointList::Node * second = m_Snake.Item(1);
		if(!second) break;
		wxPoint * secondPoint = second->GetData();
		if(!secondPoint) break;
		if((secondPoint->x != pos.x) || (secondPoint->y != pos.y)) break;
		return false;
	} 
	while (false);
	return true;
}

Итак, что мы тут добавили:

  • Поле m_Snake содержит список координат сегментов змейки.
  • Поле m_Target содержит координаты “цели” для змейки (цель – это та штука, которую змейка должна “съесть” чтобы вырасти).
  • Поле m_GameOver – это признак завершения игры. Содержит true, если игра закончена.
  • Поле m_Score содержит количество набранных очков.
  • Метод InitSnake() инициализирует список сегментов змейки начальными значениями (изначально в змейке 3 сегмента).
  • Метод CreateNewTarget() устанавливает случайные значения координат «цели» в пределах игрового поля.
  • Метод IsSnakeDead() выполняет проверку положения первого сегмента змейки. Если координаты первого сегмента равны координатам любого другого сегмента, метод возвращает true, в противном случае возвращает false.
  • Метод CanMoveTo() выполняет проверку возможности перемещения змейки в указанную ячейку на игровом поле. Перемещение возможно только «вперед», т.е. указанная ячейка должна быть по направлению движения змейки. Если по указанным координатам находится второй сегмент змейки, то в эту ячейку перемещение невозможно и метод возвращает false.

Далее нам необходимо научить змейку перемещаться. Для этого у нас есть методы MoveLeft(), MoveUp(), MoveRight(), MoveDown().
HabraSnakeDocument.cpp

bool HabraSnakeDocument::MoveLeft(bool & targetFound)
{
	do
	{
		if(m_GameOver) break;
		wxPointList::Node * node = m_Snake.GetFirst();
		if(!node) break;
		wxPoint * point = node->GetData();
		if(!point) break;
		wxPoint * newPoint = new wxPoint(
			(point->x-1 < 0) ? HabraSnakeDocument::GameFieldSize.GetWidth()-1 :
			 point->x-1, point->y);
		if(!CanMoveTo(*newPoint))
		{
			wxDELETE(newPoint);
			break;
		}
		ProcessNewPoint(newPoint, targetFound);
		if(IsSnakeDead()) m_GameOver = true;
		return true;
	}
	while(false);
	return false;
}

bool HabraSnakeDocument::MoveUp(bool & targetFound)
{
	do
	{
		if(m_GameOver) break;
		wxPointList::Node * node = m_Snake.GetFirst();
		if(!node) break;
		wxPoint * point = node->GetData();
		if(!point) break;
		wxPoint * newPoint = new wxPoint(
			point->x,
			(point->y-1 < 0) ? HabraSnakeDocument::GameFieldSize.GetHeight()-1 : 
			point->y-1);
		if(!CanMoveTo(*newPoint))
		{
			wxDELETE(newPoint);
			break;
		}
		ProcessNewPoint(newPoint, targetFound);
		if(IsSnakeDead()) m_GameOver = true;
		return true;
	}
	while(false);
	return false;
}

bool HabraSnakeDocument::MoveRight(bool & targetFound)
{
	do
	{
		if(m_GameOver) break;
		wxPointList::Node * node = m_Snake.GetFirst();
		if(!node) break;
		wxPoint * point = node->GetData();
		if(!point) break;
		wxPoint * newPoint = new wxPoint(
			(point->x+1 > HabraSnakeDocument::GameFieldSize.GetWidth()-1) ? 0 : 
			point->x+1, point->y);
		if(!CanMoveTo(*newPoint))
		{
			wxDELETE(newPoint);
			break;
		}
		ProcessNewPoint(newPoint, targetFound);
		if(IsSnakeDead()) m_GameOver = true;
		return true;
	}
	while(false);
	return false;
}

bool HabraSnakeDocument::MoveDown(bool & targetFound)
{
	do
	{
		if(m_GameOver) break;
		wxPointList::Node * node = m_Snake.GetFirst();
		if(!node) break;
		wxPoint * point = node->GetData();
		if(!point) break;
		wxPoint * newPoint = new wxPoint(
			point->x,
			(point->y+1 > HabraSnakeDocument::GameFieldSize.GetHeight()-1) ? 0 : 
			point->y+1);
		if(!CanMoveTo(*newPoint))
		{
			wxDELETE(newPoint);
			break;
		}
		ProcessNewPoint(newPoint, targetFound);
		if(IsSnakeDead()) m_GameOver = true;
		return true;
	}
	while(false);
	return false;
}

Каждый из приведенных выше четырех методов проверяет возможность перемещения змейки в новую ячейку и, если перемещение возможно, вызывает метод ProcessNewPoint(), код которого приведен ниже:
HabraSnakeDocument.cpp

void HabraSnakeDocument::ProcessNewPoint(wxPoint * newPoint, bool & targetFound)
{
	if(*newPoint == m_Target)
	{
		m_Score += 10;
		targetFound = true;
		CreateNewTarget();
	}
	else
	{
		targetFound = false;
		m_Snake.DeleteNode(m_Snake.GetLast());
	}
	m_Snake.Insert((size_t)0, newPoint);
}

Метод ProcessNewPoint() проверяет равенство координат новой ячейки координатам “цели” и затем, если координаты новой ячейки и координаты цели совпадают, то просто добавляет эту ячейку в начало списка сегментов змейки. “цель” в этом случае перемещается в новую ячейку со случайно выбранными координатами (как уже говорилось ранее, для этого используется метод CreateNewTarget()). Если же координаты не совпадают, то последний сегмент змейки удаляется.

Отрисовка сцены

Работу над классом документа, который содержит состояние игры, мы закончили, теперь нам необходимо организовать визуальное представление состояния игры, т.е. отрисовку сцены.
Эту задачу у нас будет выполнять класс представления:
HabraSnakeView.h

class HabraSnakeView : public wxView
{
	wxRect GetGameFieldRectangle(const wxSize & size, int rectSize);
	void DrawGrid( wxDC* dc, wxRect &rect, int rectSize );
	void DrawSnake(wxDC * dc, wxRect & rect, int rectSize, const wxPointList & snake);
	void DrawTarget(wxDC * dc, wxRect & rect, int rectSize, const wxPoint & target);
	void DrawGameOverLabel(wxDC * dc, const wxRect & rect);
	void DrawScore(wxDC * dc, unsigned int score);
	void RefreshScene();
public:
	...
	virtual void OnDraw(wxDC* dc);	
	...
}; 

HabraSnakeView.cpp

void HabraSnakeView::OnDraw(wxDC* dc)
{
	int rectSize = dc->GetSize().GetWidth()/
            HabraSnakeDocument::GetGameFieldSize().GetWidth();
	rectSize = wxMin(rectSize, 
		(int)dc->GetSize().GetHeight()/
                HabraSnakeDocument::GetGameFieldSize().GetHeight());
	wxRect rect = GetGameFieldRectangle(dc->GetSize(), rectSize);
	DrawGrid(dc, rect, rectSize);
	do 
	{
		HabraSnakeDocument * document = wxDynamicCast(GetDocument(), HabraSnakeDocument);
		if(!document) break;
		DrawScore(dc, document->GetScore());
		if(!document->IsGameOver())
		{
			DrawSnake(dc, rect, rectSize, document->GetSnake());
			DrawTarget(dc, rect, rectSize, document->GetTarget());
		}
		else
		{
			DrawGameOverLabel(dc, rect);
		}
	} 
	while (false);
}

void HabraSnakeView::DrawScore(wxDC * dc, unsigned int score)
{
	int w(0), h(0);
	dc->GetSize(&w, &h);
	wxString scoreLabel = _("SCORE");
	wxString scoreValue = wxString::Format(wxT("%u"), score);
	wxSize textSize = dc->GetTextExtent(scoreLabel);
	wxRect scoreRect(wxPoint(0,0), textSize);
	textSize = dc->GetTextExtent(scoreValue);
	scoreRect.SetWidth(wxMax(scoreRect.GetWidth(), textSize.GetWidth()));
	scoreRect.SetHeight((int)(scoreRect.GetHeight() + (double)6 / (double)5 * 
		(double)textSize.GetHeight()));
	scoreRect.Inflate(5, 5);
#if defined(__WXWINCE__)
	int distance = 5;
#else
	int distance = 10;
#endif
	scoreRect.SetPosition(wxPoint(w - scoreRect.GetWidth() - distance, distance));
	dc->SetPen(*wxBLACK_PEN);
	dc->SetBrush(*wxWHITE_BRUSH);
	dc->DrawRectangle(scoreRect);
	scoreRect.Deflate(5, 5);
	dc->DrawLabel(scoreLabel, scoreRect, wxALIGN_TOP|wxALIGN_CENTER_HORIZONTAL);
	dc->DrawLabel(scoreValue, scoreRect, wxALIGN_BOTTOM|wxALIGN_CENTER_HORIZONTAL);
}

void HabraSnakeView::DrawGameOverLabel(wxDC * dc, const wxRect & rect)
{
	wxFont font = dc->GetFont();
	font.SetPointSize(12);
	dc->SetFont(font);
	dc->SetTextForeground(wxColour(127,0,0));
	wxString label = _("GAME OVER");
	wxSize textSize = dc->GetTextExtent(label);
	dc->SetBackground(wxBrush(wxColour(200,200,200)));
	dc->SetPen(*wxBLACK_PEN);
	wxRect labelRect(rect.GetLeft() + (rect.GetWidth()-textSize.GetWidth())/2 - 10,
		rect.GetTop() + (rect.GetHeight()-textSize.GetHeight())/2 - 10,
		textSize.GetWidth()+20, textSize.GetHeight()+20);
	dc->DrawRectangle(labelRect);
	dc->DrawLabel(label, labelRect, wxALIGN_CENTER);
}

void HabraSnakeView::DrawSnake(wxDC * dc, wxRect & rect, 
	int rectSize, const wxPointList & snake)
{
	dc->SetPen(*wxTRANSPARENT_PEN);
	dc->SetBrush(wxBrush(wxColour(0,0,127)));
	wxRect cell(0,0,rectSize, rectSize);
	for(wxPointList::Node * node = snake.GetFirst(); node; node = node->GetNext())
	{
		wxPoint * point = node->GetData();
		if(!point) continue;
		cell.SetPosition(wxPoint(
			rect.GetLeft()+point->x*rectSize, 
			rect.GetTop()+point->y*rectSize));
		dc->DrawRectangle(cell);
	}
}

void HabraSnakeView::DrawTarget(wxDC * dc, wxRect & rect, 
	int rectSize, const wxPoint & target)
{
	dc->SetPen(*wxTRANSPARENT_PEN);
	dc->SetBrush(wxBrush(wxColour(0,150,50)));
	wxRect cell(rect.GetLeft()+target.x*rectSize,rect.GetTop()+
		target.y*rectSize,rectSize, rectSize);
	dc->DrawRectangle(cell);
}

wxRect HabraSnakeView::GetGameFieldRectangle(const wxSize & size, int rectSize)
{
	wxRect rect(0, 0, HabraSnakeDocument::GetGameFieldSize().GetWidth()*rectSize+1,
		HabraSnakeDocument::GetGameFieldSize().GetHeight()*rectSize+1);
	rect.Offset((size.GetWidth()-rect.GetWidth())/2,
		(size.GetHeight()-rect.GetHeight())/2);
	return rect;
}

void HabraSnakeView::DrawGrid( wxDC* dc, wxRect &rect, int rectSize )
{
	dc->DrawRectangle(rect);
	dc->SetPen(wxPen(wxColour(192,192,192)));
	for(int i = 1; i < HabraSnakeDocument::GetGameFieldSize().GetWidth(); i++)
	{
		dc->DrawLine(rect.GetLeft()+i*rectSize, rect.GetTop()+1,
			rect.GetLeft()+i*rectSize, rect.GetBottom());
	}
	for(int i = 1; i < HabraSnakeDocument::GetGameFieldSize().GetHeight(); i++)
	{
		dc->DrawLine(rect.GetLeft()+1, rect.GetTop()+i*rectSize,
			rect.GetRight(), rect.GetTop()+i*rectSize);
	}
}

void HabraSnakeView::RefreshScene()
{
	HabraSnakeCanvas * frame = wxDynamicCast(GetFrame(), HabraSnakeCanvas);
	if(frame)
	{
		frame->RefreshScene();
		frame->Refresh();
	}
}

Краткое описание приведенных выше методов:

  • Метод GetGameFieldRectangle() возвращает объект wxRect, содержащий координаты игрового поля на канве (прямоугольник центрировано по горизонтали и по вертикали).
  • Метод RefreshScene() позволяет отрисовать double-buffer и обновить/перерисовать канву.
  • Метод DrawGrid() отрисовывает игровое поле, а также все ячейки на нем.
  • Метод DrawTarget() отображает “цель” на игровом поле.
  • Метод DrawSnake() отображает змейку на игровом поле.
  • Метод DrawGameOverLabel() отображает надпись “GAME OVER” когда игра закончена.
  • Метод DrawScore() отображает набранные очки в правом верхнем углу канвы.
  • Виртуальный метод OnDraw(), унаследованный от wxView, вызывается канвой для отрисовки сцены на double-buffer’е. В зависимости от состояния игры, он вызывает остальные методы отрисовки составных частей сцены.

Обработка событий

Кроме отрисовки, класс представления позволяет выполнять обработку событий, т.к. является производным от wxEvtHandler. В нашем случае нам необходимо обеспечить постоянное перемещение змейки, т.е. обрабатывать сообщения от таймера, а также обеспечить реакцию на нажатие клавиш:
HabraSnakeView.h

class HabraSnakeView : public wxView
{
	typedef bool (HabraSnakeDocument::* MovementFunction)(bool & targetFound);
	...
	wxDirection m_CurrentDirection;
	wxTimer * m_GameTimer;
	void CreateGameTimer();
	void DoMove(HabraSnakeDocument * document, 
		MovementFunction func, wxDirection direction);
	...
public:
	...
	DECLARE_EVENT_TABLE()
	void OnKeyDown(wxKeyEvent & event);
	void OnGameTimer(wxTimerEvent & event);
};

Мы добавили два новых поля в класс HabraSnakeView:

  • m_CurrentDirection – направление движения змейки.
  • m_GameTimer – таймер для реализации автоматического перемещение змейки.

HabraSnakeView.cpp

BEGIN_EVENT_TABLE(HabraSnakeView, wxView)
EVT_KEY_DOWN(HabraSnakeView::OnKeyDown)
END_EVENT_TABLE()

HabraSnakeView::HabraSnakeView()
: m_CurrentDirection(wxLEFT)
{
	do 
	{
		...
		CreateGameTimer();
	} 
	while (false);
} 

HabraSnakeView::~HabraSnakeView()
{
	wxDELETE(m_GameTimer);
} 

void HabraSnakeView::CreateGameTimer()
{
	long gameTimerID = wxNewId();
	m_GameTimer = new wxTimer(this, (int)gameTimerID);
	Connect(gameTimerID, wxEVT_TIMER, 
		wxTimerEventHandler(HabraSnakeView::OnGameTimer));
	m_GameTimer->Start(300);
}

void HabraSnakeView::DoMove(HabraSnakeDocument * document, 
	HabraSnakeView::MovementFunction func, wxDirection direction)
{
	bool targetFound(false);
	if((document->*func)(targetFound))
	{
		m_CurrentDirection = direction;
		if(targetFound)
		{
			int interval = 350 - 15 * 
				wxMin(20, document->GetSnake().GetCount()-3);
			m_GameTimer->Start(interval);
		}
	}
}

void HabraSnakeView::OnKeyDown(wxKeyEvent & event)
{
	do 
	{
		HabraSnakeDocument * document = 
			wxDynamicCast(GetDocument(), HabraSnakeDocument);
		if(!document) break;
		if(document->IsGameOver()) break;
		switch(event.GetKeyCode())
		{
		case WXK_LEFT:
		case WXK_NUMPAD_LEFT:
			if(m_CurrentDirection == wxLEFT) return;
			DoMove(document, &HabraSnakeDocument::MoveLeft, wxLEFT);
			break;
		case WXK_RIGHT:
		case WXK_NUMPAD_RIGHT:
			if(m_CurrentDirection == wxRIGHT) return;
			DoMove(document, &HabraSnakeDocument::MoveRight, wxRIGHT);
			break;
		case WXK_UP:
		case WXK_NUMPAD_UP:
			if(m_CurrentDirection == wxUP) return;
			DoMove(document, &HabraSnakeDocument::MoveUp, wxUP);
			break;
		case WXK_DOWN:
		case WXK_NUMPAD_DOWN:
			if(m_CurrentDirection == wxDOWN) return;
			DoMove(document, &HabraSnakeDocument::MoveDown, wxDOWN);
			break;
		default:
			event.Skip();
			return;
		}
	} 
	while (false);
	RefreshScene();
}

void HabraSnakeView::OnGameTimer(wxTimerEvent & event)
{
	do 
	{
		HabraSnakeDocument * document = 
			wxDynamicCast(GetDocument(), HabraSnakeDocument);
		if(!document) break;
		MovementFunction func = NULL;
		switch(m_CurrentDirection)
		{
		case wxLEFT:
			func = &HabraSnakeDocument::MoveLeft;
			break;
		case wxUP:
			func = &HabraSnakeDocument::MoveUp;
			break;
		case wxRIGHT:
			func = &HabraSnakeDocument::MoveRight;
			break;
		case wxDOWN:
			func = &HabraSnakeDocument::MoveDown;
			break;
		default:
			return;
		}
		if(func)
		{
			DoMove(document, func, m_CurrentDirection);
			RefreshScene();
		}
	} 
	while (false);
}
  • OnKeyDown() – обработчик нажатия клавиш. В нем, в зависимости от направления движения змейки выбирается соответствующая функция перемещения из класса HabraSnakeDocument и и затем вызывается метод DoMove().
  • OnGameTimer() – обработчик события от таймера.
  • DoMove() – этот метод вызывает функцию перемещения и, если при перемещении змейка добралась до “цели”, то уменьшает интервал работы таймера, тем самым увеличивая скорость перемещения змейки.

В классе канвы нам также необходимо добавить обработчик нажатия клавиш в котором мы просто будем перенаправлять событие на обработку классу представления.
HabraSnakeCanvas.h

class HabraSnakeCanvas: public wxWindow
{   
    ... 
    void OnKeyDown( wxKeyEvent& event );
};

HabraSnakeCanvas.cpp

void HabraSnakeCanvas::OnKeyDown( wxKeyEvent& event )
{
	if(m_View)
	{
		m_View->ProcessEvent(event);
	}
}

После всех перечисленных выше манипуляций мы должны получить такую структуру классов:

Для того чтобы при запуске приложения автоматически запускалась новая игра, добавим такие строки в метод CreateControls() класса главной формы:
HabraSnakeMainFrame.h

void HabraSnakeMainFrame::CreateControls()
{
	...
	wxCommandEvent event(wxEVT_COMMAND_MENU_SELECTED, wxID_NEW);
	ProcessEvent(event);
	m_Canvas->SetFocus();
}

Ну вот, запускаем приложение и получаем такую картинку:

Но это еще не все. Т.к. wxWidgets – это кросс-платофрменная библиотека, то после небольших манипуляций с настройками проекта можно собрать его для работы под управлением Windows для десктопов. Как собрать wxWidgets-приложение для работы под управлением Windows NT/2000/XP/Vista можно узнать здесь. В результате получим что-то подобное.


Но и это еще не все! Нашу игру можно собрать еще и под Linux.

Скачать исходный код игры, а также CAB-инсталлятор для Windows Mobile можно здесь.

W.

Windows® Marketplace for Mobile Developer Strategy

Сегодня Microsoft опубликовала информацию о том, как будет функционировать online-сервис продажи мобильных приложений Windows® Marketplace for Mobile.

Итак, информация к размышлению:

  1. Сколько будет зарабатівать разработчик на продаже своих приложений?
    • Разработчик будет получать 70% от продаж в Windows Marketplace for Mobile (на сколько я понимаю, процент сравним с AppStore от Apple).
    • Приложение может продаваться на 29 торговых площадках (markets) с ценовым разграничением по каждой из них.
    • Также приложение может распространяться бесплатно, т.е. в Windows Marketplace for Mobile можно будет запостить и бесплатные приложения.
  2. Что нужно для регистрации?
    • Информация о регистрации будет доступна чуть позже (весной). Прием приложений планируется начать к лету.
    • Разработчики смогут выкладывать 5 приложений ежегодно за $99. И еще прийдется платить по $99 за каждое дополнительное приложение.
    • Для студентов, участвующих в программе DreamSpark, цены будут значительно снижены.
  3. Что нужно для того, чтобы приложение попалов Marketplace?
    • Сказано, что значительное внимание будет уделено совместимости и корректной работе приложений на мобильных устройствах. Планируется организовать процесс сертификации и тестирования приложений, выкладываемых в Marketplace.
    • разработчикам будет предоставляться детальная информация о результатах сертификации на Windows Marketplace for Mobile developer portal.
  4. Что нужно для того, чтобы начать разработку для Windows Mobile?
    • Можно использовать Visual Studio и .NET Compact Framework 3.5 (я так понимаю, они это говорят в рекламных целях, C++ еще вроде никто не отменял).
    • Скачать Windows Mobile 6.0 SDK и ознакомиться с информацией на http://developer.windowsmobile.com.

Ознакомиться с пресс-релизом можно здесь.

Интервью с Inigo Lopez, Marketplace Product Manager: