Создание кроссплатформенного GUI-приложения на wxWidgets

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

Концепция плавающих панелей Aui-приложения wxWidgets

В данном руководстве не будет подробно рассказываться об используемых классах wxWidgets и их спецификации. Всю исчерпывающую информацию можно найти из официальных источников. Будет рассмотрен принцип построения приложения на основе классов Aui wxWidgets. Основная идея Aui-приложений заключается в том, что главный фрейм окна приложения включает в себя плавающие панели которые содержат дочерний контент, такой как кнопки, поля редактирования, ввода, чекбоксы и прочие контроллы и элементы интерфейса. Панелями управляет Aui-менеджер, который определяет их количество, компоновку и поведение. Использование Aui-классов в wxWidgets является пожалуй, самым универсальным и логически правильным способом для построения GUI любой сложности и направленности. Теперь настало время проделать это на практике.

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

Первым делом, нужно определить структуру проекта будущего приложения. Директория проекта будет называться «wxMyApp». Внутри этой директории нужно создать некоторые подкаталоги и файлы для хранения исходных текстов и заголовочных файлов:

  • каталог src – здесь должен быть файл cpp, содержащий код реализации графического интерфейса;
  • в каталоге include – содержатся заголовочные файлы h и main.h;
  • каталог obj – здесь будут сохраняться объектные файлы после компиляции исходников для последующей сборки;
  • build – это каталог для сборки приложения, в котором будет храниться его готовый исполняемый файл;
  • файл cpp – содержит код для инициализации wxWidgets-приложения.

Предполагается, что в системе установлены все необходимые пакеты (такие как bin-utils и компилятор GCC) для компиляции приложений из исходных кодов.

Создание главного фрейма окна с главным меню

Итак, добавим в файл Gui.h следующий код:

#ifndef GUI_H
#define GUI_H

//(*Headers(Gui)
#include <wx/wx.h>
#include <wx/aui/aui.h>
#include <wx/aui/auibar.h>
#include <wx/aui/framemanager.h>
#include <wx/artprov.h>
#include <wx/sizer.h>
#include <wx/notebook.h>
#include <wx/menu.h>
#include <wx/panel.h>
#include <wx/statusbr.h>
#include <wx/frame.h>
//*)

class Gui : public wxFrame
{
enum
{
ID_SampleItem
};

public:
Gui(wxWindow* parent, wxWindowID id = -1);
virtual ~Gui();

private:
//(*Handlers ()
void OnQuit(wxCommandEvent& event);
void OnAbout(wxCommandEvent& event);
//*)

//(*Identifiers()
static const long idMenuQuit;
static const long idMenuAbout;

//(*Declarations()

wxMenuBar* MenuBarMain;
wxMenu* MenuFile;
wxMenuItem* MenuFileClose;
wxMenu* MenuHelp;
wxMenuItem* MenuHelpAbout;

DECLARE_EVENT_TABLE()

};

#endif // GUI_H

А файл реализации Gui.cpp должен содержать код, реализующий отрисовку GUI:

#include "../include/Gui.h"
#include <wx/msgdlg.h>

//(*InternalHeaders()
#include <wx/string.h>
#include <wx/intl.h>
//*)

//helper functions
enum wxbuildinfoformat {
short_f, long_f };

wxString wxbuildinfo(wxbuildinfoformat format)
{
wxString wxbuild(wxVERSION_STRING);

if (format == long_f )
{
#if defined(__WXMSW__)
wxbuild << _T("-Windows");
#elif defined(__UNIX__)
wxbuild << _T("-Linux");
#endif

#if wxUSE_UNICODE
wxbuild << _T("-Unicode build");
#else
wxbuild << _T("-ANSI build");
#endif // wxUSE_UNICODE
}

return wxbuild;
}

//(*IdInit(Gui)
const long Gui::idMenuQuit = wxNewId();
const long Gui::idMenuAbout = wxNewId();
//*)

BEGIN_EVENT_TABLE(Gui, wxFrame)
//(*EventTable(Gui)
//*)
END_EVENT_TABLE()

