Здравствуйте, Lonely Dog, Вы писали:
LD>Правильно ли я понимаю, что в этом случае, в обработчике ON_UPDATE_COMMAND_UI для кнопки OK я должен написать примерно следующее:
LD>Мне бы хотелось бы несколько другого поведения. Например, зачастую кнопка OK должна быть запрещена, пока не будут введены все данные. Следовательно, можно сказать, что кнопку OK мы разрешаем, когда все edit-box диалога содержат какие-то данные. По идее, можно привязать изменения всех полей ввода к установке неких флагов, а потом сказать, что если все флаги равны TRUE, то кнопку OKразрешаем. Моя идея заключалась в том, чтобы выделить те ситуации, когда некие контролы изменяют свое состояние в ответ на изменение состояние неких других контролов диалога. Возможно, для каждой ситуации можно написать некий универсальный кусок кода, который можно будет использовать в дальнейшем.
По сути, мы имеем функцию U: (набор значений V -> набор состояний S контролов). Правильно?
Подход с UpdateCmdUI сводится к тому, что эта функция раскладывается на набор функций u[k] : (V -> состояние контрола s[k]). Эти функции реализуются в обработчиках сообщений WM_UPDATE_COMMAND_UI.
Причём моменты вычисления (и, соответственно, обновления состояний) произвольны. Можно в любой момент сказать, какое сейчас должно быть состояние, вне зависимости от истории. И в любой момент инициировать обновление.
А твоя идея (автомат переходов), как я понимаю, состоит в нахождении инкрементной функции dU: (dV -> dS), раскладываемой по изменениям значений: du[k]: (изменение одного параметра dv[k] -> смена состояний dS).
Сложность здесь в том, что
1) Операции изменения должны образовать коммутативную группу, т.е. правило ромба:
S0 -(dv1)-> S1 -(dv2)-> S12
S0 -(dv2)-> S2 -(dv1)-> S21 // исправил очепятку
S1 == S2
Отдаст ли программист этот долг — зависит от кривизны его рук. Есть риск ошибиться.
2) Одновременное изменение нескольких значений сразу (например, обновление данных по таймеру) нужно или самостоятельно обработать (предусмотрев все комбинации), причём непротиворечиво с элементарными переходами, или же разложить на последовательность элементарных переходов.
В последнем случае есть шанс для мелькания (например, кнопка видима, если включено чётное число чекбоксов).
Так что, ИМХО, проще написать функцию состояния U(V->S) и отрабатывать её в моменты переходов.
Здравствуйте, Lonely Dog, Вы писали:
LD>Привет!
LD>Родилась мысль о том, как можно упростить реализацию GUI, конкретнее, хочется LD>... LD>PS: Есть мысль развить эту идею до уровня библиотеки. Сам я пишу на WTL, т.ч. первая версия будет для нее. В дальнейшем, можно будет сделать и для остальных библиотек (и языков).
Здравствуйте, Lonely Dog, Вы писали:
LD>Родилась мысль о том, как можно упростить реализацию GUI, конкретнее, хочется заставить систему саму отсжеливать состояния контролов. (enabled/disabled, visible, и пр)
Похожую мысль и реализацию родил недавно...
Очень удобно разделять документ от вида.
пример использования:
class CMyDlg :
public CDialogImpl<CMyDlg>,
public CBindingImpl
{
public:
enum { IDD = IDD_MY };
DECLARE_BIND_MAP()
BEGIN_MSG_MAP(CMyDlg)
CHAIN_MSG_MAP(CBindingImpl)
END_MSG_MAP()
CIntProperty m_prop;
};
// Эта карта делает так, что IDC_TEXT1 доступен, когда чекнут IDC_RADIO1 и IDC_TEXT2 доступен, когда чекнут IDC_RADIO2
BEGIN_BIND_MAP(CMyDlg)
BIND_INT_RADIO(IDC_RADIO1, m_prop, 1)
BIND_INT_RADIO(IDC_RADIO2, m_prop, 2)
BIND_INT_ENABLED(IDC_TEXT1, m_prop, 1)
BIND_INT_ENABLED(IDC_TEXT2, m_prop, 2)
END_BIND_MAP()
// Ну и общая концепция:
/*********************************************************************************\
CProperty - базовый класс для всех пропертей
\*********************************************************************************/class CProperty
{
public:
CProperty();
CProperty(const CProperty& prop);
virtual ~CProperty();
CProperty& operator = (const CProperty& prop);
CEvent OnChange;
};
/*********************************************************************************\
CPropertyBind - базовый класс для всех биндов (связь между свойством и элементом управления)
\*********************************************************************************/class CPropertyBind
{
public:
CPropertyBind(HWND hWnd, CProperty& prop) :
m_hWnd(hWnd)
{
m_hander.init(prop.OnChange, &CPropertyBind::OnChangeProp, this);
}
void OnChangeProp(){ OnChangePropImpl(); }
virtual void OnCommand(UINT nCode) = 0;
protected:
virtual void OnChangePropImpl() = 0;
protected:
HWND m_hWnd;
CHandler m_hander;
};
/*********************************************************************************\
CIntProperty - свойство типа int
\*********************************************************************************/class CIntProperty :
public CProperty
{
public:
class CBind : public CPropertyBind
{
public:
CBind(HWND hWnd, CIntProperty& prop) :
CPropertyBind(hWnd, prop),
m_prop(prop),
m_fSelfUpdate(FALSE){}
virtual void OnCommand(UINT nCode);
protected:
virtual void OnChangePropImpl();
protected:
CIntProperty& m_prop;
BOOL m_fSelfUpdate;
};
class CBindRadio : public CPropertyBind
{
public:
CBindRadio(HWND hWnd, CIntProperty& prop, int nFlags) :
CPropertyBind(hWnd, prop),
m_prop(prop),
m_nFlags(nFlags),
m_fSelfUpdate(FALSE){}
virtual void OnCommand(UINT nCode);
protected:
virtual void OnChangePropImpl();
protected:
CIntProperty& m_prop;
int m_nFlags;
BOOL m_fSelfUpdate;
};
class CBindCheck : public CPropertyBind
{
public:
CBindCheck(HWND hWnd, CIntProperty& prop, int nFlags) :
CPropertyBind(hWnd, prop),
m_prop(prop),
m_nFlags(nFlags),
m_fSelfUpdate(FALSE){}
virtual void OnCommand(UINT nCode);
protected:
virtual void OnChangePropImpl();
protected:
CIntProperty& m_prop;
int m_nFlags;
BOOL m_fSelfUpdate;
};
class CBindEnabled : public CPropertyBind
{
public:
CBindEnabled(HWND hWnd, CIntProperty& prop, int nFlags, BOOL fAsFlag = FALSE) :
CPropertyBind(hWnd, prop),
m_prop(prop),
m_nFlags(nFlags),
m_fAsFlag(fAsFlag){}
virtual void OnCommand(UINT nCode);
protected:
virtual void OnChangePropImpl();
protected:
CIntProperty& m_prop;
int m_nFlags;
BOOL m_fAsFlag;
};
CIntProperty(int value = 0) : m_nValue(value){}
operator int() { return m_nValue; }
CIntProperty& operator = (int nValue){ SetValue(nValue); return *this; }
int GetValue()const{ return m_nValue; }
void SetValue(int value){ m_nValue = value; OnChange.raise(); }
private:
int m_nValue;
};
typedef std::multimap<UINT, CPropertyBind*> CPropertyBindMap;
typedef CPropertyBindMap::iterator CPropertyBindIter;
/*********************************************************************************\
CBindingImpl - "связыватель" свойств с контролами (от него наследовать диалог)
не забывать про CHAIN_MSG_MAP(CBindingImpl)
\*********************************************************************************/class CBindingImpl
{
public:
CBindingImpl();
virtual ~CBindingImpl();
void ClearBindings();
BEGIN_MSG_MAP(CBindingImpl)
MESSAGE_HANDLER(WM_COMMAND, OnCommand)
MESSAGE_HANDLER(WM_NOTIFY, OnNotify)
END_MSG_MAP()
private:
// Handler prototypes (uncomment arguments if needed):
// LRESULT MessageHandler(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/);
// LRESULT CommandHandler(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/);
// LRESULT NotifyHandler(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/);
LRESULT OnCommand(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/);
LRESULT OnNotify(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/);
void ProcessCommand(UINT nID, UINT nCode);
protected:
CPropertyBindMap m_mapBindProp;
};
#define DECLARE_BIND_MAP() void InitBinding();
#define BEGIN_BIND_MAP(theClass) \
void theClass::InitBinding() \
{
#define BIND_INT(id, prop) \
m_mapBindProp.insert(std::make_pair(id, new CIntProperty::CBind(GetDlgItem(id), prop))); \
prop.OnChange.raise();
#define BIND_INT_RADIO(id, prop, flags) \
m_mapBindProp.insert(std::make_pair(id, new CIntProperty::CBindRadio(GetDlgItem(id), prop, flags)));\
prop.OnChange.raise();
#define BIND_INT_CHECK(id, prop, flags) \
m_mapBindProp.insert(std::make_pair(id, new CIntProperty::CBindCheck(GetDlgItem(id), prop, flags))); \
prop.OnChange.raise();
#define BIND_INT_ENABLED(id, prop, flags) \
m_mapBindProp.insert(std::make_pair(id, new CIntProperty::CBindEnabled(GetDlgItem(id), prop, flags))); \
prop.OnChange.raise();
#define BIND_INT_ENABLED_FLAG(id, prop, flags) \
m_mapBindProp.insert(std::make_pair(id, new CIntProperty::CBindEnabled(GetDlgItem(id), prop, flags, TRUE))); \
prop.OnChange.raise();
Если интересно, могу более подробно все расписать...
Или же соорудить простенький проектик с использованием всего этого...
Здравствуйте, Lonely Dog, Вы писали:
LD>Здравствуйте, Miroff, Вы писали:
M>>Таблицы решений, например. Для простых диалогов неплохо подходят, для сложных применять не доводилось. LD>А не могли бы вы рассказать, что это такое? Или где про это можно почитать?
Здравствуйте, Аноним, Вы писали:
А>Здравствуйте, Lonely Dog, Вы писали:
А>В итоге все выльется в ON_UPDATE_COMMAND_UI или аналоги, которые универсальные, А>но все равно требуют ручной записи условий. А>Проблема в том, что условия то как раз совсем не уверсальные. А>Для кнопки OK при вводе пароля нужны другие условия, чем для той же кнопки на другом диалоге.
Резонно и серьезно, настолько , что может похоронить идею на корню. Лучше начать с проработки условий и способов их простого задания. Например установления связей между контролами и определения их характеристик типа "когда первый пустой — второй disabled". "когда первый равен второму — третий показывает prompt1" — Что-то сложно получается.
В итоге все выльется в ON_UPDATE_COMMAND_UI или аналоги, которые универсальные,
но все равно требуют ручной записи условий.
Проблема в том, что условия то как раз совсем не уверсальные.
Для кнопки OK при вводе пароля нужны другие условия, чем для той же кнопки на другом диалоге.
Здравствуйте, Lonely Dog, Вы писали:
LD>Привет!
LD>Родилась мысль о том, как можно упростить реализацию GUI, конкретнее, хочется заставить систему саму отсжеливать состояния контролов. (enabled/disabled, visible, и пр) LD>Поясню на примере: пусть у нас есть диалог смены пароля. В этом диалоге есть поле ввода нового пароля и поле его подтверждения. LD>Кроме того, на диалоге есть кнопки OK и Cancel. Если поле вводе нового пароля пустое, кнопка OK должна быть в состоянии disabled. Если строка введенная в поле ввода нового пароля не равна строке в поле подтверждения, то OK также должна быть в состоянии disabed. Кроме того, пусть на диалоге будет текстовое поле с подсказкой. Если поле нового пароля пустое, подсказка будет отображать строку "пароль не может быть пустым". Если новый пароль и его подтверждение не совпадают, подсказка будет отображать строку "пароли не совпадают." Хочется описывать большую часть этого поведения декларативно, на уровне некой карты поведения диалога. (как карта сообщений в WTL или MFC.) Не очень понятно, как это сделать. Пока я думаю в сторону конечных автоматов, но возможно вы сможете подсказать что-нибудь еще?
А я думаю, что КА для мало-мальски сложного диалога быстро приведут в тупик. Причины уже приведены. Из одного требования коммутативности реакций на действия пользователя сразу получается, что состояние зависимых контролов описывается не последовательностью изменений в управляющих контролах, а просто некоторой функцией от самого этого состояния.
Так что более надежным подходом скорее будет являться функциональный. Т.е. вместо карты переходов разрешаем ассоциировать свойства Enabled и Visible с некоторыми предикатами, которые однозначно вычисляются на основе свойств контролов диалога (и, может быть, чего-то еще — к примеру, настроек приложения или текущего состояния document в модели MVC).
У этого подхода тоже есть одна грабля: выражения должны гарантировать отсутствие циклов. Иначе набор этих предикатов может стать вырожденной системой уравнений, у которой либо больше одного решения, либо нет ни одного.
Можно вообще запретить использовать в предикатах свойства Enabled и Visible, а применять предикаты — только к этим двум свойствам.
Можно сделать детектирование циклов а-ля Excel.
1.1.4 stable rev. 510
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Родилась мысль о том, как можно упростить реализацию GUI, конкретнее, хочется заставить систему саму отсжеливать состояния контролов. (enabled/disabled, visible, и пр)
Поясню на примере: пусть у нас есть диалог смены пароля. В этом диалоге есть поле ввода нового пароля и поле его подтверждения.
Кроме того, на диалоге есть кнопки OK и Cancel. Если поле вводе нового пароля пустое, кнопка OK должна быть в состоянии disabled. Если строка введенная в поле ввода нового пароля не равна строке в поле подтверждения, то OK также должна быть в состоянии disabed. Кроме того, пусть на диалоге будет текстовое поле с подсказкой. Если поле нового пароля пустое, подсказка будет отображать строку "пароль не может быть пустым". Если новый пароль и его подтверждение не совпадают, подсказка будет отображать строку "пароли не совпадают." Хочется описывать большую часть этого поведения декларативно, на уровне некой карты поведения диалога. (как карта сообщений в WTL или MFC.) Не очень понятно, как это сделать. Пока я думаю в сторону конечных автоматов, но возможно вы сможете подсказать что-нибудь еще?
Заранее спасибо.
PS: Есть мысль развить эту идею до уровня библиотеки. Сам я пишу на WTL, т.ч. первая версия будет для нее. В дальнейшем, можно будет сделать и для остальных библиотек (и языков).
Здравствуйте, Lonely Dog, Вы писали:
LD>Кроме того, на диалоге есть кнопки OK и Cancel. Если поле вводе нового пароля пустое, кнопка OK должна быть в состоянии disabled. Если строка введенная в поле ввода нового пароля не равна строке в поле подтверждения, то OK также должна быть в состоянии disabed. Кроме того, пусть на диалоге будет текстовое поле с подсказкой. Если поле нового пароля пустое, подсказка будет отображать строку "пароль не может быть пустым". Если новый пароль и его подтверждение не совпадают, подсказка будет отображать строку "пароли не совпадают." Хочется описывать большую часть этого поведения декларативно, на уровне некой карты поведения диалога. (как карта сообщений в WTL или MFC.) Не очень понятно, как это сделать. Пока я думаю в сторону конечных автоматов, но возможно вы сможете подсказать что-нибудь еще?
Таблицы решений, например. Для простых диалогов неплохо подходят, для сложных применять не доводилось.
Здравствуйте, Lonely Dog, Вы писали:
LD>Привет!
LD>Родилась мысль о том, как можно упростить реализацию GUI, конкретнее, хочется заставить систему саму отсжеливать состояния контролов. (enabled/disabled, visible, и пр) LD>Поясню на примере: пусть у нас есть диалог смены пароля. В этом диалоге есть поле ввода нового пароля и поле его подтверждения. LD>Кроме того, на диалоге есть кнопки OK и Cancel. Если поле вводе нового пароля пустое, кнопка OK должна быть в состоянии disabled. Если строка введенная в поле ввода нового пароля не равна строке в поле подтверждения, то OK также должна быть в состоянии disabed. Кроме того, пусть на диалоге будет текстовое поле с подсказкой. Если поле нового пароля пустое, подсказка будет отображать строку "пароль не может быть пустым". Если новый пароль и его подтверждение не совпадают, подсказка будет отображать строку "пароли не совпадают." Хочется описывать большую часть этого поведения декларативно, на уровне некой карты поведения диалога. (как карта сообщений в WTL или MFC.) Не очень понятно, как это сделать. Пока я думаю в сторону конечных автоматов, но возможно вы сможете подсказать что-нибудь еще?
LD>Заранее спасибо.
LD>PS: Есть мысль развить эту идею до уровня библиотеки. Сам я пишу на WTL, т.ч. первая версия будет для нее. В дальнейшем, можно будет сделать и для остальных библиотек (и языков).
1. Думаю, Ваш пример для автомата с одним состоянием (состояния controls я считаю внешними воздействиями).
2. Есть опасность появления циклов в графе состояний автомата. Это может привести к зависанию.
Здравствуйте, Lonely Dog, Вы писали:
LD>Родилась мысль о том, как можно упростить реализацию GUI, конкретнее, хочется заставить систему саму отсжеливать состояния контролов. (enabled/disabled, visible, и пр)
В MFC для тулбаров (в том числе с произвольными контролами) и меню эта штука уже существует — смотри ON_UPDATE_COMMAND_UI.
Теоретически, можно прикрутить его и к диалогам. (Не пробовал).
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, Lonely Dog, Вы писали:
К>В MFC для тулбаров (в том числе с произвольными контролами) и меню эта штука уже существует — смотри ON_UPDATE_COMMAND_UI. К>Теоретически, можно прикрутить его и к диалогам. (Не пробовал).
Правильно ли я понимаю, что в этом случае, в обработчике ON_UPDATE_COMMAND_UI для кнопки OK я должен написать примерно следующее:
if (strNewPassword.GetLength() && (strNewPassword == strNewPasswordConfirm))
wndOK.EnableWindow(TRUE);
else
wndOK.EnableWindow(FALSE);
Мне бы хотелось бы несколько другого поведения. Например, зачастую кнопка OK должна быть запрещена, пока не будут введены все данные. Следовательно, можно сказать, что кнопку OK мы разрешаем, когда все edit-box диалога содержат какие-то данные. По идее, можно привязать изменения всех полей ввода к установке неких флагов, а потом сказать, что если все флаги равны TRUE, то кнопку OKразрешаем. Моя идея заключалась в том, чтобы выделить те ситуации, когда некие контролы изменяют свое состояние в ответ на изменение состояние неких других контролов диалога. Возможно, для каждой ситуации можно написать некий универсальный кусок кода, который можно будет использовать в дальнейшем.
Здравствуйте, Sergey640, Вы писали:
S>1. Думаю, Ваш пример для автомата с одним состоянием (состояния controls я считаю внешними воздействиями).
Почему? Насколько я понимаю, есть три состояния:
1. пароль не введен
2. пароль введен, но не равен подтверждению.
3. пароль введен и равен подтверждению.
В первом и втором состоянии кнопка OK должна быть запрещена. В третьем разрешена.
S>2. Есть опасность появления циклов в графе состояний автомата. Это может привести к зависанию.
Да, наверное вы правы. Это надо будет как-то учитывать.
Здравствуйте, Miroff, Вы писали:
M>Таблицы решений, например. Для простых диалогов неплохо подходят, для сложных применять не доводилось.
А не могли бы вы рассказать, что это такое? Или где про это можно почитать?
Здравствуйте, Lonely Dog, Вы писали:
LD>Здравствуйте, Sergey640, Вы писали:
S>>1. Думаю, Ваш пример для автомата с одним состоянием (состояния controls я считаю внешними воздействиями). LD>Почему? Насколько я понимаю, есть три состояния: LD>1. пароль не введен LD>2. пароль введен, но не равен подтверждению. LD>3. пароль введен и равен подтверждению. LD>В первом и втором состоянии кнопка OK должна быть запрещена. В третьем разрешена.
Для автомата Мура — правда Ваша , для автомата Мили — моя.
Все дело в реализации. Флажок "Disabled" я склонен рассматривать как внешний, т.е. Какая-то ON_CHANGE function вызывается , проверяет состояние Controls устанавливает флажок и все. Дополнительных переменных состояния нет. Функция оценки и изменения флажка — одна для всех. Мне так кажется.
Автомат Мура будет дублировать в своих состояниях состояние флажка: добавляется переменная состояния, появляются функции переходов — это более громоздко для данного примера. Но для более сложных случаев, когда требуется помнить историю действий, это возможно и подойдет.
Правда автомат типа Мили обычно всегда меньше — я исходил из этого.
Здравствуйте, Sergey640, Вы писали:
S>Резонно и серьезно, настолько , что может похоронить идею на корню. Лучше начать с проработки условий и способов их простого задания.
Вот-вот. Именно это я и хочу сделать.
Это не принципиально. Я предпочитаю в случае сложных условий использовать if/else.
LD>>Мне бы хотелось бы несколько другого поведения. Например, зачастую кнопка OK должна быть запрещена, пока не будут введены все данные. Следовательно, можно сказать, что кнопку OK мы разрешаем, когда все edit-box диалога содержат какие-то данные. По идее, можно привязать изменения всех полей ввода к установке неких флагов, а потом сказать, что если все флаги равны TRUE, то кнопку OKразрешаем. Моя идея заключалась в том, чтобы выделить те ситуации, когда некие контролы изменяют свое состояние в ответ на изменение состояние неких других контролов диалога. Возможно, для каждой ситуации можно написать некий универсальный кусок кода, который можно будет использовать в дальнейшем.
К>По сути, мы имеем функцию U: (набор значений V -> набор состояний S контролов). Правильно?
К>Подход с UpdateCmdUI сводится к тому, что эта функция раскладывается на набор функций u[k] : (V -> состояние контрола s[k]). Эти функции реализуются в обработчиках сообщений WM_UPDATE_COMMAND_UI. К>Причём моменты вычисления (и, соответственно, обновления состояний) произвольны. Можно в любой момент сказать, какое сейчас должно быть состояние, вне зависимости от истории. И в любой момент инициировать обновление.
Да, логично.
К>А твоя идея (автомат переходов), как я понимаю, состоит в нахождении инкрементной функции dU: (dV -> dS), раскладываемой по изменениям значений: du[k]: (изменение одного параметра dv[k] -> смена состояний dS).
Класс!!!. Очень точная формулировка.
К>Сложность здесь в том, что К>1) Операции изменения должны образовать коммутативную группу, т.е. правило ромба: К>S0 -(dv1)-> S1 -(dv2)-> S12 К>S0 -(dv2)-> S2 -(dv1)-> S21 // исправил очепятку К>S1 == S2 К>Отдаст ли программист этот долг — зависит от кривизны его рук. Есть риск ошибиться.
Не очень понял, что здесь имеется ввиду. Ты имеешь ввиду, что независимо от порядка применения правил, результат должен быть один и тот же? К>2) Одновременное изменение нескольких значений сразу (например, обновление данных по таймеру) нужно или самостоятельно обработать (предусмотрев все комбинации), причём непротиворечиво с элементарными переходами, или же разложить на последовательность элементарных переходов. К>В последнем случае есть шанс для мелькания (например, кнопка видима, если включено чётное число чекбоксов).
Я что-то не помню, чтобы мне приходилось писать что-то подобное. Но конечно, в этом случае, надо будет что-то придумать.
Здравствуйте, Lonely Dog, Вы писали:
К>>Сложность здесь в том, что К>>1) Операции изменения должны образовать коммутативную группу, т.е. правило ромба: К>>S0 -(dv1)-> S1 -(dv2)-> S12 К>>S0 -(dv2)-> S2 -(dv1)-> S21 // исправил очепятку К>>S1 == S2 К>>Отдаст ли программист этот долг — зависит от кривизны его рук. Есть риск ошибиться.
LD>Не очень понял, что здесь имеется ввиду. Ты имеешь ввиду, что независимо от порядка применения правил, результат должен быть один и тот же?
Да. Результат должен быть один, даже не потому, что "группа операций бла-бла-бла", а потому, что это визивиг.
Пользователь может не помнить истории, для него состояние контролов должно непротиворечиво вытекать из того, что он прямо сейчас видит. Из чего уже и следует коммутативность...
К>>2) Одновременное изменение нескольких значений сразу (например, обновление данных по таймеру) нужно или самостоятельно обработать (предусмотрев все комбинации), причём непротиворечиво с элементарными переходами, или же разложить на последовательность элементарных переходов. К>>В последнем случае есть шанс для мелькания (например, кнопка видима, если включено чётное число чекбоксов).
LD>Я что-то не помню, чтобы мне приходилось писать что-то подобное. Но конечно, в этом случае, надо будет что-то придумать.
Родилась еще одна мысль по поводу представления карты поведения диалога. Можно вместо конечного автомата диалога указывать набор его инвариантов. Т.е., вместо постулирования "когда в текстовое поле будет что-то введено, кнопка OK должна стать Enabled.", мы говорим, что "Если в поле ничего не введено, то OK должна быть Disabled." В этом случае, можно ввести отношения "Если условие X, то контрол Y должен быть в состоянии Z" или "Контрол Y находится в состоянии Z тогда и только тогда когда условие X выполнено."
Далее останется написать только набор стандартных условий вида "Поле пусто", "В списке что-то выбрано", и набор функций, меняющих состояние объекта. Как вам такая формулировка?
Здравствуйте, Lonely Dog, Вы писали:
LD>Здравствуйте, Lonely Dog, Вы писали:
LD>Родилась еще одна мысль по поводу представления карты поведения диалога. Можно вместо конечного автомата диалога указывать набор его инвариантов. Т.е., вместо постулирования "когда в текстовое поле будет что-то введено, кнопка OK должна стать Enabled.", мы говорим, что "Если в поле ничего не введено, то OK должна быть Disabled." В этом случае, можно ввести отношения "Если условие X, то контрол Y должен быть в состоянии Z" или "Контрол Y находится в состоянии Z тогда и только тогда когда условие X выполнено." LD>Далее останется написать только набор стандартных условий вида "Поле пусто", "В списке что-то выбрано", и набор функций, меняющих состояние объекта. Как вам такая формулировка?
"Когда" — это переходы. "Если" — функции.
Ну а выбрать прямые или инверсные предикаты ("enabled если текст непуст" vs. "disabled если текст пуст") — дело хозяйское.
В одной GUI-библиотеке (Zinc) у контролов было свойство Disabled, по умолчанию false. Там удобнее было задавать условия от противного.
Здравствуйте, Vector, Вы писали:
V>Если интересно, могу более подробно все расписать... V>Или же соорудить простенький проектик с использованием всего этого...
Пока не очень понял принцип работы, но выглядит хорошо. Примерно тоже самое я и хотел сделать.
Если есть возможность расписать более подробно, то сделай это плиз.
Заранее спасибо.
Здравствуйте, Lonely Dog, Вы писали:
LD>Здравствуйте, Vector, Вы писали:
V>>Если интересно, могу более подробно все расписать... V>>Или же соорудить простенький проектик с использованием всего этого... LD>Пока не очень понял принцип работы, но выглядит хорошо. Примерно тоже самое я и хотел сделать. LD>Если есть возможность расписать более подробно, то сделай это плиз. LD>Заранее спасибо.
Вобщем идея в том, чтобы использовать вместо простых типов классы, отнаследованные от CProperty, у которого есть событие OnChange.
У конкретных пропертей (CStringProperty, CIntProperty, CSystemTimeProperty...) Есть Get-теры и Set-теры, а так же преобразование к ссылке на тип, который оно заменяет, вобщем, ведут они себя как те типы, которые они "оборачивают", только кидаются OnChange-ем при изменении.
Дальше — есть базовый классик CPropertyBind — обеспечивает связь CProperty с элементом управления. CPropertyBind будет уведомляться от окна о событиях WM_COMMAND и WM_NOTIFY от контрола (будет вызываться его абстрактный метод OnCommand), так же он подписывается на OnChange у Property и обновляет состояние элемента управления.
Ну, и есть класс CBindingImpl, у которого есть std::multimap<UINT, CPropertyBind*> m_mapBindProp и MSG_MAP, который ловит WM_COMMAND и WM_NOTIFY и уведомляет о них Bind-ы(В multimap-е ключом является ID-шник элемента управления).
Теперь тупо реаизовываются всякие разные Property, мне хватило Int, String, SystemTime, и для каждого из них различные Bind-ы, к примеру для IntProperty есть:
CBind — помещает число в контрол (SetDlgItemInt, GetDlgItemInt)
CBindRadio — содержит значение, который нужно запихнуть в IntProperty, если RadioButton сделают "выбранным"
CBindCheck — содержит флаг, который нужно запихнуть(убрать) в IntProperty, если CheckBox чекнут(анчекнут)
CBindEnabled — содержит значение(или флаг), котрое должно содержать свойство, чтобы контрол за-Enable-лся.
Этого уже достаточно, чтобы рулить GUI, в котором много переключателей и от каждого из них зависит доступность разных контролов(именно для этого, в первую очередь, я это все и затеял)
Да, еще всякие макросы для BIND-а, это понятно, для удобства описания.
здесь лежит тестовый проектик. Идея реализована совсем недавно, поэтому, думаю, что еще буду додумывать, буду рад услышать предложения, мысли и все такое
Re: Мысль о GUI
От:
Аноним
Дата:
22.06.06 20:48
Оценка:
Здравствуйте, Lonely Dog, Вы писали:
LD>Привет!
LD>Родилась мысль о том, как можно упростить реализацию GUI, конкретнее, хочется заставить систему саму отсжеливать состояния контролов. (enabled/disabled, visible, и пр) LD>Поясню на примере: пусть у нас есть диалог смены пароля. В этом диалоге есть поле ввода нового пароля и поле его подтверждения. LD>Кроме того, на диалоге есть кнопки OK и Cancel. Если поле вводе нового пароля пустое, кнопка OK должна быть в состоянии disabled. Если строка введенная в поле ввода нового пароля не равна строке в поле подтверждения, то OK также должна быть в состоянии disabed. Кроме того, пусть на диалоге будет текстовое поле с подсказкой. Если поле нового пароля пустое, подсказка будет отображать строку "пароль не может быть пустым". Если новый пароль и его подтверждение не совпадают, подсказка будет отображать строку "пароли не совпадают." Хочется описывать большую часть этого поведения декларативно, на уровне некой карты поведения диалога. (как карта сообщений в WTL или MFC.) Не очень понятно, как это сделать. Пока я думаю в сторону конечных автоматов, но возможно вы сможете подсказать что-нибудь еще?
LD>Заранее спасибо.
LD>PS: Есть мысль развить эту идею до уровня библиотеки. Сам я пишу на WTL, т.ч. первая версия будет для нее. В дальнейшем, можно будет сделать и для остальных библиотек (и языков).
Возможно с точки зрения организации классов можно использовать паттерн Меdiator, описанный в книге Эриха Гаммы с сотоварищами, про паттерны проектирования.
Здравствуйте, Vector, Вы писали:
V>здесь лежит тестовый проектик. Идея реализована совсем недавно, поэтому, думаю, что еще буду додумывать, буду рад услышать предложения, мысли и все такое
Вот моя мысль.
Мне не нравится это:
class CIntProperty : public CProperty
{
//...private:
int m_nValue;
};
Почему класс, который является чем-то вспомогательным для диалога, ответственнен за хранение данных?
Я бы сделал возможность принимать различные данные из вне, причём что бы это можно было расширить для своих классов с помощью шаблонов/динамического полиморфизма. Самый простой вариант для простого int:
class CIntProperty : public CProperty
{
public:
CIntProperty(int& value)
: m_nValue(value)
{}
//...private:
int& m_nValue;
};
Что бы я мог расширить так:
class MyIntProperty : public CIntProperty
{
public:
CIntProperty(IntStorageThatAlreadyUsedInWholeProject& value)
: m_nValue(value)
{}
//...private:
IntStorageThatAlreadyUsedInWholeProject& m_nValue;
};
IntStorageThatAlreadyUsedInWholeProject должен реализовывать интерфейс типа:
Тогда я смогу заюзать MyIntProperty, причём он не будет навязывать мне некасающихся его проектных решений.
Возможна функциональность вплоть до того, что IntStorageThatAlreadyUsedInWholeProject будет меняться другим потоком и через механизм подписки на события изменения автоматически обновляться в интерфейсе. С обычным int ты такого не сделаешь.
Здравствуйте, remark, Вы писали:
R>Здравствуйте, Vector, Вы писали:
V>>здесь лежит тестовый проектик. Идея реализована совсем недавно, поэтому, думаю, что еще буду додумывать, буду рад услышать предложения, мысли и все такое
Вот ещё одна мысль.
Нужны функции-преобразователи из внутреннего представления во внешнее и обратно.
Например, внутри программы временные интервалы хранятся в миллисекундых, а в диалоге они должны выводится в секундах.
В принципе даже тип внутреннего представления и внешнего могут не совпадать. Например, внутри программы хранится int/enum/ещё_что_то, а в диалог должна выводится строка.
R>
Здравствуйте, remark, Вы писали:
R>Вот моя мысль. R>Мне не нравится это:
R>Почему класс, который является чем-то вспомогательным для диалога, ответственнен за хранение данных?
R>Я бы сделал возможность принимать различные данные из вне, причём что бы это можно было расширить для своих классов с помощью шаблонов/динамического полиморфизма. Самый простой вариант для простого int:
Класс CIntProperty не является вспомогательным для диалога. Он является оберткой над простым типом, и занимается уведомлением об изменении значения. Я эти самые SomeProperty выношу в класс документа, а не диалога/окна. Можно конечно не делать оберток над типами, но тогда нужно вручную вызывать что-то типа RefreshView, а я человек ленивый и писать это постоянно не хочу
Здравствуйте, remark, Вы писали:
R>Вот ещё одна мысль. R>Нужны функции-преобразователи из внутреннего представления во внешнее и обратно. R>Например, внутри программы временные интервалы хранятся в миллисекундых, а в диалоге они должны выводится в секундах.
R>В принципе даже тип внутреннего представления и внешнего могут не совпадать. Например, внутри программы хранится int/enum/ещё_что_то, а в диалог должна выводится строка.
Это можно решить написанием своего Bind класса, и биндить свойство через него. Конечно, не удобно, что все в ручную, но, думаю, что универсализма в данном случае будет проблематично добиться, и нужен ли он тут. Строку вместо enum нужно отображать обычно в ComboBox, для этого у меня есть CBindCombo. В комбик заливаются нужные строчки и проставляется у каждой строчки соответствующая Data (SetItemData), и все получается так как хотелось. Разве что каждый комбик заполняется отдельно, независимо от Property. Вобщем, не очень удобно, согласен, но жить с этим можно.
Re: Мысль о GUI
От:
Аноним
Дата:
24.07.06 08:07
Оценка:
Здравствуйте, Lonely Dog, Вы писали:
LD>Привет!
LD>Родилась мысль о том, как можно упростить реализацию GUI, конкретнее, хочется заставить систему саму отсжеливать состояния контролов. (enabled/disabled, visible, и пр)
Здравствуйте, Кодт, Вы писали:
К>В одной GUI-библиотеке (Zinc) у контролов было свойство Disabled, по умолчанию false. Там удобнее было задавать условия от противного.
Кстати вот да, простое и отличное решение не позволяющее запинать реализацию корректного свойства на потом.
При использовании MVC или какого либо похожего варианта (например можно лениво совместить все в одном модуле) нам не требуется ВООБЩЕ как то особо прописывать отношения между контролами.
Мы пишем набор предикатов ручками , например update кнопки OK вызывает прописанный условие isVisible() в ктором мы кодим конкретную инфу.
Теперь когда юзер набирает пароль, мы получаем ON_CHANGE на поле ввода , и логично было бы там вызвать некий updateOKButton , но это грубейшая ОШИБКА. Единственное что он должен делать это запланировать Update всей формы.
А вот Update всей формы пробегает по контролам и все они меняют состояние на соответствующие. А создавать отдельный язык для предикатов контролов , кажется слишком неблагодарное занятие, слишком много вариантов.
P.S. задача этого посат не описать идеальную архитектуру а показать стандартные ошибки начинающих GUI строителей. Опять я не знаю насколько знаком автор поста с MVC , если знаком то прошу прощения за мой пример , может он послужит другим.