Руководство по wxwidgets

Скачать PDF-версию (240 кб)

Я буду очень признателен, если вы будете сообщать обо всех орфографических и пунктуационных ошибках, которые встретите в тексте. Также желательно сообщать о всех не совсем ясно написанных частях текста.

Начало

В этой главе рассматривается структура простой программы, написанной с использованием wxWidgets.

Мы покажем запуск и процесс завершения wxWidgets-приложения, как показать главное окно и как обрабатывать команды от пользователя. На этом, следуя философии wxWidgets использовать только простые и красивые решения, мы главу и закончим. Для компиляции примеров вам возможно придется обратится к Приложению 1 Установка wxWidgets.

Небольшой пример приложения на базе wxWidgets

Рис. 2.1 показывает, как наше приложение будет выглядеть в операционной системе Windows.Минимальное приложение на wxWidgets показывает главное окно (класс wxFrame) со строками меню и статуса. Меню позволяет увидеть вам информацию О программе или выйти из программы. Это очень простое приложение, однако его вполне достаточно, чтобы проиллюстрировать некоторые базовые принципы построения приложений. Кроме того с накоплением опыта вы можете использовать этот пример, добавляя в него необходимую вам функциональность.

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

Каждое приложение на wxWidgets определяет свой собственный класс приложения, являющийся потомком от wxApp. В программе существует единственный экземпляр данного класса, который представляет из себя представление данного приложения. Обычно этот класс объявляет функцию OnInit, которая вызывается, когда библиотека wxWidgets готова запустить ваш код (функция является эквивалентом функции main языка C или WinMain в Win32-приложениях).Ниже дано минимально возможное объявление класса приложения:

// Объявляем класс приложения
class MyApp : public wxApp
{
public:
    // Вызывается при старте приложения
    virtual bool OnInit();
};

Реализация функции OnInit обычно создает по крайней мере одно окно, считывает параметры из командной строки, инициализирует нужные для работы структуры данных и выполняет другие действия, необходимые для запуска программы. Если функция возвращает true, то wxWidgets запускает петлю сообщений, которая обрабатывает ввод пользователя и запускает обработчик сообщений в случае необходимости. Если данная функция возвращает false, то wxWidgets корректно очищает свои внутренние структуры и завершает работу приложения.Простейшая реализация функции OnInit может иметь следующий вид:

// Инициализируем приложение
bool MyApp::OnInit()
{
    // Создаем главное окно приложения
    MyFrame *frame = new MyFrame(wxT("Minimal wxWidgets App"));

    // Показываем его
    frame->Show(true);

    // Запускаем петлю сообщений
    return true;
}

Реализация создает экземпляр нового класса окна MyFrame (мы определим этот класс позднее), показывает окно на экране и возвращает true, чтобы запустить петлю сообщений. Главные окна(такие как фреймы и диалоги), в отличие от дочерних, должны быть показаны сразу после создания.Заголовок главного окна передается конструктору, обернутым в макрос wxT(). В дальнейшем вы заметите, что этот макрос используется в большинстве примеров библиотеки wxWidgets и позволяет преобразовать строки и символы к правильному типу, что позволяет приложению компилироваться в режиме Unicode. Данный макрос является синонимом макроса _T(). Также в программах вы возможно встретите макрос _(), который говорит библиотеке, что строку можно перевести на другой язык. Обратитесь к Главе 16 Написание локализованных приложений за дополнительной информацией.Где же находится код, который создает экземпляр класса MyApp? wxWidgets делает это самостоятельно, но вы должны явно указать тип объекта, который необходимо создать. Поэтому вам необходимо добавить следующие строки в ваш файл с реализацией:

// Говорит wxWidgets, что надо создать объект MyApp
IMPLEMENT_APP(MyApp)

Без этого определения wxWidgets не сможет создать объект вашего приложения. Данный макрос также вставляет код, который проверяет, что объект является приложением и библиотека была скомпилирована с необходимыми опциями. Это позволяет wxWidgets сообщать о некоторых ошибках, которые впоследствии могли бы вызвать появление трудно выявляемых ошибок времени выполнения.Когда wxWidgets создает объект типа MyApp, результат сохраняется в глобальной переменной wxTheApp. Можно использовать эту переменную в вашей программе, однако лучше явно не обращаться к ней в своем коде. Вместо этого, после объявления вашего класса приложения поместите макрос

// Реализация MyApp& GetApp()
DECLARE_APP(MyApp)

после чего вы сможете использовать в программе функцию wxGetApp, которая возвращает ссылку на объект приложения типа MyApp.Совет: Даже если вы не используете макрос DECLARE_APP, вы все равно можете использовать переменную wxTheApp для доступа к функциям wxApp. Это позволяет избежать необходимости включения заголовочных файлов вашего приложения. Также это может быть полезно внутри кода, который не знает о специфичных классах приложения (например, при создании библиотек), а также для уменьшения времени компиляции.

Класс фрейма

Рассмотрим класс фрейма MyFrame. Фрейм является основным окном, которое содержит все остальные окна и обычно имеет меню и строку состояния. Вот простейший пример объявления класса для фрейма, которое мы разместим после объявления MyApp:

// Объявляем класс главного окна
class MyFrame : public wxFrame
{
public:
    //Конструктор
    MyFrame(const wxString& title);

    // Обработчики сообщений
    void OnQuit(wxCommandEvent& event);
    void OnAbout(wxCommandEvent& event);

private:
    // Этот класс перехватывает сообщения
    DECLARE_EVENT_TABLE()
};

Наш класс для фрейма содержит конструктор, два обработчика сообщений (связывающих пункты меню с C++ кодом) и макрос, который сообщает, что класс содержит обработчики сообщений.

Обработчики сообщений

Как вы уже знаете, функции обработки сообщений в MyFrame не являются виртуальными и не должны быть виртуальными. Тогда как же они вызываются? Ответ расположен в следующей таблице сообщений:

// Таблица сообщений для MyFrame
BEGIN_EVENT_TABLE(MyFrame, wxFrame)
    EVT_MENU(wxID_ABOUT, MyFrame::OnAbout)
    EVT_MENU(wxID_EXIT,  MyFrame::OnQuit)
END_EVENT_TABLE()

Таблица сообщений, помещенная в файл с реализацией класса, говорит wxWidgets каким образом связываются сообщения, поступающие от пользователя, и методы класса.В указанной выше таблице нажатие кнопки мыши на пунктах меню с идентификаторами wxID_EXIT и wxID_ABOUT направляется функциям MyFrame::OnQuit и MyFrame::OnAbout соответственно. Макрос EVT_MENU является одним из многих возможных макросов таблицы сообщений, которые вы можете использовать, чтобы указать wxWidgets какие типы сообщений направлять в функцию. Идентификаторы, используемые в нашем примере, являются предопределенными, но чаще всего вы должны вводить свои собственные с помощью перечислений (enum), констант или директив препроцессора (#define).Этот тип таблицы сообщений называется статическим, то есть не может быть изменен в процессе выполнения программы. В следующей главе мы расскажем, как создавать динамические обработчики сообщений.Пока мы разбираемся с таблицами сообщений, давайте посмотрим на две функции, которые у насиспользуются для обработки сообщений.

void MyFrame::OnAbout(wxCommandEvent& event)
{
    wxString msg;
    msg.Printf(wxT("Hello and welcome to %s"),
               wxVERSION_STRING);

    wxMessageBox(msg, wxT("About Minimal"),
                 wxOK | wxICON_INFORMATION, this);
}

void MyFrame::OnQuit(wxCommandEvent& event)
{
    // Уничтожаем фрейм
    Close();
}

Метод MyFrame::OnAbout показывает небольшое сообщение пользователю, когда тот выбирает из меню пункт About. Функция wxMessageBox принимает в качестве аргументов текст сообщения, заголовок окна, комбинацию стилей и указатель на родительское окно.Как это указано в таблице сообщений, функция MyFrame::OnQuit вызывается, когда пользователь выбирает в меню пункт Quit. Наш обработчик данного сообщения вызывает метод Closeдля уничтожения фрейма и таким образом завершает работу приложения, так как программасостоит всего из одного фрейма. На самом деле метод Close не уничтожает приложение, он просто генерирует сообщение wxEVT_CLOSE_WINDOW, обработчик которого по умолчанию уничтожает фрейм, используя для этого метод wxWindow::Destroy.Существуют другие пути для завершения работы приложения — пользователь может щелкнуть на кнопке закрытия, расположенной на фрейме, или выбрать пункт Close из системного меню (или меню оконного менеджера среды запуска). Когда метод OnQuit вызывается в этихслучаях? Ну если честно, то он не вызывается. В указанных случаях wxWidgets посылает фрейму сообщение wxEVT_CLOSE_WINDOW через вызов функции Close (подобно тому, как это делаем мы в методе OnQuit). wxWidgets по умолчанию перехватывает это сообщение и уничтожает окно. Ваше приложение может переопределить данное поведение и, например, запрашивать подтверждение у пользователя перед закрытием. Обратитесь к Главе 4 Окна за дополнительной информацией.В нашем примере этого делать не требовалось, но большая часть приложений реализует функцию OnExit в классе приложения для корректной очистки структур данных перед выходом. Обратите внимание, что эта функция вызывается только если OnInit возвратила true.

Создание фрейма

Наконец, у нас есть конструктор для фрейма, который инициализирует иконку для приложения, меню и строку состояния.

#include "mondrian.xpm"

MyFrame::MyFrame(const wxString& title)
       : wxFrame(NULL, wxID_ANY, title)
{
    // Устанавливаем иконку для фрейма
    SetIcon(wxIcon(mondrian_xpm));

    // Создаем меню
    wxMenu *fileMenu = new wxMenu;

    // Добавляем пункт "About" (о приложении), который
    // должен показывать маленькую помощь
    wxMenu *helpMenu = new wxMenu;
    helpMenu->Append(wxID_ABOUT, wxT("&About...tF1"),
                     wxT("Show about dialog"));

    fileMenu->Append(wxID_EXIT, wxT("E&xittAlt-X"),
                     wxT("Quit this program"));

    // Теперь добавляем созданное меню в строку меню...
    wxMenuBar *menuBar = new wxMenuBar();
    menuBar->Append(fileMenu, wxT("&File"));
    menuBar->Append(helpMenu, wxT("&Help"));

    // ... и присоединяем к фрейму
    SetMenuBar(menuBar);

    // Создаем строку состояния для красоты
    CreateStatusBar(2);
    SetStatusText(wxT("Welcome to wxWidgets!"));
}

Данный конструктор вызывает конструктор родительского класса с указанием родительского окна (NULL означает его отсутствие), идентификатором этого окна и заголовком. Вместо идентификатора внашем случае передается значение wxID_ANY, которое говорит wxWidgets, что идентификатор должен быть создан библиотекой самостоятельно. Таким образом, конструктор создает настоящее окно с помощью перенаправления запроса на создание к родительскому классу.Маленькие изображения и иконки могут быть помещены в программу с помощью мультиплатформенного формата XPM. Файл XPM представляет из себя обычный C++ код, а потому может быть включен так, как показано в нашем примере. Строка с SetIcon создает иконку в стеке, используя C++ переменную mondrian_xpm (определенную в файле mondrian.xpm) и связывает полученную иконку с фреймом.Далее создается меню. При добавлении элементов меню указывается их идентификатор (например, стандартный идентификатор wxID_ABOUT, как в нашем случае), текстовую метку для элемента меню и строка помощи, которая может быть показана в строке состояния. Внутри каждой метки можно поместить мнемоники (указатели для вызова пункта с клавиатуры) с помощью предшествующего букве амперсанда (&), а также указать акселератор, отделяя его от сообщения с помощью символа табуляции (t). Мнемоники позволяют пользователю выбрать пункт меню с помощью нажатия соответствующей клавиши во время навигации по меню. Акселератор — это комбинация клавиш (например, Alt+X), которая может быть использована для выполнения действия вообще без вызова меню.В конце конструктор создает строку статуса, состоящую из двух полей и расположенную снизу фрейма. Для первого поля устанавливается значение Welcome to wxWidgets!.

Текст программы

Объединим все кусочки вместе, чтобы увидеть как будет выглядеть программа целиком. Обычно я отделяю заголовочные файлы и файлы реализации, но для данного крошечного примера я объединил их в одином файле.

// Имя:         minimal.cpp
// Цель:        Пример минимального приложения на wxWidgets
// Автор:       Julian Smart

#include "wx/wx.h"

// Объявляем класс приложения
class MyApp : public wxApp
{
public:
    // Вызывается при старте приложения
    virtual bool OnInit();
};

// Объявляем класс главного окна
class MyFrame : public wxFrame
{
public:
    //Конструктор
    MyFrame(const wxString& title);

    // Обработчики сообщений
    void OnQuit(wxCommandEvent& event);
    void OnAbout(wxCommandEvent& event);

private:
    // Этот класс перехватывает сообщения
    DECLARE_EVENT_TABLE()
};

// Реализация MyApp& GetApp()
DECLARE_APP(MyApp)

// Говорит wxWidgets, что надо создать объект MyApp
IMPLEMENT_APP(MyApp)

// Инициализируем приложение
bool MyApp::OnInit()
{
    // Создаем главное окно приложения
    MyFrame *frame = new MyFrame(wxT("Minimal wxWidgets App"));

    // Показываем его
    frame->Show(true);

    // Запускаем петлю сообщений
    return true;
}

// Таблица сообщений для MyFrame
BEGIN_EVENT_TABLE(MyFrame, wxFrame)
    EVT_MENU(wxID_ABOUT, MyFrame::OnAbout)
    EVT_MENU(wxID_EXIT,  MyFrame::OnQuit)
END_EVENT_TABLE()

void MyFrame::OnAbout(wxCommandEvent& event)
{
    wxString msg;
    msg.Printf(wxT("Hello and welcome to %s"),
               wxVERSION_STRING);


    wxMessageBox(msg, wxT("About Minimal"),
                 wxOK | wxICON_INFORMATION, this);
}

void MyFrame::OnQuit(wxCommandEvent& event)
{
    // Уничтожаем фрейм
    Close();
}

#include "mondrian.xpm"

MyFrame::MyFrame(const wxString& title)
       : wxFrame(NULL, wxID_ANY, title)
{
    // Устанавливаем иконку для фрейма
    SetIcon(wxIcon(mondrian_xpm));

    // Создаем меню
    wxMenu *fileMenu = new wxMenu;

    // Добавляем пункт "About" (о приложении), который
    // должен показывать маленькую помощь
    wxMenu *helpMenu = new wxMenu;
    helpMenu->Append(wxID_ABOUT, wxT("&About...tF1"),
                     wxT("Show about dialog"));

    fileMenu->Append(wxID_EXIT, wxT("E&xittAlt-X"),
                     wxT("Quit this program"));

    // Теперь добавляем созданное меню в строку меню...
    wxMenuBar *menuBar = new wxMenuBar();
    menuBar->Append(fileMenu, wxT("&File"));
    menuBar->Append(helpMenu, wxT("&Help"));

    // ... и присоединяем к фрейму
    SetMenuBar(menuBar);

    // Создаем строку состояния для красоты
    CreateStatusBar(2);
    SetStatusText(wxT("Welcome to wxWidgets!"));



}

Компиляция и запуск программыДанный пример можно найти на прилагаемом CD-ROM в папке examples/chap02. Для компиляции его необходимо скопировать на ваш жесткий диск. В связи с тем, что невозможно создать makefile, который будет работать “из коробки” с любой средой программирования, которая возможно установлена у читателя, мы сделали проект для среды DialogBlocks с конфигурацией, подходящей для большинства платформ и компиляторов. Обратитесь к Приложению 3 Создание приложений с помощью DialogBlocks, чтобы настроить программу DialogBlocks для ее использования на вашем компьютере. Детали компиляции wxWidgets-приложений рассматриваются в Приложении 2 Сборка ваших собственных приложений на wxWidgets.Установите wxWidgets и DialogBlocks с предлагаемого CD. В системе Windows вам также необходимо установить подходящий компилятор (один из них присутствует на CD), если он у вас еще не установлен. После этого пропишите пути до библиотеки wxWidgets и вашего компилятора в настройках DialogBlocks на вкладке Paths и откройте файл examples/chap02/minimal.pjd. Выберете подходящую конфигурацию для вашего компилятора и платформы, например MinGW Debug или VC++ Debug (Windows), GCC Debug GTK+ (Linux) или GCC Debug Mac (Mac OS X). Далее нажмите кнопку Build and Run Project. Возможно среда спросит не хотите ли вы собрать библиотеку wxWidgets, если вы еще этого не сделали.Вы можете найти похожий пример в папке samples/minimal в дистрибутиве wxWidgets. Если вы не хотите использовать DialogBlocks, то вы можете просто скомпилировать указанный пример вместо нашего. Обратитесь к Приложению 1 Установка wxWidgets, чтобы узнать как собрать стандартные примеры wxWidgets.

Поток выполнения

Действия, происходящие при старте программы:

  • В зависимости от платформы запускается функция main, WinMain или эквивалентная ей (эта функция предоставляется библиотекой wxWidgets, а не вашим приложением). wxWidgets инициализирует внутренние структуры данных и создает экземпляр класса MyApp.
  • wxWidgets вызывает метод MyApp::OnInit, который создает экземпляр класса MyFrame.
  • Конструктор класса MyFrame создает окно через конструктор wxFrame и добавляет иконку, меню и строку состояния.
  • MyApp::OnInit показывает фрейм и возвращает true.
  • wxWidgets запускает петлю сообщений, ждет сообщения и вызывает соответствующий обработчик сообщений.

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

Итоги

Глава дает представление о том, как работает простейшее приложение на wxWidgets. Мы коснулись темы обработки сообщений, использования класса wxFrame инициализации приложения, создания меню и строки состояния. Однако, несмотря на сложность вашего реального приложения, базовые принципы его функционирования останутся теми же самыми, как и у нашего маленького примера. В следующей главе мы подробнее рассмотрим сообщения и методы их обработки.

Время на прочтение
31 мин

Количество просмотров 27K

Введение

Уже долгое время не пишу статьи о разработке, хотя сам процесс написания мне очень нравится и позволяет привести мысли в порядок.

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

В этот раз речь пойдет о создании кроссплатформенных приложений с плагинами на C++ с использованием библиотеки wxWidgets. Рассматриваться будут операционные системы Windows, Linux и OS X, как наиболее популярные.

Как обычно, первая часть будет обзорной, для того, чтобы снизить порог входа для читателей. Кому-то информация из первой части покажется очевидной (особенно то, что касается инструментария), но, все же, я считаю ее необходимой, ибо для новичков информация из первой части позволит с минимальными усилиями организовать процесс разработки.
Дальше — поэтапный разбор кода тестового приложения с пояснениями.

Инструментарий

wxWidgets

Для начала нам понадобятся:

Библиотека wxWidgets в исходных кодах. Я использую наиболее новые версии из SVN. Они, конечно, не без багов, зато в них реализован функционал, которого обычно не хватает в официальных релизах.

Исходный код можно взять здесь: http://svn.wxwidgets.org/svn/wx/wxWidgets/trunk

Более подробно о процессе сборки библиотеки можно почитать здесь: http://habrahabr.ru/post/123588/

Разница в процессе сборки, по сравнению с указанной выше статьей заключается лишь в том, что нужно использовать конфигурацию DLL Debug и DLL Release вместо Debug и Release. К тому же, обязательно необходимо чтобы в настройках всех проектов, входящих в дистрибутив wxWidgets, в параметре C/C++ -> Code Generation -> Runtime Library были указаны значения Multi-Threaded Debug DLL и Multi-Threaded DLL. Именно с «DLL» в конце. В этом случае у нас wxWidgets будет собрана в виде динамических библиотек и с динамическим CRT.

При сборке конфигураций DLL Debug и DLL Release может быть такое что не все библиотеки соберутся с первого раза. Все это из-за проблем с указанием зависимостей. Если не собралось, запускаем сборку еще раз. Обычно 2-3 итераций достаточно для того, чтоб получить полный комплект динамических библиотек.

Напомню также, что для работы с wxWidgets необходимо наличие переменной окружения %WXWIN% (для Windows), которая указывает на папку с исходными кодами wxWidgets. Для Linux и OS X достаточно выполнить configure && make && make install.

Параметры для configure:

  • Debug: configure --enable-shared --disable-static --enable-unicode --disable-compat28 --disable-final --enable-debug
  • Release: configure --enable-shared --disable-static --enable-unicode --disable-compat28 --enable-final --disable-debug

CMake

Для того, чтобы облегчить работу по созданию файлов проектов для различных платформ на разных рабочих машинах с разными настройками, будем использовать систему генерации проектов CMake, о которой, кстати, есть несколько неплохих обзорных статей на Хабре, например вот:

  • http://habrahabr.ru/post/155467/
  • http://habrahabr.ru/post/178839/
  • http://habrahabr.ru/post/132313/

В общем, CMake – это инструмент, с помощью которого на разных машинах мы сможем генерировать файлы проектов Visual Studio (Windows), Makefile/CodeBlocks (Linux), Makefile/XCode (OS X) с правильно прописанными путями к исходным кодам и сторонним библиотекам, что позволит нам избавиться от довольно большого объема лишней работы по настройке сборки.

Скачать CMake можно здесь: http://www.cmake.org/cmake/resources/software.html

Если вы собрали wxWidgets (Linux, OS X) с отладочной информацией, а потом хотите установить Release-версию, то надо сделать make uninstall для Debug-версии и вручную удалить файлы

  • /usr/local/bin/wx-config
  • /usr/local/bin/wxrc

Если указанные выше файлы не удалить вручную, то для Release-версии библиотеки будут использоваться настройки от Debug-версии. Приложение соберется, но не запустится.

Также надо иметь в виду тот факт, что если вы установили Debug-версию wxWidgets, то в Linux и OS X у вас, скорее всего, получится собрать только Debug-версию приложения. Это же касается и Release-версии. А все потому что CMake берет параметры компиляции и линковки из скрипта wx-config, который по умолчанию отдает параметры для одной текущей конфигурации. Или для Debug отдельно, или отдельно для Release.

Visual C++ (Windows)

Для сборки wxWidgets и нашего приложения из исходных кодов в Windows будем использовать Visual C++ 2012. Express редакция тоже подойдет. Это значит, что все средства разработки, включая IDE и компилятор, будут бесплатными.

Для тех, кто в танке, ссылка на бесплатный Visual C++ 2012: http://www.microsoft.com/visualstudio/rus/products/visual-studio-express-products

DialogBlocks

Для создания интерфейса пользователя, дабы не писать все руками, рекомендую использовать приложение DialogBlocks. Таки-да, он платный, но есть бесплатная пробная версия, которой достаточно для создания несложных форм. Хотя опять же, никто не мешает писать все руками (кстати, это даже неплохо в воспитательных целях и явно положительно сказывается на понимании кода).

Скачать DialogBlocks можно здесь: http://www.anthemion.co.uk/dialogblocks/download.htm

Начало

Структура папок

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

  • build – папка с общим CMake скриптом и shell-скриптами для генерирования проектов
  • build/bin/<Configuration> — папка, куда компилятор складывает бинарные файлы
  • /include – папка с общими заголовками (например, для precompiled headers)
  • /<ProjectName> — папка с исходными кодами проекта из главного решения (может быть более одного проекта в решении, у каждого своя папка)
  • /<ThirdParty> — папка, в которой лежат сторонние библиотеки (в виде исходников или собранные, каждая в своем подкаталоге)
  • /ThirdParty/build – папка с общим CMake скриптом и shell-скриптами для генерирования проектов сторонних библиотек (если вы решите вынести их в отдельный solution)
  • /ThirdParty/<LibName> — папка с исходными кодами сторонней библиотеки (их может быть более одной)
  • /<ProjectName>/<OS-Name> — сюда CMake складывает файлы проектов для каждой ОС.

Главный CMakeList

Главный скрипт CMake содержит общие параметры и настройки для всех проектов, а также описание некоторых общих переменных.

build/CMakeLists.txt

cmake_minimum_required(VERSION 2.6.0)

# We will generate both Debug and Release project files at the same time
# for Windows and OS X
if(WIN32 OR APPLE)
	set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "" FORCE)
	set(LIB_SUFFIX "")
