Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Общие вопросы > класс для облегчения работы с GUI


Автор: Alek86 9.1.2008, 18:36
в общем, вопрос из разряда "хотелось бы отакую штуку, а вдруг уже есть?"

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

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

К примеру, если есть эдит, в который записывается сообщение, и кнопка - "Послать сообщение в чат".
Если эдит не заполнен, то и кнопке "Послать" нужно быть недоступной, а когда приходит событие "Эдит стал непустым", она становится доступной. При событии "Эдит опустел" она снова тухнет.

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

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

в общем, это и весь вопрос - знает кто-либо что-либо подобное?

Автор: JackYF 9.1.2008, 18:41
Кстати, да - дваждую вопрос. Пока не очень сильно надобилось, но задумывался.

Автор: Earnest 9.1.2008, 19:50
ИМХО, такие вещи лучше делать примерно так (без автоматов и явного прописывания состояний): 
1) каждый контрол имеет вирт функцию OnUpdate с каким-то подходящим параметром или, что почти тоже самое, родительская форма имеет функцию OnUpdateItem, которая вызывается для каждого элемента в определенные периоды времени.
Во втором случае можно завести таблицу (ид-р элемента -> Update-функция) - чтобы со свичами не связываться. В общем, какой-то диспетчер.

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

Функцию, которая вызывает эти Update'ы, назовем UpdateItems.
2) Эта функция может вызываться в цикле простоя (автоматически, средой) или прямо (формой, по наступлению каких-то событий). 

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


Автор: archimed7592 9.1.2008, 20:06
Цитата(Alek86 @  9.1.2008,  18:36 Найти цитируемый пост)
К примеру, если есть эдит, в который записывается сообщение, и кнопка - "Послать сообщение в чат".
Если эдит не заполнен, то и кнопке "Послать" нужно быть недоступной, а когда приходит событие "Эдит стал непустым", она становится доступной. При событии "Эдит опустел" она снова тухнет.

QAction, TAction(VCL) и иже с ними.

В общих чертах даже вижу как реализовать такой велосипед на Qt smile.

Автор: JackYF 9.1.2008, 20:15
Earnest, я почему-то подобные решения и представлял как собственный велосипед. Например, чтобы контролу заиметь функцию OnUpdate, от него надо отнаследоваться. Разным контролам в onUpdate надо подсовывать разные типы данных - строку, число, булевское значение...

Автор: Earnest 9.1.2008, 20:42
Цитата(JackYF @  9.1.2008,  21:15 Найти цитируемый пост)
Earnest, я почему-то подобные решения и представлял как собственный велосипед. Например, чтобы контролу заиметь функцию OnUpdate, от него надо отнаследоваться. Разным контролам в onUpdate надо подсовывать разные типы данных - строку, число, булевское значение... 

Если велосипед написать один раз, и написать хорошо, то это уже будет мопед или даже мотоцикл smile 

Насчет "отнаследоваться" - да, верно, проще пользоваться стандартными контролами в общем случае. Поэтому я и написала про несколько Апдейтов на родительской форме + диспетчеризация вызовов. А передавать разные переменные вовсе не обязательно - спрашиваем у формы то, что нас в каждом конкретном случае интересует. Например: кнопка - "Послать сообщение в чат", которая зависит от набитого в поле текста. Пишем OnUpdateSendMsg: форма, дай текстик от поля трам-пам-пам. Пустой? - засериваюсь! Ну и т.д.
Логически, вообще никакие параметры не нужны (кроме формы, конечно - эти функции могут быть и не частью формы, можно такую обновлялку как отдельный класс писать). Технически - может потребоваться какой-то вспомогательный параметр типа контекста...

Добавлено через 3 минуты и 2 секунды
Кстати, развитые фрейворки наверняка такой или похожий подход предоставляют. MFC, например. А общего класса-объекта, конечно, нет, т.к. сильно от среды зависит.

Автор: Alek86 10.1.2008, 10:53
Цитата(Earnest @  9.1.2008,  20:42 Найти цитируемый пост)
А общего класса-объекта, конечно, нет, т.к. сильно от среды зависит.

почему это он зависит от среды?

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

Цитата(archimed7592 @  9.1.2008,  20:06 Найти цитируемый пост)
В общих чертах даже вижу как реализовать такой велосипед на Qt

реализовать-то можно, просто думалось, что уже есть такое....

Добавлено через 5 минут и 37 секунд
Цитата(archimed7592 @  9.1.2008,  20:06 Найти цитируемый пост)
QAction, TAction(VCL) и иже с ними.

