суббота, 20 октября 2018 г.

00.05.02 GUI классы. Ресурсная система на основе XML (XRC).

Источник: https://docs.wxwidgets.org/3.1/overview_xrc.html

XRC был написан Вацлавом Славиком. XRC позволяет в текстовых файлах хранить элементы интерфейса (диалоги, панели меню, панели инструментов и т.д.) и загружать их во время выполнения. 

Файлы XRC также могут быть скомпилированы в двоичные файлы XRS или в код C ++, первый вариант позволяет хранить все ресурсы в одном файле, а второй полезен, когда нужно внедрить ресурсы в исполняемый файл.


Преимуществ использования ресурсов XRC:
  • Не требуется повторная компиляция и линковка при изменении файла ресурсов.
  • Бывает, что используемый конструктор диалогов генерирует код на C ++, который сложно интегрировать с существующим кодом на C ++. Разделение ресурсов и кода - более элегантное решение.
  • При необходимости, во время выполнения можно делать выбор между различными файлами альтернативных ресурсов.
  • Формат XRC использует сайзеры, что обеспечивает гибкость, можно создавать диалоги изменяемого размера и повторно использовать их в разных местах.
  • Формат XRC является стандартом wxWidgets и может генерироваться или обрабатываться любой программой, которая его понимает. Так как он основан на стандарте XML, для простого редактирования можно использовать существующие редакторы XML.




Начало работы с XRC

Создание файла XRC
Создадим файл XRC. Хотя это можно сделать вручную даже в блокноте, все же рекомендуется использовать специализированный инструмент. К ним относятся:
Не бесплатно :
Свободно:
  • XRCed http://xrced.sourceforge.net/ , редактор диалогов на основе wxPython.
  • wxFormBuilder http://wxformbuilder.org/ , конструктор форм на C++, который формирует код  C++, XRC или python.
  • wxCrafter (бесплатная версия) http://www.codelite.org/wxcrafter/ , конструктор форм на C++, который формирует код C++ или XRC.
...
И так, файл XRC. Содержит простой диалог:
<?xml version="1.0" ?>
<resource version="2.3.0.1">
<object class="wxDialog" name="SimpleDialog">
<title>Simple dialog</title>
<object class="wxBoxSizer">
<orient>wxVERTICAL</orient>
<object class="sizeritem">
<object class="wxTextCtrl" name="text"/>
<option>1</option>
<flag>wxALL|wxEXPAND</flag>
<border>10</border>
</object>
<object class="sizeritem">
<object class="wxBoxSizer">
<object class="sizeritem">
<object class="wxButton" name="clickme_btn">
<label>Click</label>
</object>
<flag>wxRIGHT</flag>
<border>10</border>
</object>
<object class="sizeritem">
<object class="wxButton" name="wxID_OK">
<label>OK</label>
</object>
<flag>wxLEFT</flag>
<border>10</border>
</object>
<orient>wxHORIZONTAL</orient>
</object>
<flag>wxALL|wxALIGN_CENTRE</flag>
<border>10</border>
</object>
</object>
</object>
</resource>
Все элементы XRC можно хранить как в одном файле, так и в нескольких.

Загрузка XRC файлов
Для использования файлов XRC в приложении, их предварительно следует загрузить. Фрагмент кода ниже показывает, как загрузить единственный файл XRC с именем  "resource.xrc" из текущей рабочей директории, а также все файлы the *.xrc, содержащиеся в поддиректории "rc".
#include "wx/xrc/xmlres.h"
bool MyApp::OnInit()
{
...
wxXmlResource::Get()->Load("resource.xrc");
...
}
Загрузка ресурсных XRC - в начале работы приложения - обычное дело. Позднее можно выгрузить эти файлы, но такое необходимо редко.

Использование элемента XRC
И так, мы загрузили XRC файл(ы) в виртуальную файловую систему приложения. Начиная с этого момента,  нужно выполнить другой тип загрузки, в случае, когда нужно использовать отдельный объект. Возможно сказанная фраза звучит коряво, но сначала нужно загрузить Load() файл, а только затем загрузить каждый нужный объект верхнего уровня..
Описанный выше простой диалог можно использовать в своем коде следующим образом:
void MyClass::ShowDialog()
{
wxDialog dlg;
if (wxXmlResource::Get()->LoadDialog(&dlg, NULL, "SimpleDialog"))
dlg.ShowModal();
}
То есть, все просто. Вся реализация делается системой XRC.
Хотя, чаще всего придется использовать wxXmlResource::LoadDialog, также есть эквиваленты, которые загружают фрейм, меню, и т.п.; и общий метод wxXmlResource::LoadObject. См. подробности в wxXmlResource.