endif(WIN32 OR APPLE)

# For Linux we will need to execute CMake twice in order to generate
# Debug and Release versions of Makefiles
if(UNIX AND NOT APPLE)
	set(LINUX ON)
	set(LIB_SUFFIX /${CMAKE_BUILD_TYPE})
endif(UNIX AND NOT APPLE)

set(PROJECT_NAME wxModularHost)
project(${PROJECT_NAME})

# If there are any additional CMake modules (e.g. module which searches
# for OpenCV or for DirectShow libs), then CMake should start searching
# for them in current folder
set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR})

if(APPLE)
	set(OS_BASE_NAME Mac)
	set(CMAKE_OSX_SYSROOT "macosx10.6")
endif(APPLE)
if(LINUX)
	set(OS_BASE_NAME Linux)
endif(LINUX)
if(WIN32)
	set(OS_BASE_NAME Win)
endif(WIN32)

# Here we specify the list of wxWidgets libs which we will use in our project
set(wxWidgets_USE_LIBS base core adv aui net gl xml propgrid html)

# Here we specify that we need DLL version of wxWidgets libs and dynamic CRT
# This is a MUST for applications with plugins. Both app and DLL plugin MUST
# use the same instance of wxWidgets and the same event loop.
set(BUILD_SHARED_LIBS 1)

# Find wxWidgets library on current PC
# You should have %WXWIN%  environment variable which should point to the
# directory where wxWidgets source code is placed.
# wxWidgets libs MUST be compiled for both Debug and Release versions
find_package(wxWidgets REQUIRED)

# For some reason CMake generates wrong list of definitions.
# Each item should start with /D but it does not.
# We need to fix that manually
set(wxWidgets_DEFINITIONS_TEMP)
foreach(DEFINITION ${wxWidgets_DEFINITIONS})

	if(NOT ${DEFINITION} MATCHES "/D.*")
		set(DEFINITION "/D${DEFINITION}")
	endif()
	set(wxWidgets_DEFINITIONS_TEMP ${wxWidgets_DEFINITIONS_TEMP}
		${DEFINITION})
endforeach(${DEFINITION})
set(wxWidgets_DEFINITIONS ${wxWidgets_DEFINITIONS_TEMP})

# Here we add some definitions which prevent Visual Studio from
# generating tons of warnings about unsecure function calls.
# See http://msdn.microsoft.com/en-us/library/ttcz0bys.aspx
if(WIN32)
	set(wxWidgets_DEFINITIONS ${wxWidgets_DEFINITIONS};
		/D_CRT_SECURE_NO_DEPRECATE;
		/D_CRT_NONSTDC_NO_DEPRECATE;
		/D_UNICODE)
	set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP /wd4996")
endif(WIN32)

# Since we are going to use wxWidgets in all subrojects,
# it's OK to create the variable which will contain
# common preprocessor definitions. This variable will be
# used in subprojects.
set(PREPROCESSOR_DEFINITIONS ${PREPROCESSOR_DEFINITIONS};
	${wxWidgets_DEFINITIONS})

# Variable which points to root folder of our source code
set(PROJECT_ROOT_DIR ${PROJECT_SOURCE_DIR}/..)

# If any ThirdParty libraries are going to be
# used in our project then it would be better to put
# them into separate subfolder. We will create
# the variable which points to this subfolder.
set(THIRD_PARTY_DIR ${PROJECT_ROOT_DIR}/ThirdParty)

set(BASE_INCLUDE_DIRECTORIES ${PROJECT_ROOT_DIR}/include)

# Add wxWidgets include paths to the list of
# include directories for all projects.
include_directories(${wxWidgets_INCLUDE_DIRS})

