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 можно довольно быстро во всём разобраться. А данный код можно в дальнейшем использовать как универсальный шаблон каркаса для собственных приложений.