Доступ к дочерним контролам XRC
Выше было показано, как загружать окна верхнего уровня типа диалогов, но как насчет вложенных в диалог окон вроде wxTextCtrl с именем "text", которые которые содержат текст? Возможности таким же способом загрузить отдельный контрол нет. Вместо этого используем макро XRCCTRL,  возвращающее указатель на дочерний элемент. Расширим предыдущий код:
void MyClass::ShowDialog()
{
wxDialog dlg;
if (!wxXmlResource::Get()->LoadDialog(&dlg, NULL, "SimpleDialog"))
return;
wxTextCtrl* pText = XRCCTRL(dlg, "text", wxTextCtrl);
if (pText)
pText->ChangeValue("This is a simple dialog");
dlg.ShowModal();
}
XRCCTRL получает ссылку на родительский контейнер и использует wxWindow::FindWindow для поиска внутри wxWindow элемента с указанным именем (это имя - "text"). И возвращает указатель на контрол, приведенный к типу третьего параметра; то же самое можно сделать и следующим образом:
pText = (wxTextCtrl*)(dlg.FindWindowByName("text"));

XRC и идентификаторы (ID-ы)
Часто бывает нужен ID контрола, например для использования в таблице событий или в методе wxEvtHandler::Bind. Его несложно найти, передав имя котрола в макрос XRCID:
void MyClass::ShowDialog()
{
wxDialog dlg;
if (!wxXmlResource::Get()->LoadDialog(&dlg, NULL, "SimpleDialog"))
return;
XRCCTRL(dlg, "text", wxTextCtrl)->Bind(wxEVT_COMMAND_TEXT_UPDATED,
wxTextEventHandler(MyClass::OnTextEntered), this, XRCID("text"));
XRCCTRL(dlg, "clickme_btn", wxButton)->Bind(wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(MyClass::OnClickme), this, XRCID("clickme_btn"));
dlg.ShowModal();
}
Замечания:
  • Значение типа int, возвращаемое XRCID("foo") является гарантированно уникальнымв рамках приложения.
  • Тем не менее, не следует надеяться, что оно будет равно какой-то заранее известной это величине, и не следует опираться полагаться на это значение как на основу при реализации взаимодействия между программами. В разных приложениях оно может быть разным.
  • Зарезервированные значения идентификаторов, такие как wxID_OK работаю верно и не требуют XRCID (так как внутри XRCID("wxID_OK") отображается на wxID_OK).
  • И XRCID и XRCCTRL используют 'name' контрола (такое, как в wxWindow::GetName). Оно отличается от мети, которую пользователь видит, например, на поверхности  wxButton.
Наследование классов в XRC
Возможно, понадобится создавать наследников классов контролов wx. Для наследования классов контролов из XRC есть три способа:
Предположим, мы хотим, чтобы контрол wxTextCtrl под именем "text" создавался как наш производный класс MyTextCtrl. Нужно изменить одну строчку XRC файла:
<object class="wxTextCtrl" name="text" subclass="MyTextCtrl"/>
Далее, в XRCCTRL следует указать MyTextCtrl. Однако, чтобы подклассы создавались правильно, важно быть уверенным, что он использует wxWidget-овский механизм RTTI: см. Subclassing.

Пример XRC

Основным ресурсом для изучения XRC является XRC Sample. Он демонстрирует все стандартные методы использования XRC, и некоторые из менее распространенных. Весьма рекомендуется к изучению для понимания работы XRC (код хорошо документирован).

Двоичные ресурсные файлы

Чтобы скомпилировать двоичный ресурсный файл, следует использовать утилиту командной строки wxrc. Она принимает один или больше параметров(входных файлов XRC) и следующих ключей и параметров:
  • -h (--help): Показывает справочное сообщение.
  • -v (--verbose): Показывает подробную информацию при работе.
  • -c (--cpp-code): Создает C++ - исходник, а не файл XRS.
  • -e (--extra-cpp-code): Если используется с -c, генерируется заголовочный файл C++, содержащий определения окон, определенных в файле XRC (см. специальные подразделы).
  • -u (--uncompressed): Не сжимает файлы XML (только для C++).
  • -g (--gettext): Выводит выровненные с помощью символа подчеркивания строки, которые могут быть сканированы poEdit или gettext. Вывод в stdout, или в файл, если используется -o.
  • -n (--function) <name>: Задает имя функции C++ (используется с -c).
  • -o (--output) <filename>: Задает выходной файл, например resource.xrs или resource.cpp.
  • -l (--list-of-handlers) <filename>: Выводит в этот файл список необходимых обработчиков.
