Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Embedded GUI
Форум разработчиков электроники ELECTRONIX.ru > Сайт и форум > В помощь начинающему > Программирование
Страницы: 1, 2
haker_fox
Добрый день, коллеги!

Посоветуйте, пожалуйста, что можно почитать по принципу создания, проектирования GUI?

Возник соблазн написать что-то свое. На Си++.

Требования к интерфейсу: работа на ARM7+, где-то (с усечениями) на AVR8, разрешение от 128x64 до 1024x768, от монохрома до 8-8-8. Как-то так. Высокая сложность не требуется, хотя я расчитываю, что с Си++ сложность нарастить будет не сложно)))

Знаю, что есть готовые системы. Но хочу поупражнятся сам.

Спасибо!
Lotor
Тоже для общего развития хотелось бы почитать какую-нибудь литературу по организации собственного gui на ООП. Наверняка до винды людей волновал этот вопрос и была литература соответствующая. Самое лучшее, что нагуглилось - IMGUI. Может кто из форумчан знает книги по теме?

PS: я в курсе, что самое правильное - взять готовую отлаженную библиотеку. sm.gif
haker_fox
QUOTE (Lotor @ Sep 7 2012, 17:19) *
PS: я в курсе, что самое правильное - взять готовую отлаженную библиотеку. sm.gif

Ну для кого же правильное? Вот для нас с Вами похоже уже не совсем правильное.

Готовое - это уже чужая идеология. Хочется свою попробывать. Хотя да, можно начинать с изучения чужой идеологии по исходникам и документации. Но лучше бы почитать теорию, каковая должна иметься)
Lotor
Цитата(haker_fox @ Sep 7 2012, 14:49) *
Готовое - это уже чужая идеология. Хочется свою попробывать. Хотя да, можно начинать с изучения чужой идеологии по исходникам и документации. Но лучше бы почитать теорию, каковая должна иметься)

Готовое - это в первую очередь отлаженная протестированная идеология. sm.gif
Свой gui есть в моих девайсах, но хочется именно что-то почитать по теме для расширения кругозора.

PS: Если нужен аккуратный оконный графический интерфейс со всякими сглаживаниями, то имхо писать свой велосипед слишком накладно.
kolobok0
Цитата(haker_fox @ Sep 7 2012, 10:45) *
...что можно почитать по принципу создания, проектирования GUI?...


Тут такое дело.
Если смотреть на форточки, то прослеживается два уровня.
1) апи ядра, уходящее корнями в железо, ускорители и прочую лабуду.
2) юзерс интерфейс, с попыткой вычленить сущности удобные для повтоного юзанья.

если брать это за основу, то по первому можно почитать книги включающие в себя справочные данные (чуть ли не МСДН перепечатанный). основные тезисы - быстрая передача в(из) ядро оси, задействование по максимуму железо.

если говорить о втором - то это практически мало-мальски известная библиотека. MFC или там QT. тут уже прослеживается попытка породить сущности отображаемые на экране - окно, кнопка, диалог, вид, грид и т.п..

Это если про стандартный гуи. игры - отдельно обычно. там ниже уровень и плотнее с железом.

где то так.
смотря, что нужно. и наверное задача. вряд-ли вас преследует идея универсализма и стандартизации для стороннего разработчика sm.gif
Lotor
Цитата(kolobok0 @ Sep 7 2012, 23:40) *
Если смотреть на форточки, то прослеживается два уровня.

Вы, конечно, всё правильно говорите, но зачем рассматривать подход в форточках для embedded? Если и брать за ориентир готовую библиотеку для изучения, то лучше тот же Micrium µC/GUI, а не QT или MFC.

PS: Кстати крайне удобная штука у Micriumа - возможность нарисовать/прикинуть гуй на ПК.
_Pasha
FTK
dxp
QUOTE (haker_fox @ Sep 7 2012, 17:49) *
Готовое - это уже чужая идеология. Хочется свою попробывать. Хотя да, можно начинать с изучения чужой идеологии по исходникам и документации. Но лучше бы почитать теорию, каковая должна иметься)

Готовой теории не найдёте, поэтому не тратьте время на это. Начните делать что-то сами - только в процессе делания и придёт понимание и осознание. Посмотреть, как сделано в других системах - полезно. Но именно посмотреть, как сделано то или иное. GUI в каждом случае индивидуален. Тот, что идёт на РС, обычно плохо подходит для embedded систем. Поэтому целиком его оттуда заимствовать достаточно плохая идея. Если взять какой-то готовый типа uCGUI, то придётся оставаться в рамках его идеологии. Не всегда она может хорошо подойти для вашего устройства.

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

  1. Обработка/обслуживание ввода. Это обработка сигналов от органов управления - кнопки, энкодеры и т.п.
  2. Виджеты и их иерархия. Вся совокупность элементов графического интерфейса, их взаимосвязь и отображение.
  3. Вывод. Связь с дисплеем через HAL (набор функций/объектов для управления устройством визуализации и передачи на него видеоинформации).

Первый и третий пункты достаточно ординарны, тут не требуются какие специальные познания в организации этих частей. Собственно графическая часть GUI - виджеты - могут организовываться по различным схемам - тут на вкус и цвет, как говорится. Если хотите реализовывать на С++, то безусловно нужно заюзать ООП: выстроить иерархию классов-виджетов. Я делал два разных GUI для разных устройств, внешне они совершенно не похожи, и виджеты (их набор и реализация) тоже очень отличаются. Но принципы там одни и те же, и приёмы программирования тоже очень сходны. Позволю себе дать несколько советов по построению такой иерархии (в контексте С++, ессно sm.gif).

Первое, о чём стоит подумать при построении иерархии виджетов, - это то, как они будут получать информацию от устройств ввода. Т.е. как будет построен механизм обработки сигналов управления и отображение реакции в виде изменения набора и состояния виджетов на экране дисплея. Ведь виджетов может быть очень много и нужно как-то упорядочить механизм передачи им информации, по которой они должны реагировать. Одним из простых и эффективных способов для этого является создание специального служебного объекта, который будет держать фокус на виджете - именно этот виджет "под фокусом" и будет получать информацию от устройств ввода. Этот вариант позволяет легко организовать переходы по цепочке виджетов и обратно - для этого достаточно будет создать очередь объектов "фокус". Тип этого объекта "фокус" должен быть типом базового класса виджета, чтобы можно было его "сфокусировать" на любой объект виждета.

Самая база содержит абстрактный класс и базовый класс виджета типа такого:
CODE
namespace gui
{
    //--------------------------------------------------------------------------
    //
    //     Main object types hierarchy
    //
    class TObject
    {
    public:
        virtual void on_message(TBaseMsgWrapper *msg_wrp) = 0;

    };
    //--------------------------------------------------------------------------
    class TWidget : public TObject
    {
    public:
        TWidget() : Pos(), Visible(true), Selected(false) { }
        TWidget(TPoint pos, bool visible = true) : Pos(pos), Visible(visible), Selected(false) { }
        
        virtual void on_message(TBaseMsgWrapper *msg_wrp);
        
        virtual void draw()      { }

        // keys
        virtual void enter()     { }
        virtual void back()      { }
        virtual void help()      { }

        // encoder
        virtual void next()      { }
        virtual void prev()      { }
      
        void   set_pos(const TPoint pos)     { Pos = pos;  }
        void   set_pos(const int_fast16_t x,
                       const int_fast16_t y) { Pos.x = x; Pos.y = y;  }

        TPoint pos    () const               { return Pos; }
    
        void set_visible(bool x) { Visible = x;    }
        bool visible    () const { return Visible; }
          
        void set_selected(bool x) { Selected = x;    }
        bool selected() const     { return Selected; }
        
    protected:
        TPoint Pos;
        bool   Visible;
        bool   Selected;
    };

    ... //
}


