Обзор
Примеры
Скриншоты
Сравнения
Приложения
Загрузить
Documentation
Базар
Статус и История
Частые вопросы (FAQ)
Авторы и лицензия
Форум
Помощь проекту
Поиск по сайту
Язык
русский













SourceForge.net Logo



Обзор Ultimate++

 

Разогреем Ваш аппетит

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

 

 

Дизайн (Layout) - (англ. - размещение) окна приложения, созданное с помощью Ultimate++ Visual Designer:

 

 

Оцените насколько "сложен" Актуальный код для этого приложения:

 

#include <CtrlLib/CtrlLib.h>

 

#define LAYOUTFILE <Days/Days.lay>

#include <CtrlCore/lay.h>

 

class Days : public WithDaysLayout<TopWindow> {

public:

    void Compute();

 

    typedef Days CLASSNAME;

    Days();

};

 

void Days::Compute()

{

    result = IsNull(date1) || IsNull(date2) ? "" :

             Format("There is %d day(s) between %` and %`",

                   abs(Date(~date1) - Date(~date2)), ~date1, ~date2);

}

 

Days::Days()

{

    CtrlLayout(*this, "Days");

    date1 <<= THISBACK(Compute);

    date2 <<= THISBACK(Compute);

}

 

GUI_APP_MAIN

{

    Days().Run();

}

 

Всё где-то лежит...

В Ultimate++, большинство объектов скрыты из области видимости. В результате, используя Ultimate++, Вы не увидите в коде множество операторов new, и  также нет операторов delete вне внутренней реализации контейнеров.

Это, конечно, не означает, что Вам не позволено использовать указатели, но хорошая практика это - использовать указатели только для указания на сущности, и никогда для управления ресурсами. Это позволяет избежать всякой путаницы в отношении права собственности на объект, времени его удаления и т.д. Если же Вам нужно  управлять наборами данных переменного размера или полиморфного типа, Вы должны предпочесть использование одного из контейнеров Ultimate++.

Говоря об этом, в Ultimate++ нет никаких общих умных указателей (типа boost::shared_ptr), для управления ресурсами кучи на интерфейсном уровне. Они не нужны и считаются плохой практикой.

В C++ этот подход зарекомендовал себя одинаково хорошо или даже лучше чем в языках  со сбором мусора типа Java или C#. Хотя эти языки способны обеспечить автоматическое управление ресурсами кучи, подход U++ обеспечивает  очень детерминированное автоматическое управление всеми ресурсами.

Ultimate++ контейнеры

Один аспект Ultimate++ порождает множество критики: Ultimate++ не использует массы стандартных C++ библиотек. Однако для этого есть серьезные причины. STL, с её разрушительным требованием чтобы каждый элемент хранящийся в контейнере имел бы copy-constructor, делает стандартные контейнеры чем-то  тяжелым для использования в разработке GUI программ.

Для контейнеров Ultimate++ такого требования нет. Вместо этого, в Ultimate++ контейнеры бывают двух видов.

Vector (Векторный) вид с обязательным требованием свойства Перемещаемости (Moveable) что делает возможной очень быстрой реализацию операций определенного вида (например, вставка элемента в произвольной позиции для Ultimate++ типа Vector<String> более чем в 10 раз быстрее чем такая же операция с типовой реализацией типа std::vector<std::string>).

Array (Массив) вид не имеет требований  к типу элементов, но цена этого - более низкая производительность.

Как результат, в Ultimate++ Вы можете, например, создать контейнер GUI виджетов который редактирует целые числа ( Array<EditInt> integer_editors) и даже сортировать их используя стандартный алгоритм сортировки  Ultimate++. Делать что-то подобное потребует использовать указатели как элементы в STL  (std::vector<EditInt *>) или некоторые виды умных указателей (такие как std:: boost::shared_ptr), но эти оба подхода увеличивают сложность кода и разбивают правило Ultimate++ в соответствии с которым Все где-то лежит.

Кто владеет виджетами

Один из моментов, который мы открыли в результате наших многочисленных экспериментов с  C++ GUI это факт, что инструментарий GUI не должен владеть GUI объектами (виджетами) . GUI объекты всегда должны принадлежать клиенту, лежащие в области видимости клиентского кода (Все где-то лежит). Инструментарий GUI должен ссылаться на виджеты, он никогда не создает и удаляет их. Каждый виджет объекта может сыграть свою GUI роль в определенном контексте (например, быть видимым в другом окне), но в тоже время он самостоятельная сущность со своим набором атрибутов, которые могут быть модифицированы или запрошены независимо от их GUI статуса.