Например:
$ wxrc resource.xrc
$ wxrc resource.xrc -o resource.xrs
$ wxrc resource.xrc -v -c -o resource.cpp
Замечание
Файл XRS фактически является переименованным ZIP архивом, что означает, что мы можем манипулировать с ним стандартными инструментами ZIP. Отметим, что при использовании файлов XRS сперва нужно инициализировать обработчик архива  wxFileSystem, это очень просто::
#include <wx/filesys.h>
#include <wx/fs_arc.h>
...
wxFileSystem::AddHandler(new wxArchiveFSHandler);

Использование внедренных ресурсов

Порой полезно внедрять ресурсы прямо в исполнительный файл вместо загрузки внешнего файла  (например, наше приложение крошечное и содержит только один exe файл). XRC предоставляет средства для преобразования ресурсов в обычный C++ файл, который может быть откомпилирован и включен в исполняемый файл.
Для создания C++ файла со встроенными ресурсами. Этот файл будет содержать функцию с именем InitXmlResource (если вы не переопределите это с помощью ключей в командной строке). Использование ее для загрузки ресурса:
extern void InitXmlResource(); // defined in generated file
...
wxXmlResource::Get()->InitAllHandlers();
InitXmlResource();
...

Генерация заголовочного файла C++

Использование ключа  -e совместно с ключом -c создает заголовочный файл C++, содержащий определения класса для окон, определенных в файле XRC. Генерация этого кода может облегчить использование XRC и автоматизировать разработку программ. Классы могут быть использованы в качестве основы для разработки, освобождая программиста от многих особенностей, связанных с XRC (например, от применения XRCCTRL).
Для каждого окна верхнего уровня, определенного в файле XRC, генерируется определение класса C++, содержащего в качестве членов класса виджеты окна. Для каждого класса также генерируется конструктор по умолчанию. Внутри конструктор выполняется вся загрузка XRC и выполняется инициализация всех членов класса, представляющих виджеты.
Следующий простой пример позволит помочь понять, как работает эта схема. Предположим, у нас есть файл XRC, в котором определено окно верхнего уровня TestWnd_Base, которое является сабклассом wxFrame (любой другой класс, вроде wxDialog будет работать точно также), и имеющий виджеты wxTextCtrl A и wxButton B.
Файл XRC и соответствующее определение класса в заголовочном файле будет примерно такими:
<?xml version="1.0"?>
<resource version="2.3.0.1">
<object class="wxFrame" name="TestWnd_Base">
<size>-1,-1</size>
<title>Test</title>
<object class="wxBoxSizer">
<orient>wxHORIZONTAL</orient>
<object class="sizeritem">
<object class="wxTextCtrl" name="A">
<label>Test label</label>
</object>
</object>
<object class="sizeritem">
<object class="wxButton" name="B">
<label>Test button</label>
</object>
</object>
</object>
</object>
</resource>
class TestWnd_Base : public wxFrame
{
protected:
private:
void InitWidgetsFromXRC()
{
wxXmlResource::Get()->LoadObject(this, NULL, "TestWnd", "wxFrame");
A = XRCCTRL(*this, "A", wxTextCtrl);
B = XRCCTRL(*this, "B", wxButton);
}
public:
TestWnd::TestWnd()
{
InitWidgetsFromXRC();
}
};
Сгенерированный класс окна может быть использован как основа для полного класса окна. К членам класса, представляющим виджеты, в любой момент можно получит доступ по имени путем ссылки на них, вместо использования макроса XRCCTRL (отметим, что они - защизенные члены класса), хотя мы все еще может использовать XRCID для ссылки на IDs виджетов в таблице событий.
Пример:
#include "resource.h"
class TestWnd : public TestWnd_Base
{
public:
TestWnd()
{
// A, B already initialised at this point
A->SetValue("Updated in TestWnd::TestWnd");
B->SetValue("Nice :)");
}
void OnBPressed(wxEvent& event)
{
Close();
}
};
wxBEGIN_EVENT_TABLE(TestWnd,TestWnd_Base)
EVT_BUTTON(XRCID("B"), TestWnd::OnBPressed)
К wxSizerItem также можно получить доступ как к части ресурса с помощью макроса XRCSIZERITEM, как показано ниже.
Ресурсный файл может иметь сайзер вроде такого:
<object class="spacer" name="area">
<size>400, 300</size>
</object>
Код для доступа к элементу - сайзеру с помощью XRCSIZERITEM (а также и XRCID):
wxSizerItem* item = XRCSIZERITEM(*this, "area");

Добавление новых обработчиков ресурсов

Добавить новые обработчики ресурсов довольно просто.
Как правило, для добавления обработчика класса MyControl, нам нужно создать файлы xh_mycontrol.h и xh_mycontrol.cpp.
Заголовочный файл должен содержать определение класса MyControlXmlHandler:
class MyControlXmlHandler : public wxXmlResourceHandler
{
public:
// Constructor.
MyControlXmlHandler();
// Creates the control and returns a pointer to it.
// Returns true if we know how to create a control for the given node.
virtual bool CanHandle(wxXmlNode *node);
// Register with wxWidgets' dynamic class subsystem.
wxDECLARE_DYNAMIC_CLASS(MyControlXmlHandler);
};

Реализация нашего пользовательского обработчика XML обычно выглядит так:
// Register with wxWidgets' dynamic class subsystem.
MyControlXmlHandler::MyControlXmlHandler()
{
// this call adds support for all wxWidgets class styles
// (e.g. wxBORDER_SIMPLE, wxBORDER_SUNKEN, wxWS_EX_* etc etc)
AddWindowStyles();
// if MyControl class supports e.g. MYCONTROL_DEFAULT_STYLE
// you should use:
// XRC_ADD_STYLE(MYCONTROL_DEFAULT_STYLE);
}
wxObject *MyControlXmlHandler::DoCreateResource()
{
// the following macro will init a pointer named "control"
// with a new instance of the MyControl class, but will NOT
// Create() it!
XRC_MAKE_INSTANCE(control, MyControl)
// this is the point where you'll typically need to do the most
// important changes: here the control is created and initialized.
// You'll want to use the wxXmlResourceHandler's getters to
// do most of your work.
// If e.g. the MyControl::Create function looks like:
//
// bool MyControl::Create(wxWindow *parent, int id,
// const wxBitmap &first, const wxPoint &posFirst,
// const wxBitmap &second, const wxPoint &posSecond,
// const wxString &theTitle, const wxFont &titleFont,
// const wxPoint &pos, const wxSize &size,
// long style = MYCONTROL_DEFAULT_STYLE,
// const wxString &name = wxT("MyControl"));
//
// Then the XRC for your component should look like:
//
// <object class="MyControl" name="some_name">
// <first-bitmap>first.xpm</first-bitmap>
// <second-bitmap>text.xpm</second-bitmap>
// <first-pos>3,3</first-pos>
// <second-pos>4,4</second-pos>
// <the-title>a title</the-title>
// <title-font>
// <!-- Standard XRC tags for a font: <size>, <style>, <weight>, etc -->
// </title-font>
// <!-- XRC also accepts other usual tags for wxWindow-derived classes:
// like e.g. <name>, <style>, <size>, <position>, etc -->
// </object>
//
// And the code to read your custom tags from the XRC file is just:
control->Create(m_parentAsWindow, GetID(),
GetBitmap(wxT("first-bitmap")),
GetPosition(wxT("first-pos")),
GetBitmap(wxT("second-bitmap")),
GetPosition(wxT("second-pos")),
GetText(wxT("the-title")),
GetFont(wxT("title-font")),
GetPosition(), GetSize(), GetStyle(), GetName());
SetupWindow(control);
return control;
}
bool MyControlXmlHandler::CanHandle(wxXmlNode *node)
{
// this function tells XRC system that this handler can parse
// the <object class="MyControl"> tags
return IsOfClass(node, wxT("MyControl"));
}
В документации wxXmlResourceHandler можно узнать, сколько встроенных геттеров скобы узнать, есть сколько встроенных геттеров. С их помощью очень легко извлекать сложные структуры из XRC-файлов.

Комментариев нет:

Отправить комментарий