Очень важная фукнция on_message() - именно она является каналом передачи информации от органов управления и других частей программы (например, приходит сообщение по UART'у с требованием что-то сделать). Поскольку у каждого объекта GUI есть такая фукнция, то он может реагировать соответствующим образом. Подробно тут останавливаться на реализации этой функции не буду, это можно обсудить отдельно, если есть интерес.

Далее, класс TWidget являет собой базу для любого виджета в данном системе - меню, пункты меню, скроллбары, диалоговые окна сообщений и т.п.. Он содержит минимальный набор данных - позиция, и два свойства: видимый или нет и выделенный или нет. Понятно, что в другой системе свойства могут быть другими - вместо этих будут что-то своё. Ну, и интерфейс для работы этим представлением. TPoint - это простая структура, реализующая функционал объекта "точка на экране", содержащая координаты и функции для работы с ними, там всё тривиально. Набор виртуальных функций важен. Функция draw() нужна всегда - это отрисовка виджета, она всегда индивидуальна. Остальные функции завязаны на целевое устройство. В данном случае у нас есть несколько кнопок и энкодер. Реакция каждого виджета на органы управления тоже, понятное дело, индивидуальна, поэтому эти функции у каждого виджета свои.

Для создания любого виджета, ясно, нужно отнаследоваться от TWidget, расширить определение и добавить функциональность, в том числе переопределив методы (виртуальные функции). Рассказывать подробнее тут смысла нет - надо пробовать, понимание, если его ещё нет, придёт сразу.

Обработка ввода и управление GUI выглядит в данном примере выглядит примерно так. Есть главный цикл GUI, в котором и осуществляется вся динамика этого процесса:
CODE
    ... //
    for(;;)
    {
        Input.handle();
        timer();
        sleep(40);
        
    }

В данном примере цикл работы движка GUI составляет 40 мс + время работы собственно движка. Input - это объект, который отвечает за обработку органов управления (кнопки, энкодер), timer - функция, которая генерирует сообщения о временных отметках - некоторые виджеты требуют таких сообщений, чтобы отрабатывать свой функционал - например, часы или таймер, которые отображают изменяющееся время на экране. Сюда можно добавить ещё подобных элементов, чтобы расширить функциональность - например, как уже говорилось выше, передавать информацию, поступающую по последовательному порту от другого устройства, данные от АЦП и т.д.

Передача собственно информации осуществляется с помощью передачи сообщения объекту под фокусом. Выше есть объявление:
CODE
TWidget                      *Focus;
stack<gui::TWidget *, 8>      FocusHistory;

Здесь есть указатель на виджет и групповой объект (на 8 элементов), для хранения фокусов.

Передача информации осуществляется следующим образом. Когда, например, сигналы от органов управления обработаны и есть информация о том, что та или иная кнопка нажата/отжата/удерживается или был осуществлён поворот энкодера, формируется тело сообщения и посылается объекту под фокусом:
CODE
    ... //  опрос кнопок и формирование результата опроса: KeyPattern описывает какие кнопки были нажаты,
    ... //   KeyEventKind - тип события - нажато/отпущено/удерживается

    if(  ( KeyPattern == ENTER && KeyEventKind == TKeyEvent::PRESSED ) ) // кнопка Enter нажата
    {
        TInputMessage Msg = gui::imENTER;             // тело сообщения, в данном случае простой объект перечислимого типа
        send_message<gui::TInputMessage>(&Msg, Focus); // передача сообщения текущему виджету
    }
    
    if( KeyPattern == BACK && KeyEventKind == TKeyEvent::PRESSED )
    {
        TInputMessage Msg = gui::imBACK;
        send_message<gui::TInputMessage>(&Msg, Focus);
    }
    ... //

Аналогично обрабатываются остальные органы управления и передаются соответствующие сообщения. Приём сообщения выглядит так:
CODE
void gui::TWidget::on_message(TBaseMsgWrapper *msg_wrp)
{
    gui::TInputMessage *msg = check_msg<gui::TInputMessage>(msg_wrp);   // проверяется соответствие типа сообщения, 0 возвращает, если не соответствует
    
    if(msg)
    {
        switch(*msg)
        {
        case gui::imENTER : enter(); break;
        case gui::imBACK  : back();  break;
        case gui::imHELP  : help();  break;
        case gui::imNEXT  : next();  break;
        case gui::imPREV  : prev();  break;
        default           : print("error: unknown message");
        }
    }
}

Здесь проверяется соответствие типа сообщения - если не соответствует, то сообщение игнорируется. Если соответствует, что в соответствии с телом сообщения вызывается соответствующий метод виджета под фокусом. Таким образом, управление всеми виджетами осуществляется единообразно и централизовано (что устраняет проблему совместного доступа) достаточно простым способом. В данном примере показана обработка сообщения базовым классом виджетов - такое поведение будет у всех виджетов по умолчанию. Если нужно для какого-то виджета обрабатывать сообщение как-то иначе, чем в этом примере, то достаточно для этого виджета переопределить функцию on_message(). Т.е. такая схема не фиксирует реакцию виджета на сообщение: есть общее для всех поведение по умолчанию (и оно подходит для подавляющего большинства виджетов), но в отдельных случаях можно без труда, и ничего не сломав, расширить функционал отдельных виджетов (и даже иерархий), где это необходимо. Необходимо помнить, что если сообщение в переопределённой функции не было обработано (перехвачено) то его нужно переправить дальше по цепочке, иначе другие сообщения не будут обрабатываться.

Может показаться, что идея с передачей сообщений несколько избыточна, ведь почему бы, например, сразу напрямую не вызывать соответствующий метод для виджета под фокусом. На самом деле смысл есть: такое построение "развязывает" модуль генерации событий (сообщений) и их обработку, что позволяет легко расширить как перечень событий, так и логику их обработки, не привязываясь к конкретным виджетам. В частности, в данном примере показана передача разнокачественной информации (о событиях на кнопках и от таймера) единообразным способом. Такой подход позволяет легко расширить как перечень передаваемой информации, так и селективность её обработки разными виджетами. Накладные расходы тут минимальны.

Подобный принцип используется во многих GUI движках, в частности в Qt очень похожая схема с объектами событий (QEvent).

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

Если что-то не ясно, готов ответить на любые вопросы.

P.S. Хотел написать три строчки, пару общих советов, а получилось...
AlexandrY
Цитата(haker_fox @ Sep 7 2012, 09:45) *
Посоветуйте, пожалуйста, что можно почитать по принципу создания, проектирования GUI?
...
Возник соблазн написать что-то свое. На Си++.


Берите Microchip Graphics Library и все получите с избытком.

Microchip отличается очень компактным и простым кодом.
Очень ясный подход к взаимодействию экранных элементов.
Все централизовано и использует связный список объектов.
Сделать новый widget можно элементарно не вспоминая от кого он там должен наследоваться и какие методы надо переопределить.

Как раз будет работенка все усложнить и перевести ее на C++. wink.gif

Мощный оконный интерфейс с виртуальными окнами, масштабируемостью под любой экран, редактором ресурсов, TTF шрифтами и т.д. конечно не получите, как в uc/GUI , но будет точно то же, что предложил dxp biggrin.gif
Lotor
Цитата(dxp @ Sep 8 2012, 08:29) *
P.S. Хотел написать три строчки, пару общих советов, а получилось...

Получился очередной шедевральный и полезный многим пост. sm.gif Спасибо!

PS: Жаль, что у движка форума нету возможности добавлять в избранное понравившееся темы или сообщения. И жаль, что нету FAQ, в котором подобные интересные сообщения собраны, а не теряются среди холиваров или флуда.
haker_fox
В общем по-маленьку начал кропать свой ГУИ. Пока инетерс не угас))) Оглядываюсь на мэтров (uc/GUI, Microchip GUI и т.д.).

Осмысливаю рекомендации уважаемого dxp... тяжело мне пока во все вникнуть, но подход мне нравится!
andrewlekar
Цитата(dxp @ Sep 8 2012, 09:29) *
Разработать GUI "по месту" не такая сложная задача

Довольно-таки неплохо спроектированый GUI. Хорошая работа.
haker_fox
QUOTE (dxp @ Sep 8 2012, 13:29) *
Подобный принцип используется во многих GUI движках, в частности в Qt очень похожая схема с объектами событий (QEvent).

Если что-то не ясно, готов ответить на любые вопросы.

Посольку работаю в контектсе scmRTOS, очень удобным кажется передавать такие события через объект OS::channel. Это правильно? rolleyes.gif
dxp
QUOTE (haker_fox @ Sep 10 2012, 21:23) *
Посольку работаю в контектсе scmRTOS, очень удобным кажется передавать такие события через объект OS::channel. Это правильно? rolleyes.gif

Если это вопрос о передаче информации объектам GUI, то нет, там используется иной механизм (могу рассказать подбробнее). А OS::channel можно использовать для передчи сообщений в главный цикл GUI из других процессов и обработчиков прерываний.
mdmitry
Цитата(dxp @ Sep 10 2012, 19:43) *
Если это вопрос о передаче информации объектам GUI, то нет, там используется иной механизм (могу рассказать подбробнее). А OS::channel можно использовать для передчи сообщений в главный цикл GUI из других процессов и обработчиков прерываний.