set(CMAKE_CXX_FLAGS_DEBUG
	"${CMAKE_CXX_FLAGS_DEBUG}
	/D__WXDEBUG__=1" )

# Now we can include all our subprojects.
# CMake will generate project files for them
add_subdirectory (../wxModularHost
	../../wxModularHost/${OS_BASE_NAME}${LIB_SUFFIX})

Скрипты для генерирования проектов

Для простоты использования CMake лучше использовать shell- или batch-скрипты. Это позволит немного сэкономить время на рутинных операциях типа вызова CMake и настройки переменных окружения.

Windows (cm.bat)

Для удобства, лучше использовать раздельные batch-скрипты для создания проектов Visual Studio для x86 и x64, а также один общий скрипт, который будет определять, под какую платформу собираем приложение:

rem @echo off
IF "%1" == "" GOTO NO_PARAMS
IF "%1" == "x86" GOTO CMAKE_86
IF "%1" == "86"  GOTO CMAKE_86
IF "%1" == "x64" GOTO CMAKE_64
IF "%1" == "64"  GOTO CMAKE_64

ECHO %1
ECHO "Nothing to do"
GOTO End

:CMAKE_86
	ECHO "Configuring for x86"
	cm86.bat
	GOTO End
:CMAKE_64
	ECHO "Configuring for x64"
	cm64.bat
	GOTO End
:NO_PARAMS
	ECHO "No parameters specified"
	IF EXIST "%ProgramW6432%" GOTO CMAKE_64
	GOTO CMAKE_86
:End
Windows (cm86.bat)

rmdir /S /Q Win
mkdir Win
cd Win
cmake ../ -G "Visual Studio 11"
cd ..
Windows (cm64.bat)

rmdir /S /Q Win
mkdir Win
cd Win
cmake ../ -G "Visual Studio 11 Win64"
cd ..

Linux (cmLinux.sh)

#!/bin/bash
echo OS Type: $OSTYPE

# ----------------------------------
# build Debug configuration makefile
# ----------------------------------
echo building Debug configuration makefile
echo directory "LinuxDebug"
rm -dr "LinuxDebug"
mkdir "LinuxDebug"
cd "LinuxDebug"
cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE:STRING=Debug ../
cd ..

# ----------------------------------
# build Release configuration makefile
# ----------------------------------
echo building Release configuration makefile
echo directory "LinuxRelease"
rm -dr "LinuxRelease"
mkdir "LinuxRelease"
cd "LinuxRelease"
cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE:STRING=Release ../
cd ..

Минимальное wxWidgets-приложение с CMake

Для начала работы нам нужен шаблон приложения, в который мы будем добавлять функционал. Создадим простое приложение, состоящее из класса приложения (например wxModularHostApp) и класса главной формы (например MainFrame).

Если использовать DialogBlocks, то, помимо пары файлов h/cpp для каждого класса, получим еще .rc файл с описанием ресурсов приложения.

Код приводить не буду. Пример можно взять из прошлых статей или из папки %WXWIN%samplesminimal

Теперь можно переходить к созданию CMake-скрипта.

wxModularHost/CMakeLists.txt

set(SRCS
	MainFrame.cpp
	wxModularHostApp.cpp)
set(HEADERS
	MainFrame.h
	wxModularHostApp.h)

set(INCLUDE_DIRECTORIES ${BASE_INCLUDE_DIRECTORIES})

if(WIN32)
	set(SRCS ${SRCS} wxModularHost.rc)
	set(PREPROCESSOR_DEFINITIONS ${PREPROCESSOR_DEFINITIONS};
		/D_USRDLL;
		/DwxUSE_NO_MANIFEST=1;
		/D__STDC_CONSTANT_MACROS)
endif(WIN32)

set(LIBS ${wxWidgets_LIBRARIES})

set(EXECUTABLE_NAME wxModularHost)

add_definitions(${PREPROCESSOR_DEFINITIONS})
include_directories(${INCLUDE_DIRECTORIES})

if(WIN32)
	set(EXECUTABLE_TYPE WIN32)
endif(WIN32)
if(APPLE)
	set(MACOSX_BUNDLE YES)
	set(EXECUTABLE_TYPE MACOSX_BUNDLE)
endif(APPLE)
if(LINUX)
	set(EXECUTABLE_TYPE "")
endif(LINUX)

set(PROJECT_FILES ${SRCS} ${HFILES})
add_executable(${EXECUTABLE_NAME} ${EXECUTABLE_TYPE} ${PROJECT_FILES})

set(EXE_DIR bin)
set(TARGET_LOCATION ${PROJECT_SOURCE_DIR}/${EXE_DIR}${LIB_SUFFIX})
set_target_properties(${EXECUTABLE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${TARGET_LOCATION})
target_link_libraries(${EXECUTABLE_NAME} ${LIBS})

Предварительно откомпилированные заголовки (Precompiled Headers)

Для ускорения процесса компиляции, есть возможность использовать предварительно откомпилированные заголовки (http://en.wikipedia.org/wiki/Precompiled_header).

Для реализации этой возможности нам понадобятся два файла:
include/stdwx.h

#ifndef _STDWX_H_
#define _STDWX_H_

#if defined(WIN32) || defined(WINDOWS)
#include <windows.h>
#include <winnt.h>
#define PLUGIN_EXPORTED_API	WXEXPORT
#else
#define PLUGIN_EXPORTED_API	extern "C"
#endif
// SYSTEM INCLUDES
// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"
#ifdef __BORLANDC__
    #pragma hdrstop
#endif
#include "wx/wx.h"
#include <wx/cmdline.h>
#include <wx/config.h>
#include <wx/defs.h>
#include <wx/dir.h>
#include <wx/display.h>
#include <wx/dynlib.h>
#include <wx/dynload.h>
#include <wx/fileconf.h>
#include <wx/filename.h>
#include <wx/frame.h>
#include <wx/glcanvas.h>
#include <wx/hashmap.h>
#include <wx/image.h>
#include <wx/imaglist.h>
#include <wx/intl.h>
#include <wx/list.h>
#include <wx/notebook.h>
#include <wx/stdpaths.h>
#include <wx/sstream.h>
#include <wx/thread.h>
#include <wx/treebook.h>
#include <wx/wfstream.h>
#include <wx/wupdlock.h>
#include <wx/textfile.h>
#include <wx/socket.h>
#include <wx/mimetype.h>
#include <wx/ipc.h>

#endif

include/stdwx.cpp

#include "stdwx.h"

Помимо файлов с исходным кодом C++ нам надо еще научить CMake добавлять в проект Visual Studio нужные правила для работы с предварительно откомпилированными заголовками. Для этого нам поможет специальный модуль. Не припомню, откуда он взялся, но вроде отсюда (http://public.kitware.com/Bug/file_download.php?file_id=901&type=bug). Исходный код CMake-модуля для поддержки предварительно компилируемых заголовков можно посмотреть здесь: https://github.com/T-Rex/wxModularApp/blob/master/build/PCHSupport.cmake.

Этот модуль надо включить в build/CmakeLists.txt таким образом:

build/CMakeLists.txt

cmake_minimum_required(VERSION 2.6.0)
include(PCHSupport.cmake)
...

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

#include "stdwx.h"

Простейший плагин без GUI

Библиотека с базовыми классами

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

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

wxNonGuiPluginBase/Declarations.h

#ifndef _DECLARATIONS_H
#define _DECLARATIONS_H

#if defined(__WXMSW__)
#ifdef DEMO_PLUGIN_EXPORTS
#define DEMO_API __declspec(dllexport)
#else
#define DEMO_API __declspec(dllimport)
#endif
#else
#define DEMO_API
#endif

#endif // _DECLARATIONS_H

wxNonGuiPluginBase/wxNonGuiPluginBase.h

#pragma once

#include "Declarations.h"

class DEMO_API wxNonGuiPluginBase : public wxObject
{
	DECLARE_ABSTRACT_CLASS(wxNonGuiPluginBase)
public:
	wxNonGuiPluginBase();
	virtual ~wxNonGuiPluginBase();

	virtual int Work() = 0;
};

typedef wxNonGuiPluginBase * (*CreatePlugin_function)();
typedef void (*DeletePlugin_function)(wxNonGuiPluginBase * plugin);

Файл Declarations.h содержит определение макроса DEMO_API, который указывает, экспортируемый у нас класс wxNonGuiPluginBase или импортируемый. Делается это с помощью атрибутов dllexport/dllimport (см. http://msdn.microsoft.com/en-us/library/3y1sfaz2(v=vs.90).aspx) в зависимости от наличия директивы препроцессора DEMO_PLUGIN_EXPORTS. При сборке библиотеки wxNonGuiPluginBase мы указываем DEMO_PLUGIN_EXPORTS в списке директив препроцессора, а при сборке плагинов, зависящих от библиотеки wxNonGuiPluginBase и при сборке основного приложения – не указываем. Таким образом для проекта wxNonGuiPluginBase значение DEMO_API будет содержать атрибут dllexport, а для всех остальных проектов – значение dllimport.

wxNonGuiPluginBase/wxNonGuiPluginBase.cpp

#include "stdwx.h"
#include "wxNonGuiPluginBase.h"

IMPLEMENT_ABSTRACT_CLASS(wxNonGuiPluginBase, wxObject)

wxNonGuiPluginBase::wxNonGuiPluginBase()
{
}

wxNonGuiPluginBase::~wxNonGuiPluginBase()
{
}

wxNonGuiPluginBase/CMakeLists.txt

set (SRCS
	wxNonGuiPluginBase.cpp)
set (HEADERS
	Declarations.h
	wxNonGuiPluginBase.h)

set(LIBRARY_NAME wxNonGuiPluginBase)

if(WIN32)
	# Only for Windows:
	# we add additional preprocessor definitons
	set(PREPROCESSOR_DEFINITIONS ${PREPROCESSOR_DEFINITIONS};
		/D_USRDLL;/DDEMO_PLUGIN_EXPORTS;/D__STDC_CONSTANT_MACROS)
endif(WIN32)

# Add 2 files for precompiled headers
set(SRCS ${SRCS} ${HEADERS}
	${PROJECT_ROOT_DIR}/include/stdwx.h
	${PROJECT_ROOT_DIR}/include/stdwx.cpp)

# Set preprocessor definitions
add_definitions(${PREPROCESSOR_DEFINITIONS})
# Set include directories
include_directories(${INCLUDE_DIRECTORIES} ${BASE_INCLUDE_DIRECTORIES})
# Set library search paths
link_directories(${LINK_DIRECTORIES})
# Setup the project name and assign the source files for this project
add_library(${LIBRARY_NAME} SHARED ${SRCS})

#Setup the output folder
set(DLL_DIR bin)
set(TARGET_LOCATION ${PROJECT_SOURCE_DIR}/${DLL_DIR}${LIB_SUFFIX})
set_target_properties(${LIBRARY_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${TARGET_LOCATION})

# Set additional dependencies
target_link_libraries(${LIBRARY_NAME} ${wxWidgets_LIBRARIES})

# Setup precompiled headers
set_precompiled_header(${LIBRARY_NAME}
	${PROJECT_ROOT_DIR}/include/stdwx.h
	${PROJECT_ROOT_DIR}/include/stdwx.cpp)

Как было сказано ранее, макрос PREPROCESSOR_DEFINITIONS содержит декларацию макроса DEMO_PLUGIN_EXPORTS, который используется в файле Definitions.h

Первый плагин

В плагине нам надо сделать класс, производный от wxNonGuiPluginBase, реализовать в нем рабочий функционал, а также сделать экспортируемые функции для создания экземпляра класса и для его удаления. Эти функции будут вызываться основным приложением.

SampleNonGuiPlugin/SampleNonGuiPlugin.h

#pragma once

#include <wxNonGuiPluginBase.h>

class SampleNonGuiPlugin : public wxNonGuiPluginBase
{
	DECLARE_DYNAMIC_CLASS(SampleNonGuiPlugin)
public:
	SampleNonGuiPlugin();
	virtual ~SampleNonGuiPlugin();

	virtual int Work();
};

SampleNonGuiPlugin/SampleNonGuiPlugin.cpp

#include "stdwx.h"
#include "SampleNonGuiPlugin.h"

IMPLEMENT_DYNAMIC_CLASS(SampleNonGuiPlugin, wxObject)

SampleNonGuiPlugin::SampleNonGuiPlugin()
{
}

SampleNonGuiPlugin::~SampleNonGuiPlugin()
{
}

int SampleNonGuiPlugin::Work()
{
	return 10;
}

SampleNonGuiPlugin/SampleNonGuiPlugin.def

LIBRARY	"SampleNonGuiPlugin"

EXPORTS
	CreatePlugin=CreatePlugin
	DeletePlugin=DeletePlugin

SampleNonGuiPlugin/SampleNonGuiPluginExports.cpp

#include "stdwx.h"
#include <wxNonGuiPluginBase.h>
#include "SampleNonGuiPlugin.h"

PLUGIN_EXPORTED_API wxNonGuiPluginBase * CreatePlugin()
{
	return new SampleNonGuiPlugin;
}

PLUGIN_EXPORTED_API void DeletePlugin(wxNonGuiPluginBase * plugin)
{
	wxDELETE(plugin);
}

SampleNonGuiPlugin/CMakeLists.txt

set (SRCS
	SampleNonGuiPlugin.cpp
	SampleNonGuiPluginExports.cpp)
set (HEADERS
	SampleNonGuiPlugin.h)

set(LIBRARY_NAME SampleNonGuiPlugin)

if(WIN32)
	set(SRCS ${SRCS} ${LIBRARY_NAME}.def)
	set(PREPROCESSOR_DEFINITIONS ${PREPROCESSOR_DEFINITIONS};/D_USRDLL;/D__STDC_CONSTANT_MACROS)
	set(LINK_DIRECTORIES
		${PROJECT_ROOT_DIR}/wxNonGuiPluginBase/${OS_BASE_NAME}${LIB_SUFFIX}/$(ConfigurationName))
	set(DEMO_LIBS wxNonGuiPluginBase.lib)
endif(WIN32)

set(SRCS ${SRCS} ${HEADERS}
	${PROJECT_ROOT_DIR}/include/stdwx.h
	${PROJECT_ROOT_DIR}/include/stdwx.cpp)

add_definitions(${PREPROCESSOR_DEFINITIONS})
include_directories(${INCLUDE_DIRECTORIES} ${BASE_INCLUDE_DIRECTORIES}
	${PROJECT_ROOT_DIR}/wxNonGuiPluginBase)
link_directories(${LINK_DIRECTORIES})
add_library(${LIBRARY_NAME} SHARED ${SRCS})

set(DLL_DIR bin)
set(TARGET_LOCATION ${PROJECT_SOURCE_DIR}/${DLL_DIR}/${CMAKE_CFG_INTDIR}/plugins)
set_target_properties(${LIBRARY_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${TARGET_LOCATION})

target_link_libraries(${LIBRARY_NAME} ${DEMO_LIBS} ${wxWidgets_LIBRARIES})
add_dependencies(${LIBRARY_NAME} wxNonGuiPluginBase)
set_precompiled_header(${LIBRARY_NAME}
	${PROJECT_ROOT_DIR}/include/stdwx.h
	${PROJECT_ROOT_DIR}/include/stdwx.cpp)

DEF-файл включен в список файлов исходного кода не случайно. Без его использования, имена экспортируемых функций будут декорированными и приложение не сможет получить указатель на эти функции из DLL. Почитать на тему использования DEF-файлов и экспортирования функций из DLL можно здесь:

  • http://msdn.microsoft.com/en-us/library/office/bb687850.aspx
  • http://msdn.microsoft.com/en-us/library/d91k01sh.aspx

Модуль управления плагинами

Итак, на данный момент у нас есть хост-приложение и минимальный плагин. Теперь надо реализовать загрузку и использование плагина в приложении. В целях универсальности, лучше выделить код, который будет заниматься поиском, загрузкой и выгрузкой плагинов из памяти, в отдельный класс, а еще лучше – в отдельную библиотеку.

Вспомним еще раз реализацию наших плагинов:

  • Плагин – это динамическая библиотека
  • В библиотеке есть экспортируемые функции CreatePlugin() и DeletePlugin()
  • Весь функционал плагина реализуется в соответствующем классе внутри динамической библиотеки, объект этого класса возвращается функцией CreatePlugin()
  • Класс внутри библиотеки реализует публичный интерфейс wxNonGuiPluginBase, о котором знает и приложение.
  • Библиотека должна быть загружена в память на протяжении всего времени жизни объект, который приложение получает из функции CreatePlugin()
  • По завершении работы с плагином нам необходимо удалить объект из памяти (это делает функция DeletePlugin()) и выгрузить из памяти библиотеку.
  • Помимо загрузки и выгрузки данных из памяти, приложение должно еще уметь находить однотипные плагины в специально предназначенной для этого папке.

Исходя из этих требований, можно прийти к таким выводам:

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

Исходя из таких требований и выводов, реализовываем класс управления плагинами и контейнеры:
wxModularCore/wxModularCore.h

#pragma once

#include <wxNonGuiPluginBase.h>

// We need to know which DLL produced the specific plugin object.
WX_DECLARE_HASH_MAP(wxNonGuiPluginBase*, wxDynamicLibrary*,
				wxPointerHash, wxPointerEqual,
				wxNonGuiPluginToDllDictionary);
// We also need to keep the list of loaded DLLs
WX_DECLARE_LIST(wxDynamicLibrary, wxDynamicLibraryList);
// And separate list of loaded plugins for faster access.
WX_DECLARE_LIST(wxNonGuiPluginBase, wxNonGuiPluginBaseList);

class wxModularCoreSettings;

class wxModularCore
{
public:
	wxModularCore();
	virtual ~wxModularCore();

	virtual wxString GetPluginsPath(bool forceProgramPath) const;
	virtual wxString GetPluginExt();
	bool LoadPlugins(bool forceProgramPath);
	bool UnloadPlugins();

	const wxNonGuiPluginBaseList & GetNonGuiPlugins() const;

	void Clear();
private:
	bool LoadNonGuiPlugins(const wxString & pluginsDirectory);
	bool UnloadNonGuiPlugins();

	bool RegisterNonGuiPlugin(wxNonGuiPluginBase * plugin);
	bool UnRegisterNonGuiPlugin(wxNonGuiPluginBase * plugin);

	wxDynamicLibraryList m_DllList;
	wxNonGuiPluginToDllDictionary m_MapNonGuiPluginsDll;
	wxNonGuiPluginBaseList m_NonGuiPlugins;

	wxModularCoreSettings * m_Settings;
};

Рассмотрим код подробно:

  • В заголовочном файле есть декларация списка загруженных библиотек (wxDynamicLibraryList), списка загруженных из библиотеки объектов-плагинов (wxNonGuiPluginBaseList), а также хеш-таблицы, которая позволяет отследить соответствие библиотеки плагину (wxNonGuiPluginToDllDictionary)
  • Класс управления плагинами содержит метод, который возвращает путь к папке, в которой приложение будет искать плагины, а также метод, который возвращает расширение файлов плагинов (по умолчанию для Windows это .dll, а для Linux и OS X это .so)
  • Также класс содержит список библиотек, список объектов-плагинов и таблицу соответствий плагинов библиотекам.
  • Есть методы загрузки и выгрузки библиотек из памяти.
  • В классе есть поле m_Settings. Это указатель на объект, который будет хранить настройки системы (например, флаг, который определяет, где искать плагины и, возможно, данные или конфигурационные файлы для них, в папке с программой или в специальной папке настроек, путь к которой определяется системой). Более подробно класс настроек мы рассмотрим далее.

wxModularCore/wxModularCore.cpp

#include "stdwx.h"
#include "wxModularCore.h"
#include "wxModularCoreSettings.h"
#include <wx/listimpl.cpp>
WX_DEFINE_LIST(wxDynamicLibraryList);
WX_DEFINE_LIST(wxNonGuiPluginBaseList);

wxModularCore::wxModularCore()
	:m_Settings(new wxModularCoreSettings)
{
	// This will allow to delete all objects from this list automatically
	m_DllList.DeleteContents(true);
}

wxModularCore::~wxModularCore()
{
	Clear();
	wxDELETE(m_Settings);
}

void wxModularCore::Clear()
{
	UnloadPlugins();
	// TODO: Add the code which resets the object to initial state
}

bool wxModularCore::LoadPlugins(bool forceProgramPath)
{
	wxString pluginsRootDir = GetPluginsPath(forceProgramPath);

	wxFileName fn;
	fn.AssignDir(pluginsRootDir);
	wxLogDebug(wxT("%s"), fn.GetFullPath().data());
	fn.AppendDir(wxT("plugins"));
	wxLogDebug(wxT("%s"), fn.GetFullPath().data());
	if (!fn.DirExists())
		return false;

	return LoadNonGuiPlugins(fn.GetFullPath());
}

bool wxModularCore::UnloadPlugins()
{
	return UnloadNonGuiPlugins();
}

bool wxModularCore::LoadNonGuiPlugins(const wxString & pluginsDirectory)
{
	wxFileName fn;
	fn.AssignDir(pluginsDirectory);
	wxLogDebug(wxT("%s"), fn.GetFullPath().data());
	fn.AppendDir(wxT("nongui"));
	wxLogDebug(wxT("%s"), fn.GetFullPath().data());
	if (!fn.DirExists())
		return false;

	if(!wxDirExists(fn.GetFullPath())) return false;
	wxString wildcard = wxString::Format(wxT("*.%s"), GetPluginExt().GetData());
	wxArrayString pluginPaths;
	wxDir::GetAllFiles(fn.GetFullPath(), &pluginPaths, wildcard);
	for(size_t i = 0; i < pluginPaths.GetCount(); ++i)
	{
		wxString fileName = pluginPaths[i];
		wxDynamicLibrary * dll = new wxDynamicLibrary(fileName);
		if (dll->IsLoaded())
		{
			wxDYNLIB_FUNCTION(CreatePlugin_function, CreatePlugin, *dll);
			if (pfnCreatePlugin)
			{
				wxNonGuiPluginBase* plugin = pfnCreatePlugin();
				RegisterNonGuiPlugin(plugin);
				m_DllList.Append(dll);
				m_MapNonGuiPluginsDll[plugin] = dll;
			}
			else
				wxDELETE(dll);
		}
	}

	return true;
}

bool wxModularCore::UnloadNonGuiPlugins()
{
	bool result = true;
	wxNonGuiPluginBase * plugin = NULL;
	while (m_NonGuiPlugins.GetFirst() && (plugin =
		m_NonGuiPlugins.GetFirst()->GetData()))
	{
		result &= UnRegisterNonGuiPlugin(plugin);
	}
	return result;
}

wxString wxModularCore::GetPluginsPath(bool forceProgramPath) const
{
	wxString path;
	if (m_Settings->GetStoreInAppData() && !forceProgramPath)
		path = wxStandardPaths::Get().GetConfigDir();
	else
		path = wxPathOnly(wxStandardPaths::Get().GetExecutablePath());
	return path;
}

wxString wxModularCore::GetPluginExt()
{
	return
#if defined(__WXMSW__)
		wxT("dll");
#else
		wxT("so");
#endif
}

bool wxModularCore::RegisterNonGuiPlugin(wxNonGuiPluginBase * plugin)
{
	m_NonGuiPlugins.Append(plugin);
	return true;
}

bool wxModularCore::UnRegisterNonGuiPlugin(wxNonGuiPluginBase * plugin)
{
	wxNonGuiPluginBaseList::compatibility_iterator it =
		m_NonGuiPlugins.Find(plugin);
	if (it == NULL)
		return false;

	do
	{
		wxDynamicLibrary * dll = m_MapNonGuiPluginsDll[plugin];
		if (!dll) // Probably plugin was not loaded from dll
			break;

		wxDYNLIB_FUNCTION(DeletePlugin_function, DeletePlugin, *dll);
		if (pfnDeletePlugin)
		{
			pfnDeletePlugin(plugin);
			m_NonGuiPlugins.Erase(it);
			m_MapNonGuiPluginsDll.erase(plugin);
			return true;
		}
	} while (false);

	// If plugin is not loaded from DLL (e.g. embedded into executable)
	wxDELETE(plugin);
	m_NonGuiPlugins.Erase(it);

	return true;
}

const wxNonGuiPluginBaseList & wxModularCore::GetNonGuiPlugins() const
{
	return m_NonGuiPlugins;
}

Есть смысл обратить внимание на метод LoadNonGuiPlugins(), в котором с помощью макроса wxDYNLIB_FUNCTION мы получаем указатель на функцию CreatePlugin(). Тип указателя CreatePlugin_function определен в wxNonGuiPluginBase.h.

Также есть смысл обратить внимание на метод UnRegisterNonGuiPlugin(), в котором происходит поиск плагина в таблице соответствий, если плагин найден то для него из найденной библиотеки вызывается функция DeletePlugin() и библиотека выгружается. Если плагин не найден в таблице (например, он реализован в приложении и мы его добавляли в список вручную), то он просто удаляется из памяти.

wxModularCore/wxModularCoreSettings.h

#pragma once

class wxModularCoreSettings
{
public:
	wxModularCoreSettings();
	wxModularCoreSettings(const wxModularCoreSettings & settings);
	wxModularCoreSettings & operator = (const wxModularCoreSettings & settings);
	virtual ~wxModularCoreSettings();

	void SetStoreInAppData(const bool & val);
	bool GetStoreInAppData() const;
protected:
	virtual void CopyFrom(const wxModularCoreSettings & settings);
private:
	bool m_bStoreInAppData; // Should we store data in Application Data folder or in .exe folder
};

wxModularCore/wxModularCoreSettings.cpp

#include "stdwx.h"
#include "wxModularCoreSettings.h"

wxModularCoreSettings::wxModularCoreSettings()
	: m_bStoreInAppData(false)
{
}

wxModularCoreSettings::wxModularCoreSettings(const wxModularCoreSettings & settings)
{
	CopyFrom(settings);
}

wxModularCoreSettings & wxModularCoreSettings::operator = (const wxModularCoreSettings & settings)
{
	if (this != &settings)
	{
		CopyFrom(settings);
	}
	return *this;
}

wxModularCoreSettings::~wxModularCoreSettings()
{

}

void wxModularCoreSettings::CopyFrom(const wxModularCoreSettings & settings)
{
	m_bStoreInAppData = settings.m_bStoreInAppData;
}

void wxModularCoreSettings::SetStoreInAppData(const bool & value)
{
	m_bStoreInAppData = value;
}

bool wxModularCoreSettings::GetStoreInAppData() const
{
	return m_bStoreInAppData;
}

wxModularCore/CMakeLists.txt

set (SRCS
	wxModularCore.cpp
	wxModularCoreSettings.cpp)
set (HEADERS
	wxModularCore.h
	wxModularCoreSettings.h)

set(LIBRARY_NAME wxModularCore)

if(WIN32)
	set(PREPROCESSOR_DEFINITIONS ${PREPROCESSOR_DEFINITIONS};/D__STDC_CONSTANT_MACROS)
	set(LINK_DIRECTORIES
		${PROJECT_ROOT_DIR}/wxNonGuiPluginBase/${OS_BASE_NAME}${LIB_SUFFIX}/$(ConfigurationName))
	set(DEMO_LIBS wxNonGuiPluginBase.lib)
endif(WIN32)

set(SRCS ${SRCS} ${HEADERS}
	${PROJECT_ROOT_DIR}/include/stdwx.h
	${PROJECT_ROOT_DIR}/include/stdwx.cpp)

add_definitions(${PREPROCESSOR_DEFINITIONS})

include_directories(${INCLUDE_DIRECTORIES} ${BASE_INCLUDE_DIRECTORIES}
	${PROJECT_ROOT_DIR}/wxNonGuiPluginBase)

link_directories(${LINK_DIRECTORIES})

add_library(${LIBRARY_NAME} STATIC ${SRCS})

set(DLL_DIR bin)
set(TARGET_LOCATION ${PROJECT_SOURCE_DIR}/${DLL_DIR}/${CMAKE_CFG_INTDIR})
set_target_properties(${LIBRARY_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${TARGET_LOCATION})

target_link_libraries(${LIBRARY_NAME} ${DEMO_LIBS} ${wxWidgets_LIBRARIES})

add_dependencies(${LIBRARY_NAME} wxNonGuiPluginBase)

set_precompiled_header(${LIBRARY_NAME} ${PROJECT_ROOT_DIR}/include/stdwx.h ${PROJECT_ROOT_DIR}/include/stdwx.cpp)

И еще надо не забыть включить путь к проекту wxModularCore в основной CMakeLists.txt:

build/CMakeLists.txt

...
add_subdirectory (../wxModularCore
	../../wxModularCore/${OS_BASE_NAME}${LIB_SUFFIX})
...

Использование плагинов без GUI в приложении

Раз класс, управляющий плагинами, у нас уже готов, то можно начать им пользоваться в приложении.

Для начала поле-указатель на wxModularCore в класс приложения:

wxModularHost/wxModularHostApp.h

...
class wxModularHostApp: public wxApp
{
	void TestNonGuiPlugins();
...
	wxModularCore * m_PluginManager;
...
};

wxModularHost/wxModularHostApp.cpp

void wxModularHostApp::Init()
{
////@begin wxModularHostApp member initialisation
	m_PluginManager = new wxModularCore;
////@end wxModularHostApp member initialisation
}

И вот таким образом мы будем вызывать метод загрузки плагинов и пользоваться самими плагинами:

wxModularHost/wxModularHostApp.cpp

bool wxModularHostApp::OnInit()
{
...
	TestNonGuiPlugins();

	MainFrame* mainWindow = new MainFrame( NULL );
	mainWindow->Show(true);

    return true;
}

/*
 * Cleanup for wxModularHostApp
 */

int wxModularHostApp::OnExit()
{
	wxDELETE(m_PluginManager);
////@begin wxModularHostApp cleanup
	return wxApp::OnExit();
////@end wxModularHostApp cleanup
}

void wxModularHostApp::TestNonGuiPlugins()
{
	if(m_PluginManager)
	{
		if(m_PluginManager->LoadPlugins(true))
		{
			for(wxNonGuiPluginBaseList::Node * node =
				m_PluginManager->GetNonGuiPlugins().GetFirst(); node; node = node->GetNext())
			{
				wxNonGuiPluginBase * plugin = node->GetData();
				if(plugin)
				{
					wxLogDebug(wxT("Non-GUI plugin returns %i"), plugin->Work());
				}
			}
		}
	}
}

В методе TestNonGuiPlugins() мы сначала вызываем метод LoadPlugins() из wxModularCore, если он отработал корректно, то проходимся по списку плагинов и для каждого элемента списка вызываем метод Work() (напомню, он задекларирован в проекте wxNonGuiPluginBase, а фактически имеет разную реализацию для каждой из загруженных библиотек).

Простейший GUI-плагин

Как создавать модули, содержащие только логику, разобрались. Теперь рассмотрим пример модуля, который кмеет создавать окно:

wxGuiPluginBase/wxGuiPluginBase.h

#pragma once

#include "Declarations.h"

class DEMO_API wxGuiPluginBase : public wxObject
{
	DECLARE_ABSTRACT_CLASS(wxGuiPluginBase)
public:
	wxGuiPluginBase();
	virtual ~wxGuiPluginBase();

	virtual wxString GetName() const = 0;
	virtual wxString GetId() const = 0;
	virtual wxWindow * CreatePanel(wxWindow * parent) = 0;
};

typedef wxGuiPluginBase * (*CreateGuiPlugin_function)();
typedef void (*DeleteGuiPlugin_function)(wxGuiPluginBase * plugin);

Публичные виртуальные методы:

  • GetName() – возвращает название модуля
  • GetId() – возвращает уникальный идентификатор модуля (можно использовать GUID для этого, в Visual Studio для этих целей есть специальная утилита. См. меню Tools -> Create GUID)
  • CreatePanel() – создает элемент управления (для демонстрации нас устроит любой контрол) и возвращает указатель на него.

Реализация плагина на основе этого интерфейса:

SampleGuiPlugin1/SampleGuiPlugin1.h

#pragma once

#include <wxGuiPluginBase.h>

class SampleGuiPlugin1 : public wxGuiPluginBase
{
	DECLARE_DYNAMIC_CLASS(SampleGuiPlugin1)
public:
	SampleGuiPlugin1();
	virtual ~SampleGuiPlugin1();

	virtual wxString GetName() const;
	virtual wxString GetId() const;
	virtual wxWindow * CreatePanel(wxWindow * parent);
};

SampleGuiPlugin1/SampleGuiPlugin1.cpp

#include "stdwx.h"
#include "SampleGuiPlugin1.h"

IMPLEMENT_DYNAMIC_CLASS(SampleGuiPlugin1, wxObject)

SampleGuiPlugin1::SampleGuiPlugin1()
{
}

SampleGuiPlugin1::~SampleGuiPlugin1()
{
}

wxString SampleGuiPlugin1::GetName() const
{
	return _("GUI Plugin 1");
}

wxString SampleGuiPlugin1::GetId() const
{
	return wxT("{4E97DF66-5FBB-4719-AF17-76C1C82D3FE1}");
}

wxWindow * SampleGuiPlugin1::CreatePanel(wxWindow * parent)
{
	wxWindow * result= new wxPanel(parent, wxID_ANY);
	result->SetBackgroundColour(*wxRED);
	return result;
}

CMakeLists.txt для этого плагина почти аналогичен тому, который мы написали для плагина без GUI. Отличия будут только в названии проекта и в списке входящих в проект файлов.

Рефакторинг модуля управления плагинами

На данный момент у нас есть два типа плагинов. Для плагинов без GUI в классе управления плагинами есть специализированный метод для загрузки библиотек, регистрации плагинов, отключения плагинов. С таким подходом нам нужно будет дублировать все эти методы для каждого типа плагинов. И есть таковых у нас будет 5-10, то класс неоправданно разрастется в размерах. Поэтому методы LoadXXXPlugins(), UnloadXXXPlugins(), RegisterXXXPlugin(), UnRegisterXXXPlugin() было решено сделать шаблонными, списки и хеш-таблицы вынести в отдельный класс-наследник класса wxModularCore, который будет содержать код, специфичный для нашего приложения.
wxModularCore/wxModularCore.h

#pragma once

// We need to keep the list of loaded DLLs
WX_DECLARE_LIST(wxDynamicLibrary, wxDynamicLibraryList);

class wxModularCoreSettings;

class wxModularCore
{
public:
	wxModularCore();
	virtual ~wxModularCore();

	virtual wxString GetPluginsPath(bool forceProgramPath) const;
	virtual wxString GetPluginExt();

	virtual bool LoadAllPlugins(bool forceProgramPath) = 0;
	virtual bool UnloadAllPlugins() = 0;
	virtual void Clear();
protected:
	wxDynamicLibraryList m_DllList;
	wxModularCoreSettings * m_Settings;

	template<typename PluginType,
		typename PluginListType>
		bool RegisterPlugin(PluginType * plugin,
		PluginListType & list)
	{
		list.Append(plugin);
		return true;
	}

	template<typename PluginType,
		typename PluginListType,
		typename PluginToDllDictionaryType,
		typename DeletePluginFunctionType>
		bool UnRegisterPlugin(
			PluginType * plugin,
			PluginListType & container,
			PluginToDllDictionaryType & pluginMap)
	{
		typename PluginListType::compatibility_iterator it =
			container.Find(plugin);
		if (it == NULL)
			return false;

		do
		{
			wxDynamicLibrary * dll = (wxDynamicLibrary *)pluginMap[plugin];
			if (!dll) // Probably plugin was not loaded from dll
				break;

			wxDYNLIB_FUNCTION(DeletePluginFunctionType,
				DeletePlugin, *dll);
			if (pfnDeletePlugin)
			{
				pfnDeletePlugin(plugin);
				container.Erase(it);
				pluginMap.erase(plugin);
				return true;
			}
		} while (false);

		// If plugin is not loaded from DLL (e.g. embedded into executable)
		wxDELETE(plugin);
		container.Erase(it);

		return true;
	}

	template<typename PluginType,
		typename PluginListType,
		typename PluginToDllDictionaryType,
		typename DeletePluginFunctionType>
	bool UnloadPlugins(PluginListType & list,
		PluginToDllDictionaryType & pluginDictoonary)
	{
		bool result = true;
		PluginType * plugin = NULL;
		while (list.GetFirst() && (plugin =
			list.GetFirst()->GetData()))
		{
			result &= UnRegisterPlugin<PluginType,
				PluginListType,
				PluginToDllDictionaryType,
				DeletePluginFunctionType>(plugin,
					list, pluginDictoonary);
		}
		return result;
	}

	template <typename PluginType,
		typename PluginListType,
		typename PluginToDllDictionaryType,
		typename CreatePluginFunctionType>
	bool LoadPlugins(const wxString & pluginsDirectory,
		PluginListType & list,
		PluginToDllDictionaryType & pluginDictionary,
		const wxString & subFolder)
	{
		wxFileName fn;
		fn.AssignDir(pluginsDirectory);
		wxLogDebug(wxT("%s"), fn.GetFullPath().data());
		fn.AppendDir(subFolder);
		wxLogDebug(wxT("%s"), fn.GetFullPath().data());
		if (!fn.DirExists())
			return false;

		if(!wxDirExists(fn.GetFullPath())) return false;
		wxString wildcard = wxString::Format(wxT("*.%s"),
			GetPluginExt().GetData());
		wxArrayString pluginPaths;
		wxDir::GetAllFiles(fn.GetFullPath(),
			&pluginPaths, wildcard);
		for(size_t i = 0; i < pluginPaths.GetCount(); ++i)
		{
			wxString fileName = pluginPaths[i];
			wxDynamicLibrary * dll = new wxDynamicLibrary(fileName);
			if (dll->IsLoaded())
			{
				wxDYNLIB_FUNCTION(CreatePluginFunctionType,
					CreatePlugin, *dll);
				if (pfnCreatePlugin)
				{
					PluginType * plugin = pfnCreatePlugin();
					RegisterPlugin(plugin, list);
					m_DllList.Append(dll);
					pluginDictionary[plugin] = dll;
				}
				else
					wxDELETE(dll);
			}
		}
		return true;
	}

};

wxModularHost/SampleModularCore.h

#pragma once

#include <wxModularCore.h>
#include <wxNonGuiPluginBase.h>
#include <wxGuiPluginBase.h>

// We need to know which DLL produced the specific plugin object.
WX_DECLARE_HASH_MAP(wxNonGuiPluginBase*, wxDynamicLibrary*,
					wxPointerHash, wxPointerEqual,
					wxNonGuiPluginToDllDictionary);
WX_DECLARE_HASH_MAP(wxGuiPluginBase*, wxDynamicLibrary*,
					wxPointerHash, wxPointerEqual,
					wxGuiPluginToDllDictionary);
// And separate list of loaded plugins for faster access.
WX_DECLARE_LIST(wxNonGuiPluginBase, wxNonGuiPluginBaseList);
WX_DECLARE_LIST(wxGuiPluginBase, wxGuiPluginBaseList);

class SampleModularCore : public wxModularCore
{
public:
	virtual ~SampleModularCore();
	virtual bool LoadAllPlugins(bool forceProgramPath);
	virtual bool UnloadAllPlugins();

	const wxNonGuiPluginBaseList & GetNonGuiPlugins() const;
	const wxGuiPluginBaseList & GetGuiPlugins() const;
private:
	wxNonGuiPluginToDllDictionary m_MapNonGuiPluginsDll;
	wxNonGuiPluginBaseList m_NonGuiPlugins;
	wxGuiPluginToDllDictionary m_MapGuiPluginsDll;
	wxGuiPluginBaseList m_GuiPlugins;
};

wxModularHost/SampleModularCore.cpp

#include "stdwx.h"
#include "SampleModularCore.h"
#include <wx/listimpl.cpp>

WX_DEFINE_LIST(wxNonGuiPluginBaseList);
WX_DEFINE_LIST(wxGuiPluginBaseList);

SampleModularCore::~SampleModularCore()
{
	Clear();
}

bool SampleModularCore::LoadAllPlugins(bool forceProgramPath)
{
	wxString pluginsRootDir = GetPluginsPath(forceProgramPath);
	bool result = true;
	result &= LoadPlugins<wxNonGuiPluginBase,
		wxNonGuiPluginBaseList,
		wxNonGuiPluginToDllDictionary,
		CreatePlugin_function>(pluginsRootDir,
		m_NonGuiPlugins,
		m_MapNonGuiPluginsDll,
		wxT("nongui"));
	result &= LoadPlugins<wxGuiPluginBase,
		wxGuiPluginBaseList,
		wxGuiPluginToDllDictionary,
		CreateGuiPlugin_function>(pluginsRootDir,
		m_GuiPlugins,
		m_MapGuiPluginsDll,
		wxT("gui"));
	// You can implement other logic which takes in account
	// the result of LoadPlugins() calls
	return true;
}

bool SampleModularCore::UnloadAllPlugins()
{
	return
		UnloadPlugins<wxNonGuiPluginBase,
			wxNonGuiPluginBaseList,
			wxNonGuiPluginToDllDictionary,
			DeletePlugin_function>(m_NonGuiPlugins,
			m_MapNonGuiPluginsDll) &&
		UnloadPlugins<wxGuiPluginBase,
			wxGuiPluginBaseList,
			wxGuiPluginToDllDictionary,
			DeleteGuiPlugin_function>(m_GuiPlugins,
			m_MapGuiPluginsDll);
}

const wxNonGuiPluginBaseList & SampleModularCore::GetNonGuiPlugins() const
{
	return m_NonGuiPlugins;
}

const wxGuiPluginBaseList & SampleModularCore::GetGuiPlugins() const
{
	return m_GuiPlugins;
}

После реализации шаблонных методов, добавление поддержки GUI-плагинов заняло совсем немного кода.

Использование GUI-плагинов в приложении

В приложении у нас есть главная форма с менеджером Docking-окон и wxAuiNotebook в качестве центральной панели. Рассмотрим как можно добавить контролы из плагинов в этот wxAuiNotebook:
wxModularHost/MainFrame.cpp

void MainFrame::AddPagesFromGuiPlugins()
{
	SampleModularCore * pluginManager = wxGetApp().GetPluginManager();
	for(wxGuiPluginBaseList::Node * node = pluginManager->GetGuiPlugins().GetFirst();
		node; node = node->GetNext())
	{
		wxGuiPluginBase * plugin = node->GetData();
		if(plugin)
		{
			wxWindow * page = plugin->CreatePanel(m_Notebook);
			if(page)
			{
				m_Notebook->AddPage(page, plugin->GetName());
			}
		}
	}
}

В результате получим такое окно с вкладками:

image

Заголовки вкладок берутся из метода GetName() каждого плагина, сами же вкладки создаются методом CreatePanel() плагина.

Доработки CMake-скриптов для Linux

В Windows для указания папки, в которую будут собираться динамические библиотеки, мы указывали с помощью настройки RUNTIME_OUTPUT_DIRECTORY. В Linux, т.к. плагин – это динамическая библиотека (именно библиотека), используется настройка LIBRARY_OUTPUT_DIRECTORY. Но здесь мы сталкиваемся с проблемой: если собирать библиотеки прямо внутрь папки bin, то линкер не будет находить эту библиотеку при сборке зависимых проектов. Для этих целей нужно добавить скрипт, который будет отрабатывать после сборки библиотеки и копировать ее в нужное место внутрь папки bin. Сделать это нужно будет для всех динамических библиотек (и для базовых и для плагинов):

SampleGuiPlugin2/CMakeLists.txt

...
set(DLL_DIR bin)
if(LINUX)
	set(TARGET_LOCATION ${PROJECT_SOURCE_DIR}/${DLL_DIR}${LIB_SUFFIX}/plugins/nongui)
else(LINUX)
	set(TARGET_LOCATION ${PROJECT_SOURCE_DIR}/${DLL_DIR}/${CMAKE_CFG_INTDIR}/plugins/nongui)
	get_target_property(RESULT_FULL_PATH ${LIBRARY_NAME} LOCATION)
	get_filename_component(RESULT_FILE_NAME ${RESULT_FULL_PATH} NAME)
endif(LINUX)
set_target_properties(${LIBRARY_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${TARGET_LOCATION})
...
if(LINUX)
	add_custom_command(TARGET ${LIBRARY_NAME} POST_BUILD
		COMMAND ${CMAKE_COMMAND} -E make_directory ${TARGET_LOCATION}
		COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:${LIBRARY_NAME}> 
			${TARGET_LOCATION}/${RESULT_FILE_NAME}
	)
endif(LINUX)

Для всех плагинов в Linux мы также должны указать список зависимостей:

SampleGuiPlugin2/CMakeLists.txt

...
if(WIN32)
	set(SRCS ${SRCS} ${LIBRARY_NAME}.def)
	set(PREPROCESSOR_DEFINITIONS ${PREPROCESSOR_DEFINITIONS};/D_USRDLL;/D__STDC_CONSTANT_MACROS)
	set(LINK_DIRECTORIES 
		${PROJECT_ROOT_DIR}/wxNonGuiPluginBase/${OS_BASE_NAME}${LIB_SUFFIX}/$(ConfigurationName))
	set(DEMO_LIBS wxNonGuiPluginBase.lib)
endif(WIN32)
if(LINUX)
	set(DEMO_LIBS wxNonGuiPluginBase)
endif(LINUX)
...

Вроде все хорошо, проект собирается. Но при попытке запустить приложение мы обнаружим что динамические библиотеки не загружаются (это может стать неприятной неожиданностью в случае переноса на другую машину уже собранного приложения).
А все потому, что при сборке внутрь библиотеки прописываются ссылки на зависимости с полными путями. Так, например, для каждого плагина будет указан полный путь к библиотеке с базовыми классами, которой на другой рабочей машине не будет. Проверить это можно с помощью утилиты ldd:

ldd libSampleGuiPlugin2.so | grep wxSampleGuiPluginBase

Для того, чтобы избавиться от полных путей, в CMake надо указать опцию RPATH для библиотек, которые ссылаются на другие динамические библиотеки из нашего решения:

SampleGuiPlugin2/CMakeLists.txt

if(WIN32)
	set(SRCS ${SRCS} ${LIBRARY_NAME}.def)
	set(PREPROCESSOR_DEFINITIONS ${PREPROCESSOR_DEFINITIONS};/D_USRDLL;/D__STDC_CONSTANT_MACROS)
	set(LINK_DIRECTORIES 
		${PROJECT_ROOT_DIR}/wxNonGuiPluginBase/${OS_BASE_NAME}${LIB_SUFFIX}/$(ConfigurationName))
	set(DEMO_LIBS wxNonGuiPluginBase.lib)
endif(WIN32)
if(LINUX)
	set(DEMO_LIBS wxNonGuiPluginBase)
	SET(CMAKE_SKIP_BUILD_RPATH  FALSE)
	SET(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) 
	SET(CMAKE_INSTALL_RPATH ".:./../../")
endif(LINUX)

Т.к. плагин находится в подкаталоге plugins/gui, то бибиотеку wxGuiPluginBase надо искать на два уровня выше, что и указано в CMakeLists.txt

Доработка CMake-скриптов для OS X

Так же, как и в Linux, в OS X у нас появляется проблема с загрузкой зависимостей у плагинов. В OS X для исправления путей к динамическим библиотекам, можно использовать утилиту install_name_tool.
Допишем код в CMakeLists.txt, который заменяет пути к библиотекам на относительные:

SampleGuiPlugin2/CMakeLists.txt

if(APPLE)
	FOREACH(DEP_LIB ${DEMO_LIBS})
		get_filename_component(ABS_ROOT_DIR ${PROJECT_ROOT_DIR} ABSOLUTE)
		set(LIBNAME_FULL "${ABS_ROOT_DIR}/${DEP_LIB}/${OS_BASE_NAME}${LIB_SUFFIX}/$(CONFIGURATION)/lib${DEP_LIB}.dylib")
                add_custom_command(TARGET ${LIBRARY_NAME} POST_BUILD
                	COMMAND install_name_tool -change "${LIBNAME_FULL}" 
						"@executable_path/../Frameworks/lib${DEP_LIB}.dylib" 
						$<TARGET_FILE:${LIBRARY_NAME}>)
        ENDFOREACH(DEP_LIB)
endif(APPLE)

В CMake-скрипте приложения тоже надо сделать аналогичные правки

wxModularHost/CMakeLists.txt

if(APPLE)
	FOREACH(DEP_LIB ${DEMO_LIBS_SHARED})
		get_filename_component(ABS_ROOT_DIR ${PROJECT_ROOT_DIR} ABSOLUTE)
		set(LIBNAME_FULL "${ABS_ROOT_DIR}/${DEP_LIB}/${OS_BASE_NAME}${LIB_SUFFIX}/$(CONFIGURATION)/lib${DEP_LIB}.dylib")
                add_custom_command(TARGET ${EXECUTABLE_NAME} POST_BUILD
                	COMMAND install_name_tool -change "${LIBNAME_FULL}" "@executable_path/../Frameworks/lib${DEP_LIB}.dylib" $<TARGET_FILE:${EXECUTABLE_NAME}>)
        ENDFOREACH(DEP_LIB)
endif(APPLE)

В завершение

Мы рассмотрели способ создания кросс-платформенных модульных приложений, а также процесс создания проектов для Windows и OS X Makefile с помощью CMake. Надеюсь, кому-то этот материал будет полезен.
Полный исходный код проекта, рассмотренного в статье, можно найти на GitHub: https://github.com/T-Rex/wxModularApp

PS: За время написания статьи вышла новая версия wxWidgets (3.0), с которой CMake еще не умеет работать (по крайней мере скрипты, которые работали для 2.9.x не отрабатывают с 3.0. Для тестирования лучше пока использовать код из ветки 2.9: svn.wxwidgets.org/svn/wx/wxWidgets/tags/WX_2_9_5

Introduction

wxWidgets is a framework for cross-platform GUI development in C++. Started by Julian Smart at the Artificial Intelligence Applications Institute of Edinburgh University in 1992, the framework was ported to many platforms since then.

This article is intended to guide a beginer programer to install the needed resources and develope a single aplication with wxWidgets using Code::Blocks as the IDE for that.

What is wxWidgets

wxWidgets is a set of C++ libraries conforming a framework for multi-platform GUI developing. It gives an easy-to-use API very similar to the MFC API. Linking it with the appropiate library and compiles makes your applications look appropiate to the target platform. wxWidgets is a very complete framework with solutions for almost every thing you need but keeping the simplicity in the usage.

Why wxWidgets

There are a number of options available for cross platform GUI development. Maybe one of the most popular is Java, but it’s not an efficient alternative for some aplications. In C++ there is QT, a great framework, but as some disadvantages that have to be mentioned in first place its emulate the platform look, while wxWidgets uses the platform libraries for create the graphic interface; and in second place QT have a diferent approach using propietary layers between the code you write and the final C++ code that is compiled. Don´t mentiones about Microsoft Visual based frameworks that only works for windows, or some ones like mono.net that addas layers over layers.

In opposite of that examples wxWidgets is C++, so have this efficiency and doesn’t adds any extra layer to your code. And it’s a set of libraries so you can compile it with the C++ compiler you want, well, not every C++ compiler but the most common ones.

Other advantages and characteristics:

  • It’s still developed and have a lot of support and colavorative of the open source community.
  • Its totaly free fir personal and comercial use.
  • Have a lot of documentation online.
  • Ease of learning. It’s just C++.
  • A lot of ready-to-use clases and libraries are available. Take a look here.

Getting Started

Let’s get down to business. First of all we need the framework itself and an IDE to helpping us in the developign. Then we’ll be able to do some magik and put a «Hello World» window in our screen.

Install

I’ll show the installation steps for windows but it’s quite the same for other OS. I choose Code::Blocks IDE because its integration with wxWidgets and because it’s free and available in stable version for windows and linux.

1. Downloading and installing Code::Blocks

You can download Code::Blocks binary realease from its own  webpage. I recomend to download the mor recent stable mingw version, in this moment is codeblocks-13.12mingw-setup, that comes with de mingw compiler. Just download the installer and install it in the classic Windows-way. Probably you will have to add the path to the mingw/bin directory to the PATH envirvment variable. If someone have any trouble with this I can add mmore information about.

2. Downloading, installing and compiling wxWidgets

You may download the wxWidgets resources from its repository . Download the wxMSW installer or the ZIP because de installer is just a compressed folder. Download the stable realease 3.0.1 because it’s the last stable version recomended for windows 7.

Once you’ve downloaded the installer or the ZIP file install/uncompress that in C:. Open a command shell, the standard console in windows, change to the wxWidget build directory  

The key here is to compile wxWidgets with the same compiler taht you will use later when developing. If you have installed the C::B IDE with mingw and add the path to the Envirvment Variable you don’t have any problem with this.

cd <wxwidgets>buildmsw

where <wxwidgets> is the path you extracted the sources to (tipically C:wxWidgets-3.0.1).

There you must tu execute the build command, that for the gcc compiler it’ll be like this:

mingw32-make -f makefile.gcc BUILD=release SHARED=1 MONOLITHIC=1 UNICODE=1 CXXFLAGS=-fno-keep-inline-dllexport

Take a moment to see the compiling vars so you can choose the adecuate for you:

BUILD: the type of build of wxWidget. In most of cases you will use ‘release’ because you woun’t debug wxWidgets itself. You can debug your own programs linking to a release version of wxWidgets.

SHARED: This variable define the linking type: dynamic linking (SHARED=1) and you have to share the required DLLs with your executable or static linking (SHARED=0) so you have to share only the executable. The excutable file generated by dynamic building has smaller size but you need the required DLLs in your PC in order to run it.

MONOLITHIC: Controls wheter a single DLL (MONOLITHIC=1) or multiple DLLs (MONOLITHIC=0) are built. With Monolithic built development is much easier and you have only one library to share with your executable, but with multifile built the linking process is more efficient because you avoid to linking with the entire wxWidget codebase.

UNICODE: Define wheter wxWidget and your programs use wide-character strings (UNICODE=1) or ANSI (UNICODE=0). It’s strongly recomended to use the wxWidget _(«string») and _T(«string») macros to ensure that your hardcoded strings are in the correct format.

Now we have the wxWidgets builded so start developing some frame-based programs.

Creating a Project

1. First start Code::Blocks and select Create New Project.

Image 1

2. In the project selection windows choose wxWidget

Image 2

3. Now you have to choose the wxWidgets version. Be sure to choose the one you have installed. If you follow this instructions choose 3.0.x

Image 3

4. Then will appear the name-the-project window. Here you enter the project name and location of the project. I was very vreative and named it HelloWorld

Image 4

5. Now you have to select the GUI Builder, that is the tool that help you to create the graphical interface graphicaly. We will use xwSmith. And the application type, choose Frame Based.

Image 5

6. The next windows wich appear asks you to select the location of wxWidgets in your computer. The better option is leave the global variable. When you click Next the global variable menu will appear, so there you have to entry the wxWidgets path in the base field. Then, if you change the location of wxWidgets you van edit the global variables from the Code::Blocks configuration.

Image 6

7. Select the compiler. Just leave it by default (GCC compiler.

Image 7

8. Now we have some configuration options. In the first section you have to match this options with the options you use when build wxWidgets.

Image 8

Use wxWidgets DLL, check if you builded with SHARED=1.

wxWidgets is built as a monolithic library, check if you builded with MONOLITHIC=1.

Enable unicode,  check if you builded with UNICODE=1.

For example, in the screenshot I leave unchecked the Use wxWidgets DLL because I builded wxWidgets with SHARED=0.

And check Advanced Options.

9. At least the last window, leave unchecked the Use __WXDEBUG__ and Debug wxWidgets lib option

Image 9

It’s probable that when you press the Finish button appears a dialog window telling that there’s no debug build, click accept. You can debug with the release wxWidget build without any problem.

10. So here we are, with the development window in front of us. It’s must be something like that:

Image 10

Adding some stuff to our window

If you press Build and Run button (the one with the gear and the triangle (play symbol)) it displays a little window with a menu bar with two options, Menu and Help, and an empty status bar. But it’s a good way to check if all its working fine.

Now I’ll explain how to add a text label and a button to the window. I woun’t go very deep in the many options and widgets you can use because the purpouse of this tutorial is to be a simple introduction to the framework. But I’ll explain a few things about the development enviroment.

In the center we can see the design window. There we can edit the graphic interface or the code depending on the file we are editing. Just over the this window we have two buttons, one open the Menu Bar editor and the other one  to open the Status Bar editor. But most important below the design window we have a set of buttons with the widgets that we can use in the development organized in categories.

In the left we see two diferent sections, the resource/file explorer up and the properties editor down.

The resource/file explorer let us find easyly the files or resources we want to edit. In the top are some tabs. With them we can change from dieferents views. The most importants are the Project tab and the Resources tab.

Image 11 Image 12

 In the Project tree we can find all the files implied in the program. In the resource tree we can find the graphical resources.

The Properties Editor, below the file/resource navigator, will let us modifie some resource properties directly.

Image 13

I will return to this later when we will be working on the window.

On the right we have a button bar wich affects some resources. The first four determine where the new resources will be added (on the pointer, inside the actual element and behind o in front of actual element). Then we have the crosed button for delete the current element and under that the Show Preview button. The lowest open the Properties Panel that has some placement and size options.

Image 14

Adding some stuff

The first three steps that I will explain are the basic steps that you may use for every new project.

1. From the widgets menu below the design window select the Layout tab. Here we find the sizers. Sizers help us in the positioning and sizing the elements in the window. The sizers take care about resizing the elements and the inter element space when someone resize the window.

So from the Layout element select the basic one wxBoxSizer. This sizers organice the elements in one line horizontal or vertical. To add some element to eh window click on the element desired, the wxBoxSizer in this case, and the click inside the dotted panel in the design window. Now the window must look like this:

Image 15

2. We can start now to throw some element into this but first we add some extra things. First, selecting the Standard tab of the widgets menu click on the wxPanel and then click inside the little square to add a panel to this. Be sure to have selected the boxSizer, to know this just look in the resource tree in the left, there you will see that the sizer was added to the tree, and when you select it in the graphical screen it be selected in the resource tree and viceversa. The wxPanel will add a fine background and add some characteristics to the window.

Image 16

3. Now we add another wxBoxSizer but inside the Panel. So with the wxPanel selected click on the wxBoxSizer in the Layout tab and click again inside the box. Now the window may look like this image:

Image 17

This steps constitute a good start up for the most of the programs. Now we add the interaction elements.

4. A Hello Worl program must show a Hello World text, so lets add it. In the Standard tab select the wxStaticText and click inside the box. This add a text label to the window.

Image 18

If you look in the resource propertie editor at the left, you can see a table with some variables and values.

Image 19

The first one is the label of the element. It’s present in all the elements that show a text, originali it says «Label». By clicking on the right column (the value column) you can edit it so do this, click on the right column of the Label row and edit the text putting something ingenious like Hello World (you know, it gives bad luck to start with another text).

5. Now add some interaction. We will add a button to exit the window. So click on the wxButton button on the Standard tab. You will see that the window we are designing turns to blue, now when you pass the mouse cursor over the Label we added in the last step one of its sides become light blue, thats indicate in wich side the new resource will add because the wxStaticText can not have any element inside so automaticaly it is added in a side of it. Click in the right side to add the button.

 Image 20

Go to the properties editor and edit the label of the button, like we did with the Static Text label, and put a reference text. As aur button will close the windows I put ‘Quit’ on it.

6. You can try to build and run the program now by clicking on the Buil & Run button. It must appear a little window with the Hello World text and an useless button. So add some habilities to the button. To close the window you can click on the close button of the bar (the traditional cross) or go to File/Quit.

In the designer double click on the button we added and it bring us to the HelloWorldMain.cpp file (or the main file with the name youo gave to the project).

Image 21

Maybe you hav to scroll down to find a function called something like

void HelloWorldFrame::OnButton1Click(wxCommandEvent& event)
{
}

This function take the button click event and do something with, it’s called whenever you click on the button. Well, for now it isn’t doing much so we’ll edit this function to give saome funtionality. Soo add to the body of the function:

void HelloWorldFrame::OnButton1Click(wxCommandEvent& event)
{
    Close();
}

The Close() function closes the window so now when you compile again you can close the window with our own button.

Here is the final result

Image 22

Some final notes

If you select any resource of the design, one of the properties you can see in the properties editor are the size (width and height) and the position (x and y) variables. You can give values to them or just check the Default Size and Default Pos options so the layout manager choose the apropriate position and size for any element and the windows itself. It’s the better option to start and for the most of the program we will make.

This was a very basic tutorial. Mi idea is to make more tutorial explaining other characteristics. But you can experiment by yourself, there a lot of resources to use.

Any suggestion, correction, doubt or comment are welcome. Thank you for reading.

Cоздаем приложение «Привет, Мир!» в VC++ 2015 и ФормБилдере[править]

Вступление[править]

Прежде чем начать этом урок, следует отметить, что он описывает один из наиболее простых способов знакомства с wxWidgets с нуля, при котором вы не просто открываете готовый (и часто непонятный) пример, а создаете свой код.
Однако, дальнейшее использование wxFormBuilder предполагает наличие у вас знаний языка C++, в частности понимания наследования классов.
Кроме того, рано или поздно вы столкнетесь со ограниченностью возможностей формбилдера и необходимостью написания части кода GUI вручную, либо использования более функциональных конструкторов GUI вроде wxSmith (Code::Blocks) или wxDev-C++.

В wxDev-C++ создать Hello World можно даже меньшими усилиями, однако проект не развивается с 2012 года. Пример создания приложения при помощи wxSmith в Code::Blocks можно найти здесь: Уроки wxSmith: Hello world. Code::Blocks тоже притормозил развитие и поддерживает компиляторы Visual C++ лишь до VS 2010. Это накладывает свои ограничения на применение вышеупомянутых конструкторов GUI и позволяет wxFormBuilder в некоторых случаях конкурировать с ними.

wxFormBuilder позволяет генерировать (и при правильном использовании дорабатывать) код графического интерфейса в проектах Visual Studio современных версий, что позволяет использовать всю мощь современных компиляторов, в частности: совместимость с новыми версиями библиотек, векторизацию и распараллеливание кода, одним словом дает возможность создания современных программ для многоядерных процессоров и гетерогенных вычислений, что «не по зубам» древним компиляторам.

Примечание:

Хорошей альтернативой описанному ниже способу создания графического интерфейса является использование в качестве библиотеки Qt и конструктора GUI Qt Creator, благо их развитие идет динамичнее, чем развитие wxWidgets, Qt имеет средства интеграции в Visual Studio, Qt активнее используют в коммерческих проектах. Однако wxWidgets более компактна (с проистекающими последствиями), а также более лояльна к коммерческому использованию, поэтому интереснее для небольших проектов, где разработчики не могут позволить себе приобретение дорогостоящих средств разработки, но хотели бы коммерциализировать свой проект, не обладая уверенностью, что он будет успешен в денежном выражении.

Итак, какой бы мотивацией вы не руководствовались, начнем наш урок.

Что потребуется:[править]

  • Visual Studio Community 2015 (далее VS), с установленными компонентами:
    • Visual C++
    • Windows SDK
  • wxFormBuilder
  • wxWidgets версии 3.0 или выше

Настройка среды разработки[править]

Можно использовать другую версию VS, но возникнут некоторые отличия в сборке библиотек wxWidgets. В VS 2015 это делается очень просто.
Вместо VS можно также использовать компилятор Visual C++ с командной строки или свободный компилятор MinGW, однако это тоже усложнит задачу.
Кроме того, если использовать wxWidgets версии 2.8 можно собрать проект даже на очень древних компиляторах (например от Borland) благо разработчики wxWindows позаботились об этом.

Вкратце об установке вышеуказанных программных продуктов:

  • VS нужно скачать c официального сайта и при установке указать необходимые компоненты;
  • wxFormBuilder последней версии можно найти на GitHub, жмем [Clone or download]->[Download ZIP]. Создаем каталог wxFormBuilder (например в корне диска C:) заливаем в него содержимое архива;
  • wxWidgets качаем с официального сайта в виде «Windows Installer», устанавливаем (лучше всего в место с не слишком длинным путем и обязательно без русских букв).

Далее нужно собрать библиотеки wxWidgets:

  • в подкаталоге библиотеки wxWigdets .buildmsw запускаем wx_vc14.sln
  • в открывшемся проекте открываем пункт меню [Build]->[Batch Build…]
  • выбираем всё [Select All], жмем [Build]
  • ждем завершения процесса сборки (достаточно долгого — можно вполне успеть приготовить и попить кофе с плюшками) и если всё завершится хорошо, то в логе будет что-то вроде «Build: 192 succeeded, 0 failed, 0 up-to-date, 0 skipped».

Если вы решили пойти более сложным путем и выбрали другой компилятор, то, возможно, вам поможет страничка Настройка wxWidgets в Code::Blocks, там описан процесс сборки wxWidgets c командной строки.

Создание приложения[править]

Итак, приступим к созданию нашего суперприложения.
Открываем VS и создаем новый проект [File]->[New]->[Project…].

В открывшемся окне выбираем [Installed]->[Templates]->[Visual С++]->[Win32]->[Win32 Project].
Даем проекту имя, например «wxHello».

В открывшемся окне мастера «Overview» жмем Next. Откроется окно настроек проекта «Application Settings».
В окне ставим галочку Empty project» чтобы создать пустой проект. Остальные настройки — по умолчанию. Завершаем работу мастера нажатием [Finish].

В обозревателе созданного проекта делаем правый клик на название проекта (wxHello). В открывшемся меню жмем на [Add]->[New Filter]. Даем ему название GUI. Здесь будут ссылки на файлы wxFormBuilder.

Делаем правый клик на папке GUI. Далее [Add]->[Existing Item…].

В открывшемся окне создаем папку GUI. Здесь будет храниться проект GUI и код его элементов.

Далее нажимаем пока что [Отмена] и сворачиваем Visual Studio.

Создание пользовательского интерфейса[править]

Открываем wxFormBuilder, будет создан проект по умолчанию. Дадим ему имя (name) gui и такое же значение свойству file.

Теперь сохраним проект в папку GUI, которую мы создали в Visual Studio. Она находится в Документы->Visual Studio 2015->Projects->wxHello->wxHello ([User Directory]DocumentsVisual Studio 2015ProjectswxHellowxHello). Дадим файлу проекта название gui (gui.fbp). Сохраняем.

В панели компонентов (Component Palette) выбираем вкладку Forms. На ней жмем иконку Frame. Будет создан фрейм (форма). Дадим ей имя frmMain

Теперь перейдем во вкладку Layout и нажмем на первую по счету иконку wxBoxSizer. Внутри проектируемой формы появится красная рамочка — это разместитель (sizer). Он будет управлять размещением вложенных в него компонентов на форме (друг над другом).

Далее перейдем на вкладку Common и нажмем на иконку Static Text. Внутри разместителя в верхнем левом углу формы появится новый объект wxStaticText «MyLabel». Изменим его свойство label на «Hello World!».

В разделе sizeritembase распахнем flags и поставим галочку у флага wxALIGN_CENTER. Теперь надпись будет в центре по горизонтали.

Далее в разделе wxWindow распахнем font и увеличим размер шрифта до 30 (или можете ввести свое значение). Надпись изменит свой размер.

Пока достаточно. Мы неплохо потрудились над дизайном. Попробуем сгенерировать C++ код GUI нашего приложения.

Откроем пункт меню [File]->[Generate Code].

Вроде бы ничего не произошло, но, уверяю вас, наш заветный код форм на C++ сформирован и лежит вместе с файлом проекта. Теперь нажмем [Tools]->[Generate Inherited Class].

Это формирует файлы отнаследованных классов, с которыми мы будем дальше работать. Обратите внимание, что эта процедура должна производиться только один раз! Иначе код который мы создадим в Visual Studio «затрется» заново сгенерированным.

«Но как же так? Как дальше работать с проектом если всё затирается новым кодом?» — наверняка спросите вы и будете абсолютно правы. Да, затирается, но нужно просто не генерировать в дальнейшем отнаследованные классы, а генеровать лишь код базовых (материнских) классов, при этом дочерние классы унаследуют весь дизайн базовых и наш код на С++, который «неимоверными усилиями» мы будем писать дальше никуда не пропадет.

Закончив с этим, не забудьте нажать [File]->[Save], чтобы сохранить проект. Всё, формбилдер можно закрыть.

Объединяем проекты[править]

Теперь давайте вернемся в Visual Studio.

Сделаем правый клик на нашей созданной с большой тщательностью и любовью папке GUI и вновь выберем в выскочившей менюшке [Add]->[Existing Item…]. (Если забыли как — ищите скриншот выше). Заходим в папку GUI в проекте на диске, выбираем файл gui.fbp и нажимаем [Add].

Теперь наш файл gui.fbp добавлен в проект VS. Попробуем его открыть. Делаем на нем правый клик и в выскочившем меню выбираем [Open With…].

В открывшемся окне жмем [Add] и добавляем наш любимый wxFormBuilder в качестве приложения для таких файлов. Выбираем его в предложенном списке (он будет последним). Нажмите [Set as default] чтобы он запускался автоматически.

Теперь закройте это окно с выбором приложения. Дважды кликните на gui.fbp. Должен открыться наш проект GUI в wxFormBuilder.

Далее аналогично добавлению в проект файла gui.fbp добавим все файлы с расширением .h в «Header Files», а с расширением .cpp в «Source Files». Результат должен выглядеть примерно так:

Создаем код приложения[править]

Теперь нам нужно создать файлы приложения. Создаем в Header Files файл HelloApp.h — постарайтесь сами разобраться, как это сделать.
Добавляем в него следующий код (объявление класса нашего приложения):

#ifndef HELLOAPP_H
#define HELLOAPP_H

#include <wx/wx.h>

class HelloApp : public wxApp {
public:
	HelloApp();
	virtual ~HelloApp();
	virtual bool OnInit();
};

DECLARE_APP(HelloApp)

#endif

Создаем в Source Files файл HelloApp.cpp , добавляем в него следующий код (определение класса нашего приложения):

#include "HelloApp.h"
#include "gui/guifrmMain.h"

IMPLEMENT_APP(HelloApp)

HelloApp::HelloApp() {
}

HelloApp::~HelloApp() {
}

bool HelloApp::OnInit() {
	guifrmMain* frame = new guifrmMain((wxWindow*)NULL);
	frame->Show();
	SetTopWindow(frame);
	return true;
}

Подключение wxWidgets к проекту и сборка приложения[править]

Теперь нужно настроить компилятор, чтобы он видел заголовки библиотеки wxWidgets.
Открываем пункт меню [Project]->[wxHello Properties…].
Выбираем в списке Configuraton: «All Configurations». Ищем внизу VC Directories и добавляем в Include Directories пути «C:wxWidgets-3.1.2include;» и «C:wxWidgets-3.1.2includemsvc;» (или где там у вас на диске лежит библиотека wxWidgets — нужно «плясать» от нее)

Если вернуться к проекту и попробовать его собрать [Build]->[Build Solution], то можно увидеть, что красные волнистые линии (ошибок в коде) пропали, но он всё еще не собирается из-за ошибки линкера:»Error LNK1104 cannot open file ‘wxbase31ud.lib’ «.
Нужно добавить ссылку на каталог с файлами .lib

Снова открываем пункт меню [Project]->[wxHello Properties…]. На этот раз добавляем в Library Directories путь «C:wxWidgets-3.1.2libvc_lib;» (с возможной поправкой на ваш путь к библиотеке wxWidgets).
Теперь снова запускаем [Build]->[Build Solution] и программа должна собраться: «Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped».

Финал[править]

Жмем кнопку с зеленым треугольником или нажимаем [F5] на клавиатуре и видим работающее приложение:

Всё, урок закончен. Надеюсь, вы удовлетворены проделанной работой.

Дополнение[править]

А давайте проверим, что будет если мы напишем какой-то код, а затем доработаем дизайн формы.

Откроем файл HelloApp.cpp и вставим в конструктор формы команду, которая изменит надпись на «Hello».

#include "guifrmMain.h"

guifrmMain::guifrmMain( wxWindow* parent )
:
frmMain( parent )
{
	m_staticText1->SetLabel(_("Hello"));
}

Запускаем программу и видим:

Теперь дважды кликнем gui.fbp и будем редактировать интерфейс в wxFormBuilder. Для начала выберем в дереве проекта (Object Tree) разместитель bSizer1. Открываем панель Common добавим в наш разместитель кнопку (wxButton).

Выделим на форме нашу кнопку. Далее в верхней панели инструментов кликаем кнопки [Expand] и [Stretch]. Видим, что свойство prortion стало равно 1, а wxExpand пометилось галочкой. Добавленная нами кнопка при этом стала занимать всё доступное оставшееся место внутри разместителя.

Сделаем в нашей программе обработчик какого-нибудь события, например нажатия добавленной нами кнопки. Зайдем в её свойствах на вкладку Events и зададим свойство OnButtonClick равным MyButtonClick. Сгенерируем код формы и перейдем в Visual Studio.

Попробуем запустить программу, она при этом заново пересоберется.

Как мы видим, наш код в конструкторе формы (пишущий «Hello») продолжает работать, но появилась и кнопка, которую мы добавляли в разместитель. Нажмем на кнопку и убедимся, что пока ничего не происходит. Нужно создать код обработчика.

Закрываем наше чудо-приложение и вставляем в guifrmMain.h объявление обработчика MyButtonClick(). Теперь класс guifrmMain выглядит так:

class guifrmMain : public frmMain
{
	public:
		/** Constructor */
		guifrmMain( wxWindow* parent );
	//// end generated class members
		void MyButtonClick(wxCommandEvent& event);
};

Вставляем в guifrmMain.cpp определение обработчика MyButtonClick():

void guifrmMain::MyButtonClick(wxCommandEvent& event)
{
	m_staticText1->SetLabel(_("Hello!"));
}

Запускаем.

Теперь при нажатии на кнопку в конце надписи появляется восклицательный знак. Обработчик работает.

На этом всё.

См. также[править]

  • Уроки wxSmith

По просьбе nealar решил набросать коротенькую статью об основных принципах написания GUI-программ с использованием wxWidgets на родном для него языке C++. Данная статья не представляет никакого интереса для тех, кто уже написал пару-тройку самых простых GUI программ на wxWidgets — она рассчитана на тех, кто никогда ранее не писал графических программ как на wx, так и вообще. =)

1) Краткое описание библиотеки.
wxWidgets представляет собой кросс-платформенную библиотеку, содержащую набор инструментов как для создания GUI, так и для решения вспомогательных задач, таких как работа со структурами данных (строками, динамическими массивами, хеш-таблицами), сокетами, файлами, XML/HTML-документами, написание многопоточных приложений и других, которые можно использовать и в консольных приложениях. Причем, при написании консольного приложения, использующего эти инструменты, в большинстве случаев библиотеку инициализировать никак не надо.
Библиотека wxWidgets, в отличии от, например, QT и GTK+, не старается рисовать всё сама, а по возможности использует нативные элементы той системы, под которой она собрана (если, конечно, не используется порт wxUniversal). Благодаря этому wx потребляет сравнительно мало ресурсов и имеет вид, неотличимый от родных приложений целевой системы. Однако, стоит отметить, что для UNIX-like систем (Linux, *BSD и тд.) wxWidgets, как правило, работает поверх GTK. Так же можно отметить, что wxWidgets портирована на огромное число платформ и имеет биндинги большому числу языков.
GUI-часть библиотеки базируется на основных понятиях — «окно» и «событие». Окном (wxWindow; собственно, раньше библиотека даже так и называлась — wxWindows) является любой графический элемент, начиная от надписи в рабочей области приложения, заканчивая, собственно, самим окном программы (фреймом). Событие — это сообщение, генерируемое каким-либо объектом в результате, например, реакции на действие пользователя, срабатывания таймера или по какой-либо другой причине. События после создания передаются по цепочке всем вышестоящим окнам в поисках обработчика, запрограммированного на обработку данного события.
Связующим звеном между окнами и событиями является идентификатор окна (WindowId). Идентификатор окна можно задать либо в ручную (обычно где-то в программе определяются константы), либо попросить библиотеку выделить идентификатор автоматически, передав в качестве id значение -1 (константа wxID_ANY). Увидев такой идентификатор система назначит окну свободный отрицательный номер. Что бы не было коллизий, вручную надо задавать только положительные идентификаторы. Кроме того, есть ещё ряд предопределенных идентификаторов, с которыми нельзя пересекаться (для них выделен диапозон 5000 — 6000, хотя, разумеется, используются далеко не все). Некоторые дополнительные замечания по работе с идентификаторами можно найти в моей заметке «В помощь начинающим программистам на C++/wxWidgets: Logic Inside.»
Координаты элементов управления (контролов) внутри фрейма рассчитываются автоматически на основе информации об их взаимном расположении с помощью механизма сайзеров (wxSizer). Этот механизм чем-то похож на использование вложенных таблиц для разметки сложной страницы в HTML. Можно задать координаты и в ручную, но тогда их придётся самостоятельно пересчитывать при изменении размера окна. (Использовать сайзеры и жесткое задание координат одновременно невозможно, что, впрочем, не удивиельно).
Как правило, простое GUI-приложение пишется путём наследования от класса фрейма. В конструкторе этого класса создаются объекты всех необходимых элементов управления и вспомогательных элементов (панель, сайзеры, меню, тулбар, статусбар и т.д.), Так же в нём описываются функции-обработчики событий, которые будут генерировать контролы. Приложение может иметь несколько фреймов. В этом случае, обычно, есть один главный фрейм, который является родителем для остальных.
В общих чертах, вроде, все. Теперь к реализации.

2) Структура программы.
В отличие от консольных приложений с использованием wxWidgets, в GUI-программах библиотеку инициализировать обязательно. Для этого используется макрос IMPLEMENT_APP(appname), который разворачивается в следующую конструкцию (кстати говоря, wxWidgets вообще ОЧЕНЬ сильно злоупотребляет макросами — к этому надо привыкнуть):

wxAppConsole *wxCreateApp()
{
	wxAppConsole::CheckBuildOptions(WX_BUILD_OPTIONS_SIGNATURE, "your program");
	return new appname;
}
wxAppInitializer wxTheAppInitializer((wxAppInitializerFunction) wxCreateApp);
extern appname& wxGetApp();
appname& wxGetApp() { return *wx_static_cast(appname*, wxApp::GetInstance()); }

Далее, в зависимости от платформы. Для Виндовс:

extern "C" int WINAPI WinMain(HINSTANCE hInstance,
			HINSTANCE hPrevInstance,
			wxCmdLineArgType lpCmdLine,
			int nCmdShow)
{
	return wxEntry(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}

для всех остальных просто:

int main(int argc, char **argv) { return wxEntry(argc, argv); }

(в некоторых особых случаях, макрос IMPLEMENT_APP может разворачиваться иначе)
Как видно, этот макрос определяет точку входа в программу, то есть объявлять функцию main не надо. Вместо этого надо описать класс-потомок абстрактного класса wxApp и передать его название в качестве аргумента IMPLEMENT_APP. В классе wxApp есть виртуальный метод OnInit(), который вызывается после инициализации библиотеки. Таким образом, можно считать, что выполнение программы начинается с метода OnInit(), унаследованного от wxApp. В нём, в типичном случае, создаётся объект главного фрейма приложения. После отработки всех действий, заданных в wxApp программа входит в главный цикл, в котором wxWidgets проверяет необходимость создания событий и, в случае такой необходимости, создаёт объект wxEvent и вызывает соответствующие обработчики, то есть, дальнейшая работа программы осуществляется исключительно через обработчики событий.
Выглядит инициализация программы примерно так:

#include <wx/wx.h>
class MyApp: public wxApp //наследуемся от wxApp
{
public:
	virtual bool OnInit(); //Переопределяем виртуальный метод OnInit()
};
IMPLEMENT_APP(MyApp) //определяем точку входа в программу
bool MyApp::OnInit() //Реализация OnInit()
{
	MyFrame *frame = new MyFrame(NULL);	//Создаем главное окно программы
	frame->Show(true);			//и отображаем его на экране
	return true; //Return true to continue processing, false to exit the application immediately.
}

3) Рисование окошек.
В общем случае, основное окно программы состоит из меню, тулбара, статусбара и, собственно, элементов управления. Далее описываются основные принципы работы с меню, статусбаром и контролами (тулбар — тема немного более сложная, да и нужен он далеко не всегда).
Описание класса фрейма может выглядеть так:

class MyFrame: public wxFrame //наследуемся от wxFrame
{
public:
	MyFrame(wxWindow* parent); //Объявляем конструктор
	void OnAbout(wxCommandEvent &event);	//объявляем
	void OnQuit(wxCommandEvent &event);	//обработчики
	void OnSave(wxCommandEvent &event);	//событий
	void OnLoad(wxCommandEvent &event);	//
	void OnSize(wxSizeEvent &event);	//
private:
	wxTextCtrl *m_txt;
	wxListBox *m_lstList;
	wxCheckBox *m_chkBox;
	DECLARE_EVENT_TABLE()	//Макрос, необходимый для связывания событий и обработчиков
};
MyFrame::MyFrame(wxWindow* parent) //реализация конструктора
	: wxFrame(parent,
	wxID_ANY, //присвоить идентификатор автоматически
	 wxT("Simple App"), //заголовок окна
	wxDefaultPosition, //не указывать расположение окна
	wxDefaultSize) //не указывать размер окна
{
//код создания элементов фрейма
}

а) Меню.
В самом верху фрейма располагается менюбар, содержащий объекты меню. Каждое меню может содержать пункты (обычные, check, radio и разделители) и другие меню (вложенное меню). Создание меню, в общем случае,выглядит так (здесь и далее подразумевается, что приведённые действия выполняются в конструкторе класса фрейма):

	wxMenu *file_menu = new wxMenu();	//Создаём
	wxMenu *about_menu = new wxMenu();	//меню
	file_menu->Append(wxID_ANY, _T("&TesttAlt-T"), _T("Test"));	//Создаём пункты меню
	file_menu->Append(wxID_EXIT);					//Стандартные идентификаторы обладают забавным свойством - 
	about_menu->Append(wxID_ABOUT);					//они могут заполнять некоторые поля значениями по-умолчанию

	wxMenuBar *menu_bar = new wxMenuBar(); //создаём менюбар
	menu_bar->Append(file_menu, wxT("&File"));	//добавляем
	menu_bar->Append(about_menu, wxT("&Help"));	//меню в менюбар
	SetMenuBar( menu_bar ); //Устанавливаем созданный менюбар в качестве менюбара данного фрейма

Метод Append принимает следующие параметры:

  • идентификатор пункта меню;
  • текст пунктаt<hotkey>;
  • комментарий, отображаемый в статусбаре;
  • тип пункта.

Либо:

  • идентификатор пункта меню;
  • текст пунктаt<hotkey>;
  • подменю;
  • комментарий, отображаемый в статусбаре.

Тип меню может быть одним из следующих:
wxITEM_NORMAL, wxITEM_CHECK, wxITEM_RADIO, wxITEM_SEPARATOR.
Однако, чем пользоваться этими идентификаторами типов, проще применять специализированные методы

  • wxMenu::AppendCheckItem
  • wxMenu::AppendRadioItem
  • wxMenu::AppendSeparator
  • wxMenu::AppendSubMenu
  • wxMenu::Break

Что бы установить состояние только что созданного ЧекИтема (или РадиоИтема) можно воспользоваться методом Check, в который передаётся идентификатор созданного пункта и состояние, в которое его надо установить.

б) Статусбар.
Статусбар — это небольшая полоска внизу фрейма, разделённая на поля. В него можно выводить текст и, при необходимости, контролы. В первое поле автоматически выводится текст подсказок пунктов меню (это можно переназначить, или вообще отключить с помощью метода SetStatusBarPane). Пример создания статус бара с четырьмя полями разных размеров и типов:

	int bar_tmp[4];
	bar_tmp[0] = -1; //Размер первого поля будет высчитываться автоматически
	bar_tmp[1] = 50;
	bar_tmp[2] = 100;
	bar_tmp[3] = 150;
	CreateStatusBar(4); //Создаём статусбар
	SetStatusWidths(4, bar_tmp); //выставляем ширины разделов
	SetStatusText(_T("DoubleClick on list item to load text"), 0); //текст надписи в первом поле
	SetStatusText(_T("StatBar"), 1); //текст надписи во втором поле

	wxStatusBar *barStatus = GetStatusBar(); //Для более сложных манипуляций со статусбаром требуется прямой доступ к его объекту
	bar_tmp[0] = wxSB_FLAT;		//Плоский
	bar_tmp[1] = wxSB_NORMAL;	//Обычный (вдавленный)
	bar_tmp[2] = wxSB_RAISED;	//Выпуклый
	bar_tmp[3] = wxSB_NORMAL;
	barStatus->SetStatusStyles(4, bar_tmp); //устанавливаем стили полей

Устанавливать текст также можно с помощью стека:

	barStatus->PushStatusText(_T("Working..."), 1);	//Выставит текст ”Working...” во втором поле
	//...code...//
	barStatus->PopStatusText(1);	//Вернёт текст "StatBar" во втором поле

Как было сказано выше, в статусбар можно вставлять и контролы, но их положением необходимо управлять вручную:

//в конструкторе:
	m_chkBox = new wxCheckBox(barStatus, wxID_ANY, _T(“stBar checkBox”)); //родителем контрола должен быть статусбар.

void MyFrame::OnSize(wxSizeEvent& event)	//Обработчик события изменения размера окна. Вызывается также и при его создании, так что наша галочка будет на месте.
{
	wxRect rect;
	wxStatusBar *barStatus = GetStatusBar();
	event.Skip();	//это событие может быть обработано и другими обработчиками
	if(NULL == barStatus) //на всякий случай, если статбара нет
		return;
	barStatus->GetFieldRect(3, rect); //позиция и размер четвёртого поля
	m_chkBox->SetSize(rect.x + 2, rect.y + 2, rect.width - 4, rect.height - 4); //устанавливаем позицию и размер контрола
}

в) Размещение контролов.
Создание окон (в том числе и контролов) более-менее стандартизировано. Окно может создаваться в один этап, или в два. При создании в один этап все параметры окна передаются в конструктор. При создании в два этапа конструктор вызывается без параметров, а передаются они позже, с помощью метода Create. Списки параметров конструктора и метода Create совпадают. Для большинства типов окон списки параметров имеют следующий вид:
wxWindow* parent — ссылка на родителя. Отношение родства используется для определения координат, передачи событий, корректного удаления объектов и решения прочих задач.
wxWindowID id — уникальный идентификатор окна. Используется в основном для работы с событиями (определение того, какое именно окно создало событие)
const wxPoint& pos = wxDefaultPosition — позиция окна в координатах его родителя
const wxSize& size = wxDefaultSize — размер окна
long style = 0 — битовая маска стилей окна. Список доступных стилей зависит от конкретного типа окна.
const wxString& name = wxPanelNameStr — имя. Ни разу не использовал… =)

Размещение элементов управления может быть реализовано немного по-разному. Например, если нужно одно большое текстовое поле во весь фрейм, можно не заморачиваться, а создать только wxTextCtrl, передав в его конструктор в качестве родителя указатель на фрейм, а в качестве координаты и размера — wxDefaultPosition и wxDefaultSize, соответственно. Я опишу классический вариант. В этом случае внутрь фрейма помещается панель (wxPanel), на которой с помощью сайзеров размещаются элементы управления. В качестве родителя контролам передаётся уже не фрейм, а панель. Сайзеры не являются окнами, по-этому не могут быть родителями, они по сути лишь элементы разметки. Самый распространённый тип сайзера — wxBoxSizer, который располагает контролы друг за другом в строку или столбец. Существуют также сайзеры, выстраивающие контролы в виде таблицы (wxGridSizer, wxFlexGridSizer и wxGridBagSizer). Далее будет рассматриваться только wxBoxSizer. Сайзер, помещённый на панель (фрейм, и тд.), как, впрочем, и любое окно с размером wxDefaultSize, занимает всё свободное пространство. Внутри себя сайзер может это поведение менять — растянуть находящиеся в нём контролы до размера своих ячеек (с учётом отступов, задаваемых для каждого контрола отдельно), либо оставлять их размеры по умолчанию, а управлять только положением контролов, причём если размер контрола меньше размера ячейки, можно задать его расположение внутри ячейки (комбинацией из трёх положений по вертикали и трёх — по горизонтали). Ну и, разумеется, можно задавать пропорции, в которых надо раздавать доступное место между ячейчами сайзера. Вместо контрола в сайзере может находиться другой сайзер, в котором… Ну, в общем, вы поняли ;)
На практике это может выглядеть примерно так:

// Создаём панель и сайзеры
	wxPanel *MainPanel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
	wxBoxSizer *VFrameMain = new wxBoxSizer(wxVERTICAL);
	wxBoxSizer *HFrameControls1 = new wxBoxSizer(wxHORIZONTAL);
	wxBoxSizer *HFrameControls2 = new wxBoxSizer(wxHORIZONTAL);
	wxStaticBoxSizer *VFrameControls1 = new wxStaticBoxSizer(wxVERTICAL, MainPanel, _T("Group 1")); //тот же BoxSizer
	wxStaticBoxSizer *VFrameControls2 = new wxStaticBoxSizer(wxVERTICAL, MainPanel, _T("Group 2")); //только с рамочкой

// Создаём контролы
	m_txt = new wxTextCtrl(MainPanel, wxID_ANY, _T(""), wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE);
	m_lstList = new wxListBox(MainPanel, ID_LIST);
	wxButton *butExit = new wxButton(MainPanel, wxID_EXIT, _T("Выход"));
	wxButton *butBut1 = new wxButton(MainPanel, ID_BUTTON, _T("Сохранить"));
	wxButton *butBut2 = new wxButton(MainPanel, wxID_ANY, _T("Кнопка 2"));
	wxButton *butBut3 = new wxButton(MainPanel, wxID_ANY, _T("Кнопка 3"));
	wxRadioButton *rdbRadio1 = new wxRadioButton(MainPanel, wxID_ANY, _T("Радиокнопка 1"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP);
	wxRadioButton *rdbRadio2 = new wxRadioButton(MainPanel, wxID_ANY, _T("Радиокнопка 2"));
	wxRadioButton *rdbRadio3 = new wxRadioButton(MainPanel, wxID_ANY, _T("Радиокнопка 3"));
	wxRadioButton *rdbRadio4 = new wxRadioButton(MainPanel, wxID_ANY, _T("Радиокнопка 4"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP);
	wxRadioButton *rdbRadio5 = new wxRadioButton(MainPanel, wxID_ANY, _T("Радиокнопка 5"));
	wxRadioButton *rdbRadio6 = new wxRadioButton(MainPanel, wxID_ANY, _T("Радиокнопка 6"));
	rdbRadio6->SetValue(true);

	MainPanel->SetSizer( VFrameMain ); //Устанавливаем сайзер верхнего уровня

	VFrameMain->Add(
		HFrameControls1, // элемент, который добавляем
		0, //Пропорция (0 => размер ячейки определяется её содержимым)
		wxEXPAND); //Флаги (wxEXPAND => максимально растягивать содержимое по размеру ячейки)
	VFrameMain->Add(
		m_txt,
		1, //Пропорция (1 => занимаем всё свободное место)
		wxEXPAND);
	VFrameMain->Add(
		HFrameControls2,
		0,
		wxEXPAND);

	HFrameControls1->Add(
		butBut1,
		0,
		wxALL | wxEXPAND, // Флаги (wxALL => делать отступ от всех границ содержимого)
		4); //величина отступа
	HFrameControls1->Add(
		butBut2,
		0,
		wxALL | wxEXPAND,
		4);
	HFrameControls1->Add(
		butBut3,
		0,
		wxALL | wxEXPAND,
		4);
	HFrameControls1->AddStretchSpacer();	// Разделитель (займёт всё свободное место)
	HFrameControls1->Add(
		butExit,
		0,
		wxALL | wxEXPAND,
		4);
	HFrameControls2->Add(
		m_lstList,
		2, // Ширина листбокса будет в два раза больше ширин радиобатонов
		wxALL | wxEXPAND,
		4);
	HFrameControls2->Add(
		VFrameControls1,
		1, //Равнопропорционально разделять место между блоками радиобатонов
		wxALL | wxEXPAND,
		4);
	HFrameControls2->Add(
		VFrameControls2,
		1, //Равнопропорционально разделять место между блоками радиобатонов
		wxALL | wxEXPAND,
		4);
	VFrameControls1->Add(
		rdbRadio1,
		1,
		wxALL | wxALIGN_RIGHT,
		4);
	VFrameControls1->Add(
		rdbRadio2,
		2,
		wxALL | wxALIGN_CENTRE,
		4);
	VFrameControls1->Add(
		rdbRadio3,
		3,
		wxALL | wxALIGN_LEFT,
		4);
	VFrameControls2->Add(
		rdbRadio4,
		3,
		wxALL | wxALIGN_LEFT,
		4);
	VFrameControls2->Add(
		rdbRadio5,
		2,
		wxALL | wxALIGN_CENTRE,
		4);
	VFrameControls2->Add(
		rdbRadio6,
		1,
		wxALL | wxALIGN_RIGHT,
		4);

	VFrameMain->SetSizeHints(this); //Фиксируем минимальный размер окна по данной компоновке.
	MainPanel->Layout();	//Перерисовать окно

4) Обработка событий.
Собственно самое главное — обработка событий. Для этого необходимо написать специальные функции — обработчики событий и связать их с соответствующими типами событий. В wxWidgets предусмотрено два вида связывания — статическое (с помощью таблиц сообщений) и динамическое (с помощью метода Connect), причём их можно использовать одновременно.
Функция-обработчик должна принимать в качестве аргумента объект-событие определённого типа. Возвращаемое значение игнорируется.

void MyFrame::OnQuit( wxCommandEvent &event )
{
	Close(true);
}

void MyFrame::OnAbout( wxCommandEvent &event )
{
	wxMessageBox( wxT("wxWidgets samplen(c) 2009 by me =)"));
}

Кроме этого, необходимо с помощью макроса DECLARE_EVENT_TABLE() указать, что данный класс может обрабатывать события. Этот макрос объявляет несколько свойств и методов:

    private:
        static const wxEventTableEntry sm_eventTableEntries[];
    protected:
        static const wxEventTable        sm_eventTable;
        virtual const wxEventTable*      GetEventTable() const;
        static wxEventHashTable          sm_eventHashTable;
        virtual wxEventHashTable&        GetEventHashTable() const;

Для реализации статического связывания пишется таблица событий. Она представляет собой перечисление макросов, связывающих события определённых типов от определённых окон с функциями-обработками, заключенное между макросами BEGIN_EVENT_TABLE(theClass, baseClass) и END_EVENT_TABLE(). Макрос BEGIN_EVENT_TABLE инициализирует переменные и реализует методы, объявленные макросом DECLARE_EVENT_TABLE, для чего в него передаются названия текущего класса и класса, от которого он унаследован. Разворачивается он так:

    const wxEventTable theClass::sm_eventTable =
        { &baseClass::sm_eventTable, &theClass::sm_eventTableEntries[0] }; 
    const wxEventTable *theClass::GetEventTable() const
        { return &theClass::sm_eventTable; }
    wxEventHashTable theClass::sm_eventHashTable(theClass::sm_eventTable);
    wxEventHashTable &theClass::GetEventHashTable() const
        { return theClass::sm_eventHashTable; }
    const wxEventTableEntry theClass::sm_eventTableEntries[] = {

Макрос END_EVENT_TABLE добавляет пустой элемент в таблицу и закрывает перечисление:

DECLARE_EVENT_TABLE_ENTRY( wxEVT_NULL, 0, 0, 0, 0 ) 
};

Сами элементы таблицы имеют следующий вид:

	EVT_MENU(wxID_ABOUT, MyFrame::OnAbout)
	EVT_MENU(wxID_EXIT,  MyFrame::OnQuit)
	EVT_BUTTON(ID_BUTTON,  MyFrame::OnSave)
	EVT_LISTBOX_DCLICK(ID_LIST, MyFrame::OnLoad)
	EVT_SIZE(MyFrame::OnSize)

где
EVT_MENU — макрос, определяющий тип события
wxID_ABOUT — идентификатор элемента, от которого ожидается событие
MyFrame::OnAbout — собственно, функция-обработчик.
Количество аргументов зависит от конкретного типа события. Один и тот же обработчик может быть связан с несколькими разными событиями.

Динамическое связывание подразумевает, что вместо этих макросов используется функция Connect, однако, макросы BEGIN_EVENT_TABLE(theClass, baseClass) и END_EVENT_TABLE() все равно должны присутствовать:

BEGIN_EVENT_TABLE(MyFrame, wxFrame)
END_EVENT_TABLE()

Само связывание выглядит либо так:

	Connect(wxID_EXIT,	// идентификатор элемента, от которого ожидается событие
		wxEVT_COMMAND_BUTTON_CLICKED, // тип события
		wxCommandEventHandler(MyFrame::OnQuit) // функция-обработчик
		);

либо так:

	Connect(wxID_EXIT,	// идентификатор элемента, от которого ожидается событие
		wxEVT_COMMAND_BUTTON_CLICKED, // тип события
		(wxObjectEventFunction)&MyFrame::OnQuit // функция-обработчик
		);

Вот, пожалуй, и всё, что надо знать для начала =).
Для полноты картины в конце приведу полный текст тестовой программы, в котором, заодно, есть пример работы с листбоксом.

#include <wx/wx.h>

class MyApp: public wxApp //наследуемся от wxApp
{
public:
	virtual bool OnInit(); //Переопределяем виртуальный метод OnInit()
};

IMPLEMENT_APP(MyApp) //определяем точку входа в программу

class MyFrame: public wxFrame //наследуемся от wxFrame
{
public:
	MyFrame(wxWindow* parent, wxString title = wxT("Simple App")); //Объявляем конструктор

	void OnAbout(wxCommandEvent &event);	//объявляем
	void OnQuit(wxCommandEvent &event);	//обработчики
	void OnSave(wxCommandEvent &event);	//событий
	void OnLoad(wxCommandEvent &event);	//
	void OnSize(wxSizeEvent &event);	//
	void OnButtonV1(wxCommandEvent &event);	//
	void OnButtonV2(wxCommandEvent &event);	//
private:
	wxTextCtrl *m_txt;
	wxListBox *m_lstList;
	wxCheckBox *m_chkBox;
	DECLARE_EVENT_TABLE()	//Макрос, необходимый для связывания событий и обработчиков
};

struct tListData
{
	tListData(wxString a_str = wxEmptyString) :
		str (a_str) {};
	wxString str;
};

enum {
    ID_BUTTON  = 108,
    ID_LIST
};

BEGIN_EVENT_TABLE(MyFrame, wxFrame)
	EVT_MENU(wxID_ABOUT, MyFrame::OnAbout)
	EVT_MENU(wxID_EXIT,  MyFrame::OnQuit)
	EVT_BUTTON(ID_BUTTON,  MyFrame::OnSave)
	EVT_LISTBOX_DCLICK(ID_LIST, MyFrame::OnLoad)
	EVT_SIZE(MyFrame::OnSize)
END_EVENT_TABLE()


MyFrame::MyFrame(wxWindow *parent, wxString title) //реализация конструктора
	: wxFrame(parent,
		wxID_ANY, //присвоить идентификатор автоматически
		title, //заголовок окна
		wxDefaultPosition, //не указывать расположение окна
		wxDefaultSize) //не указывать размер окна
{
	wxMenu *file_menu = new wxMenu();	//Создаём
	wxMenu *about_menu = new wxMenu();	//меню
	file_menu->Append(wxID_ANY, _T("&TesttAlt-T"), _T("Test"));	//Создаём пункты меню
	file_menu->Append(wxID_EXIT);					//Стандартные идентификаторы обладают забавным свойством - 
	about_menu->Append(wxID_ABOUT);					//они могут заполнять некоторые поля значениями по-умолчанию

	wxMenuBar *menu_bar = new wxMenuBar(); //создаём менюбар
	menu_bar->Append(file_menu, wxT("&File"));	//добавляем
	menu_bar->Append(about_menu, wxT("&Help"));	//меню в менюбар
	SetMenuBar( menu_bar ); //Устанавливаем созданный менюбар в качестве менюбара данного фрейма

	int bar_tmp[4];
	bar_tmp[0] = -1; //Размер первого поля будет высчитываться автоматически
	bar_tmp[1] = 50;
	bar_tmp[2] = 100;
	bar_tmp[3] = 150;
	CreateStatusBar(4); //Создаём статусбар
	SetStatusWidths(4, bar_tmp); //выставляем ширины разделов
	SetStatusText(_T("DoubleClick on list item to load text"), 0); //текст надписи в первом поле
	SetStatusText(_T("StatBar"), 1); //текст надписи во втором поле

	wxStatusBar *barStatus = GetStatusBar(); //Для более сложных манипуляций со статусбаром требуется прямой доступ к его объекту
	bar_tmp[0] = wxSB_FLAT;		//Плоский
	bar_tmp[1] = wxSB_NORMAL;	//Обычный (вдавленный)
	bar_tmp[2] = wxSB_RAISED;	//Выпуклый
	bar_tmp[3] = wxSB_NORMAL;
	barStatus->SetStatusStyles(4, bar_tmp); //устанавливаем стили полей

	m_chkBox = new wxCheckBox(barStatus, wxID_ANY, _T("stBar checkBox")); //родителем контрола должен быть статусбар.

// Создаём панель и сайзеры
	wxPanel *MainPanel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
	wxBoxSizer *VFrameMain = new wxBoxSizer(wxVERTICAL);
	wxBoxSizer *HFrameControls1 = new wxBoxSizer(wxHORIZONTAL);
	wxBoxSizer *HFrameControls2 = new wxBoxSizer(wxHORIZONTAL);
	wxStaticBoxSizer *VFrameControls1 = new wxStaticBoxSizer(wxVERTICAL, MainPanel, _T("Group 1")); //тот же BoxSizer
	wxStaticBoxSizer *VFrameControls2 = new wxStaticBoxSizer(wxVERTICAL, MainPanel, _T("Group 2")); //только с рамочкой

// Создаём контролы
	m_txt = new wxTextCtrl(MainPanel, wxID_ANY, _T(""), wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE);
	m_lstList = new wxListBox(MainPanel, ID_LIST);
	wxButton *butExit = new wxButton(MainPanel, wxID_EXIT, _T("Выход"));
	wxButton *butBut1 = new wxButton(MainPanel, ID_BUTTON, _T("Сохранить"));
	wxButton *butBut2 = new wxButton(MainPanel, wxID_ANY, _T("0"));
	wxButton *butBut3 = new wxButton(MainPanel, wxID_ANY, _T("0"));
	wxRadioButton *rdbRadio1 = new wxRadioButton(MainPanel, wxID_ANY, _T("Радиокнопка 1"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP);
	wxRadioButton *rdbRadio2 = new wxRadioButton(MainPanel, wxID_ANY, _T("Радиокнопка 2"));
	wxRadioButton *rdbRadio3 = new wxRadioButton(MainPanel, wxID_ANY, _T("Радиокнопка 3"));
	wxRadioButton *rdbRadio4 = new wxRadioButton(MainPanel, wxID_ANY, _T("Радиокнопка 4"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP);
	wxRadioButton *rdbRadio5 = new wxRadioButton(MainPanel, wxID_ANY, _T("Радиокнопка 5"));
	wxRadioButton *rdbRadio6 = new wxRadioButton(MainPanel, wxID_ANY, _T("Радиокнопка 6"));
	rdbRadio6->SetValue(true);

	MainPanel->SetSizer( VFrameMain ); //Устанавливаем сайзер верхнего уровня

	VFrameMain->Add(
		HFrameControls1, // элемент, который добавляем
		0, //Пропорция (0 => размер ячейки определяется её содержимым)
		wxEXPAND); //Флаги (wxEXPAND => максимально растягивать содержимое по размеру ячейки)
	VFrameMain->Add(
		m_txt,
		1, //Пропорция (1 => занимаем всё свободное место)
		wxEXPAND);
	VFrameMain->Add(
		HFrameControls2,
		0,
		wxEXPAND);

	HFrameControls1->Add(
		butBut1,
		0,
		wxALL | wxEXPAND, // Флаги (wxALL => делать отступ от всех границ содержимого)
		4); //величина отступа
	HFrameControls1->Add(
		butBut2,
		0,
		wxALL | wxEXPAND,
		4);
	HFrameControls1->Add(
		butBut3,
		0,
		wxALL | wxEXPAND,
		4);
	HFrameControls1->AddStretchSpacer();	// Разделитель (займёт всё свободное место)
	HFrameControls1->Add(
		butExit,
		0,
		wxALL | wxEXPAND,
		4);
	HFrameControls2->Add(
		m_lstList,
		2, // Ширина листбокса будет в два раза больше ширин радиобатонов
		wxALL | wxEXPAND,
		4);
	HFrameControls2->Add(
		VFrameControls1,
		1, //Равнопропорционально разделять место между блоками радиобатонов
		wxALL | wxEXPAND,
		4);
	HFrameControls2->Add(
		VFrameControls2,
		1, //Равнопропорционально разделять место между блоками радиобатонов
		wxALL | wxEXPAND,
		4);
	VFrameControls1->Add(
		rdbRadio1,
		1,
		wxALL | wxALIGN_RIGHT,
		4);
	VFrameControls1->Add(
		rdbRadio2,
		2,
		wxALL | wxALIGN_CENTRE,
		4);
	VFrameControls1->Add(
		rdbRadio3,
		3,
		wxALL | wxALIGN_LEFT,
		4);
	VFrameControls2->Add(
		rdbRadio4,
		3,
		wxALL | wxALIGN_LEFT,
		4);
	VFrameControls2->Add(
		rdbRadio5,
		2,
		wxALL | wxALIGN_CENTRE,
		4);
	VFrameControls2->Add(
		rdbRadio6,
		1,
		wxALL | wxALIGN_RIGHT,
		4);

	VFrameMain->SetSizeHints(this); //Фиксируем минимальный размер окна по данной компоновке.
	MainPanel->Layout();	//Перерисовать окно
/*
	Connect(wxID_EXIT,	// идентификатор элемента, от которого ожидается событие
		wxEVT_COMMAND_BUTTON_CLICKED, // тип события
		wxCommandEventHandler(MyFrame::OnQuit) // функция-обработчик
		);
 либо так:
	Connect(wxID_EXIT,	// идентификатор элемента, от которого ожидается событие
		wxEVT_COMMAND_BUTTON_CLICKED, // тип события
		(wxObjectEventFunction)&MyFrame::OnQuit // функция-обработчик
		);
*/
	Connect(butBut2->GetId(),	// идентификатор элемента, от которого ожидается событие
		wxEVT_COMMAND_BUTTON_CLICKED, // тип события
		wxCommandEventHandler(MyFrame::OnButtonV1) // функция-обработчик
		);
	Connect(butBut3->GetId(),	// идентификатор элемента, от которого ожидается событие
		wxEVT_COMMAND_BUTTON_CLICKED, // тип события
		wxCommandEventHandler(MyFrame::OnButtonV2) // функция-обработчик
		);
}

void MyFrame::OnSize(wxSizeEvent &event)	//Обработчик события изменения размера окна. Вызывается также и при его создании, так что наша галочка будет на месте.
{
	wxRect rect;
	wxStatusBar *barStatus = GetStatusBar();
	event.Skip();	//это событие может быть обработано и другими обработчиками
	if(NULL == barStatus) //на всякий случай, если статбара нет
		return;
	barStatus->GetFieldRect(3, rect); //позиция и размер четвёртого поля
	m_chkBox->SetSize(rect.x + 2, rect.y + 2, rect.width - 4, rect.height - 4); //устанавливаем позицию и размер контрола
}

void MyFrame::OnQuit(wxCommandEvent &event)
{
	Close(true);
}

void MyFrame::OnButtonV1(wxCommandEvent &event)
{
	wxString tmp;
	long val;
	tmp = dynamic_cast<wxButton*>(event.GetEventObject())->GetLabel();
	tmp.ToLong(&val);
	dynamic_cast<wxButton*>(event.GetEventObject())->SetLabel(wxString::Format(_T("%d"), ++val));
}

void MyFrame::OnButtonV2(wxCommandEvent &event)
{
	wxString tmp;
	long val;
	tmp = FindWindowById(event.GetId())->GetLabel();
	tmp.ToLong(&val);
	FindWindowById(event.GetId())->SetLabel(wxString::Format(_T("%d"), --val));
}

void MyFrame::OnAbout(wxCommandEvent &event)
{
	wxMessageBox(_T("wxWidgets samplen(c) 2009 by me =)"));
}

void MyFrame::OnSave(wxCommandEvent &event)
{
	unsigned int num;
	m_lstList->Append(_T("Val "));
	num = m_lstList->GetCount()-1;	//порядковый номер только что добавленного элемента
	m_lstList->SetString(num, m_lstList->GetString(num) + wxString::Format(_T("%d"), num)); //меняем название
		//класс wxString реализует конкетенацию с помощью оператора '+'
		//wxString::Format - статический метод для форматирования строк в стиле printf
	tListData *list_data = new tListData(m_txt->GetValue()); //полезная нагрузка
	m_lstList->SetClientData(num, (void *) list_data);	//Многие классы позволяют прицепить
		//пользовательские данные, что зачастую позволяет избежать создания CustomControl`а, если надо просто связать объект с какими-то данными.
}
void MyFrame::OnLoad(wxCommandEvent &event)
{
	tListData *list_data = (tListData *)event.GetClientData(); //забираем данные, которые были прицеплены к итему листбокса, сгенерировавшему сбытие
	m_txt->SetValue(list_data->str); //и загружаем их в текстовое поле
}


bool MyApp::OnInit() //Реализация OnInit()
{
	MyFrame *frame = new MyFrame(NULL);	//Создаём главное окно программы
	MyFrame *frame1 = new MyFrame(frame, wxT("Frame1"));	//Создаём
	MyFrame *frame2 = new MyFrame(frame, wxT("Frame2"));	//вспомогательные окна

	frame2->Connect(wxID_EXIT,	// идентификатор элемента, от которого ожидается событие
		wxEVT_COMMAND_BUTTON_CLICKED, // тип события
		wxCommandEventHandler(MyFrame::OnQuit), // функция-обработчик
		NULL, // пользовательские данные
		frame1 // истачник события
	);

	frame->Show(true);			//отображаем
	frame1->Show(true);			//фреймы
	frame2->Show(true);			//на экране
	return true; //Return true to continue processing, false to exit the application immediately.
}

Скрин

UPD 01.05.09: поправил пример для иллюстрации работы динамического связывания, обсуждаемой в комментах.
UPD 17.08.09: поправил пример для иллюстрации доступа к объекту из обработчика его события (см. комменты).

В этой иллюстрированной статье описано, как можно создавать графические приложения в CodeBlocks с помощью библиотеки инструментов wxWidgets. Статья предназначена для новичков, но предполагает, что читатель уже не испытывает проблем с созданием консольных приложений вида «Hello World». CodeBlocks можно скачать c codeblocks.org, wxWidgets c wxwidgets.org.

wxWidgets — это библиотека графических инструментов (по-английски Widget toolkit). Она необходима для того, чтобы в среде CodeBlocks можно было создавать программы с графическим интерфейсом. Считаю это лучшим решением, чем GTK+, которая более ориентирована на Линукс-платформы и создаёт интерфейсы, тяжело работающие на Windows. Библиотека преднаначена для использования на огромном числе различных платформ, у каждой из которых свои особенности. И кроме того, зависит от некоторых элементов окружения. Поэтому создатели wxWidgets не дают готовые дистрибутивы, а предлагают пользователям самостоятельно их скомпилировать. Порт библиотеки на каждую платформу имеет своё название. Для варианта на Windows используется наименование wxMSW (wxWidgets MicroSoft Windows), на это слово следует ориентироваться при чтении документации.

Нижеизложенная статья является иллюстрированным пересказыванием шагов, приведённых на официальном сайте wiki.codeblocks.org и в документе docsmswinstall.txt , вложенном в распространяемые исходники. В некоторых местах Интернета можно отыскать немного отличающиеся наборы параметров для коппиляции. Автор данной статьи не берётся судить, какой подход более корректен, следует официальным документам и сразу предупреждает, чтов результате при создании графических элементов в CodeBlocks отмечены некоторые глюки. В качестве ОС используется Windows 7, компилятор MinGW в разновидности GCC. Именно этот компилятор идёт в комплекте с CodeBlocks, он также может быть установлен отдельно с сайта mingw.org. Если используете другой компилятор, то лучше поищите более специализированную инструкцию.

1. Исходим из того, что в вашей системе уже имеется компилятор С++, но путь к нему ещё не занесён в систему. Выполним это. Компилятор С++ нужен, поскольку на этом языке написан исходный код wxWidgets. И если у вас установлен и работает CodeBlocks, то значит, имеется компилятор MinGW/GSS. Необходимо найти папку с этим компилятором и в ней подпапку bin. Если CodeBlocks установлен в систему (т.е. не портативная версия), то быстрее всего это будет C:Program FilesCodeBlocksMinGWbin . Скопируйте в буфер полный адрес данной подпапки.

2. Из меню Пуск зайдите в Control Panel > System > Advanced system settings > Environment Variables… Появится окно Environment Variables.
(Примечание: вы можете сразу попасть в окно System, если воспользуетесь горячей клавишей <Win>+<Pause>. Рекомендую её запомнить.)

3. В списке «System variables» найдите Path. Нажмите кнопку «Edit…». В конце списка папок поставьте точку с запятой и добавьте ранее скопированный путь. Будьте очень внимательны на этом шаге: вы конфигурируете всю Windows. Если вначале у вас там было, к примеру,
%SystemRoot%system32;%SystemRoot%;%SystemRoot%System32Wbem;%SYSTEMROOT%System32WindowsPowerShellv1.0
то после внесения должно получиться
%SystemRoot%system32;%SystemRoot%;%SystemRoot%System32Wbem;%SYSTEMROOT%System32WindowsPowerShellv1.0;C:Program FilesCodeBlocksMinGWbin
Скриншок окна "Edit System Varialbe" после добавления нового пути

4. Скачайте исходный код wxWidgets с wxwidgets.org/downloads, вариант «Windows ZIP» (не Installer). Традиционно рекомендуется брать последний стабильный релиз (не «development»). В настоящий момент это 3.0.3. Если хотите релиз 3.1.0, то учтите, что официальная версия CodeBlocks 16.01 его не поддерживает, вам нужна редакция «Nightly builds». (о ней в конце статьи)

5. Распакуйте архив в папку, в путях к которой нет пробела. К примеру,  C:wxWidgets .

6. Запустите CMD (интерпретатор командной строки). Обычно это делается обычно через нажатие на клавиатуре <Win>+<R> и пропечатывание в появившемся окне команды cmd.
Окно "Run"

7. В CMD перейдите в папку с распакованным wxWidgets и углубитесь в поддеректорию buildmsw. Если устанавливаемая библиотека у нас находиться по адресу C:wxWidgets, то перейти туда можно командой «cd wxWidgetsbuildmsw» (без кавычек). (cd означает «change directory» — сменить директорию. А означает, что адрес новой директории нужно вычислять, начиная с корня диска.)
окно CMD при переходе в папку wxWidgets

8. Скопируйте в командную строку страшную команду
mingw32-make -f makefile.gcc BUILD=release SHARED=1 MONOLITHIC=1 UNICODE=1 CXXFLAGS=-fno-keep-inline-dllexport
Она взята с wiki.codeblocks.org. Внизу страницы есть краткое объяснение, что означает каждый компонент. Также за разъяснениями можно обратиться в конец упоминавшегося документа docsmswinstall.txt из установочного дистрибутива.
окно CMD после вставки команды компиляции wxWidgets

9. Идите пить чай и заниматься подобными вещами: компиляци занимает около получаса, от вас более ничего не нужно. Наберитесь терпения и дождитесь, пока опять не появится неторопливо мигающая строка запроса.
Успешное завершение компиляции библиотеки

(Создатели wxWidgets рекомендуют добавить в систему environment variable WXWIN, указывающую на папку с установленным wxWidgets. Это в дальнейшем позволит производить некоторые действия более коротким способом. Производится в том же окне, где мы в самом начале добавляли в систему путь к компилятору MinGW. Только в этот раз используйте кнопку New. Подробности не расписываю. Если это действие приводит вас в смятение, то пропустите. На работу wxWidgetx внутри CodeBlocks наличие этой переменной в системе не сказывается.)
Занесение новой Environment varialbe в систему

Второй этап. wxWidgets уже скомпилирован, теперь все дальнейшие операции выполняем только внутри CodeBlocks. Для создания программы с графическим интерфейсов выполните следующее:
1. Убедитесь, что у вас подключён плагин wxSmith. Для этого зайдите в главно меню на Plugins > Manage plugins. Плагин традиционно отображается в самом низу. Достаточно включить сам wxSmith. Дополнительные модули «wxSmith — Aui», «wxSmith — Contrib Items» и «wxSmith — MIME plugin» по желанию. Влияния этих модулей на удобство и безглючность реализации основного функционала базовых элементов замечено не было.

2. Традиционное File > New > Projects…

3. В списке шаблонов выбрать «wxWidgets project«.
Окно создания нового проекта

4. На следующем экране wxWidgets version выбираем ту версию, которую подключаем. В нашем случае «wxWidgets 3.0.x«.
Выбор версии wxWidgets

5. Далее идут несколько стандартных экранов. Всё также, как и при создании консольных приложений.

6. Preferred GUI Builder: wxSmith (если выбрать None, то инструментов WYSIWYG не будет, а wxFormBuilder — это внешнее приложение, которое нужно отдельно устанавливать.)
Application Type: Рекомендую выставить Frame Based, поскольку Dialoge Based плохо стыкуется с wxWidgets, именно в этом режиме наблюдается подавляющее большинство глюков.

В двух словах об особенностях данных типов. Dialog Based предназначен для маленьких приложений с интерфейсом вида ‘нажал на кнопку — выскочило сообщением’. Frame Based предназначен для серьёзных приложений, подразумевающих наличие меню, строки состояния и многокомпонентных пользовательских окон. Почти все программы разрабатываются на Frame Based, так что кривость wxWidgets на Dialog Based не является серьёзной проблемой.
Выбор GUI и типа создаваемого приложения

7. wxWidgets’ location. При нажатии на многоточие выскочит окно запроса, в нём в поле base укажите папку, где вы разместили wxWidgets. После закрытия окна этот адрес автоматически перенесётся в wxWidgets’ location. (В будущем вы всегда сможете подправить значение этой переменной через Settings > Global variables…)

Прописывание значения глобальной переменной wx

Указание расположения папки с wxWidgets

8. На следующем окне уберите галочку с «Create Debug configuration«, поскольку библиотека wxWidgets скомпилирована нами только в режиме Release. Нам нет смысла залезать в отладку компонентов самого wxWidgets.
Настройка параметров компилятора

9. Следующем шаге в группе полей «wxWidgets Library Settings» необходимо проставить галочки в соответствии с теми параметрами, с которыми мы ранее скомпилировала wxWidgets. Т.е. все три опции должны быть отмечены. В группе полей «Miscellaneous Settings» — на ваш выбор.
Настройка параметров компилятора -2

Рекомендуется отметить «Configure Advanced Options» и на следующем шаге убедиться, что опция «Use_WXDEBUG_and Debug wxWidgets lib» пуста (нам не нужно заниматься отладкой самого wxWidget). «Release Target» лучше переставить в «GUI Mode«, чтобы создавалось чисто-оконное приложение. В случае выбора «Console Mode» одновременно создадутся два окна: консольное и оконное.

Настройка параметров компилятора -3

10. Всё сделано. Теперь можно создавать графический интерфейс. У процесса есть свои особенности, которые нужно понять. Для тестирования можете попробовать создать кнопку, произведите на ней двойной щелчок и в появившемся коде внутри функции ::OnButton1Click прописать
wxString msg = «Hello wxWidgets!»;
wxMessageBox(msg, _(«Very clever text»));

Код, вызывающий простейшее окно с сообщением

wxWidgets работает

Если собираетесь в будущем регулярно создавать графические интерфейсы посредтсвом wxWidgets, то ознакомьтесь с программой wxFormBuilder — возможно, что создавать интерфейс через неё будет удобнее.

Примечание:
Если имеете в системе внешний компилятор, вроде MinGW, то лучше установите CodeBlocks в редакции «Nightly builds». Делается это просто: на форуме открываете тему с последним релизом, скачиваете 3 архива с компонентами программы (это сама программа и две библиотеки от третих разработчиков) и распаковываете их в одну папку, не содержащую в своём пути пробелов (к примеру, С:CodeBlocks).

Понравилась статья? Поделить с друзьями:
  • Подарок в старину молодоженам своими руками фото с инструкцией
  • Повер банк xiaomi 10000 с беспроводной зарядкой инструкция
  • Д3 капс 5000 инструкция по применению взрослым
  • Арбидол инструкция по применению взрослым для лечения орви капсулы цена
  • Руководство полиции новороссийска