не заметил, посмотрим-с....

Автор: archimed7592 10.1.2008, 11:08
Цитата(Alek86 @  10.1.2008,  10:53 Найти цитируемый пост)
почему это он зависит от среды?

От GUI-библиотеки, если быть точнее smile.

Цитата(Alek86 @  10.1.2008,  10:53 Найти цитируемый пост)
что уже есть такое....

Ну, по хорошему, велосипед - это массив экшэнов(Action) с некоторой, минимальной, логикой smile.

Автор: Lazin 10.1.2008, 12:34
Как то городил подобное на VCL, у меня был TActionList и набор TAction. Каждое действие назначалось как минимум одному контролу, а чаще нескольким. Затем у меня возникла необходимость некоторые из них прятать время от времени, для этого я использовал свойство Tag. Для разных категорий действий (TAction) я установил разные значения Tag и потом в коде определял по ним какие TAction когда должны-быть активны, видимы и т.д.
код уродлив но крайне эффективен  smile 
Код

//callback для обработки изменения активн. слоя
void __fastcall TMainForm::LayerChanges(TObject *Sender)
{
    for (int i = 0; i < Alist->ActionCount; i++) {
        TAction * act = dynamic_cast<TAction*>(Alist->Actions[i]);
        switch (Alist->Actions[i]->Tag) {
        case 10:
        act->Visible = (edit->layer_type() == TLayerObj::vector) ||
                        (edit->layer_type() == TLayerObj::advanced);
        break;
        case 20:
        act->Visible = (edit->layer_type() == TLayerObj::raster);
        break;
        }
    }

    StatusBar->Panels->Items[1]->Text = edit->active_layer()->name;
    update_stat_glyph(dynamic_cast<TObject*>(Select));
}

Автор: mes 15.1.2008, 14:32
в обшем виде  я делаю так  так:

В одном месте строю модель UI (без widget'ов).
При изменение своего состояния он вызывает событие на обновление вида.
Событие распределяется по всем widget`ам которые имееют отношение к этой модели

например модель контрола для отправки сообшения в чат CChatTextSender имеет
события "Активен" "Неактивен" "СтрокаОчишена" "ПоявилсяТекст" "ТекстНаОтправку" "ТекстОтправлен"

Например представление состоит из строки, кнопки и возможно еше чего нибудь.
Тогда у всех них вызывается метод UpdateUI_OnChatTextSenderEvent (...);

Итого получается что логика собрана в одном месте, а представление логики в другой. 


P.S Вид диспетчеризации сообшения приведен условно.. Вариантов несколько - но эта другая тема


Автор: Alek86 15.1.2008, 14:54
повторюсь, у меня главная идея была в том, чтобы абстрагироваться от контролов и собрать все это в одном классе smile

Автор: mes 15.1.2008, 17:14
Цитата(Alek86 @  15.1.2008,  14:54 Найти цитируемый пост)
повторюсь, у меня главная идея была в том, чтобы абстрагироваться от контролов и собрать все это в одном классе  


Ну и я про то же...
Вначале забываешь про контролы и составляешь  функциональный объект.
Каждый такой объект может генерировать UpdateUI  с параметрами события.
например так:
Код

class CChatTextSender
{
public:
   void  SetEnable (bool flag);
   void ClearText ();
   void SendText ();
protected:
   void SendUpdateUIEvent(.....);
   
};
class CChatTextView
{
public:
   void OnMsg (Msg)
protected:
   void SendUpdateUIEvent(.....);
};

Потом составляешь форму.  Бросаешь туда контролы.
Каждый контрол подписываешь на события от одного или более функциональнго объекта.
А в в обработку внутренних событий контрола (например нажатие кнопки) бросаешь метод от ф. обьекта.

И того в контралах только вызов функции и изменение вида при каких то определенных событиях (независимо от состояния других) .

то есть контролы выглядит примерно так:
Код

nameespace ChatTextSender 
{

class WButtonControl : public ...
{  ...
public:
   OnUpdateUI (....) ;
   SetPChatTextSender (....) {...};
protected:
   OnButtonPressed () { if (p_Sender)  p_Sender->SetText(); };   
private:
    CChatTextSender * p_Sender;
};

class WTextControl: ..
{
public:
   OnUpdateUI (....) ;
   SetPChatTextSender (....) {...};
protected:
   OnEnterPressed  () { if (p_Sender)  p_Sender->SetText(); };   
private:
    CChatTextSender * p_Sender;
};
};


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

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

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)