Skip to content Skip to footer

Перевод книги Julian’а Smart’а – Глава IX – Написание собственных диалогов – Часть III

Читать вторую часть главы “Написание собственных диалогов”.

Дополнительные заметки о дизайне диалогов

Несколько советов, которые помогут вашим диалогам выглядеть профессионально выглядящими.

Указывайте мнемоники для меток со статическим текстом и для других элементов управления с метками. Для этого необходимо поставить перед необходимым символом амперсанд (&). На некоторых платформах (особенно Windows и GTK+) мнемоники помогают пользователю быстро передвигаться между элементами управления.

Всегда предоставляйте пользователю возможность закрыть диалог без записи, предпочтительно при помощи нажатия клавиши клавиатуры Escape. Если диалог имеет кнопку с идентификатором wxID_CANCEL, то ее обработчик будет автоматически вызван, если пользователь нажмет клавишу Esc. Поэтому, если у вас на диалоге есть кнопка Close (Закрыть), то разумно дать ей идентификатор wxID_CANCEL.

Указывайте кнопку по умолчанию (обычно “OK”), используя метод wxButton::SetDefault. Команда для этой кнопки вызовется, если пользователь нажмет клавишу Enter.

Отделение интерфейса от данных

В целях упрощения примера все используемые PersonalRecordDialog данные мы храним внутри диалога. Однако более правильным будет создание отдельного класса данных, который будет отделен от класса диалога. С помощью конструктора копирования и оператора присваивания вы можете передать копию данных в диалог и получить модифицированную копию данных из диалога только если пользователь подтвердил изменения. Это же правило применимо и для некоторых стандартных диалогов. В качестве упражнения вы можете переписать PersonalRecordDialog, чтобы он использовал класс PersonalRecordData. При такой реализации конструктор диалога должен получать ссылку на PersonalRecordData. В диалоге также желательно создать функцию GetData, которую может вызывать приложение, чтобы получить данные из диалога.

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

Если в вашем диалоге нет кнопки “Apply” (Применить), которая сохраняет ваши изменения, то нажатие на “Cancel” должно оставлять данные данные приложения в том же состоянии, как они были до открытия диалога. Использование отдельного класса данных очень легко решает эту задачу, так как диалог редактирует не “настоящие” данные, а только их копию.

Макет

Если диалог выглядит излишне компактным или сжатым, то можно добавить в него немного пространства. Попробуйте увеличить пространство около границы диалога, используя дополнительные сайзеры (как это сделано в PersonalRecordDialog) и добавив пространство между группами элементов. Рекомендуется использовать wxStaticBoxSizer и wxStaticLine, чтобы логически сгруппировать или разделить элементы управления. Сайзеры wxGridSizer и wxFlexGridSizer позволяют выравнять элементы и их метки так, чтобы у них не появлялся случайный отступ. Для выравнивания групп элементов очень часто используют расширяющиеся спейсеры. Например, кнопки “OK”, “Cancel” и “Help” обычно помещают в отдельную группу, которую выравнивают по правой границе диалога. Этого можно достичь поместив спейсер и кнопки в горизонтальный wxBoxSizer и установив у спейсера расширение по горизонтали (задав положительный коэффициент расширения).

Если это возможно и допустимо – делайте ваши диалоги с изменяемым размером. Традиционно диалоги в Windows очень редко позволяют изменять свой размер пользователю, однако не существует причины почему это надо запрещать, так как маленькие элементы управления на большом экране могут быть очень неудобными. wxWidgets упрощает создание таких диалогов с помощью сайзеров, поэтому необходимо использовать их везде, что позволит диалогу учитывать текущий шрифт и размер элементов, а также упростит поддержку различных языке. Внимательно выбирайте какие из элементов управления могут расти; например, многострочный элемент редактирования является хорошим кандидатом и при увеличении размера даст пользователю дополнительное место для редактирования. Расширяющиеся спейсеры можно использовать, чтобы реализовать выравнивание в изменяющемся диалоге. Заметим, что не нужно самостоятельно изменять размер элементов управления увеличивая или уменьшая текст в нем, просто давайте больше или меньше пространства элементу управления. Обратитесь к Главе 7 за дополнительной информацией.

Если ваш диалог получается слишком большим – разбейте его на панели и используйте wxNotebook, wxListbook или wxChoicebook, чтобы можно было работать только с одной из этих панелей. Использование множества независимых диалогов раздражает пользователя и перегружает меню, поэтому использование нескольких панелей более предпочтительно. Вы должны избегать прокручивающихся панелей, пока не появится реальная причина в их использовании. Возможность прокручивания элементов управления поддерживается не на всех платформах, поэтому может оказаться, что диалог выглядит не так как планировалось. Если у вас есть множество свойств для редактирования, то возможно стоит использовать редактор свойств на основе wxGrid или сторонних классов (например, класс wxPropertyGrid, который указан в Приложении E “Сторонние инструменты для wxWidgets”).