Весьма интересно узнать, какие идеи заложены в Вашем решении.
haker_fox
QUOTE (dxp @ Sep 10 2012, 23:43) *
Если это вопрос о передаче информации объектам GUI, то нет, там используется иной механизм (могу рассказать подбробнее). А OS::channel можно использовать для передчи сообщений в главный цикл GUI из других процессов и обработчиков прерываний.

Если можно, расскажите, пожалуйста, подробнее. В чем отличие. Для меня это все пока одинаково выглядит.

На данном этапе развития ГУИ я планирую сделать так.

Завести структуру сообщения в виде
CODE
enum FMessageType
{
    FMsgFromKbd,
    FMsgFromTouch,
    FMsgFromADC
    // и т.д. по мере необходимости
};

struct FMessage
{
    FMessageType msg; // тип сообщения (от кого пришло)
    // Здесь надо подумать, как унифицировать данные от клавиатуры и других объектов, т.к. типы передаваемых данных разные

}


Ну и отправлять сообщения из любого потока ОС, которое что-то хочет отправить ГУИ. А последняя пусть сама разбиарается, что с этим сообщением делать (игнорировать, построить очередную точку на диаграмме, "нажать" кнопку...)...
AHTOXA
Подумайте сразу о параметрах сообщения. Ну, типа координат нажатия на экран, кода нажатой клавиши, значения АЦП...
haker_fox
QUOTE (AHTOXA @ Sep 11 2012, 10:51) *
Подумайте сразу о параметрах сообщения. Ну, типа координат нажатия на экран, кода нажатой клавиши, значения АЦП...

Совершенно верно! rolleyes.gif Поскольку очередь сообщений OS::channel предназначена для передачи данных конкретного типа (а это ее преимущество, чтобы не плодить сущности с указателями void*), необходимо как-то унифицировать тип сообщения. Пока думаю...
dxp
QUOTE (mdmitry @ Sep 11 2012, 04:15) *
Весьма интересно узнать, какие идеи заложены в Вашем решении.

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

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

Реализация механизма следующая. Сразу приведу весь код, ниже будет дано пояснение:

CODE
//------------------------------------------------------------------------------
inline uint16_t generate_id() { static uint16_t id; return ++id; }

class TBaseMsgWrapper
{
public:
    virtual uint16_t get_id() = 0;
};

template<typename T> class msg_wrapper : public TBaseMsgWrapper
{
public:
    msg_wrapper(T *msg) : body(msg), TBaseMsgWrapper() { }
    
    static  uint16_t get_class_id() { static const uint16_t id = generate_id(); return id; }
    virtual uint16_t get_id()       { return get_class_id(); }
    
    T *get_msg() const { return body; }
    
private:
    T *body;
};

template<typename T> T *check_msg(TBaseMsgWrapper *msg_wrp)
{
    if( msg_wrp->get_id() == msg_wrapper<T>::get_class_id() )
    {
        return (static_cast<msg_wrapper<T> *>(msg_wrp))->get_msg();
    }
    else
    {    
        return 0;
    }
}

template<typename T> bool send_message(T *msg, gui::TObject *dst)
{
    msg_wrapper<T> msg_wrp(msg);
    dst->on_message(&msg_wrp);
    
    return true;
}
//------------------------------------------------------------------------------

Ключевыми частями являются функция generate_id() и шаблон msg_wrapper. Функция generate_id() при каждом вызове возвращает целое с новым значением (увеличенным на 1 в данном случае), и служит, как ясно из названия, для генерации уникальных идентификаторов. Внутри шаблона msg_wrapper есть механизм получения и предъявления уникального (для каждого типа) идентификатора. Реализован с помощью пары функций get_class_id() и get_id().

Функция get_class_id(), собственно, и производит получение уникального идентификатора и его хранение. Сделано это на основе свойства статических локальных переменных инициализироваться "по месту":
CODE
static  uint16_t get_class_id() { static const uint16_t id = generate_id(); return id; }

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

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

Ну, далее пара функций - передачи и приёма. Передача:

CODE
template<typename T> bool send_message(T *msg, gui::TObject *dst)
{
    msg_wrapper<T> msg_wrp(msg);
    dst->on_message(&msg_wrp);
    
    return true;
}

Тут всё просто. Функция принимает указатель на тело сообщения и адрес объекта назначения. Поскольку у нас назначением является любой объект из иерархии виджетов, то в качестве типа объекта взят самый главный предок всей этой иерархии. Передаётся при вызове, разумеется, адрес конкретного объекта, в вышеописанной схеме GUI - адрес виджета под фокусом. Далее, внутри функции передачи конструируется собственно сообщение на основе обёртки msg_wrapper (ничего особенного тут не делается, просто инициализируется указатель на тело сообщения, накладные расходы минимальны) и посылается по указанному адресу - вызов той самой важной функций on_message(). В коде такая передач выглядит так:
CODE
    .... //
    if( ... )
    {
        TMessageBody Msg = ...;
        send_message<TMessageBody>(&Msg, Focus);
    }
    ... //



Приём выглядит так (пример приводился выше, повторяю для связности изложения):

CODE
void gui::TWidget::on_message(TBaseMsgWrapper *msg_wrp)
{
    gui::TInputMessage *msg = check_msg<gui::TInputMessage>(msg_wrp);
    
    if(msg)
    {
        switch(*msg)
        {
        case gui::imENTER : enter(); break;
        case gui::imBACK  : back();  break;
        case gui::imHELP  : help();  break;
        case gui::imNEXT  : next();  break;
        case gui::imPREV  : prev();  break;
        default           : print("error: unknown message");
        }
    }
}

Здесь на входе проверяется соответствие типа сообщения ожидаемому; если соответствует, то дальше производится обработка тела сообщения. Соответствие выражается в значении возвращаемого указателя функции check_msg(). Если сообщение соответствует ожидаемому, то возвращается указатель на тело сообщения, если не соответствует, то возвращается ноль:

CODE
template<typename T> T *check_msg(TBaseMsgWrapper *msg_wrp)
{
    if( msg_wrp->get_id() == msg_wrapper<T>::get_class_id() )
    {
        return (static_cast<msg_wrapper<T> *>(msg_wrp))->get_msg();
    }
    else
    {    
        return 0;
    }
}

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

Таким образом осуществляется фильтрация сообщений. Это позволяет выстроить систему передачи сообщений, не "привязываясь" к конкретным типам, и в дальнейшем расширять сколько угодно перечень передаваемых сообщений, ничего не переделывая в этом коде. В примере выше при приёме ожидается сообщение от устройств ввода (в данном случае ими являются кнопки прибора и энкодер). Это поведение по умолчанию для всех виджетов. Если нужно для какого-то виджета изменить поведение, достаточно, как уже говорилось, для него переопределить функцию on_message(). Вот пример, как в одном из виджетов обрабатываются свои сообщения:

CODE
void gui::TModeControl::on_message(TBaseMsgWrapper *msg_wrp)
{
    gui::TTimeIntervals *TimeMsg = check_msg<gui::TTimeIntervals>(msg_wrp);   // сообщения о временнЫх метках
    gui::TModeContolMsg *MCMsg   = check_msg<gui::TModeContolMsg>(msg_wrp);   // "проприетарные" сообщения для данного виджета

    if(TimeMsg)
    {
        switch(*TimeMsg)
        {
              ... //
        }
    }
    else if(MCMsg)
    {
        switch(*MCMsg)
        {
        case gui::mcmCONTACT_LOST:
            {
                 ... //
            }
            break;

        case gui::mcmELECTRODE_OFF:
            {
                ... //
                print("Electrode is off\r\n");
            }
        ... //
        default:
            print("error: unknown mode control message\r\n");
        }
        
    }
    else
    {
        TWidget::on_message(msg_wrp);    // переслать необработанные сообщения дальше
    }
}
//------------------------------------------------------------------------------

Очень важный момент - последняя ветка else. Если этого не сделать, то все сообщения, которые не обработаны, дальше не пойдут, когда данный виджет под фокусом. Внешне это будет выражаться так, что данный виджет не будет реагировать на устройства ввода (т.к. в вышеприведённой функции on_message() сообщения от устройств ввода не обрабатываются). Если их переслать дальше, то сообщения попадут в TWidget::on_message() (показанную выше) и там будут обработаны. Если требуется какая-то иная их обработка, то можно её реализовать в этой же функции, не передавая дальше. Словом, тут полная свобода действий, расширяемость, хорошая управляемость и очень небольшие накладные расходы.