Gui::Gui(wxWindow* parent, wxWindowID id)
{
//(*Initialize(Gui)
Create(parent, id, _T("wxAuiHost - шаблон с использованием плавающих панелей"),
wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE,
_T("id"));
SetClientSize(wxSize(800, 600));
SetMinSize(wxSize(550, 350));

MenuBarMain = new wxMenuBar();
MenuFile = new wxMenu();
MenuFileClose = new wxMenuItem(MenuFile, idMenuQuit, _T("Выход\tAlt-F4"),
_T("Выйти из приложения"), wxITEM_NORMAL);
MenuFile->Append(MenuFileClose);
MenuBarMain->Append(MenuFile, _T("&Файл"));
MenuHelp = new wxMenu();
MenuHelpAbout = new wxMenuItem(MenuHelp, idMenuAbout, _T("О программе...\tF1"),
_T("Показать информацию об этом приложении"), wxITEM_NORMAL);
MenuHelp->Append(MenuHelpAbout);
MenuBarMain->Append(MenuHelp, _T("Справка"));
SetMenuBar(MenuBarMain);

Layout();
Center();

Connect(idMenuQuit,wxEVT_COMMAND_MENU_SELECTED,
(wxObjectEventFunction)&Gui::OnQuit);
Connect(idMenuAbout,wxEVT_COMMAND_MENU_SELECTED,
(wxObjectEventFunction)&Gui::OnAbout);
//*)
}
Gui::~Gui()
{
//Destroy (*Gui)

//*)
}
void Gui::OnQuit(wxCommandEvent& event)
{
Close();
}
void Gui::OnAbout(wxCommandEvent& event)
{
wxString msg = wxbuildinfo(long_f);
wxMessageBox(msg, _("Welcome to..."));
}

В секции //(*Initialize(Gui) производится создание фрейма главного окна приложения. Далее, устанавливается размер окна по-умолчанию (800×600), а также минимальные его габариты — 550×350.

Затем создаются объекты главного меню и его элементов (MenuBarMain, MenuFile и MenuHelp). Создаются также пункты MenuFileClose и MenuHelpAbout для элементов MenuFile и MenuHelp соответственно. С помощью метода Append() все элементы включаются в соответствующие родительские элементы меню в соответствии с задуманной структурой и иерархией.

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

Файл main.h должен содержать следующий код:

#ifndef MAIN_H
#define MAIN_H

#include <wx/wx.h>

// application class
class GuiApp : public wxApp
{
public:
// Этот метод инициализирует приложение wxWidgets
virtual bool OnInit();
};

#endif // MAIN_H

Здесь лишь объявляется класс GuiApp, наследуемый от основного класса wxWidgets wxApp, на котором строится всё приложение. Метод OnInit() предназначен для инициализации приложения.
Реализация класса GuiApp в файле main.cpp:

#include "regex.h"
#include <pcre.h>
#include "./include/main.h"

//(*AppHeaders
#include "./include/Gui.h"
#include <wx/image.h>
//*)

IMPLEMENT_APP(GuiApp);

bool GuiApp::OnInit()
{
//(*AppInitialize
bool wxsOK = true;
wxInitAllImageHandlers();
if(wxsOK)
{
Gui* MainFrame = new Gui(0);

MainFrame->Show();

SetTopWindow(MainFrame);
}
//*)

// enter the application's main loop
return wxsOK;
}

Теперь остаётся только скомпилировать исходный код и собрать приложение, предварительно перейдя в каталог wxMyApp:

$ g++ -c -o obj/Gui.o src/Gui.cpp -g -O0 -Wall `wx-config --cxxflags`
$ g++ -c -o obj/main.o main.cpp -g -O0 -Wall `wx-config --cxxflags`
$ g++ -o build/wxMyApp obj/main.o obj/Gui.o -L. `wx-config --libs --unicode=yes`

Запуск готового приложения:

$ build/wxMyApp

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

Добавление строки состояния Statusbar, отображающей подсказки

Для создания объекта строки состояния внизу окна необходимо его сначала объявить в файле Gui.h, добавив в него следующие строки:

  • static const long ID_STATUSBAR1; — в секцию «//(*Identifiers()»;
  • wxStatusBar* StatusBar; — в секцию «//(*Declarations()».

В файл реализации Gui.cpp после «SetMenuBar(MenuBarMain);» нужно добавить такой код:

StatusBar = new wxStatusBar(this, ID_STATUSBAR1, 0, _T("ID_STATUSBAR1"));
int __wxStatusBarWidths_1[1] = { -1 };
int __wxStatusBarStyles_1[1] = { wxSB_NORMAL };
StatusBar->SetFieldsCount(1,__wxStatusBarWidths_1);
StatusBar->SetStatusStyles(1,__wxStatusBarStyles_1);
SetStatusBar(StatusBar);