Эстетика

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

Альтернатива диалогам

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

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

Вы можете загружать определения диалогов, фреймов, меню, панелей инструментов и так далее из специальных XML-файлов с расширением xrc, вместо того, чтобы создавать все эти элементы непосредственно с помощью C++. Это позволяет гораздо элегантнее разделить код и пользовательский интерфейс, а также дает возможность поменять дизайн диалогов приложения во время выполнения программы. Файлы XRC могут быть экспортированы во множество инструментов по редактированию пользовательского интерфейса, включая такие как wxDesigner, DialogBlocks, XRCed и wxGlade.

Загрузка ресурсов

Чтобы использовать XRC-файлы в вашем приложении вам необходимо включить файл wx/xrc/xmlres.h в код вашего приложения.

Если вы преобразуете свои XRC-файлы в бинарные XRS-файлы (как это сделать будет показано далее), то вам также необходимо включить поддержку файловой системы zip, поместив соответствующий вызов AddHandler в вашу функцию OnInit:

#include "wx/filesys.h"
#include "wx/fs_zip.h"

wxFileSystem::AddHandler(new wxZipFSHandler);

Далее инициализируем поддержку XRC, добавив следующую строку в OnInit:

wxXmlResource::Get()->InitAllHandlers();

XRC-файлы загружаются с помощью следующего кода:

wxXmlResource::Get()->Load(wxT("resources.xrc"));

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

MyDialog dlg;
wxXmlResource::Get()->LoadDialog(&dlg, parent, wxT("dialog1"));
dlg.ShowModal();

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

MyFrame::MyFrame(const wxString& title): wxFrame(NULL, -1, title)
{
    SetMenuBar(wxXmlResource::Get()->LoadMenuBar(wxT("mainmenu")));
    SetToolBar(wxXmlResource::Get()->LoadToolBar(this,
                                                  wxT("toolbar")));

    wxMenu* menu = wxXmlResource::Get()->LoadMenu(wxT("popupmenu"));

    wxIcon icon = wxXmlResource::Get()->LoadIcon(wxT("appicon"));
    SetIcon(icon);

    wxBitmap bitmap = wxXmlResource::Get()->LoadBitmap(wxT("bmp1"));

    // Заканчиваем создание panelA и создаем ее экземпляр
    MyPanel* panelA = new MyPanel;
    panelA = wxXmlResource::Get()->LoadPanel(panelA, this,
                                                       wxT("panelA"));

    // Второй метод: срузу из XRC создаем и загружаем panelB
    wxPanel* panelB = wxXmlResource::Get()->LoadPanel(this,
                                                       wxT("panelB"));
}

wxWidgets содержит один глобальный объект wxXmlResource, который вы можете использовать, но также можете самостоятельно создать объект wxXmlResource, загрузить ресурсы, а после уничтожить его. Вы также можете вызвать wxXmlResource::Set, чтобы установить новый глобальный объект, уничтожив старый.

Чтобы определить таблицу сообщений для окна, загруженного из ресурсного файла, вы не можете использовать целочисленные идентификаторы, так как ресурсы имеют строковое представление. Вместо этого вы должны использовать макрос XRCID, который принимает в качестве параметра имя ресурса и возвращает целочисленный идентификатор, ассоциированный с этим именем. XRCID является просто синонимом функции wxXmlResource::GetXRCID. Вот пример использования XRCID:

BEGIN_EVENT_TABLE(MyFrame, wxFrame)
    EVT_MENU(XRCID("menu_quit"),  MyFrame::OnQuit)
    EVT_MENU(XRCID("menu_about"), MyFrame::OnAbout)
END_EVENT_TABLE()

Использование бинарных и встраиваемых ресурсных файлов

Очень часто удобнее скомбинировать несколько ресурсных файлов в один бинарный файл (с расширением xrs). Чтобы скомпилировать XRC-файлы в один zip-файл из которого можно загружать ресурсы используйте утилиту wxrc, расположенную в каталоге utils/wxrc вашего дистрибутива wxWidgets:

wxrc resource1.xrc resource2.xrc -o resource.xrs

Метод wxXmlResource::Load позволяет загрузить бинарный файл, абсолютно также как и в случае с обычными ресурсами.

Совет: вместо создания отдельного zip-файла с вашими ресурсными файлами, вы можете включить их в один zip-файл, который включает другие файлы, необходимые вашему приложению, такие как HTML-файлы, изображения и так далее. wxXmlResource::Load принимает спецификатор виртуальной файловой системы, как будет описано в Главе 14 “Файлы и потоки”, поэтому вы можете написать:

wxXmlResource::Get()->Load(wxT("resources.bin#zip:dialogs.xrc"));

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

wxrc resource1.xrc resource2.xrc c -o resource.cpp

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

extern void InitXmlResource(); // определена в сгенерированом файле
wxXmlResource::Get()->InitAllHandlers();
InitXmlResource();

Таблица содержит аргументы и опции командной строки для программы wxrc.

  • -h (–help) – Показывает помощь.
  • -v (–verbose) – Включает подробную информацию о работе.
  • -c (–cpp-code) – Генерирует исходники C++, вместо XRS-файла.
  • -p (–python-code) – Генерирует исходники Python, вместо XRS-файла.
  • -e (–extra-cpp-code) – Если используется совместно с командой -c, генерирует заголовочный файл C++, содержащий определение для окон, содержащихся в XRC-файле.
  • -u (–uncompressed) – Не сжимать XML файлы (только для C++).
  • -g (–gettext) – Выводит все строки с подчеркиваниями, чтобы poEdit или gettext смогли их просканировать. Вывод производится в stdout или файл, если задан ключ -o.
  • -n (–function <имя>) – Определяет функцию инициализации для C++ (используется совместно с -c)
  • -o <имя_файла> (–output <имя_файла>) – Определяет файл вывода, такой как resource.xrs или resource.cpp.
  • -l <имя_файла> (–list-of-handlers <имя_файла>) – Выводит список обработчиков ресурсов, необходимых для заданных файлов.

Перевод ресурсов

Если объект wxXmlResource создан с флагом wxXRC_USE_LOCALE (это поведение по умолчанию), то все отображаемые строки являются объектами перевода, как это описано в Главе 16 “Написание многоязыковых приложений”. Однако poEdit не может просканировать XRC-файлы, чтобы выбрать строки для перевода, как это можно сделать с программой на C++ , поэтому вам необходимо создать файл, содержащий такие строки, используя опцию -g. Например:

wxrc -g resources.xrc -o resource_strings.cpp

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

Формат XRC-файлов

Так как формат книги не позволяет подробно рассмотреть формат XRC-файлов, то дальше просто приводится пример маленького диалога с сайзерами:

<?xml version="1.0"?>
<resource version="2.3.0.1">
<object class="wxDialog" name="simpledlg">
    <title>A simple dialog</title>
    <object class="wxBoxSizer">
      <orient>wxVERTICAL</orient>
      <object class="sizeritem">
        <object class="wxTextCtrl">
          <size>200,200d</size>
          <style>wxTE_MULTILINE|wxSUNKEN_BORDER</style>
          <value>Hello, this is an ordinary multilinen textctrl....</value>
        </object>
        <option>1</option>
        <flag>wxEXPAND|wxALL</flag>
        <border>10</border>
      </object>
      <object class="sizeritem">
        <object class="wxBoxSizer">
          <object class="sizeritem">
            <object class="wxButton" name="wxID_OK">
              <label>Ok</label>
              <default>1</default>
            </object>
          </object>
          <object class="sizeritem">
            <object class="wxButton" name="wxID_CANCEL">
              <label>Cancel</label>
            </object>
            <border>10</border>
            <flag>wxLEFT</flag>
          </object>
        </object>
        <flag>wxLEFT|wxRIGHT|wxBOTTOM|wxALIGN_RIGHT</flag>
        <border>10</border>
      </object>
    </object>
  </object>
</resource>

Детальную спецификация формата XRC можно найти в технических замечаниях в файле docs/tech/tn0014.txt вашего дистрибутива wxWidgets. Если вы используете какой-нибудь редактор для пользовательского интерфейса, то вам не нужно знать о формате XRC.

Вам может быть интересно как можно использовать текстовые XRC-файлы, чтобы поместить в них изображения и иконки. Эти ресурсы необходимо определить как URL, а механизм виртуальных файловых систем извлечет их из необходимого источника, такого как zip-файл. Например:

<object class="wxBitmapButton" name="wxID_OK">
  <bitmap>resources.bin#zip:okimage.png</bitmap>
</object>

Обратитесь к Главе 10 “Работа с изображениями” и Главе 14 “Файлы и потоки” за детальной информацией об использовании виртуальных файловых систем для загрузки ресурсов, таких как изображения.

Определение обработчика ресурсов

Система XRC использует обработчики ресурсов, чтобы опознать определения XML для каждого типа ресурсов. Если вы напишите ваш собственный элемент управления, то вы возможно захотите написать такой обработчик, чтобы иметь возможность загружать ваш элемент управления из XRC.

Для иллюстрации посмотрим как реализован обработчик ресурсов для стандартного класса wxButton:

#include "wx/xrc/xmlres.h"

class wxButtonXmlHandler : public wxXmlResourceHandler
{
DECLARE_DYNAMIC_CLASS(wxButtonXmlHandler)
public:
    wxButtonXmlHandler();
    virtual wxObject *DoCreateResource();
    virtual bool CanHandle(wxXmlNode *node);
};

Реализация обработчика достаточно простая. В конструкторе обработчика вы, используя макрос XRC_ADD_STYLE, добавляете стили, которые должна поддерживать кнопка, а в конце вызываете AddWindowStyles, чтобы добавить стандартные стили для окон. В методе DoCreateResource объект-кнопка создается в два шага, используя XRC_MAKE_INSTANCE и Create, извлекая параметры, такие как метка, положение и размер. И, наконец, CanHandle проверяет может ли обработчик обработать узел, который ему передали. Последняя возможность позволяет в одном обработчике реализовать обработку нескольких типов ресурсов.

IMPLEMENT_DYNAMIC_CLASS(wxButtonXmlHandler, wxXmlResourceHandler)

wxButtonXmlHandler::wxButtonXmlHandler()
: wxXmlResourceHandler()
{
    XRC_ADD_STYLE(wxBU_LEFT);
    XRC_ADD_STYLE(wxBU_RIGHT);
    XRC_ADD_STYLE(wxBU_TOP);
    XRC_ADD_STYLE(wxBU_BOTTOM);
    XRC_ADD_STYLE(wxBU_EXACTFIT);
    AddWindowStyles();
}

wxObject *wxButtonXmlHandler::DoCreateResource()
{
   XRC_MAKE_INSTANCE(button, wxButton)

   button->Create(m_parentAsWindow,
                    GetID(),
                    GetText(wxT("label")),
                    GetPosition(), GetSize(),
                    GetStyle(),
                    wxDefaultValidator,
                    GetName());

    if (GetBool(wxT("default"), 0))
        button->SetDefault();
    SetupWindow(button);

    return button;
}

bool wxButtonXmlHandler::CanHandle(wxXmlNode *node)
{
    return IsOfClass(node, wxT("wxButton"));
}

Чтобы использовать обработчик приложение должно включить заголовочный файл и зарегистрировать обработчик, как показано ниже:

#include "wx/xrc/xh_bttn.h"
wxXmlResource::AddHandler(new wxBitmapXmlHandler);

Сторонние элементы управления

XRC-файл может определить сторонние, или “неизвестные” элементы управления, указав class="unknown" в определении объекта. Вы можете использовать такую возможность для элементов управления, которые создаются с помощью кода C++, после того как их родительское окно будет загружено из XRC. После того, как XRC загрузит сторонний объект создается окно, которое помещается на место данного объекта. Далее приложение вызывает AttachUnknownControl для помещения реального окна в окно, созданное на прошлом шаге с правильными размерами и расположением. Например,

wxDialog dlg;

// Загружаем диалог
wxXmlResource::Get()->LoadDialog(&dlg, this, wxT("mydialog"));

// Создаем экземпляр нашего элемента управления
MyCtrl* myCtrl = new MyCtrl(&dlg, wxID_ANY);

// Добавляем его к диалогу
wxXmlResource::Get()->AttachUnknownControl(wxT("custctrl"), myCtrl);

// Показываем диалог
dlg.ShowModal();

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

<object class="unknown" name="custctrl">
  <size>100,100</size>
</object>

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

Итоги

В этой главе мы изучили основные принципы проектирования и реализации диалогов, включая краткое описание сайзеров, валидаторов и преимущества использования событий об обновлении интерфейса. Примеры создания диалогов смотрите в каталоге samples/dialogs вашего дистрибутива wxWidgets. Также в каталоге samples/validate есть пример использования валидаторов. В следующей главе будет рассказано о работе с изображениями.

2 Comments

  • progician
    Posted July 20, 2009 at 13:04

    Hi!

    I’m just wondering can you help us, wxwidgets learning programmers and publish these entries in english as well? This blog is awesome so it would be even better…

    • T-Rex
      Posted July 21, 2009 at 13:06

      It is a translation of wxWidgets book. You can download the book in English at wxWidgets official site. There was a PDF version. UIf you need English version of some other post, then please let me know.

Leave a comment

0.0/5