Напоследок замечание (это больше для haker_fox). Не следует путать сообщения, передаваемые по вышеописанной схеме, с сообщениями, передаваемыми через средства межпроцессного взаимодействия (типа OS::channel), механизмы реализации и назначение совершенно разные. Вариант send_message/check_msg - это передача сообщения в главном цикле GUI от источников к виджетам. Передача эта синхронная - в момент передачи сообщение и принимается, и обрабатывается - это всё одна и та же цепочка вызовов функций. А передача сообщений через OS::message или OS::channel - это передача асинхронная, потокобезопасная и через промежуточный объект, в котором хранится тело сообщения. Передавать через send_message() из другого процесса можно, но это небезопасно с точки зрения межпроцессного взаимодействия.

Собственно, GUI и OS - вещи несколько ортогональные. GUI - это, как правило один из процессов (потоков, задач), крутится в своём цикле. Если требуется передавать ему сообщения из других процессов программы, то это логично сделать, например, через OS::channel, организовав очередь сообщений, и уже в процессе, в котором крутится GUI, извлекать сообщения из очереди, преобразовывать их и передавать по вышеописанной схеме виджетам.

Вообще, сам способ генерации и проверки уникальных идентификаторов типов, описанный выше, достаточно универсален и может применяться везде, где требуется подобная проверка на рантайме. В некоторых случаях может вполне успешно заменять RTTI. Прикручивал этот механизм к объектам QEvent, легло вообще отлично благодаря сходности базы объектов событий пакета Qt. Там им только не хватало автоматической генерации идентификаторов и проверки, а сами типы событий там идентифицируются тоже на основе целочисленных переменных.
haker_fox
dxp, спасибо большое за подробнейшее разъяснение! Вникаю sm.gif
_pv
Цитата(dxp @ Sep 11 2012, 10:48) *
По условиям задачи требуется передавать разнокачественную информацию объектам, тип которых, вообще говоря, на этапе компиляции не известен.

