Программа реализует набор комманд, доступных через элементы меню.
(например File.Open File.New File.Close File.Save ...) Число комманд достаточно большое ~100
При этом при активации отдельных элементов возможно изменение состояния других элементов.
Например если вы выполнили File.Close для единственного открытого документа, то File.Save становится
недоступным. Зависимости могут быть достаточно сложными.
Не существует ли для такой задачи какого-нибудь стандартного решения?
Приоритетом при выборе вариантов решений является возможность не сложного
изменениния структуры программы при добавлении дополнительных комманд и
зависимостей между ними. В книге Эриха Гаммы про паттерны описывается
паттерн Медиатор, но там же написано что он эффективность его применения быстро уменьшается
при увеличении числа зависящих друг от другка элементов. У меня как раз такой случай.
Буду признателен за любые идеи.
Здравствуйте, Аноним, Вы писали:
А>Программа реализует набор комманд, доступных через элементы меню. А>(например File.Open File.New File.Close File.Save ...) Число комманд достаточно большое ~100 А>При этом при активации отдельных элементов возможно изменение состояния других элементов.
Во-первых, идея, чтоб состояние одного пункта меню зависело от состояния другого пункта меню — архисомнительная. Навесить себе тут циклическую зависимость — как два байта переслать.
Во-вторых, есть надёжный и простой способ обновления — обновлять их просто по таймеру. При этом, правда, приходится заботиться о том, чтобы в начале каждого обработчика команды был дубликат проверки. Но об этом я бы позаботился независимо от механизма обновлений — просто для надёжности.
--
wbr, Peter Taran
Re[2]: Система доступных элементов меню в программе
Здравствуйте, tarkil, Вы писали:
А>>Программа реализует набор комманд, доступных через элементы меню. А>>(например File.Open File.New File.Close File.Save ...) Число комманд достаточно большое ~100 А>>При этом при активации отдельных элементов возможно изменение состояния других элементов. T>Во-первых, идея, чтоб состояние одного пункта меню зависело от состояния другого пункта меню — архисомнительная. Навесить себе тут циклическую зависимость — как два байта переслать.
Согласен.
T>Во-вторых, есть надёжный и простой способ обновления — обновлять их просто по таймеру. При этом, правда, приходится заботиться о том, чтобы в начале каждого обработчика команды был дубликат проверки. Но об этом я бы позаботился независимо от механизма обновлений — просто для надёжности.
Это по-моему тоже не самый нормальный способ. На мой взгляд правельней делать примерно следующим образом:
1. Заводим класс отвечающий за состояние системы, сгребаем туда всё относящееся к текущему состоянию + события на изменение всего чего туда надобавляли.
2. Команды подписываются на события и при наступлении события изменяют свое состояние.
ЗЫ. Немного запутано наверно, но если надо можно на конкретном примере разобрать.
Re[2]: Система доступных элементов меню в программе
От:
Аноним
Дата:
28.06.06 17:57
Оценка:
Здравствуйте, tarkil, Вы писали:
T>Во-вторых, есть надёжный и простой способ обновления — обновлять их просто по таймеру.
ИМХО, стрелять надо за такое
Такая чудо-проверка элементарно обходится с помощью быстрых кликов проворного юзера. Он успеет вклиниться между квантами таймера и вызвать то, что реально должно быть запрещено.
T> При этом, правда, приходится заботиться о том, чтобы в начале каждого обработчика команды был дубликат проверки.
И потом идут жалобы пользователей: "почему разрешенная команда не работает, а при следующей попытке делается запрещенной?"
Нет, не наш выбор.
Re[3]: Система доступных элементов меню в программе
Здравствуйте, Аноним, Вы писали:
А>Такая чудо-проверка элементарно обходится с помощью быстрых кликов проворного юзера. Он успеет вклиниться между квантами таймера и вызвать то, что реально должно быть запрещено.
При этом, правда, приходится заботиться о том, чтобы в начале каждого обработчика команды был дубликат проверки.
А>И потом идут жалобы пользователей: "почему разрешенная команда не работает, а при следующей попытке делается запрещенной?"
Собственно, мы ещё ни одной жалобы не получили. И именно так устроены обновления енабленности тулбарных кнопок и в MFC и практически у всех MS-продуктов.
Здравствуйте, tarkil, Вы писали:
T>И именно так устроены обновления енабленности тулбарных кнопок и в MFC и практически у всех MS-продуктов.
В MFC состояние пунктов меню изменяется по WM_INITMENUPOPUP в зависимости от состояния документа. Это сообщение приходит после активации соответствующего popup-меню, но перед его отображением.
Состояние кнопок тулбара изменяется по OnIdle. Этот обработчик вызывается, когда в очереди потока нет сообщений.
Здравствуйте, Кирилл Лебедев, Вы писали:
КЛ>Состояние кнопок тулбара изменяется по OnIdle. Этот обработчик вызывается, когда в очереди потока нет сообщений.
И где противоречие с написанным мной? Что не по WM_TIMER? Так не в этом суть — суть в том, что состояние перевычисляется в произвольные моменты времени с определённой периодичностью.
--
wbr, Peter Taran
Re[6]: Система доступных элементов меню в программе
Здравствуйте, tarkil, Вы писали:
T>И где противоречие с написанным мной? Что не по WM_TIMER? Так не в этом суть — суть в том, что состояние перевычисляется в произвольные моменты времени с определённой периодичностью.
Процитирую себя еще раз:"В MFC состояние пунктов меню изменяется по WM_INITMENUPOPUP в зависимости от состояния документа. Это сообщение приходит после активации соответствующего popup-меню, но перед его отображением". Момент времени, выбранный для обновления меню, — отнюдь не произвольный.
Что касается основного вопроса, обсуждаемого в этой теме, то хочу отметить следующее. Изменять состояние одних пунктов меню в зависимости от состояния других пунктов меню — неправильно. Пункты меню должны изменять свое состояние в зависимости от состояния документа. При помощи команд, поступающих, в том числе, и от меню пользователь может изменять состояние документа. В свою очередь, пункты меню меняют свое состояние в зависимости от состояния документа. Мне кажется, что именно в этом подходе и заключается решение задачи.
Действительно, если состояние пунктов меню зависит от состояния документа, то их (пункты меню) можно обновлять в любое удобное время. Например, по WM_INITMENUPOPUP, что и делается в MFC.
Здравствуйте, Кирилл Лебедев, Вы писали:
КЛ>Процитирую себя еще раз:"В MFC состояние пунктов меню изменяется по WM_INITMENUPOPUP в зависимости от состояния документа. Это сообщение приходит после активации соответствующего popup-меню, но перед его отображением". Момент времени, выбранный для обновления меню, — отнюдь не произвольный.
Самоцитирование это хорошо. Это возвеличивает.
Но я-то собственно, про OnIdle. Что, есть уже какое-то работающее приложение написанное в расчёте на то, что оно прилетит в конкретный момент времени? Ню-ню. Я всегда пишу в предположении, что это может случиться (почти) когда угодно. Всем остальным рекомендую то же самое.
Команды, меню, тулбары, — все они IContextual. Главное окно — IContextProvider, но оно просто берёт свойства у редакторов, редактируемых элементов и других вещей (это долгая история), плюс добавляет пару своих. Соответственно, при каждом изменении глобального контекста (контекста главного окна) для каждой команды вызывается MatchContext. В зависимости от того, вписывается команда в контекст или нет иконки в тулабрах и в меню будут Enabled/Disabled. То же самое верно и для элементов меню и тулбаров (фактически содержащих ссылки на команды), только в случае невписывания в контекст соответствующая иконка не будет вообще показана.
Есть стандартная реализация команд, меню, тулбаров — они грузятся из специального xml-файла. Там кроме того есть раздел <context-mapping> где можно задавать для каждой команды набор условий — контекстных свойств.
Механизм оказался очень удобным, т.к. очень многое делается декларативно. Кроме того, я не сказал о других применениях подхода, например, организация контекстных меню, показ/скрытие докающихся окон и т.д. Кстати, приложение модульное, и такой подход позволяет расширять имеющуюся функциональность за минимум действий.
ЗВ книге Эриха Гаммы про паттерны описывается А>паттерн Медиатор, но там же написано что он эффективность его применения быстро уменьшается А>при увеличении числа зависящих друг от другка элементов. У меня как раз такой случай. А>Буду признателен за любые идеи.
А чем не подходит Observer?
Делаешь Subject класс, каждый пункт меню регистрируется у него как Observer, и получает callback "stateChanged" на любое изменение состояния. Ну а обработчик stateChanged для каждого Observer уже запрашивает необходимы данные у Subject-а.
class DocumentCloseMenuItem : public Observer
{
void stateChanged(){
setEnabled(Application.getActiveDocument() != 0)
}
};
class DocumentSaveMenuItem : public Observer
{
void stateChanged(){
setEnabled(Application.getActiveDocument() != 0 && Application.getActiveDocument()->isDirty())
}
};
Дальше уже оптимизация. Что бы на каждый чих не дёргать stateChanged у 10000 Observer-ов, можно отложить вызов некоторых Observer::stateChanged на "потом". Например, когда придёт время показать меню пользователю. Возможно, что state приложение сменится 10 раз, а stateChanged вызовется только 1 — перед показом.
Так же можно помечать Observer-ы, у которых ещё не вызывался stateChanged и вызывать по одному в onIdle.
При таких мерах оптимизации можно в любой функции помечать state как изменённый, не задумываясь — а не делает ли то же самое какая-то из тех функций, что ты вызываешь.
Ну и ещё можно сделать несколько Subject-ов. Например, ActiveDocumentSubject (для save/close), ActiveViewSubject (next/prev), ActiveWindow(copy/paste/find) и т.д.
Надеюсь не очень скомканно объяснил?
... << RSDN@Home 1.1.4 stable rev. 510>>
Re[2]: Система доступных элементов меню в программе
Здравствуйте, konsoletyper, Вы писали:
K>Не знаю, насколько это хорошо, но я делаю так. K>Есть интерфейсы IContextual и IContextProvider.
[скипнуто]
Очень заинтересовала ваша реализация сабжа. Вы не могли бы по подробнее описать работу системы.
Я так понял что система командноцентрированная (т.е. реализация всех операций в сситеме делается неким подобием Команды
посылаемой элементами системы на выполнение и эти команды каким то образом связаны с меню и тулбарами).
а длаее не совсем понятно... если можно чуть более подробно описать ?
NetDigitally yours ....
Re[8]: Система доступных элементов меню в программе
Здравствуйте, tarkil, Вы писали:
T>Но я-то собственно, про OnIdle. Что, есть уже какое-то работающее приложение написанное в расчёте на то, что оно прилетит в конкретный момент времени? Ню-ню. Я всегда пишу в предположении, что это может случиться (почти) когда угодно. Всем остальным рекомендую то же самое.
Состояние кнопок и меню — должно зависеть только от состояния окна. Лучше реализовать систему подобную дельфовой TAction(насколько я помню, он так называется), в которой все достаточно детерменированно и синхронно изменяются как кнопки так и меню. И разделить понятия — отображения, и состояния. При состоянии запрещения — команда не должна приходить независимо от активности кнопки.
ЗЫ. Это касается только окна синхронизированного через Message Query.
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[3]: Система доступных элементов меню в программе
Здравствуйте, Jericho113, Вы писали:
J>Очень заинтересовала ваша реализация сабжа. Вы не могли бы по подробнее описать работу системы. J>Я так понял что система командноцентрированная (т.е. реализация всех операций в сситеме делается неким подобием Команды J>посылаемой элементами системы на выполнение и эти команды каким то образом связаны с меню и тулбарами). J>а длаее не совсем понятно... если можно чуть более подробно описать ?
При загрузке ядро запрашивает у репозитория список сборок. Сборки, поддерживающие специальный интерфейс, считаются модулями. У модуля можно запросить интерфейс (QueryInterface), узнать имя и версию. Модули опрашиваются на предмет поддержки интерфейса IApplication. Фактически, объект, являющийся IApplication есть плагин (приложение в нашей терминологии). Затем приложения загружаются.
При загрузке приложение добавляет объекты в реестр глобальных объектов. Глобальные объекты поддерживают интерфейс с единственным свойством Id. Кстати, модули и приложения так же являются глобальными объектами и автоматически добавляются ядром в реестр. Команды в том числе являются глобальными объектами.
Я уже описывал интерфейсы IContextual и IContextProvider. Команды поддерживают интерфейс IContextual. Команда, для которой MatchContext возвращает false, считается недействительной и для неё нельзя вызывать Execute. Сам Execute содержит единственный параметр типа IContextProvider. Команда должна получить некоторые данные у контекста и на основе этого сделать те или иные действия. Например, в контексте редактора присутствует свойство "context-property:Editor" значением которого является сам этот редактор. Команда "command:Cut" (это её глобальный идентификатор) считается действительной, если в контексте есть "context-property:Editor". Работает она приблизительно так:
void Execute(IContextProvider provider)
{
if (!provider.HasProperty("context:Editor"))
{
throw new InvalidOperationException();
}
IEditor editor = (IEditor)provider.GetPropertyValue("context-property:Editor");
editor.Cut();
}
В принципе, можно придумать любой контекст. Если сервисный класс, позволяющий объединить контексты. Например, контекст главного окна приложения объединяет контексты текущего редактора, редактируемого в нём документа, а так же добавляет туда несколько дополнительных свойств. Контекст главного окна учасвует в валидации команд меню. Другие контексты могут использоватся для формирования контекстных меню. Кстати, контекстное меню одно на все задачи, посредством механизма контекстов из него выбирается и показывается только необходимый набор элементов.
В меню и в тулбарах показываются не сами команды, а элементы тулбаров и меню. Элементы содержат ссылки на комнды. Элементы, во-первых, являются глобальными объектами, во-вторых, IContextual. Существуют специальные средства, чтобы встраивать элементы в иерархическую структуру меню; в том числе, можно добавлять элементы в "чужие" меню, т.е. сформированне другими приложениями. При формировании физических меню пункт будет спрятан (если соответсвующий элемент окажется недействительным) или запрещён (если команда, на которую ссылается элемент, недействительна).
Приложения не обязаны реализовывать ICommand, IMenuItem и прочие вещи. Вместо этого они могут пользоваться модулем "module:Utils", где есть стандартные реализации этих вещей, плюс класс, который умеет загружать всё это дело из xml-документа. Execute в таком случае перенаправляется приложению, а контектные условия для команд прописываются в виде набора условий, куда включается и/или. Такой подход предпочтителен, но его никто не навязывает.
Нужно носить в себе еще хаос, чтобы быть в состоянии родить танцующую звезду.