Это имеет имеет серьезные последствия. Наиболее важным является то, что Ultimate++ не требует чтобы виджеты были бы созданы в куче (heap) . Это делает возможным организовать GUI диалог очень эффективным способом, вместо

 

struct MyDialog {

    Option *option;

    EditField *edit;

    Button *ok;

};

 

мы напишем:

 

struct MyDialog {

    Option option;

    EditField edit;

    Button ok;

};

 

Еще более важно, время жизни этих виджетов не зависит от жизненного цикла MyDialog - структура MyDialog может быть закрыта или еще не открыта, но атрибуты виджетов доступны все время.

Шаблоны диалогов - это шаблоны C++

Теперь, когда мы заложили фундамент, настало время для познакомиться с мощнейшим аспектом  программирования GUI в Ultimate++  - диалоговые шаблоны:

Если Вы разрабатываете визуальный дизайн окна (обычно, но не только, дизайн диалогового окна) используя TheIDE's Layout designer, этот дизайн будет отражен в Вашем коде как шаблон C++ , который наследуется от базового класса виджетов и декларирует все виджеты, как его переменные-члены, и соответствующие функции (InitLayout) , которые устанавливают позиции виджетов и их предварительно разработанные атрибуты по умолчанию.

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

 

template <class T>

struct WithMyDialogLayout : public T {

    Option option;

    EditField edit;

    Button ok;

};

 

template <class T>

void InitLayout(WithMyDialogLayout<T> *layout, ...);

// implementation details omitted

 

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

Такой подход обеспечивает радикальное сокращение сложности - множество раздражающих вещей, которые представляются необходимыми для идентификации виджетов в клиентском коде, типа идентификаторов (ID) виджетов или их имен, просто исчезли навсегда. Все с чем Вам придется иметь дело в Ultimate++ - локальные переменные - экземпляры классов.

Тип Value и Null

Один аспект, который делает разработку в Ultimate++ чем-то ортогональным по отношению к обычной C++ практике, - это существование типа Value (перев.Значение) - тип полиморфного значения. Любой из базовых типов Ultimate++  (int, double, String, Color, Rect, Font, Image etc...) может быть сохранен в... и извлечен из... переменной типа Value. Value сам может быть получен по типу содержащегося в нем значения. Также  очень легко сделать любые клиентские типы Value-совместимыми.

Применительно к Value существует общее понятие "пустое значение". Специальная константа Ultimate++ Null представляет собой пустое значение. Большинство основных типов поддерживают Null. Null также определен для базовых типов  - int, double and int64 - как значение, которое меньше любого другого значения данного типа (например, Null равен INT_MIN для int). Чтобы проверить переменную основного типа на Null,Вы можете использовать общую функцию IsNull.

Тип Value (и Null) производят поразительный эффект по повышению гибкости GUI. Множество виджетов логически имеют их "естественные" значения, (для целочисленного поля это введенные числа, для виджета option это истина или ложь, согласно его состоянию)  Ultimate++ поддерживает унифицированный доступ к этим значениям через Value и виртуальные методы GetData / SetData. Например, очистка диалогового окна обычно может быть выполнена присваиванием Null всем его виджетам.

Display и Convert

Классы, основанные на Display и Convert дополнительно расширяют гибкость Ultimate++ используя Value.

Классы Convert работают как двунаправленные конвертеры Value - Value. Обычно, но не ограничиваясь этим, это преобразование между значением логического типа и его текстовым представлением (преобразование текстового представления в логический тип иногда могут быть опущены). Примеры ConvertInt или ConvertDate.

Множество виджетов Ultimate++ могут использовать эти классы Convert как свойства. В качестве примера можно привести класс EditField, универсальное поле ввода. Присваивая определенные классы, созданные на основе Convert, полю EditField, Вы можете "научить" его редактировать числа, даты или что-то, что имеет текстовое представление.

Что-то похожее на классы Convert - это классы, основанные на классе Display. Это классы, которые описывают как должны отображаться Значения. Еще раз, множество виджетов Ultimate++ используют классы Display как свои свойства. Например, для того чтобы  "обучить" виджет DropList  (DropList к так называемому "combo box" на других платформах) отображать цвета,всё что Вам надо сделать - это установить его атрибут  Display в DisplayColor (запомните, Color это значение с типом, совместимым с Value и список DropList состоит из значений типа Value). Между тем, Вы можете использовать этот DisplayColor как свойство многих других классов виджетов.