возможно я не до конца осознал всю радость от знания типа объёктов, но чем хуже использовать просто int в качестве сообщений, у самого базового класса Widget сделать виртуальный handle(int event), за который и дергать потом все виджеты подряд.
а уже в этом обработчике доставать откуда надо необходимые данные, если нужно:
int handle(int event){
switch (event){
case EVENT_KEYBOARD: int key = get_key(); ...
...
}
так, насколько я понимаю, в FLTK сделано.
dxp
QUOTE (_pv @ Sep 11 2012, 14:37) *
возможно я не до конца осознал всю радость от знания типа объёктов, но чем хуже использовать просто int в качестве сообщений, у самого базового класса Widget сделать виртуальный handle(int event), за который и дергать потом все виджеты подряд.
а уже в этом обработчике доставать откуда надо необходимые данные, если нужно:
int handle(int event){
switch (event){
case EVENT_KEYBOARD: int key = get_key(); ...
...
}
так, насколько я понимаю, в FLTK сделано.

А если сообщения совсем разные? В одном случае мы передаём паттерн, который содержит карту нажатых кнопок (например, допускается и требуется одновременное нажатие нескольких кнопок), в другом случае передаём временную метку, в третьем команда от терминала, в четвёртом информация от АЦП (нужно выводить на экран данные) и т.п. И как в этом случае обойтись одним интом? Информация не только разнокачественная, но и разная по размеру - от 8-битного целого до целой структуры.

К тому же, как сделать фильтрацию? Вот дёрнули handle() для текущего виджета - так эта функция должна уметь обрабатывать все события. Можно, конечно, завести систему кодирования на основе этого передаваемого инта и анализировать значения. Но это будет "закат Солнца вручную" - как раз примерно то, что делает схема передачи сообщений из предыдущего моего поста.
mdmitry
Цитата(dxp @ Sep 11 2012, 12:40) *
К тому же, как сделать фильтрацию? Вот дёрнули handle() для текущего виджета - так эта функция должна уметь обрабатывать все события. Можно, конечно, завести систему кодирования на основе этого передаваемого инта и анализировать значения. Но это будет "закат Солнца вручную" - как раз примерно то, что делает схема передачи сообщений из предыдущего моего поста.

Уважаемый dxp, правильно ли я понял, что на этой же идее у Вас реализована передача информации меджу элементами GUI. Имеется в виду следующее: выбор какого-то пункта меню может блокировать доступ к другим элементам.
dxp
QUOTE (mdmitry @ Sep 11 2012, 19:23) *
Уважаемый dxp, правильно ли я понял, что на этой же идее у Вас реализована передача информации меджу элементами GUI. Имеется в виду следующее: выбор какого-то пункта меню может блокировать доступ к другим элементам.

Это как напишете. У меня в примере информация от устройств ввода передаётся виджету под фокусом, он и является получателем. Можно и другие схемы реализовывать - например, сделать несколько фокусов и метать сообщения, фильтруя на передающем конце, тогда блокировки не будет. Например, можно сообщения от кнопок слать элементам меню, диалогам, а от таймера - виджету часов, а от АЦП - виждету, отображающему уровень заряда батареи, и т.д. Всё в руках программиста. А механизм передачи один и тот же.

Говорить, что передаётся информация между виджетами тут нельзя - этого нет. Информация передаётся от входных сигналов виджетам, задача которых адекватно реагировать и отображать реакцию на устройстве визуализации.
AlexandrY
Цитата(dxp @ Sep 11 2012, 18:31) *
Это как напишете. У меня в примере информация от устройств ввода передаётся виджету под фокусом, он и является получателем. Можно и другие схемы реализовывать - например, сделать несколько фокусов и метать сообщения, фильтруя на передающем конце, тогда блокировки не будет. Например, можно сообщения от кнопок слать элементам меню, диалогам, а от таймера - виджету часов, а от АЦП - виждету, отображающему уровень заряда батареи, и т.д. Всё в руках программиста. А механизм передачи один и тот же.

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


Не очевидная логика.
Подвергать GUI потоку событий от реалтайм задач это значит перегрузить процессор лишней работой либо принудить задачи подстраиваться под GUI и все время беспокоиться о профайлинге GUI.
Надежней в самой задаче GUI планировать обращение за информацией к другим задачам.
Тогда и ивентов становиться гораздо меньше, упрощается их парсинг и поддержка GUI проще.
Вон в uc/GUI всего 32 базовых сообщения.
И хватает за глаза, это учитывая, что там развитый оконный движок наподобие Windows.
mdmitry
Цитата(dxp @ Sep 11 2012, 19:31) *
Говорить, что передаётся информация между виджетами тут нельзя - этого нет. Информация передаётся от входных сигналов виджетам, задача которых адекватно реагировать и отображать реакцию на устройстве визуализации.

Спасибо, dxp.
Поясните, пожалуйста, как Вы делаете именно связь между элементами GUI для управления. По нажатию кнопки выбран элемент из списка №1 (режимы работы). Есть связанный со списком №1 список №2 (специфические (уникальные) для данного режима подрежимы). Как в этом случае делается переключение на нужный список №2.
Как-то так:
Код
режим1
    подрежим11
    подрежим12
    подрежим13
режим2
    подрежим21
    подрежим22
режим3
    подрежим31
    подрежим32
    подрежим33
dxp
QUOTE (mdmitry @ Sep 12 2012, 04:40) *
Поясните, пожалуйста, как Вы делаете именно связь между элементами GUI для управления. По нажатию кнопки выбран элемент из списка №1 (режимы работы). Есть связанный со списком №1 список №2 (специфические (уникальные) для данного режима подрежимы). Как в этом случае делается переключение на нужный список №2.

Ну, т.е. что-то вроде системы меню? Это несколько другая тема, чем передача сообщений. Тут тоже может быть несколько реализаций. Я делаю связь через промежуточный объект Target. При создании пункта меню ему присваивается объект Target, который представляет собой простой объект из иерархии таржетов. Все их объединяет метод (виртуальная функция) exec(), которая и вызывается внутри метода объекта пункта меню, отвечающего за реакцию на нажатие соответсвующей кнопки. Схема действий проста: внутри переключается фокус и вызывается перерисовка, а также выполняются все сопутствующие действия.

Если приводить аналогию из С, то этот способ похож на связть через указатель на функцию, которая и выполняет действия. Тут просто эта функция "завёрнута" в объект Target, что делает использование более безопасным и удобным (можно хранить внутри этого объекта какие-то служебные переменные). Использование промежуточных объектов позволяет гибко настроить целевые действия при активации виджета. Если не очень понятно описано, скажите, пример приведу (сейчас его нет под рукой).

AlexandrY, именно так и делается, см самый первый мой пост в этой теме - главный цикл GUI. Все сообщения посылаются из него с его темпом.
AlexandrY
Цитата(dxp @ Sep 12 2012, 04:20) *
AlexandrY, именно так и делается, см самый первый мой пост в этой теме - главный цикл GUI. Все сообщения посылаются из него с его темпом.


Цикл в первом посте выглядит совсем плохо.
Получается искусственно созданный задержкой затор.
В это время в очереди может накопиться куча сообщений которые GUI должно быть выполнить все одним рывком.
Это как минимум будет выглядеть некрасиво на экране.
dxp
QUOTE (AlexandrY @ Sep 12 2012, 13:33) *
Цикл в первом посте выглядит совсем плохо.
Получается искусственно созданный задержкой затор.
В это время в очереди может накопиться куча сообщений которые GUI должно быть выполнить все одним рывком.
Это как минимум будет выглядеть некрасиво на экране.

Да, давайте обсудим недостатки простых примеров, приведённых для упрощения понимания. Ясно, что ничего не мешает этот цикл сделать так, как хочется - например, создать очередь сообщений от других процессов. К тому же, даже в том примере цикл составляет 40 мс, этого вполне достаточно для комфортной реакции GUI на события - врядли вы успеете заметить торможение при обновлении информации, если, конечно, не рубитесь в Unreal Tournament.
AlexandrY
Цитата(dxp @ Sep 12 2012, 11:50) *
Да, давайте обсудим недостатки простых примеров, приведённых для упрощения понимания.
...цикл составляет 40 мс, этого вполне достаточно для комфортной реакции GUI на события - врядли вы успеете заметить торможение при обновлении информации...


Я думаю задержка там концептуально поставлена, а не для простоты.
Она именно дает GUI передохнуть.
Скорее всего жертвуется событиями от клиентских для GUI задач.

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

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

Вообщем преимущества продемонстрированной реализации весьма спорны, либо она уж очень упрощена wink.gif
mdmitry
Цитата(dxp @ Sep 12 2012, 05:20) *
Ну, т.е. что-то вроде системы меню? Это несколько другая тема, чем передача сообщений. Тут тоже может быть несколько реализаций. Я делаю связь через промежуточный объект Target. При создании пункта меню ему присваивается объект Target, который представляет собой простой объект из иерархии таржетов. Все их объединяет метод (виртуальная функция) exec(), которая и вызывается внутри метода объекта пункта меню, отвечающего за реакцию на нажатие соответсвующей кнопки. Схема действий проста: внутри переключается фокус и вызывается перерисовка, а также выполняются все сопутствующие действия.

Если приводить аналогию из С, то этот способ похож на связть через указатель на функцию, которая и выполняет действия. Тут просто эта функция "завёрнута" в объект Target, что делает использование более безопасным и удобным (можно хранить внутри этого объекта какие-то служебные переменные). Использование промежуточных объектов позволяет гибко настроить целевые действия при активации виджета. Если не очень понятно описано, скажите, пример приведу (сейчас его нет под рукой).

Спасибо, dxp.
Пример был бы интересен.
У меня элементы меню - объекты с соответствующими свойствами. Обработка клавиш - вызов виртуальной функции для элемента. Вопрос как эффективно (красиво) обеспечить управление видом и содержанием элемента меню (ведомого) (или листбокса, едитбокса) в зависимости от состояния другого элемента (ведущего). Мое решение ассоциируется у меня с костылями (некрасиво, неэффективно).
_Pasha
Цитата(AlexandrY @ Sep 12 2012, 16:42) *
Интересно, упор делается на такой мелочи как автоматическая генерации идентификаторов сообщений, хотя на самом деле главный цикл для большого количества сообщений не приспособлен. Это не считая того, что автоматически сгенерированные идентификаторы усложняют отладку и ведение лога.

Никто не мешает использовать итерацию главного цикла там, где надо ускорить реакцию. Имхо, главный цикл - отправная точка в самом низкоприоритетном потоке. Так же везде и делается...
dxp
QUOTE (AlexandrY @ Sep 12 2012, 20:42) *
Я думаю задержка там концептуально поставлена, а не для простоты.
Она именно дает GUI передохнуть.

Не передохнуть, а просто нет необходимости обновлять информацию на экране чаще.

QUOTE (AlexandrY @ Sep 12 2012, 20:42) *
Скорее всего жертвуется событиями от клиентских для GUI задач.

Какими же это? Мне про это ничего не известно. Расскажите?

QUOTE (AlexandrY @ Sep 12 2012, 20:42) *
Для маленьких монохромных экранов 40 мс скорее всего будет незаметно.
Но для больших цветных панелей с тачскрином где весь цикл прорисовки занимает больше 40 мс последствия задержки сразу будут заметны.

Посмотрел, в текущей работе экран 320х240 прорисовывается целиком за 3 мс. Учитывая, что полная перерисовка требуется весьма редко (обновляются только гораздо меньшие фрагменты), то никаких проблем нет. В другом приборе экран 800х600, время обновления не замерял, ни малейших тормозов не наблюдается. С большими экранами и платформа побыстрее.

QUOTE (AlexandrY @ Sep 12 2012, 20:42) *
Интересно, упор делается на такой мелочи как автоматическая генерации идентификаторов сообщений,

Мелочи? Покажите, как вы делаете подобные вещи? Как осуществляется доставка сообщений от событий к виджетам?

QUOTE (AlexandrY @ Sep 12 2012, 20:42) *
хотя на самом деле главный цикл для большого количества сообщений не приспособлен.

Настал момент вам показать свой, хороший, правильный вариант главного цикла. Покажите, очень интересно?

QUOTE (AlexandrY @ Sep 12 2012, 20:42) *
Это не считая того, что автоматически сгенерированные идентификаторы усложняют отладку и ведение лога.

С чего это? Обоснуйте?

QUOTE (AlexandrY @ Sep 12 2012, 20:42) *
Вообщем преимущества продемонстрированной реализации весьма спорны, либо она уж очень упрощена wink.gif

Ждём правильный вариант.
AlexandrY
Цитата(dxp @ Sep 12 2012, 18:20) *
С чего это? Обоснуйте?


Вопрос не в необходимой частоте вывода на экран, а в том что каждый внешний (из другой задачи) ивент вы обрабатываете по всей цепочке виджетов заново. Итого куча холостых циклов. Вот и ставите задержку чтоб не перенапрягать GUI холостой работой. А затора избегаете пропуском ивентов. Может очищаете буфер очереди или еще что. Это уж вам виднее. Вы ж свою реализацию не выкладываете. wink.gif

Про доставку я уже говорил. У Microchip-а просто есть глобальный связный список виджетов. Все идентификаторы событий сведены в одном заголовочном файле. Просто и понятно.
При отладке какой бы релиз библиотеки не взяли каждый определенный числовой идентификатор всегда соответствует одному и тому же событию.
Не надо шерстить все исходники чтобы отыскать все старые и новые типы событий для того чтобы правильно их описать в логе.

Кстати, упор на фокус тоже не очень понятен. Если экран с тачскрином, то зачем вообще иметь такой атрибут как фокус?
Может быть это синоним привязки видджетов ввода к виджетам вывода? Но тогда фокусов должно быть много.

Вообщем я то выложу свои исходники, но только после того как выложите вы свои. wink.gif
А то из коротких постов так и не понятно насколько развита ваша GUI и что умеет.
Много вопросов остается.
Например, действительно ли там нужна была такая масштабируемость, что потребовалось автоматическая генерация идентификаторов?
haker_fox
Гм, не думал, что скромный вопрос разовъется в жаркую дискусию) Это хорошо!

По-маленьку создаю виджеты. Вроде получается. Основные сложности намечаются в:
1. Перересовке виджетов (весь экран перерисовывать нецелесообразно, надо подсмотреть в какой-нибудь библиотеке красивый алгоритм).
2. Получение данных "в ГУИ" от клавиатуры, АЦП и т.п. Здесь уже алгоритм предложен, но нужно все еще раз обдумать и "вживит" в свою реализацию.
dxp
QUOTE (AlexandrY @ Sep 13 2012, 02:58) *
Вопрос не в необходимой частоте вывода на экран, а в том что каждый внешний (из другой задачи) ивент вы обрабатываете по всей цепочке виджетов заново.

По какой цепочке? Цепочке, состоящий из чего? Покажите? Объясните, с чего вы сделали такой вывод?

1.
QUOTE (AlexandrY @ Sep 13 2012, 02:58) *
Итого куча холостых циклов.

2.
QUOTE (AlexandrY @ Sep 13 2012, 02:58) *
Вот и ставите задержку чтоб не перенапрягать GUI холостой работой.

3.
QUOTE (AlexandrY @ Sep 13 2012, 02:58) *
А затора избегаете пропуском ивентов. Может очищаете буфер очереди или еще что.


Аж целых три сильных заявления! И ни одно не имеет даже намёка на обоснование. Это ваши фантазии, ничего этого там нет. Может, перестанете уже фантазировать и попробуете разобраться в том коде, который я привёл (если что-то не понятно, готов ответить на вопросы). А если не желаете, считаете это ниже своего достоинства, то тогда прошу перестать писать комментарии, содержание которых не имеет к действительности никакого отношения, и демонстрирующие непонимание комментируемого.

QUOTE (AlexandrY @ Sep 13 2012, 02:58) *
Это уж вам виднее. Вы ж свою реализацию не выкладываете. wink.gif

Уже практически всё выложил. А вы и не заметили.

QUOTE (AlexandrY @ Sep 13 2012, 02:58) *
Про доставку я уже говорил. У Microchip-а просто есть глобальный связный список виджетов. Все идентификаторы событий сведены в одном заголовочном файле. Просто и понятно.

Понятно. Самый примитивный вариант. На С по-другому особо и не разбежишься. При добавлении и/или удалении события придётся ручками следить за соответствием идентификаторов. Закат Солнца вручную. Именно то, от чего всегда хотелось уйти - пусть машина этим занимается, она не ошибается. Другое дело, что используемый инструментарий не позволяет.

QUOTE (AlexandrY @ Sep 13 2012, 02:58) *
При отладке какой бы релиз библиотеки не взяли каждый определенный числовой идентификатор всегда соответствует одному и тому же событию.
Не надо шерстить все исходники чтобы отыскать все старые и новые типы событий для того чтобы правильно их описать в логе.

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

QUOTE (AlexandrY @ Sep 13 2012, 02:58) *
Кстати, упор на фокус тоже не очень понятен. Если экран с тачскрином, то зачем вообще иметь такой атрибут как фокус?

Если. А если нет? Показан простой пример. Тем не менее, прекрасно работающий. Будет тачскрин, будет чуть иная реализация.

QUOTE (AlexandrY @ Sep 13 2012, 02:58) *
Может быть это синоним привязки видджетов ввода к виджетам вывода? Но тогда фокусов должно быть много.

Сколько угодно. В данном примере один. Выше описывал вариант, когда может быть несколько. Ничего ничему не противоречит, не мешает, расширяется сколько угодно в любую сторону без переделки "каркаса".

QUOTE (AlexandrY @ Sep 13 2012, 02:58) *
Вообщем я то выложу свои исходники, но только после того как выложите вы свои. wink.gif

Во-первых, я и не просил ваши исходники. Вы цикл критиковали, я попросил ваш вариант цикла, лишённого недостатков критикуемого. Принцип, псевдокод - компилировать его никто не будет.

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

Да, этот GUI такой маленький, что вы и не заметили, что это практически всё (остальное рюшечки). Да, такой "примитивный", что кода всего на пару экранов. Тем не менее, схема уже обкатана на двух серьёзных приборах и отлично себя зарекомендовала. Результирующего кода, конечно, гораздо больше (несколько тысяч строк), но это реализация функциональности виджетов.

QUOTE (AlexandrY @ Sep 13 2012, 02:58) *
А то из коротких постов так и не понятно насколько развита ваша GUI и что умеет.

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

QUOTE (AlexandrY @ Sep 13 2012, 02:58) *
Много вопросов остается.
Например, действительно ли там нужна была такая масштабируемость, что потребовалось автоматическая генерация идентификаторов?

Во-первых, не масштабируемость, а расширяемость. Расширяемость нужна всегда.

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


* * *

QUOTE (mdmitry @ Sep 12 2012, 20:59) *
Пример был бы интересен.
У меня элементы меню - объекты с соответствующими свойствами. Обработка клавиш - вызов виртуальной функции для элемента. Вопрос как эффективно (красиво) обеспечить управление видом и содержанием элемента меню (ведомого) (или листбокса, едитбокса) в зависимости от состояния другого элемента (ведущего). Мое решение ассоциируется у меня с костылями (некрасиво, неэффективно).

Не очень понял, что именно требуется. Вы имеете в виду вариант, когда, скажем, перемещаем курсор по меню от одного пункта к другому, и при этом где-то рядом отрисовывается подменю, каждый раз другое (у каждого пункта меню своё подменю)? Этот вариант? Или что-то иное? Объясните словами поподробнее, что должно быть на экране, как оно должно реагировать на события?



* * *

QUOTE (haker_fox @ Sep 13 2012, 06:12) *
1. Перересовке виджетов (весь экран перерисовывать нецелесообразно, надо подсмотреть в какой-нибудь библиотеке красивый алгоритм).

Это в большей степени зависит от особенностей аппаратуры визуализации, проще говоря, от контроллера дисплея (хотя не всегда он есть). Например, SSD1963 хорошо приспособлен для заливки прямоугольных фрагментов, но не имеет теневого буфера. Поэтому тут нужно по месту быстро обновлять изменившиеся фрагменты. В другом приборе у меня был теневой буфер в SDRAM и возможность быстро залить этот буфер в рабочий буфер, с которого идёт рефреш. Тут без разницы, сколько информации на экране вы меняете - всё равно перезаливается весь буфер, но происходит это очень быстро (800х600 за 2.4 мс) в паузах между рефрешем очередного кадра (там VESA). Поэтому не ищите универсального решения - в каждом случае свои особенности, плясать тут приходится от особенностей доступной аппаратной части видеовывода.

QUOTE (haker_fox @ Sep 13 2012, 06:12) *
2. Получение данных "в ГУИ" от клавиатуры, АЦП и т.п. Здесь уже алгоритм предложен, но нужно все еще раз обдумать и "вживит" в свою реализацию.

Для начала возьмите, как есть и сделайте простейший вариант - обработка событий от кнопок парой виджетов. Когда заработает, сразу поймёте, что хорошо, что нет, что нравится, а что не нравится, чего не хватает, а что лишнее. И увидите вариант, который вам подойдёт лучше всего.
haker_fox
QUOTE (AlexandrY @ Sep 13 2012, 03:58) *
Вообщем я то выложу свои исходники, но только после того как выложите вы свои. wink.gif

Уважаемый AlexandrY, просим, просим! rolleyes.gif Выложите, хотя бы шкелет rolleyes.gif

QUOTE (dxp @ Sep 13 2012, 11:51) *
Это в большей степени зависит от особенностей аппаратуры визуализации

У меня аппаратный котроллер LCD в LPC2478 rolleyes.gif
QUOTE (dxp @ Sep 13 2012, 11:51) *
И увидите вариант, который вам подойдёт лучше всего.

Спасибо! rolleyes.gif
Lotor
dxp, минимум в одной теме на форуме встречал уже Ваши идеи по GUI и передачи сообщений - там они были описаны не так развернуто, но уже тогда я разглядел полезность для себя и успел сохранить, пока тема не захлебнулась в холиваре и самоутверждении некоторых товарищей...

Этот топик ждет такая же участь.

Не приходила ли Вам мысль зафиксировать подобные примеры в документации к scmRTOS в разделе "Примеры использования"?
dxp
QUOTE (Lotor @ Sep 13 2012, 12:18) *
Не приходила ли Вам мысль зафиксировать подобные примеры в документации к scmRTOS в разделе "Примеры использования"?

Так это ведь не про ось, это как бы ортогональные вещи. GUI сам по себе. А способ передачи сообщений - это типа лёгкий аналог dynamic_cast<> (RTTI), не везде полностью заменяет, но во многих случаях вполне. И применяться может почти на любой мелочи в отличие от.

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

В подфоруме по ПЛИС есть тема под названием "Схемотехнические трюки" или как-то так. Там народ делился всякими HDL приёмами. Возможно, что и по программированию имеет смысл создать такую тему и складывать туда интересные посты по программерским приёмам, паттернам. Тему сделать закрытой с доступом только модераторов, чтобы не флудили в ней. Правда, это работа для модераторов.

P.S. Я подумаю над вашим предложением. Возможно, имеет смысл всё-таки написать нормальную статью (когда со свободным временем будет получше).
AlexandrY
Цитата(haker_fox @ Sep 13 2012, 07:10) *
Уважаемый AlexandrY, просим, просим! rolleyes.gif Выложите, хотя бы шкелет rolleyes.gif


Ну нет, я пас.
Нет исходников - нет обсуждения.
GUI интересен рендерингом, организацией шрифтов, импортом графических файлов, оконными сервисами с клипингом, скролингом и проч.
А здесь речь только об одном незначительном аспекте.
dxp
QUOTE (AlexandrY @ Sep 13 2012, 13:54) *
Ну нет, я пас.
Нет исходников - нет обсуждения.
GUI интересен рендерингом, организацией шрифтов, импортом графических файлов, оконными сервисами с клипингом, скролингом и проч.
А здесь речь только об одном незначительном аспекте.

А не надо нам виртуальные окна. Организацию главного цикла и передачу евентов виджетам покажите (обсуждение ведь идёт об этом "одном незначительном аспекте")?
mdmitry
Цитата(dxp @ Sep 13 2012, 07:51) *
Не очень понял, что именно требуется. Вы имеете в виду вариант, когда, скажем, перемещаем курсор по меню от одного пункта к другому, и при этом где-то рядом отрисовывается подменю, каждый раз другое (у каждого пункта меню своё подменю)? Этот вариант? Или что-то иное? Объясните словами поподробнее, что должно быть на экране, как оно должно реагировать на события?

Например:
Есть три листбокса. В первом устанавливается режим устройства (несколько). Во втором в зависимости от установленного режима предлагаются варианты подрежимов (число ращное в зависимости от режима) для выбранного режима. Третий листбокс устанавливает параметры подрежима, число которых (параметров) зависит от выбранного подрежима) . Смена режима должна изменить предлагаемые подрежимы и параметры. Смена подрежима должна изменить список допустимых параметров.
режим->подрежим -> параметр.
Надеюсь прояснил ситуацию

