пятница, 5 июня 2009 г.

ГУЙ и меню

Настроение последнее время какое-то совсем неписательское…То ли лето пришло, то ли лыжи не едут!?! Сразу как-то вспоминается Андрей Вознесенский:
    «Ни дня без строчки – друг мой дрочит! 
    А у меня ни дней, ни строчек…»

Посему самое время заняться наведением порядка и выложить что-нибудь из давно написанного “в стол”. Сегодня пара мыслей о построении меню в больших приложениях – то бишь в таких, где “меню многа”.

Возьмем какой-нибудь всем известный Total Commander. Легко увидеть, что практически любую задачу в нем можно выполнить далеко не единственным способом: это и команды, и кнопки, и клики в самых неприличных местах, и контекстные меню и чего там только нет.

Но главное меню традиционно играет еще и роль справочника команд. По идее в нем должны быть представлены все команды... По крайней мере, все основные. Буквально вчера ваш покорный слуга достаточно продолжительное время “шарился” в поисках нужной команды по главному меню Google Earth… Ни нашёл, блин! В конце концов, нужная команда, конечно же, нашлась – но скрыта она была в недрах диалога, и в меню никак не проглядывалась. А между тем, если бы она присутствовала в главном меню, я был бы избавлен от необходимости лезть в документацию… Т.е. преимущества меню как полной “энциклопедии возможностей ” вполне очевидны.

Но вот какое дело:, если команда меню где-то названа “А”, то она должна быть везде “А” и только “А”, и никак не “а”, “а” или “А…

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

Обычно каждое меню описывается в ресурсах приложения. Но в большом приложении повторяющихся команд в разных меню будет достаточно дофига много. Полбеды само создание меню – все-таки они развиваются вместе с продуктом, постепенно наращиваясь от версии к версии. По ягодке, по ягодке и вот уже полные штаны  кузовок. Но совсем другой объем работы нарисовывается, когда нужно банально изменить название команды меню. Из-за того, что одни и те даже команды рассованы по самым разным контекстным меню, то исправлять придется далеко не единственное название. А это настолько “интеллектуальная” работа, что… в общем, на это просто кладётся! :)

Отдельные контекстные меню в ресурсах дело важное и нужное – другой бы спорил!?! Но обычно при этом  99 из 100 этих команд уже и так есть в главном меню приложения. Посмотрите на тот же самый Microsoft Word: практически любые его команды есть в главном меню.

Но есть и другой подход к формированию меню: создавать контекстные меню на лету, в runtime. Рекурсивно обходится главное меню (тривиально через GetPopupMenu + GetMenuItemCount +  GetMenuItemID), и извлекаются строки по идентификаторам команд. Затем извлеченные строки вставляются в создаваемое на лету контекстное меню через AppendMenuItem. Всё просто.

В развитом продукте выгоды такого подхода вполне ощутимы:

  • Выполняется правило единственного определения данных (One Definition Rule, ODR). Если есть какая-либо команда меню, в ресурсах она описана только однажды – только в главном меню. И именно так она и будет выглядеть везде. Пользовательский интерфейс выглядеть “причесанным”. Если команда названа XYZ, то именно так она и названа во всём пользовательском интерфейсе, и никак иначе.
  • Сам объем ресурсов в бинарнике будет значительно меньше. “Усилия, потраченные на оптимизацию работы с памятью, всегда оправдывают свои затраты“ – (© кого-то между прочим аж из самой Microsoft, что-ты, что-ты). Сейчас, пожалуй, весомость этого довода не столь уж актуальна, но всё же.
  • Рано или поздно придет товарищ Познер встанет вопрос о локализации интерфейса. Понятно, что при таком подходе с минимумом меню в ресурсах переводчику просто физически достанется меньше работы. И опять же снова, как бы не было переведено название команды – оно везде будет идентичным.
  • При таком способе разработчик фактически создает GUI руками, и может его поменять в любой момент, как заблагорассудится. Кстати, пункт выше про локализацию и тут имеет значение. В этом случае можно менять строение пользовательского интерфейса, порядок и содержание меню в коде, не оборачиваясь на старые локализации – все равно у вас есть все переведенные команды в главном меню. А дополнительные меню конструируются на лету и именно код определяет структуру и расположение команд.
  • Кесерю кесарево, а слесарю слесарево. В ресурсах остаются только строки для перевода, а сам GUI формируется разработчиком в коде, на лету. Предельно легко разделить разработку и локализацию, и выполнять их отдельно и параллельно.  К тому же без взаимных последствий – переводчик изменяет перевод, но никак не структуру интерфейса.

Больше ГУЯ – хорошего и разного… А главное, функционирование и структура которого, легко отделяются от оформления и дизайна.

PS: нет, нет, если у вас всего три меню в приложении – то и “огород” городить не стоит. Но когда их число переваливает за десяток-полтора, и команды дублируются в нескольких местах – этот способ работает, причем работает еще как, с лихвой окупая время потраченное на пару-тройку десятков строк кода. Проверено!
PPS: “Совершенство – это когда не нечего добавить, это когда нечего убрать” (© Антуан Мари Жан-Батист Рожер де Сент-Экзюпери).

4 комментария:

  1. Сложная эта штука, юзабилити :)

    "создавать контекстные меню на лету, в runtime"

    тоже думал о настраиваемом и динамическом меню, в свободное время потестирую варианты.

    ОтветитьУдалить
  2. 1) Юзабилити оно эмпирично - либо удобно, либо нет. Один фиг имхо все проблемы вытекают из неверного use-case анализа: пользователь пытается\хочет сделать какую-то совершенно другую задачу, чем та, которую подразумевал разработчик.

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

    ОтветитьУдалить
  3. [чешет репу] всю жизнь использовал Actions в борландовском бьюлдере, и ни разу не задумывался о вышеописанных проблемах по причине их полного отсутствия [пожимает плечами]

    ОтветитьУдалить
  4. А кто такой Action в Бильдере? Чего он делает?
    Я бильдером пользовался последний раз году так в 98-ом, и то чего-то он мне реально так не покатил, тогда вернулся на обычный Borland C++ 4.5-5.0... А потом уже на MS студию пересел.

    ОтветитьУдалить