Callback (Обратные вызовы)

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

Наше решение для этих целей называется Обратный вызов (Callback). Вы можете считать что обратные вызовы (callback) это очень общая форма указателей на функции. Каждый Callback представляет собой какое-либо действие - обычно это включает вызов определенных функций или определенных методов объекта - это  может быть использовано в любое время.

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

 

void MyDlg::SetEditorValue(int x)

{

    editor <<= x;

}

 

MyDlg::MyDlg()

{

    button1 <<= THISBACK1(SetEditorValue, 1);

    button2 <<= THISBACK1(SetEditorValue, 2);

 

В этом фрагменте, мы имеем две кнопки и поле ввода целого типа. Нажатие первой или второй кнопки устанавливает поле ввода в значение 1 или 2 соответственно.

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

Просто, чтобы внести ясность для тех кто знаком с библиотекой boost  - да, классы Обратные вызовы  на самом деле очень похожи на boost::функции, с немного более полированным интерфейсом для соответствия нуждам среды Ultimate++  (они являются перемещаемыми (Moveable) - могут храниться в контейнерах типа Vector).

Набор виджетов Ultimate++

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

Так, здесь следует не полный, но представительный список:

Label, Button и Option это основные, всем известные виджеты.

Switch это то, что обычно называется "группа radio-кнопок", в любом случае в U++ это единый виджет (таким образом, чтение Значения виджета switch более логично).

EditField, EditInt, EditDouble, EditIntSpin, EditDate, EditString это основные поля ввода. Заметьте, что  U++ обеспечивает различные типы полей ввода для разных типов значений.

LineEdit и DocEdit - два вида простого текстового редактора. LineEdit работает со строками, в то время как DocEdit работает с абзацами.

ScrollBar и ScrollBars. Хотя их названия говорят сами за себя (ScrollBars это просто пара, состоящая из вертикального и горизонтального ScrollBar'а), также нужно отметить что ScrollBar также поддерживает все вычисления позиции и области просмотра.

Slider это "аналоговый" виджет ввода, его значение определяется позицией  "ползунка".

HeaderCtrl представляет шапку различных таблиц, а именно ArrayCtrl

ArrayCtrl это, возможно, наиболее сложный и запутанный виджет в Ultimate++. Это в основном табличный виджет, используемый для оперирования матрицей значений типа Value . Он может комбинировать значения типа Value для отображения (используя класс Display) как колонку (да, несколько значений в строке могут быть скомбинированы в одну колонку, если нужно) и редактировать их используя подчиненные Ctrl'ы (они могут быть внутри таблицы показанные при действии пользователя "редактирование" внутри таблицы, всегда видимые, или снаружи таблицы в диалоговом окне, отображая значения текущей выбраной строки таблицы).

Option, EditString, DropList, Switch and ArrayCtrl in action.

SqlArray происходит от  ArrayCtrl и добавляет возможности выступать в качестве SQL редактора табличных данных, включая возможности типа мастер-деталь.

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

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

TabCtrl используется для диалогов с закладками (Табами).

TreeCtrl используется для отображения произвольной древовидной иерархии.

ColorSelector, ColorPusher и ColorButton это виджеты для графического выбора цвета пользователем.

ColorButton

MenuBar и ToolBar управляются немного неортодоксадьно для Ultimate++, поскольку действия меню, представленые как обратные вызовы (Callback), передают действие к методам, создавшим соответствующий пункт меню или тулбар. Это дает некоторые серьезные преимущества - положение и присутствие индивидуальных кнопок или пунктов меню можно легко настроить в соответствии с текущим состоянием приложения. Также часто возможно иметь единый метод для создания и обработки и тулбара и меню.

ColumnList отображает значения в колонках, количество которых может выбирать пользователь.

FileList это вариант ColumnList для отображения списка файлов.

В заключении, Ultimate++ имеет инструменты для использования расширенного форматирования текста:

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

RichTextView это виджет для просмотра RichText текстов.

RichEdit Это полнофункциональный RichText редактор текста (включая проверку правописания) в виде стандартного пакета/виджета, готовый для включения в любое U++ приложение.

RichEdit

 

Вы можете найти полный алфавитный список основных  U++ виджетов здесь.

SQL программирование

Один из мотивов "за" Ultimate++ всегда было использование в разработке клиент-серверных SQL приложений энтерпрайз класса. Используя общую Ultimate++ философию мы верим, что достигли некоторых экстраординарных результатов, в основном делая Ultimate++/SQL разработку легче чем использование  специальных средств SQL разработки .

Конечно, SQL это область, где использование абстракции типов Value чрезвычайно окупается. Задача чтения значений базы данных и запись их в GUI виджеты никогда не была такой тривиальной как в Ultimate++.

Одним из важных инструментов, связанных с SQL - идея "SQL выражений". SQL выражение это конструкция представляющая  собой SQL команду. Ultimate++ поддерживает способ для построения SQL выражений используя механизм перегрузки C++. Например, в Ultimate++ SQL выражение  может выглядеть так:

 

Select(NAME, SURNAME).From(PERSON).Where(PERSONID == personid);

 

где NAME, SURNAME, PERSON и PERSON это специальные значения типа SqlId , в то время как personid это обычная C++ переменная. Важная деталь здесь -это то, что SQL выражения могут быть построены из маленьких подвыражений - что особенно важно во время построения условий Where.

 

SqlBool where;

if(!IsNull(findname))

    where = NAME == findname;

if(!IsNull(findsurname))

    where = where && SURNAME == findsurname;

SqlExp exp = Select(PERSONID).From(PERSON).Where(where);

 

Когда SQL выражение готово к исполнению, оно может быть исполнено на  Sql курсоре используя оператор *. После этого, Вы можете забирать результат командой Fetch.

 

Sql sql;

sql * exp;

while(sql.Fetch()) {

    Sql sqlu;

    sqlu * Update(PERSON)(SALARY, SALARY + 100).Where(PERSONID == sql[0])

}

 

Другой эффективный инструмент - это концепция файла схемы описания базы данных. Эти файлы используются для описания модели базы данных, используя специализированные C-macro конструкции:

 

TABLE_(PERSON)

    INT_     (PERSONID) PRIMARY_KEY

    STRING_  (NAME, 200)

    STRING_  (SURNAME, 200)

    DOUBLE_  (SALARY)

END_TABLE

 

Эти файлы описания затем используются для синхронизации модели базы данных на SQL сервере, для генерации SqlId констант,  используемых в SQL выражениях, и, последнее, но не менее важное, для генерации C++ структур (по имени таблицы с  S_ префиксом), которые могут быть использованы для формы SQL выражений и для получения результатов запроса:

 

S_PERSON person;

SQL * Select(person).From(PERSON);

while(SQL.Fetch(person))

    person_table.Add(person.PERSONID, person.NAME, person.SURNAME);

 

Благодаря концепции Value, описанной выше, большинство виджетов бесшовно соединяются с  SQL кодом "прямо из коробки". Один из инструментов, которые используют эти возможности, является класс SqlCtrls, который дирижирует обменом данных между диалоговыми виджетами и записями базы данных:

 

void EditPerson(int persionid) {

    WithPersonLayout<TopWindow> dlg;

    SqlCtrls ctrls;

    ctrls(PERSON, dlg.person)(NAME, dlg.name)(SURNAME, dlg.surname);

    SQL * Select(ctrls).From(PERSON).Where(PERSONID == personid);

    ctrls.Fetch(SQL);

    if(dlg.Run() == IDOK)

        SQL * ctrls.Update(PERSON).Where(PERSONID == personid);

}

 

Резюме

В этом обзоре мы попытались объединить наиболее интересные возможности Ultimate++. Есть еще много более важных особенностей, включая интересные реализации технологий таких, как нулевой перерасход при выделении памяти, совершенный метод масштабирования изображений и т.д.

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

Теперь, приблизительно после 7 лет разработки, Ultimate++ - зрелая платформа, которая приносит огромные сокращения наших затрат на разработку. Большинство интерфейсов, кажется, закончено и оптимально. Впереди еще много работы например: реализация порта Linux все еще не совершенна и нуждается в некоторой дальнейшей работе, есть запрос на новые особенности, такие как продвинутую anti-aliased и сглаженную графику.

Если Вы находите наш Ultimate++ способ программирования интересным, ничто не стоит на Вашем пути к его загрузке . Но будьте осторожны: Вы должны быть готовывыбросить старые привычки и обычный способ мышления о том, как "вещи всегда делаются", и это приведет Вас к вознаграждению, вместе со здоровой непочтительностью к почетным, известным средствам разработки.

 

Последнее изменение sergeynikitin в 24.06.2010. Страница доступна на english, čeština, deutsch и français языке. Вы хотите внести вклад?. T++