_pv
Цитата(mdmitry @ Sep 13 2012, 13:43) *
Смена режима должна изменить предлагаемые подрежимы и параметры. Смена подрежима должна изменить список допустимых параметров.
режим->подрежим -> параметр.
Надеюсь прояснил ситуацию

посмотрите на FLTK, легкий и простой, можно посмотреть как некоторые веши сделаны перед тем как свои велосипеды изобретать.
у каждого виджета есть колбэк за который его дергает родитель в определённых случаях. в этом колбэке, который может быть и один зарегистрирован у всех заинтересованных виджетов, меняем переметры у всех остальных в зависимости от значений:
ну и виджеты иерархически огранизованны, и есть базовый класс Group от которого унаследованы окна/составные виджеты/... соответственно они хранят список подопечных виджетов и по приходу сообщения сверху дергают передают его через handle или дергают за callback всех кто снизу.

Код
void uiSomeWindow::Choice_cb(){
  if (list1->value() == 0) {
    list2->clear();
    list2->add("value1|value2|value3")
  }
  if (list2->value() == 2) {
    list3->clear();
    list3->add("value4|value5|value6")
  }
}
mdmitry
Цитата(_pv @ Sep 13 2012, 13:06) *
посмотрите на FLTK, легкий и простой, можно посмотреть как некоторые веши сделаны перед тем как свои велосипеды изобретать.

Спасибо, уже смотрел. Наверно не очень внимательно. А так же глянул на gtkmm и wxWidgets.

Раз уж здесь такая дискуссия, то позволю вбросить ещё вопрос.
Система меню, IMHO, совокупность связанных меджу собой однородных (или почти) объектов. К объектам можно отнести и другие элементы GUI, такие как, листбоксы, эдитбоксы и др. Объект может быть визуализирован, причем на разных экранах (размеры, цвета) визуализация получается разная. Отсюда вопрос: имеет ли смысл делать виджеты, завязанные на графическое представление и дающие необходимый функционал? Альнернатива: объекты хранят информацию, связи и взаимодействия (передча управления друг другу). Графический драйвер обеспечивает необходимую визуализацию. Смена экрана означает только смену драйвера. Идею этого разделения увидел тут
dxp
QUOTE (mdmitry @ Sep 13 2012, 14:43) *
Например:
Есть три листбокса. В первом устанавливается режим устройства (несколько). Во втором в зависимости от установленного режима предлагаются варианты подрежимов (число ращное в зависимости от режима) для выбранного режима. Третий листбокс устанавливает параметры подрежима, число которых (параметров) зависит от выбранного подрежима) . Смена режима должна изменить предлагаемые подрежимы и параметры. Смена подрежима должна изменить список допустимых параметров.
режим->подрежим -> параметр.
Надеюсь прояснил ситуацию

Ага, вроде, понял. Типа, нужно менять и отрисовывать виджеты по цепочке. Модель, как я понимаю, выглядит следующим образом. Есть Список1, в котором некоторое количество пунктов. Каждый пункт имеет связь с другим объектом Список2. Аналогично, каждый пункт у объекта типа Список2 имеет связь с объектом типа Список3. Вопрос в том, как связать пункты и их объекты списков.

Один из вариантов может выглядеть так (я применяю приблизительно такой способ в текущем проекте). Линки между пунктами списков и их целевыми объектами реализовываются через промежуточный объект (TList - тип списка, TListItem - тип элемента списка, все они потомки TWidget):

CODE
    class TTarget
    {
    public:
        virtual void exec() = 0;
        virtual void draw() = 0;
    };

    class TListTarget : public TTarget
    {
    public:
        TListTarget(TList *target) : Target(target) { }

        virtual void exec();
        virtual void draw() { Target->draw(); }
                      
    protected:
        TList     *Target;
    };

Все таржеты, в духе идеологии, являются потомками от TTarget, в данном случае у нас пока один тип. Далее, у пункта списка есть привязка к таржету:

CODE
class TListItem : public TWidget
{
public:
    TListItem(TListTarget *target...) : Target(target), ... { ... }
    ...
    virtual void exec() { Target->exec(); } // выполнить целевые действия
    void draw_target() { Target->draw(); }
    ...

private:
    TListTarget *Target
    ...
};


Активизация следующего по цепочке списка осуществляется, скажем, по нажатию на кнопку (пусть будет enter), которая вызывает выполнение метода enter() виджета, в случае списка это будет:

CODE
void TList:enter()
{
    FocusHistory.push(Focus);  // помещаем текущий фокус в стек фокусов (чтобы вернуться обратно по цепочке)
    set_active(false);         // делаем текущий список неактивным
    draw();                    // и перерисовываем его, теперь он выглядит неактивным
    Items.curr_item()->exec(); // целевые действия, включая, например, передачу фокуса на целевой объект - список
}

Комментарий. Items - групповой объект, который собственно содержит список пунктов. Может быть реализован на основе простого массива, может - на основе std::vector, std::list или какого-либо другого вида контейнера. curr_item() - функция-член Items, возвращает указатель на текущий элемент (пункт списка в данном случае). Реализация хранения группы элементов и интерфейс доступа к ним может быть каким угодно, это не накладывает ограничений на общий принцип.

Если при прохождении по списку нужно отрисовывать цель (для пунктов объекта типа Список1 - это объекты типа Список2), то внутри метода draw() пункта списка вызывать метод draw() цели:

CODE
void TListItem::draw()
{
    ...//
    if(Selected) // текущий пункт является выбранным в данном списке
    {
        draw_target();
    }
    ...//
}


Функция draw_target() вызовет перерисовку списка, который является целью, например, если это был объект типа Список2, то он будет перерисован, при этом его выбранный пункт будет вести себя аналогично - перерисует свой таржет.

Схема с промежуточными объектами-таржетами может показаться громоздкой, но зато она "развязывает" виджет от его цели. Аналогом может служить указатель (или несколько указателей) на функцию, которая выполняет целевые действия. Но реализация через объект получается компактнее, проще - ведь все его методы, выполняющие действия по отношению к таржету, заключены в одном месте, кроме того, объект может кроме функций иметь какие-то служебные данные и вспомогательные функции, чтобы иметь возможность производить какие-то более нетривиальные действия, не привязываясь ни к виджету-владельцу, ни к цели. Это даёт расширяемость.

Итоговое подключение состоит из создания объекта-таржета и передачи указателя на него виджету:

