Действия (Actions) и связанные с ними компоненты
С давних пор повелось, что стандарты на пользовательский интерфейс Windows-приложений Microsoft задает "явочным порядком". Первая громкая история на эту тему связана с появлением в 1994 г. Excel 2.0 for Windows, когда разработчики из Редмонда впервые применили интерфейс со многими документами (впоследствии широко известный как MDI) и даже не подумали задокументировать и опубликовать его. После справедливого возмущения широких кругов программистской общественности Microsoft исправился и теперь новые возможности интерфейса публикуются если не до выхода продукта, то, по крайней мере, ненамного позже. Вспомним, с Internet Explorer появилась панель CoolBar и кнопки, картинки, которые подсвечивались при прохождении над ними курсора мыши. Большинство же новинок связано с флагманским продуктом Microsoft — Office. Одна из них — весьма, кстати, полезная — это система настраиваемых пользователем меню и панелей инструментов.
В Delphi 7 разработчику предоставляется доступ к аналогичным возможностям. Правда, для работы с ними придется забыть "старый" интерфейс — компоненты TMainMenu, TToolBar — и полностью переучиться. Теперь "продвинутый" интерфейс состоит из новых компонентов TActionManager, TActionMainMenuBar, TActionToolBar и примкнувшего к ним TCustomizeDlg (страница Палитры компонентов Additional). Для читателя уже знакомого с действиями (Actions) названия этих компонентов покажутся знакомыми. Действительно, это — гибриды прежнего TActionList и панелей инструментов, плюс новые свойства и методы, плюс большое число полезных стандартных действий.
Данная глава посвящена рассмотрению принципов их использования. Сначала поговорим о понятии действия (Action) и рассмотрим компонент TActionList, который является кроссплатформенным (работает как в Delphi 7, так и в Kylix). Далее рассмотрим обширный набор стандартных действий.
И в заключение читатель узнает о Windows-потомке TActionList под названием TActionManager и о связанных с ним компонентах.
Действия. Компонент TActionList
Пользовательский интерфейс современных приложений весьма многообразен, и зачастую один и тот же результат можно получить разными способами — щелчком на кнопке на панели инструментов, выбором пункта меню, нажатием комбинации клавиш и т. п. Можно решить проблему "в лоб" и повторить один и тот же код два, три раза и т. д. Недостатки такого подхода, очевидно, не обязательно комментировать. Можно воспользоваться Инспектором объектов и назначить пункту меню тот же обработчик события, что и кнопке, благо событие onclick имеет везде одинаковый синтаксис. Этот способ неплох, но при большом количестве взаимных ссылок легко запутаться.
Наконец, современный способ — это воспользоваться компонентом TActionList и разом решить эти проблемы. Как следует из названия, это — централизованное хранилище, где воздействия со стороны пользователя связываются с реакциями на них.
Действием (Action) будем именовать операцию, которую пользователь хочет произвести, воздействуя на элементы интерфейса. Тот компонент, на который он хочет воздействовать, называется целью действия (Action target). Компонент, посредством которого действие инициировано (кнопка, пункт меню), — клиент действия (Action client). Таким образом, в иерархии классов Delphi действие TAction — это невизуальный компонент, который играет роль "черного ящика", получающего сигнал от одного или нескольких клиентов, выполняющих действия над одной (или разными) целями.
Примечание
Действия могут работать только будучи объединенными в список компонентов TActionList или TActionManager. Вне этих компонентов применение действий невозможно.
Спроектировав на бумаге пользовательский интерфейс, начните работу с помещения на форму компонента TActionList. Он находится в Палитре компонентов на первой странице Standard (вот видите какое ему уделяется внимание!). После этого следует запустить редактор списка действий двойным щелчком мышью на компоненте или с помощью контекстного меню (рис. 8.1).
Теперь можно начинать добавление действий. Для программиста предусмотрен набор типовых, наиболее часто встречающихся действий, которые описаны в следующем разделе.
![](image/gl-8-1.gif)
Рис. 8.1. Внешний вид редактора действий компонента TActionList
![](image/gl-8-2.gif)
Рис. 8.2. Опубликованные свойства объекта TAction
Помимо них можно вставить и обычное действие, которое получит имя Action1. Итак, что же из себя представляет действие? Его опубликованные свойства показаны на рис. 8.2. Рассмотрим их по группам.
Изменение и настройка внешнего вида панелей
Мы подошли к совсем новому свойству панелей — TActionMainMenuBar. Теперь — как в Microsoft Office — возможно прятать редко используемые пункты меню. В самом деле, интерфейс программ подчас настолько сложен, что используют его на 100% минимальное количество пользователей. Поэтому элементы интерфейса, которые пользователь не задействовал в каком-то числе предыдущих запусков, автоматически прячутся.
Что и когда прятать, определяется свойством
property PrioritySchedule: TStringList;
значение которого по умолчанию приведено в табл. 8.4. В левой колонке содержится общее количество запусков приложения, в течение которых пользователь применял данное действие; в правой колонке — число запусков, прошедших со времени последнего его использования. По истечении этого числа запусков клиенты действия маскируются. Например, в меню они доступны не сразу, а после нажатия специального пункта с двумя стрелочками, обращенными вниз.
Естественно, чем чаще пользователь обращался к действию, тем дольше оно удержится на виду. Впрочем, если у вас другие взгляды на интерфейс, вы можете изменить значение priorityScedule.
Таблица 8.4. Условия скрытия элементов панелей действий
Количество запусков приложения с обращением к действию |
Количество запусков приложения после последнего обращения |
0, 1 |
3 |
2 |
6 |
3 |
9 |
4,5 |
12 |
6-8 |
17 |
9-13 |
23 |
14-24 |
29 |
Более 25 |
31 |
Для подсчета величин, указанных в этой таблице, введены такие свойства:
у объекта TActionBars (дочерний объект TActionManager) есть свойствоproperty SessionCount: Integer;
которое представляет собой глобальный счетчик запусков приложения;
у каждого объекта TActionclientitem есть два свойства: property LastSession: Integer;в этом свойстве хранится номер последнего запуска приложения, в течение которого использовался данный элемент (нумерация совпадает сSessionCount);
property UsageCount: Integer;счетчик использования элемента.
Но для того, чтобы оперировать данными о количестве запусков, их надо где-то хранить. Организована система хранения следующим образом. У самого менеджера действий есть свойство
property FileName: TFileName;
которое указывает на файл, содержащий все настройки панелей, связанных с данным менеджером. Он имеет формат двоичной формы и считывается/записывается при запуске и выходе из приложения. Впрочем, можно это сделать и в любой момент при помощи методов LoadFormFile и SaveToFile.
Все эти величины меняются автоматически, и их описание приведено для понимания сути происходящего. Сбросить же счетчик статистики запусков можно следующим образом: на этапе разработки на странице Options редактора свойств менеджера действий есть кнопка Reset Usage Count. На этапе выполнения точно такая кнопка есть в диалоге TCustomizeDlg.
Помимо данных для подсчета запусков в этом файле хранится и вся прочая информация о настройках. Последний из не упоминавшихся нами компонентов — диалог настройки TCustomizeDlg. Он представляет собой точную копию редактора свойств TActionManager, но позволяет делать все операции с действиями в режиме выполнения. Вызвать его просто — вызовом метода show. А можно поступить еще проще — есть стандартное действие Customize (категория Tools), которое и подразумевает вызов этого диалога.
Категория Dataset
Эти действия можно увидеть, например, в качестве кнопок на любом компоненте TDBNavigator: TDataSetFirst, TDataSetPrior, TDataSetNext, TDataSetLast, TDataSetDelete, TDataSetlnsert, TDataSetEdit, TDataSetPost, TDataSetCancel, TDataSetRef resh. Читатель задаст вопрос: а как действие связывается с набором данных? Очень просто: через дополнительное (для данной категории) свойство DataSource. Если источник данных существует и связан с имеющимся набором данных (свойство DataSource.Dataset), то действие выполняется над ним.
Категория Dialog
Эта категория примыкает к предыдущей, в ней содержатся остальные пять типовых действий-диалогов: TPrintoig, TCoiorSeiect, TFontEdit (из модуля StdActns), TOpenPicture, TSavePicture (модуль ExtActns).
Категория Edit
В эту категорию входят компоненты, которые работают с редактируемыми элементами — потомками TCustomEdit. Это, к примеру, TEdit, TMemo, TMaskedEdit, TRichEdit, новый компонент TLabeledEdit И др. Причем целью может являться не любой редактируемый элемент, а только тот, что имеет фокус ввода. К Категории относятся: TEditCut, TEditCopy, TEditPaste, TEditSelectAll, TEditDelete, TEditUndo.
Категория File
Эти действия скорее всего будут наиболее востребованы разработчиками. И они же являются довольно простыми в использовании. TFiieOpen, TFileSaveAs, TFilePrintSetup — это оболочки над соответствующими диалогами. О том, как работать с такими действиями, описано выше. Действие TFlieExit вообще не требует комментариев — оно просто завершает приложение, закрывая его главную форму.
Особняком стоит только TFileRun!!!
Категория Format
Действия этой категории представляют собой расширенные операции редактирования для "продвинутого" редактора TRichEdit. Эти операции должны быть знакомы вам по программе WordPad из состава Windows. В крайнем случае откройте демонстрационный пример Delphi с тем же названием — там присутствуют действия настоящей категории и подавляющее большинство остальных в списке присутствуют TRichEditBold, TRichEditltalic, TRichEditUnderline, TRichEditStrikeout (установка стиля шрифта), TRichEditBullets (значки абзацев), TRichEditAlignLeft, TRichEditAlignRight, TRichEditAiignCenter (выравнивание текста).
Категория Help
С помощью этих действий (табл. 8.2) вы можете вызвать справочную систему вашего приложения.
Таблица 8.2. Стандартные действия категории Help
Компонент |
Назначение |
THelpContents |
Показывает оглавление системы справки |
THelpOnHelp |
Показывает подсказку по использованию системы справки |
THelpContext |
Показывает справку по контексту активного элемента управления (причем он должен быть ненулевым) |
THelpTopi cSearch |
Показывает окно поиска системы справки |
Категория Internet
Здесь всего три — типовых для пользователя Сети — действия.
Действие TBrowseURL открывает URL, заданный в одноименном свойстве. Поскольку это происходит при помощи функции shellExecute, для просмотра открывается браузер, зарегистрированный в системе по умолчанию.
Действие TSendMail запускает программу — почтового клиента для отправки письма (с помощью интерфейса MAPI). Текст письма вы можете задать в свойстве Text. Но! Ни получателя, ни тему, ни вложений задать нельзя — это придется делать вручную в почтовой программе. При желании полностью автоматизировать процесс отправки вам придется породить дочерний компонент от действия TSendMail, где и перекрыть метод ExecuteTarget.
Исходные тексты — в модуле ExtActns.
Наконец, самый сложный компонент TDownloadURL. Он позволяет загрузить содержимое с адреса URL и сохранить его на локальной машине под именем FileName.
Поскольку загрузка — процесс долгий, в то время, пока она происходит, периодически возникает событие
property OnDownloadProgress: TDowriloadProgressEvent;
TDownloadProgressEvent = procedure(Sender: TDownLoadURL;
Progress,
ProgressMax: Cardinal; StatusCode: TURLDownloadStatus;
StatusText: String;
var Cancel: Boolean) of object;
Параметры обработчика этого события следующие.
Progress и ProgressMax — текущее и максимальное значение показателя хода скачивания. Во-первых, не все HTTP-серверы правильно сообщают о размере ответа; во-вторых, для некоторых типов файлов (например, HTML) эти параметры вычисляются не всегда верно (вы можете это видеть в Internet Explorer); в-третьих, из-за маршрутизации пакетов ожидать ритмичного изменения параметра Progress не следует. Поэтому пользователю надо показывать соотношение progress/ProgressMax.Примечание
Значение ProgressMax может быть равно нулю. В этом случае о ходе загрузки численно судить нельзя. Информацию несут другие параметры события.
StatusCode и StatusText — код, описывающий текущее состояние операции и соответствующий ему текст. Список возможных кодов содержит около 30 значений. Для тех, кто знает протокол HTTP и хочет разобраться в этом глубже, следует обратиться к описанию интерфейса IBindstatusCallback в MSDN. Если же вам достаточно показать пользователю текст, то он содержится во втором параметре. По содержанию он представляет примерно то же, что вы видите при загрузке файлов с помощью Internet Explorer. Cancel — этот параметр одинаков для всех долго продолжающихся операций. Установив его в значение True, вы можете прервать выполнение загрузки.Категория List
В этой категории выделяются две группы действий. Первые пять из них (табл. 8.3) автоматизируют работу с выделенными элементами списков. Оставшиеся два — TStaticListAction И TVirtualListAction — требуют отдельного рассмотрения.
Таблица 8.3. Действия по работе с выделенными элементами списков
Действие |
Назначение |
TListControlSelectAll |
Выделяет все элементы списка. Активно, только если у списка свойство MultiSelect установлено в значение True |
TListControlClearSelection |
Отменяет выделение элементов в списке |
TListControlDeleteSelection |
Удаляет выделенные элементы |
TListControlCopySelection |
Копирует выделенные элементы списка в список Destination |
TListControlMoveSelection |
Переносит выделенные элементы списка в список Destination |
Действия работают с компонентом TListBox, а в среде Kylix — еще и с TListview (не путать с одноименным компонентом для Windows — он не годится для данной категории). Подходит также и TCоmbовох.
В отличие от многих других действий члены этой категории могут явно связываться с нужным вам списком. Если задано значение свойства Listcontrol, то все действия производятся над ним. Если нет, то выбирается активный список из числа имеющихся на форме.
Особняком стоят два действия — TStaticListAction И TVirtualListAction
По замыслу разработчиков они являются централизованными хранилищами элементов для многих списков. Причем элементы списка могут храниться сразу с заданными картинками (т. е. свойствами imageindex) и указателями на сопутствующие данные.
Дальнейшее просто — разработчик выбирает нужные компоненты TListBox, TComboBox и т. п. и в их свойстве Action указывает на действие — хранилище. Опубликовано свойство Action у компонента TCоmbовохЕх (впервые появившегося в Delphi 6). У остальных потомков TControl это свойство относится к группе видимости public, поэтому вы можете сделать присвоение при запуске приложения (в методе onCreate главной формы).
Если действие и компонент-список связаны, то должны происходить две вещи:
при изменении текущего элемента в любом из компонентов происходит синхронное изменение во всех остальных; когда пользователь выбирает один из элементов списка, выполняется действие, связанное с этим списком, и вызывается метод-обработчикtype TItemSelectedEvent = procedure(Sender: TCustomListAction;
Control: TControi) of object;
property OnltemSelected: TItemSelectedEvent;
Категория Search
Действия поиска и замены тоже производятся только над потомками TCustomEdit. И это не прихоть разработчиков Borland: это сделано для вашего же удобства. Раньше для поиска приходилось самому программировать события OnFind и OnReplace соответствующих диалогов, а сейчас требуемый код уже содержится внутри действий.
К Компонентам этой категории относятся: TSearchFind, TSearchFindFirst, TSearchFindNext, TSearchReplace.
Категория Tab
Здесь всего два компонента — TNextTab и TPreviousTab. Если цель действия — набор страниц (TPageControl), они переключат его на следующую и предыдущую страницу соответственно.
Категория Tools
Здесь содержится один-единственный член: TCustomizeActionBars. Будучи вызванным, это действие вызывает диалог настройки панелей действий, относящихся к компоненту TActionManager, о котором, собственно, сейчас и пойдет речь.
Категория Window
Эти действия стоит включать в интерфейс, только если вы используете многодокументный интерфейс (MDI). Названия компонентов говорят сами за себя: TWindowClose, TWindowCascade, TWindowTileHorizontal, TWindowTileVertical, TWindowMinimizeAll, TWindowArrange.
Компонент TActionManager
Если вы не думаете о переносе своего приложения в среду Linux, то имеются все основания воспользоваться потомком TActionList — компонентом TActionManager (далее в тексте — менеджер действий). Более современный и "продвинутый" он обеспечит вас многими дополнительными возможностями. Итак, обо всем по порядку.
Будучи применен сам по себе, компонент TActionManager ничем не отличается от предшественника. Отличия проявляются, если действия из этого компонента разместить на специальных панелях — TActionMainMenuBar (будем называть его панелью главного меню) и TActionToolBar (далее — панель действий).
На первой странице редактора TActionManager (вызывается двойным щелчком или командой Customize из контекстного меню; показан на рис. 8.5) как раз и содержится список всех панелей, связанных с данным менеджером действий. Вы можете добавить новый или убрать компонент TActionToolBar нажатием кнопок New и Delete соответственно. С компонентом TActionMainMenuBar так по понятным причинам поступить нельзя — меню полагается иметь одно.
![](image/gl-8-5.gif)
Рис. 8.5. Первая страница редактора свойств компонента TActionManager
Самый простой и рекомендованный Borland способ для связи действий с одной стороны и панелей меню и инструментов с другой — это перетаскивание (Drag-and-Drop). На второй странице редактора содержится список всех действий по категориям. И отдельное действие, и целую категорию можно брать и тащить мышью на нужную панель (рис. 8.6).
Когда вы перетаскиваете действие на панель, на нем появляется специальный компонент, похожий на пункт меню или кнопку. Его роль — служить клиентом данного действия. Поэтому, естественно, он сразу и автоматически получает нужные Caption, imageindex, Hint и прочие общие для всех клиентов свойства, о которых говорилось выше. Класс этого клиента — TActionclientitem; будем называть их псевдокнопками или псевдоэлементами.
При перетаскивании нет особых сложностей, но надо иметь в виду следующие аспекты:
при перетаскивании всей категории на панель главного меню она появляется в виде пункта меню верхнего уровня и содержит при этом все свои дочерние действия;при перетаскивании всей категории на панель действий создаются псевдокнопки для всех дочерних действий в категории. Логично поступить по принципу "одна категория — одна панель действий", это будет полезно для настройки интерфейса пользователем; если вы ошиблись при перетаскивании, не нажимайте кнопку Delete — при этом удалится не только псевдокнопка, но и само действие. Перетяните ненужный псевдоэлемент за пределы панелей действий, тогда он будет удален; если вы уже перетянули категорию на панель главного меню, а потом решили добавить к ней действия, то вам придется убрать соответствующий ей псевдоэлемент и перетянуть всю категорию заново. Либо воспользоваться ручным способом, который описывается ниже.
![](image/gl-8-6.gif)
Рис. 8.6. Страница действий редактора свойств компонента TActionManager
Прочие свойства
Чтобы связать с действием комбинацию "горячих" клавиш, выберите одну из возможных комбинаций в редакторе свойства shortcut. Более того, в Delphi 7 существует возможность добавлять не одну, а множество комбинаций "горячих" клавиш. Вторая и последующие должны быть заданы в свойстве secondaryshortcuts. Когда вызывается редактор этого свойства, пользователь видит обычный редактор набора строк. И вводить комбинации нужно по принципу "как слышится, так и пишется": например <Ctrl>+<F12>, <Shift>+<Alt>+<0> и т. п., по одной комбинации на каждой строке.
Для упорядочивания все действия разбиты на категории:
property Category: string;
Это свойство содержит условное название категории, к которой относится действие, например, File, Edit, Help и т. п. Роль категории сводится к тому, чтобы объединить сходные действия при показе в ActionList или ActionManager. Названия категорий вы видите на панели меню на самом верхнем уровне.
Иногда программисту все-таки необходимо знать, какой конкретно клиент — меню, кнопка — привел к выполнению действия. Узнать это можно, воспользовавшись значением свойства компонента TAction:
property ActionComponent: TComponent;
Перед вызовом onExecute это свойство содержит указатель на клиента, инициировавшего действие. После вызова значение свойства очищается.
После того как определены действия и написан код, реагирующий на них, осталось поставить завершающую точку и связать их с пользовательским интерфейсом. У большого числа элементов управления (например, всех кнопок и пунктов меню) есть опубликованное свойство Action. Если действия к этому моменту описаны, то на панели инструментов из выпадающего списка среди возможных значений этого свойства достаточно выбрать нужное действие.
Хорошо знакомые со времен Delphi
Хорошо знакомые со времен Delphi 1 составляющие интерфейса — меню (TMainMenu, TPopupMenu), кнопки (TButton, TSpeedButton), панели TPanel —постепенно уходят, уступая место компонентам с расширенной функциональностью. Центральным местом, где обрабатывается весь ввод пользователя, становится хранилище действий — TActionList или TActionManager.
В этой главе мы подробно рассмотрели оба компонента; читателю решать, на базе чего строить свой интерфейс.
С одной стороны, материал этой главы не оказывает прямого влияния на последующие. С другой, пользовательский интерфейс — та "печка", от которой "пляшут" при создании больших приложений. Поэтому имейте в виду при чтении всех прочих данную главу.
Ручное редактирование коллекций панелей и действий
Перетаскивание имеет много достоинств, однако оно не всегда удобно. Поэтому было бы странно, если бы не было предусмотрено другого способа. Хоть он напрямую и не рекомендован в документации, но в ряде случаев более эффективен.
Рассмотрим работу с дочерними объектами менеджера действий, которые упакованы один в другой, как матрешки.
Итак, щелкнем на свойстве ActionManager на форме и посмотрим на содержимое Инспектора объектов. Внутри него мы обнаружим сразу две "матрешки" — свойство ActionBars содержит коллекцию ссылок на дочерние панели, свойство LinkedActionList может содержать дополнительный список действий, отданных "в управление" данному менеджеру — например, для централизованной установки общих свойств.
Щелкнем на свойстве ActionBars. Появится редактор панелей (рис. 8.7), а в Инспекторе объектов обратим внимание на следующее свойство объекта ActionBars:
property Customizable: Boolean;
Это свойство указывает, может ли коллекция редактироваться во время выполнения.
В коллекции содержатся не сами панели, а их "заместители" — объекты типа TActionBaritem, которые на них указывают. Надпись на рисунке "1-ActionBar -> ActionToolBarl" показывает, что первый элемент коллекции связан с панелью ActionTooiBar2. Вы можете добавлять и удалять элементы этой коллекции, по мере необходимости связывая их через свойство ActionBar с уже существующей панелью.
![](image/gl-8-7.gif)
Рис. 8.7. Редактор коллекции панелей компонента TActionManager
Через Инспектор объектов вы можете изменять внешний вид объектов типа TActionBaritem и соответствующих им панелей.
Свойство
property Color: TColor;
отвечает за фоновый цвет панели. Если вам изменения цвета недостаточно, в качестве фона выберите картинку
property Background: TPicture;
которая будет расположена на панели в соответствии со значением свойства
property BackgroundLayout: TBackgroundLayout;
TBackgroundLayout = (blNormal, blStretch, blTile, blLeftBanner, blRightBanner);
Помимо внешнего вида можно разрешить/запретить перетаскивание панелей и их дочерних элементов. Обратимся к свойству
property ChangesAllowed: TChangesAllowedSet;
TChangesAllowed = (caModify, caMove, caDelete);
TChangesAllowedSet = set of TChangesAllowed;
Множество из трех возможных значений позволяет запретить те или иные нежелательные изменения для дочерних элементов панели. Если в него не включен режим caDelete, то элемент нельзя убирать (перетаскивать) с панели. Если нет режима caMove — нельзя передвигать внутри панели. Наконец, отсутствие режима caModify означает запрет на изменение визуальных свойств (заголовка и т. п.).
Внутри коллекции TActionBaritem спрятаны еще две "матрешки" — свойства items и Contextitems. Оба свойства представляют из себя коллекции объектов, указывающих на действия (класс коллекции TActionCiients, класс элемента коллекции TActiondientitem). Первое свойство указывает непосредственно на дочерние действия, второе — на действия, которые будут показаны в качестве всплывающего меню при нажатии правой кнопки мыши.
У коллекции TActionClients есть заслуживающие особого упоминания свойства.
Свойство
property CaptionOptions: TCaptionOptions;
TCaptionOptions = (coNone, coSelective, coAll);
задает показ/отсутствие заголовков дочерних действий. В случае установки в coNone они не показываются, COAII — показываются все, coSelective — показываются в соответствии со значением showCaption дочернего объекта TActiondientitem. Это свойство можно также установить на первой странице редактора менеджера действий в одноименном выпадающем списке.
Свойство
property Smalllcons: Boolean;
указывает размер значков, соответствующих действиям. По умолчанию установлено в значение True (маленькие значки). Визуально оно доступно через тот же редактор — третья страница, флажок Large Icons.
Свойство
property HideUnused: Boolean;
разрешает скрытие редко используемых действий, описанное в предыдущем разделе. Если вы не хотите пользоваться механизмом скрытия, на третьей странице редактора менеджера действий и диалога TCustomizeDig есть флажок Menu show recent items first. Сбросьте его, и свойства HideUnused у клиентов действий установятся в значение False.
И, наконец, коллекцию можно сделать нередактируемой. Для этого у нее есть свойство Customizable.
Ну вот, мы уже добрались до самой маленькой матрешки — TActiondientitem. Этот объект связывается напрямую с одним действием через свойство Action. Правда в него можно спрятать еще меньшую матрешку — у него также есть свойства items и contextitems. Эти свойства используются при организации многоуровневых меню и меню, выпадающих из кнопок (точнее, псевдокнопок — напомним, объекты TActiondientitem на панелях не являются ни кнопками, ни компонентами вообще).
События, связанные с действиями
Компонент TAction реагирует на три события: OnExecute, OnUpdate И OnHint.
Первое — и самое главное — должно быть как раз реакцией на данное действие. Это событие возникает в момент нажатия кнопки, пункта меню — короче, при поступлении сигнала от клиента действия. Здесь — как правило—и пишется обработчик. Почему "как правило"? Потому что схема обработки сигнала 4-этапная:
1. Сначала вызывается обработчик события OnExecute списка действий
TActionList:
property OnExecute: TActionEvent; TActionEvent = procedure (Action: TBasicAction; var Handled: Boolean)
of object;
Если обработчик этого события вами не предусмотрен, или в параметре Handled он вернул значение False, происходит генерация следующего события — шаг 2.
2. Вызывается обработчик события onActionExecute глобального объекта Application (тип события тот же — TActionEvent). Если и оно не обработало сигнал действия, переходим к следующему шагу.
3. Вызывается обработчик события onExecute самого действия (объекта типа TAction или его потомка).
4. Если первые три шага не обработали ситуацию (вернули False), то, вероятно, это было связано с неправильной целью (Target) действия. В качестве "последнего шанса" приложению посылается сообщение CM_ACTIONEXECUTE. В этом случае происходит поиск другой цели для данного действия (об алгоритме поиска цели см. ниже).
Первые две возможности в этом списке используются относительно редко. Тем не менее они полезны, если вам нужно глобально добавлять/удалять/разрешать/запрещать действия.
Введение события onupdate является очень хорошей находкой, о нем напишем подробно. И автор этих строк, и, возможно, вы потратили немало времени, чтобы в разрабатываемых программах элементы управления находились в актуальном состоянии. Если, скажем, вашей программой открыт первый файл, то нужно активировать ряд кнопок и пунктов меню (Save, Save as, Print и т. п.); как только закрыт последний — отключить их. Если в буфере обмена есть что-то подходящее, необходимо активизировать пункт меню и кнопку Paste, если нет — отключить. В результате код, отслеживающий это, у неопытных программистов "размазывается" по всему приложению. А ведь можно поступить проще. Событие TAction.onUpdate возникает в моменты простоя приложения, т. е. тогда, когда оно не занято обработкой сообщений (цикл содержится в методе idle объекта Application). Это гарантирует, что оно возникнет ДО ТОГО, как пользователь щелкнет мышью и увидит выпадающие пункты меню; поэтому можно успеть обновить их состояние. Пример использования события onupdate:
procedure TForml.PasteActionUpdate(Sender: TObject); begin
TAction(Sender).Checked := Clipboard.HasFormat(CFJTEXT);
end;
Примечание
Перед вызовом события onupdate также происходит 4-этапная последовательность действий, точно такая же, как при OnExecute.
Третье событие имеет такой тип:
THintEvent = procedure (var HintStr: string; var CanShow: Boolean) of object;
Оно вызывается тогда, когда от элемента управления требуется показать подсказку, связанную с данным действием. В обработчике события можно указать, будет ли что-нибудь показываться (параметр CanShow) и, если да, то что именно (параметр Hintstr).
Это были события, относящиеся к компоненту TAction. Сам компонент TActionList также имеет три события: OnExecute, OnUpdate И OnChange. О первых двух мы уже сказали; третье происходит в момент изменения списка (добавления или удаления действий).
Стандартные действия
Те, кто собирается пропустить этот раздел, считая, что в нем описаны очевидные вещи, сильно ошибаются. Как раз в применении стандартных действий разработчики Borland продвинулись очень сильно. Кто хочет в этом убедиться, может открыть пример WordPad, поставляемый с Delphi 7. Полнофункциональный текстовый редактор, построенный полностью на стандартных действиях, содержит всего две строчки кода.
Шаблоны и заготовки для типовых меню и кнопок появились еще в самой первой версии Delphi. Но в шестой версии действия действительно стали действиями. Это значит, что раньше заготовка содержала только подходящий заголовок. Теперь они содержат в себе все субкомпоненты, весь программный код и делают всю необходимую работу сами.
Возьмем, например, действие TFlieOpen. Оно уже содержит внутри компонент типа TOpenDialog, показывающий список открываемых файлов. Вместо ручного программирования процедуры задания имени файла теперь нужно написать обработчик события TFiieOpen.onAccept (если пользователь ввел в диалоге кнопку ОК) или OnCancel (если отказался от открытия файла). Вот так выглядит весь программный код приложения WordPad:
procedure TForml.FileOpenlAccept(Sender: TObject);
begin
RichEditl.Lines.LoadFromFile
(FileOpenl.Dialog.FileName);
end;
procedure TForml.FileSaveAslAccept(Sender: TObject);
begin
RichEditl.Lines.SaveToFile
(FileSaveAsl.Dialog.FileName);
end;
![](image/gl-8-4.gif)
Рис. 8.4. Окно выбора стандартных действий
С точки зрения программирования стандартное действие — это класс-потомок TCustomAction. Классы действий описаны в трех модулях: более распространенные в stdActns, менее — в ExtActns, действия с наборами данных содержатся в DBActns. Наконец, два действия, работающие со списками, — TStaticListAction И TVirtualLitAction — описаны в отдельном модуле ListActns.
Для выполнения ряда стандартных действий нужно определить понятие "цели" действия (Action Target). Под целью понимается компонент, в отношении которого будет совершаться данное действие. Так, операции редактирования могут выполняться, когда на форме активен текстовый элемент управления (TEdit, TMemo И Т. П.). У любого действия потомка TBasicAction) есть три метода:
function HandlesTarget(Target: TObject): Boolean; virtual;
procedure UpdateTarget(Target: TObject); virtual;
procedure ExecuteTarget(Target: TObject); virtual;
Метод HandiesTarget проверяет, применимо ли действие к цели Target. Если да, то действие производится вызовом метода ExecuteTarget. Если нет, поиск подходящей цели продолжается.
Цель в Delphi 7 определяется по следующему правилу:
первым кандидатом является активный элемент управления на форме (свойство ActiveControl); если такового нет или он не является подходящим (метод HandiesTarget вернул значение False), целью становится текущая форма, получившая сигнал о действии; если и она не подходит, происходит рекурсивный перебор всех компонентов на форме в поисках первого подходящего. В ряде случаев вы можете произвести действие над желаемым компонентом, вызвав метод ExecuteTarget и передав в него в качестве параметра этот компонент.
Примечание
Стандартные действия редактирования, чьи имена начинаются с TEdit, и поиска (TSearch...) применимы только к потомкам компонента TCustomEdit. Стандартные действия расширенного редактирования, имена которых начинаются с TRichEdit, применимы только к потомкам TCustomRichEdit. Оконные стандартные действия (упорядочивание, смена, закрытие дочерних окон; имена начинаются с TWindow) применимы только к формам многодокументного интерфейса, чье свойство FormStyle установлено в fsMDiForm (рис. 8.4).
Многие классы стандартных действий не требуют элемента управления — цели. Так устроены все действия, вызывающие стандартные диалоговые окна (выбор файла, цвета, шрифта, настройка принтера и т. п.). Чтобы отреагировать на такое действие, нужно написать обработчики следующих событий:
property BeforeExecute: TNotifyEvent;
property OnAccept: TNotifyEvent;
property OnCancel: TNotifyEvent;
Первое возникает до показа диалога, второе — после нажатия пользователем кнопки ОК, третье — после нажатия Cancel.
Примечание
Поскольку диалоги входят в действия в качестве дочерних компонентов, вы можете реагировать и на все "дочерние" события, которые происходят в соответствующем диалоге (OnShow, OnCanClose, OnClose и т. п.)
Поместив на форму стандартные действия, вы заметите, что все они имеют предопределенное значение свойства imageindex. Если так, то где изображение, на которое эти индексы указывают? Вы можете раздобыть его, открыв демонстрационный проект WordPad (папка Demos\ActionBands в поставке Delphi 7). Откройте редактор компонента imageList1 и экспортируйте весь список в виде одного большого файла формата BMP.
Свойства, распространяемые на клиентов действия
Если у нескольких кнопок или пунктов меню общий обработчик, разумно потребовать, чтобы у них были и другие общие свойства. Так оно и реализовано в Delphi. В табл. 8.1 перечислены свойства, чье значение автоматически распространяется на всех клиентов данного действия.
Таблица 8.1. Свойства компонента TAction, автоматически распространяемые на всех его клиентов
Свойство |
Назначение |
property Caption: string; |
Заголовок, связанный с действием |
property Hint: string; |
Подсказка к действию |
property Enabled: Boolean; |
Устанавливает, разрешено/запрещено ли действие |
property Checked: Boolean; |
Устанавливает, отмечено ли действие |
property Grouplndex: Integer; |
Индекс группы, в которую входит действие. Объекты TAction с одним значением этого свойства (причем большим нуля) имеют зависимое переключение. Если свойство Checked любого объекта из группы устанавливается в True, у остальных оно сбрасывается в False |
property AutoCheck: boolean; |
Установка в True автоматически меняет значение свойства checked на противоположное после каждого выполнения действия |
property Imagelndex: Integer; |
Индекс картинки в общем наборе картинок (набор указывается в свойствах родительского TActionList) |
property HelpType: THelpType; |
Указывает на тип значения, связывающего действие с разделом системы помощи (htKeyword/htContext) |
property HelpContext: THelpContext; |
Если свойство HelpType установлено в htContext, это свойство содержит ID раздела системы помощи |
property HelpKeyword: string; |
Если свойство HelpType установлено в htKeyword, то свойство содержит ключевое слово (термин), по которому происходит открытие соответствующего раздела системы помощи |
Вы привыкли к программам с картинками в меню и на панелях инструментов? Действие также можно снабдить картинкой. Компонент TActionList связывается со списком картинок TimageList, а действие TAction — с конкретной картинкой через свойство imageindex. Таким образом, все элементы управления, связанные с действием, — кнопки и пункты меню — будут иметь одну и ту же картинку, как показано на рис. 8.3. Впрочем, это относится ко всем остальным свойствам из табл. 8.1.
![](image/gl-8-3.gif)
Рис. 8.3. Меню и панель инструментов используют один список действий