А также в секцию «//(*IdInit(Gui)» определение идентификатора для статусбара:

const long Gui::ID_STATUSBAR1 = wxNewId();

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

Включение Aui-менеджера и добавление плавающего ToolBar

Для универсального и настраиваемого GUI-приложения нужно, чтобы панели инструментов можно было перемещать и компоновать между собой и в разных частях фрейма практически как угодно. Именно это позволяет сделать класс wxAuiToolBar, который работает под управлением Aui-менеджера. Для включения последнего и для добавления объектов панели инструментов необходимо сделать некоторые добавления соответствующего кода. В Gui.h в секции «//(*Declarations()»:

wxAuiManager* LayoutManager;
wxAuiToolBar* ToolbarMain;

Для Gui.cpp после строки «SetMinSize(wxSize(550, 350));» добавить:

LayoutManager = new wxAuiManager(NULL, wxAUI_MGR_DEFAULT);
LayoutManager->SetManagedWindow(this);

А также после строки «SetStatusBar(StatusBar);» добавить реализацию, собственно, панели инструментов:

ToolbarMain = new wxAuiToolBar(this, wxID_ANY, wxDefaultPosition,
wxDefaultSize, wxAUI_TB_PLAIN_BACKGROUND | wxAUI_TB_OVERFLOW);
ToolbarMain->SetToolBitmapSize(wxSize(48, 48));
ToolbarMain->SetToolBorderPadding(0);
ToolbarMain->AddTool(ID_SampleItem, wxT("New File"),
wxArtProvider::GetBitmap(wxART_NEW, wxART_TOOLBAR));
ToolbarMain->AddSpacer(5);
ToolbarMain->AddTool(ID_SampleItem + 1, wxT("Open File"),
wxArtProvider::GetBitmap(wxART_FILE_OPEN, wxART_TOOLBAR));
ToolbarMain->AddTool(ID_SampleItem + 2, wxT("Save File"),
wxArtProvider::GetBitmap(wxART_FILE_SAVE, wxART_TOOLBAR));
ToolbarMain->AddSeparator();
ToolbarMain->AddTool(ID_SampleItem + 3, wxT("Search"),
wxArtProvider::GetBitmap(wxART_FIND, wxART_TOOLBAR));

Иконки для кнопок тулбара определяются вторым параметром метода GetBitMap класса wxArtProvider. Для каждой платформы и для каждой графической среды окружения будут подгружены иконки по-умолчанию, предоставляемые самой платформой. Никаких сторонних ресурсов для этого не нужно.

Также в деструктор Gui::~Gui() нужно добавить реализацию уничтожения Aui-менеджера при закрытии приложения:

Gui::~Gui()
{
//Destroy (*Gui)
LayoutManager->UnInit();
//*)
}

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

ToolbarMain->AddTool(ID_SampleItem + 3, wxT("Search"),
wxArtProvider::GetBitmap(wxART_FIND, wxART_TOOLBAR));

нужно добавить следующий код:

LayoutManager->AddPane(ToolbarMain, wxAuiPaneInfo().Name(wxT("ToolbarMain")).
ToolbarPane().Caption(wxT("Main Toolbar")).Layer(10).Top().
BottomDockable(false).PaneBorder(false));

LayoutManager->Update();

Приложение теперь нужно собирать несколько иначе. В команду:

$ g++ -o build/wxMyApp obj/main.o obj/Gui.o -L. `wx-config --libs --unicode=yes`

теперь нужно добавить использование требуемых библиотек линковщиком, чтобы приложение могло использовать функционал Aui-менеджера:

$ g++ -o build/wxMyApp obj/main.o obj/Gui.o -L. `wx-config --libs aui core --unicode=yes`

В результате, панель инструментов будет отображена, но пока она будет растянута на всю высоту окна, ведь ещё не добавлены следующие элементы — плавающие панели.

Добавление плавающих панелей

Теперь можно реализовать завершающий этап создания GUI-приложения на wxWidgets – добавить плавающие панели. В данном примере будет три панели, демонстрирующие классическую компоновку современных приложений: левая панель, нижняя и основная панель. Для левой панели будет добавлено две вкладки, реализуемые методами класса wxAuiNoteBook. Итак, для фала Gui.h нужно в секции «//(*Declarations()» добавить следующий код:

wxAuiNotebook* Panel1;
wxAuiNotebook* Panel2;
wxAuiNotebook* Panel3;
wxPanel* Panel1Tab1;
wxPanel* Panel1Tab2;
wxBoxSizer* BoxSizer1;

Для файла Gui.cpp должна быть реализация, добавленная между строкой:

ToolbarMain->AddTool(ID_SampleItem + 3, wxT("Search"),
wxArtProvider::GetBitmap(wxART_FIND, wxART_TOOLBAR));

и строкой:

LayoutManager->AddPane(ToolbarMain, wxAuiPaneInfo().Name(wxT("ToolbarMain")).
ToolbarPane().Caption(wxT("Main Toolbar")).Layer(10).Top().
BottomDockable(false).PaneBorder(false));

Эта реализация должна содержать следующий код:

Panel1 = new wxAuiNotebook(this, wxID_ANY,
wxDefaultPosition, wxSize(200, 150),
wxAUI_NB_TAB_MOVE|wxAUI_NB_WINDOWLIST_BUTTON|wxAUI_NB_CLOSE_ON_ACTIVE_TAB
|wxNO_BORDER);
Panel1Tab1 = new wxPanel(Panel1, wxID_ANY, wxDefaultPosition,
wxDefaultSize, wxNO_BORDER, _T("Panel1Tab1"));
BoxSizer1 = new wxBoxSizer(wxVERTICAL);
Panel1Tab1->SetSizer(BoxSizer1);
BoxSizer1->Fit(Panel1Tab1);
BoxSizer1->SetSizeHints(Panel1Tab1);

Panel1Tab2 = new wxPanel(Panel1, wxID_ANY, wxDefaultPosition,
wxDefaultSize, wxTAB_TRAVERSAL, _T("Panel1Tab2"));

Panel1->AddPage(Panel1Tab1, _T("Вкладка 1"));
Panel1->AddPage(Panel1Tab2, _T("Вкладка 2"));

Panel2 = new wxAuiNotebook(this, wxID_ANY,
wxDefaultPosition, wxSize(200, 150),
wxAUI_NB_TAB_MOVE|wxAUI_NB_WINDOWLIST_BUTTON|wxAUI_NB_CLOSE_ON_ACTIVE_TAB
|wxNO_BORDER);

Panel3 = new wxAuiNotebook(this, wxID_ANY,
wxDefaultPosition, wxSize(200, 150),
wxAUI_NB_TAB_MOVE|wxAUI_NB_WINDOWLIST_BUTTON|wxAUI_NB_CLOSE_ON_ACTIVE_TAB
|wxNO_BORDER);

// Добавление панелей в раскладку Aui-менеджера
LayoutManager->AddPane(Panel1, wxAuiPaneInfo().Name(_T("NB1")).
DefaultPane().Caption(_T("Боковая панель")).MinimizeButton().
MaximizeButton().PinButton().Left().FloatingSize(wxSize(230, 370)).
BestSize(wxSize(230, 120)));
LayoutManager->AddPane(Panel3, wxAuiPaneInfo().Name(_T("NB3")).
DefaultPane().Caption(_T("Главная панель")).
CaptionVisible().MinimizeButton().MaximizeButton().PinButton().Center());
LayoutManager->AddPane(Panel2, wxAuiPaneInfo().
Name(_T("NB2")).DefaultPane().
Caption(_T("Нижняя панель")).
CaptionVisible().MinimizeButton().MaximizeButton().PinButton().Bottom().
TopDockable(false).LeftDockable(false).RightDockable(false).
BestSize(wxSize(95, 85)));

Теперь снова нужно пересобрать приложение (также с использованием библиотек aui и core) и в результате должно получиться нечто подобное:

Можно перемещать панель инструментов и сами плавающие панели:

Заключение

В заключение очень важно отметить, что для первого знакомства данный пример может оказаться несколько сложным. Однако поэкспериментировав с кодом (который, кстати достаточно понятен всем, кто знаком с C++) и изучив документацию по используемым в нём классам wxWidgets можно довольно быстро во всём разобраться. А данный код можно в дальнейшем использовать как универсальный шаблон каркаса для собственных приложений.

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.

Понравилась статья? Поделиться с друзьями:
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!:

ИТ Проффи

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: