Почти все мы сталкиваемся с разработкой довольно сложных пользовательских интерфейсов. Приходится работать с нагромождениями кнопок, тулбаров, меню и всего, что только может придти в голову. К счастью, в Delphi есть компонент TActionList, немного упрощающий жизнь. И он ведь действительно помогает бороться с растущей сложностью интерфейсов.
Но рано или поздно оказывается, что действий (TAction) на форме у нас уже многие десятки, а код, который их обслуживает, все больше напоминает лапшу из многих сотен, а то и тысяч однообразных строк.
Как говорится, любую проблему можно решить добавлением еще одного уровня абстракции. И такая возможность есть.
Как правило всю огромную кучу действий можно логически разделить на несколько категорий с более-менее похожим поведением. И грех этим не воспользоваться, благо Delphi позволяет создавать собственные типы «стандартных» действий.
В свое время это было для меня небольшим откровением, заметно облегчившим работу со сложными формами. Все это настолько просто, что даже удивительно, что такая возможность столь редко используется.
На примере будет понятно. Представим, что часть действий необходимы для работы с данными и их свойство Enabled жестко привязано к тому, открыт соответствующий датасет или нет. Здесь можно использовать обычные TAction и писать каждый раз SomeAction.Enabled := DataSet.Active в событии OnUpdate, можно придумать что-то более изощренное, а можно просто написать соответствующий «стандартный» наследник TAction.
unit uDataAction; interface uses System.SysUtils, System.Classes, System.Actions, Vcl.ActnList, Data.DB; type TDbAction = class(TAction) private FDataSet: TDataSet; public function Update: Boolean; override; published property DataSet: TDataSet read FDataSet write FDataSet; end; procedure Register; implementation { TDbAction } function TDbAction.Update: Boolean; begin if not (csDesigning in ComponentState) then self.Enabled := Assigned(FDataSet) and FDataSet.Active; Result := inherited Update; end; procedure Register; begin RegisterActions('Custom Actions', [TDbAction], nil); end; end.
Все до очевидного просто. Этот класс унаследован от обычного TAction, в него добавлено поле DataSet и переопределена функция Update, где значение свойства Enabled по умолчанию привязывается к значению свойства DataSet.Active. Ключевой момент — процедура Register, которая позволит увидеть TDbAction в IDE. Этот модуль нужно добавить в пакет и установить его. И тогда, выбрав в диалоге редактирования TActionList пункт «New Standard Action…», можно увидеть новый TDbAction.
Далее с этим TDbAction можно работать точно так же, как и с привычными действиями, добавляя весь необходимый код в события OnUpdate и OnExecute. С той лишь разницей, что с состоянием датасета оно уже умеет работать.
Пара слов в заключение.
Если на форме используется только «чистый» TAction, то для действий с похожим поведением неизбежно в той или иной степени возникнет дублирование кода. Это влечет за собой самые разные негативные последствия. Данный подход этого недостатка лишен. Общий код вынесен в общий класс, благодаря чему он куда более управляем.
1. Николай Зверев
17 Сен 2013 12:24 дп
А вот это вот:
inherited Update
не приведёт ли к 100% загрузке ЦП? Если в обработчике OnUpdate менять Enabled на противоположное значение от умалчиваемого?
2. Alex W. Lulin
17 Сен 2013 12:51 дп
Надеюсь, что Роман простит меня.. Немножко поспамлю..
http://18delphi.blogspot.com/2013/09/blog-post_16.html
Вот там — вы «свои» Action’ы делали — «только в путь»…
3. Alex W. Lulin
17 Сен 2013 12:52 дп
«Общий код вынесен в общий класс, благодаря чему он куда более управляем.»
— а вот тут — простор для применения примесей..
Опять же — надеюсь — Роман простит.
4. Alex W. Lulin
17 Сен 2013 12:54 дп
«В свое время это было для меня небольшим откровением, заметно облегчившим работу со сложными формами. Все это настолько просто, что даже удивительно, что такая возможность столь редко используется.»
Роман, не ты один… Всё интересное — лежит на поверхности и «банально».. К сожалению…
5. Alex W. Lulin
17 Сен 2013 1:00 дп
Может будет интересно — http://18delphi.blogspot.ru/2013/09/vcm-vs-mvc.html
6. Всеволод Леонов
17 Сен 2013 12:07 пп
А категории в ActionList не являются такой полезной, но малоиспользуемой возможностью? :)
http://blogs.embarcadero.com/roschinspb/2013/04/03/subcategoryru/
7. Роман Янковский
17 Сен 2013 1:02 пп
Николай Зверев,
Я как-то совсем упустил это из виду. Решение, которое сходу приходит в голову — это поменять строки местами, немного изменив условие. Это еще оставляет возможность увести программу в бесконечный цикл, но штатным образом этого уже не удастся добиться. Хотя и этот вариант имеет свои недостатки.
Спасибо, приятно, что кто-то прочитал заметку настолько внимательно :)
8. Роман Янковский
17 Сен 2013 1:04 пп
Alex W. Lulin, конечно Роман простит :)
Всеволод Леонов, да, категории — полезная штука.
9. Cep
17 Сен 2013 1:58 пп
Спасибо. Действительно сколько «велосипедов» я насмотрелся в разных конторах, но почему-то экшены аццки сложны для понимания подавляющего числа программистов.
Есть пара замечаний.
1. Что будет, если некто удалит DataSet? Правильно, будет обращение по адресу убитого объекта да еще и по таймеру, т.е. куча AV. Необходимо обязательно обрабатывать ситуация, когда разрушен объект и обнулять на него ссылку.
2. Если предполагается наличие кроссплатформенности, то перед FDataSet: TDataSet; надо поставить директиву [weak]. Если не предполагается, то все равно лишним не будет.
3. >> не приведёт ли к 100% загрузке ЦП? Если в обработчике OnUpdate менять Enabled на противоположное значение от умалчиваемого?
К стопроцентному не приведет, но неприятное мерцание будет. Можно воспользоваться тем, что SetEnabled это виртуальный метод и придумать решение чтобы свойство предка не могло меняться пока выставлен некий флаг…, но в большинстве случаев хватает орг. решений.
В этой связи надо обратить внимание на свойство DisableIfNoHandler, для TAction оно по умолчанию True, надо сделать False в перекрытом конструкторе.
4. Кстати, может быть полезным свойство StatusAction, для указания более подробной информации о состоянии БД.
10. Роман Янковский
17 Сен 2013 2:41 пп
Cep, да, все так.
По поводу FreeNotification очень важное дополнение. А с штуками вроде [weak] я, честно говоря, пока еще не успел толком разобраться. Все еще не было необходимости.
11. Alex W. Lulin
17 Сен 2013 11:18 пп
«но почему-то экшены аццки сложны для понимания подавляющего числа программистов»
Странно слышать. Наверное вы конечно правы. Но странно. По-моему — вполне себе естественная и логичная вещь.
12. Alex W. Lulin
17 Сен 2013 11:32 пп
«Я как-то совсем упустил это из виду»
А я делаю несколько не так. Я ввёл метод DoUpdate. Куда передаю запись вида:
внутри Update я заполняю эту запись.
Потом зову DoUpdate.
Перекрываю именно DoUpdate.
Потом поля этой записи присваиваю свойствам Action’а.
Получается буферизация до конца вычисления параметров. И параметры Action’а гарантированно меняются один раз.
13. Alex W. Lulin
17 Сен 2013 11:43 пп
«По поводу FreeNotification очень важное дополнение.»
Очень ВАЖНО! Спасибо автору.
«А с штуками вроде [weak] я, честно говоря, пока еще не успел толком разобраться.»
[weak] — означает, что присваивание переменной будет осуществляться БЕЗ взвода счётчика ссылок. Это важно «пока» только для LLVM-based компиляторов. А именно для Apple и Android.
14. Alex W. Lulin
17 Сен 2013 11:55 пп
Тулбары, меню и контекстные меню мы кстати строим — автоматом. Из списка «операций» на форме. Которая «в фокусе». Т.е. форма публикует операции и не задумывается о том как они будут визуализированы. Правда с этим конечно возникли некоторые сложности «дизайнерского произвола». Иногда всё же — «надо задумываться». Но эти проблемы — мы преодолели. А сама по-себе идея — по-моему — перспективна. Форма (View) служит лишь источником публикации операций, а где и как они будут опубликованы — она не думает.
Я тут сильно думаю, о применении для этой цели атрибутов. Например метить обработчики операций атрибутом [operation] и [operationUpdate]. Чтобы «руками» не регистрировать операции. У меня-то — «не руками». У меня из UML генерируется соответствующий код. Но атрибуты — по-моему — технологичнее. Дарю идею. Вдруг кому пригодится.
Детали таковы — метим методы формы атрибутами [operation] и/или [operationUpdate]. А потом в Run-tine — смотрим RTTI формы и по этим атрибутам порождаем соответствующие TAction’ы.
15. Alex W. Lulin
17 Сен 2013 11:58 пп
Ну или связываем существующие «глобальные» Action’ы с этими обработчиками. Когда форма создаётся, ну или получает фокус. А когда уничтожается — отвязываем.
16. Alex W. Lulin
17 Сен 2013 11:59 пп
«Ну или связываем существующие «глобальные» Action’ы с этими обработчиками.»
— это если мы не хотим связываться с автоматическим построением меню и /или тулбаров.
17. Alex W. Lulin
18 Сен 2013 12:03 дп
Ну или чтобы было понятнее. Атрибуты — [actionExecute] и [actionUpdate].
MeForm = class(TForm)
…
[actionExecute] procedure MyAction1Execute(aSender : TObject);
[actionUpdate] procedure MyAction1Update(aSender : TObject);
[actionExecute] procedure MyAction2Execute(aSender : TObject);
…
end;
Имя Action’а — берём из имени метода — отрезав возможные Execute и/или Update.
По-моему — хорошая идея…
18. Cep
30 Сен 2013 1:39 пп
>>>>«но почему-то экшены аццки сложны для понимания подавляющего числа программистов»
>>Странно слышать. Наверное вы конечно правы. Но странно. По-моему — вполне себе естественная и логичная вещь.
Я такой вывод я сделал на основе многолетнего опыта общения с коллегами и просмотра кучи старого кода во многих разных организациях. Для использования действий требуется некоторая абстрактность мышления и способность просчитать трудозатраты в отдаленной перспективе.
Гораздо проще кинуть на форму две кнопки с обработчиками. А появится ли третья кнопка и кто будет её реализовывать? За размышление над этими вопросами ни кто лишних денег не платит…
19. nannie
18 Дек 2020 7:25 пп
Thanks for visiting,
I’m Nannie.
If you’ve ever been too tired and couldn’t finish a academic paper, then you’ve come to the right place. I assist students in all areas of the writing steps . I can also write the paper from start to finish.
My career as an academic writer started during high school. After learning that I was very able in the field of academic writing, I decided to take it up as a job .
Talented Academic Writer- Nannie Morris- elearninag.com Corp
20. alyssa
15 Июн 2021 9:42 дп
My name is Alyssa Krause. And I am a professional Content writer with many years of experience in writing.
My primary goal is to solve problems related to writing. And I have been doing it for many years. I have been with several groups as a volunteer and have assisted clients in many ways.
My love for writing has no end. It is like the air we breathe, something I cherish with all my being. I am a full-time writer who started at an early age.
I’m happy that I`ve already sold several copies of my works in different countries like England and others too numerous to mention.
I also work in an organization that provides assistance to many students from different parts of the world. People always come to me because I work no matter how difficult their projects are. I help them to save money, because I feel happy when people come to me for writing help.
Professional academic Writer – Alyssa — kirklandwriter.com Company