CODE
TList1 List1;
TList2 List2_1;
TList2 List2_2;
TList2 List2_3;
...
TListTarget ListTarget2_1(&List2_1);
TListTarget ListTarget2_2(&List2_2);
TListTarget ListTarget2_3(&List2_3);
...
List1.add_item( new TListItem(&ListTarget2_1,...) );
List1.add_item( new TListItem(&ListTarget2_2,...) );
List1.add_item( new TListItem(&ListTarget2_3,...) );
...


Возможно, сумбурно написано - сложно, не зная тонкостей задачи, написать коротко и точно. Данный пример преследовал цель продемонстрировать принцип. Имена типов и объектов, конкретная реализация функций могут (и должны) быть своими.

Честно говоря, мне самому не доставляет восторга такая схема, несколько громоздко, на мой взгляд. Но она работает и нет проблем с "завязками". Если у кого-то есть на примете более лёгкий и логичный вариант, но в то же время гибкий и удобный, я удовольствием бы ознакомился.

QUOTE (mdmitry @ Sep 13 2012, 20:04) *
Система меню, IMHO, совокупность связанных меджу собой однородных (или почти) объектов. К объектам можно отнести и другие элементы GUI, такие как, листбоксы, эдитбоксы и др. Объект может быть визуализирован, причем на разных экранах (размеры, цвета) визуализация получается разная. Отсюда вопрос: имеет ли смысл делать виджеты, завязанные на графическое представление и дающие необходимый функционал? Альнернатива: объекты хранят информацию, связи и взаимодействия (передча управления друг другу). Графический драйвер обеспечивает необходимую визуализацию. Смена экрана означает только смену драйвера.

А кто отвечает за собственно внешний вид виджетов? Их размеры, цвета и прочее.
mdmitry
Цитата(dxp @ Sep 13 2012, 18:00) *
Ага, вроде, понял. Типа, нужно менять и отрисовывать виджеты по цепочке. Модель, как я понимаю, выглядит следующим образом. Есть Список1, в котором некоторое количество пунктов. Каждый пункт имеет связь с другим объектом Список2. Аналогично, каждый пункт у объекта типа Список2 имеет связь с объектом типа Список3. Вопрос в том, как связать пункты и их объекты списков.

Что-то в таком виде:
Код
[/code]
картофель-> резать кубиками    -> отварить
                                                  жарить
                                                  заморозить                
                   резать ромбиками ->  жарить
                                                  отварить
                   резать параллелепипедами -> жарить
                                                              заморозить
                                                              запечь
морковь   -> резать кубиками   ->  отварить
                                                  жарить
                                                  заморозить

                  резать кружочками - > отварить
                                                  заморозить
                  резать вдоль          -> заморозить
                                                  консервировать
огурец     -> резать кружочками -> консервировать
                                                  ничего не делать
                  резать вдоль          -> консервировать
                                                  ничего не делать
                  не резать    ->           консервировать
                                                  ничего не делать

Список1: картофель морковь огурец
Список2: зависит от элемента Списка1
Список3: зависит от элемента Списка2

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

Цитата
А кто отвечает за собственно внешний вид виджетов? Их размеры, цвета и прочее.

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

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

add: _Pasha, спасибо за тэги.
kolobok0
Цитата(dxp @ Sep 8 2012, 08:29) *
...
Код
void gui::TWidget::on_message(TBaseMsgWrapper *msg_wrp)
{
    gui::TInputMessage *msg = check_msg<gui::TInputMessage>(msg_wrp);   // проверяется соответствие типа сообщения, 0 возвращает, если не соответствует
    
    if(msg)
    {
        switch(*msg)
        {
        case gui::imENTER : enter(); break;
        case gui::imBACK  : back();  break;
        case gui::imHELP  : help();  break;
        case gui::imNEXT  : next();  break;
        case gui::imPREV  : prev();  break;
        default           : print("error: unknown message");
        }
    }
}

...


маленькое ОФФ-топ замечание-предложение.
Поймите только правильно - я проходил мимо беседы, дабы не размывать инфу основанной на вашем опыты. но конструкция свитч-кэйс основанная на обращении к базовому классу - мягко говоря избыточна (скажем не строго) в некоторых архитектурах кода. хочу обратить ваше внимание - что ВТД уже есть переход на нужный обработчик. и если это задействовать, то код будет состоять из одной строчки - вызов обработчика у базового класса. при этом если виртуальные функции перегружены для каждого типа - вы получите переход на чётко свой обработчик с чёткой типизацией участников события(мессэдж-виджете-метод).

этот и другие замечательние языковые(не относящиеся к ООА и ООП) приёмы не плохо изложен в книге Джефа Элджера "C++". Чёрная такая обложечка. Если не смотрели - рекомендую почитать.
dxp
QUOTE (mdmitry @ Sep 14 2012, 03:09) *
Что-то в таком виде:
картофель-> резать кубиками -> отварить
жарить
заморозить
резать ромбиками -> жарить
отварить
резать параллелепипедами -> жарить
заморозить
запечь

Список1: картофель морковь огурец
Список2: зависит от элемента Списка1
Список3: зависит от элемента Списка2

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

Как-то не очень понятно, почему разнокачественные вещи в одном списке: картофель, резать ромбиками, отварить. Я так понимаю, что элементы первого списка: картофель, морковь, огурец; элементы второго списка: резать кубиками, резать ромбиками, резать параллелепипедами; элементы третьего списка: жарить, варить, заморозить, запечь.

Если оно так, то всё получается достаточно логично.

Список1.добавить_элемент(картофель)
Список1.добавить_элемент(морковь)
Список1.добавить_элемент(огурец)

Список2_1.добавить_элемент(резать_кубиками)
Список2_2.добавить_элемент(резать_ромбиками)
Список2_3.добавить_элемент(резать_параллелепипедами)

Список2_1.добавить_элемент(резать_колёсиками)
Список2_2.добавить_элемент(резать_соломкой)
Список2_3.добавить_элемент(шинковать_колечками)


Список3_1.добавить_элемент(жарить)
Список3_1.добавить_элемент(варить)
Список3_1.добавить_элемент(парить)

Список3_2.добавить_элемент(морозить)
Список3_2.добавить_элемент(парить)
Список3_2.добавить_элемент(жарить)

картофель.назначить_цель(Список2_1)
морковь.назначить_цель(Список2_2)
огурец.назначить_цель(Список2_1)

Правда, тут я вот вижу уже нестыковку - линковать Список3 на элементы Списка2 как-то нелогично, а логично Список3 линковать на элементы, опять же, Списка1. Т.е. картофель->варить, а не резать_кубиками->варить. Т.е. тут над дизайном подумать надо. Но механизм связывания пунктов списков с их целями остаётся таким же. Как это делается по коду, приведено в предыдущем моём посте.


QUOTE (kolobok0 @ Sep 14 2012, 03:39) *
конструкция свитч-кэйс основанная на обращении к базовому классу - мягко говоря избыточна (скажем не строго) в некоторых архитектурах кода.

А где вы видите "свитч-кэйс" при обращении к базовом классу?

QUOTE (kolobok0 @ Sep 14 2012, 03:39) *
хочу обратить ваше внимание - что ВТД уже есть переход на нужный обработчик. и если это задействовать, то код будет состоять из одной строчки - вызов обработчика у базового класса. при этом если виртуальные функции перегружены для каждого типа - вы получите переход на чётко свой обработчик с чёткой типизацией участников события(мессэдж-виджете-метод).

Что такое ВТД?
mdmitry
Цитата(dxp @ Sep 14 2012, 10:14) *
Как-то не очень понятно, почему разнокачественные вещи в одном списке: картофель, резать ромбиками, отварить. Я так понимаю, что элементы первого списка: картофель, морковь, огурец; элементы второго списка: резать кубиками, резать ромбиками, резать параллелепипедами; элементы третьего списка: жарить, варить, заморозить, запечь.

Если оно так, то всё получается достаточно логично.

Список1.добавить_элемент(картофель)
Список1.добавить_элемент(морковь)
Список1.добавить_элемент(огурец)

Так у меня также и написано в сообщении
Цитата
Список1: картофель морковь огурец

Отобразилось неправильно на форуме, пробелы для выравнивания исчезли. Причем при редактировании всё выравнивание пробелами есть.

P.S. Вопрос к модераторам или администраторам: как правильно делать сообщение, чтобы введенные пробелы отображались корректно.
_Pasha
Цитата(mdmitry @ Sep 14 2012, 10:55) *
P.S. Вопрос к модераторам или администраторам: как правильно делать сообщение, чтобы введенные пробелы отображались корректно.

Я ни тот ни этот, но знаю: теги code и /code как раз на этот случай
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.