Статьи

Какой класс действий платформы NetBeans мне следует использовать?

Система действий в платформе NetBeans с каждым выпуском становилась все более надежной, однако за последние годы в этой области произошло много изменений. Хотя все остается обратно совместимым, вы, вероятно, захотите использовать самый современный подход при запуске нового проекта на платформе NetBeans или при рефакторинге существующего кода.

Иерархия классов Action в платформе NetBeans описана следующим образом в «Полном руководстве по платформе NetBeans» Хейко Бека:

Важное изменение по сравнению с IDE NetBeans 6.7 состоит в том, что больше не рекомендуется создавать подклассы для любых классов на основе SystemAction. Т.е. в вашем коде вы не должны расширять или реализовывать какие-либо классы, унаследованные от SystemAction , то есть BooleanStateAction , CallableSystemAction , CallbackSystemAction , NodeAction и CookieAction .

Однако, если у вас есть существующие действия с использованием вышеупомянутых классов, и они работают, нет особых оснований переписывать их, используя новый подход, описанный ниже. Даже старые и более не рекомендуемые действия, основанные на CookieAction и NodeAction, продолжают работать так же, как и раньше. Фактически, в самой среде IDE NetBeans есть множество таких действий. В команде IDE NetBeans есть достаточно других дел, вместо того, чтобы работать над заменой действий, которые уже работают нормально, что также является причиной, по которой эти классы не устарели. Но когда в IDE NetBeans создаются новые действия, описанный ниже новый подход используется последовательно, т. Е. Вышеупомянутые классы действий больше не вводятся в исходный код IDE NetBeans.

Действия. * Заводские методы

Вместо того, чтобы напрямую использовать вышеупомянутые классы SystemAction в своем коде, вы сможете выполнять те же задачи примерно так же легко и с меньшими затратами на запуск, используя недавно введенные фабричные методы Actions. * . Например, для действия, которое всегда должно быть включено (игнорируется контекст), используйте
Actions.alwaysEnabled из вашего слоя (строка 2 в фрагменте ниже):

 <file name="your-pkg-action-id.instance">
<attr name="instanceCreate" methodvalue="org.openide.awt.Actions.alwaysEnabled"/>
<attr name="delegate" methodvalue="your.pkg.YourAction.factoryMethod"/>
<attr name="displayName" bundlevalue="your.pkg.Bundle#key"/>
<attr name="iconBase" stringvalue="your/pkg/YourImage.png"/>
<!-- if desired: <attr name="noIconInMenu" boolvalue="true"/> -->
<!-- if desired: <attr name="asynchronous" boolvalue="true"/> -->
</file>

Для действия, которое должно вызывать обратный вызов ключа в карте действий, используйте Actions.callback из вашего слоя (строка 2 ниже): 

 <file name="action-pkg-ClassName.instance">
<attr name="instanceCreate" methodvalue="org.openide.awt.Actions.callback"/>
<attr name="key" stringvalue="KeyInActionMap"/>
<attr name="surviveFocusChange" boolvalue="false"/> <!-- defaults to false -->
<attr name="fallback" newvalue="action.pkg.DefaultAction"/> <!-- may be missing -->
<attr name="displayName" bundlevalue="your.pkg.Bundle#key"/>
<attr name="iconBase" stringvalue="your/pkg/YourImage.png"/>
<!-- if desired: <attr name="noIconInMenu" boolvalue="true"/> -->
<!-- if desired: <attr name="asynchronous" boolvalue="true"/> -->
</file>

(Подробнее об этом фрагменте читайте в этой записи блога .)

Для действия, которое должно быть контекстно включено, используйте Actions.context из вашего слоя (строка 2 ниже):

 <file name="action-pkg-ClassName.instance">
<attr name="instanceCreate" methodvalue="org.openide.awt.Actions.context"/>
<attr name="type" stringvalue="org.netbeans.api.actions.Openable"/>
<attr name="selectionType" stringvalue="ANY"/> &lt-- or EXACTLY_ONE -->
<attr name="delegate" newvalue="action.pkg.YourAction"/>

<!--
Similar registration like in case of "callback" action.
May be missing completely:
-->
<attr name="key" stringvalue="KeyInActionMap"/>
<attr name="surviveFocusChange" boolvalue="false"/>
<attr name="displayName" bundlevalue="your.pkg.Bundle#key"/>
<attr name="iconBase" stringvalue="your/pkg/YourImage.png"/>
<!-- if desired: <attr name="noIconInMenu" boolvalue="true"/> -->
<!-- if desired: <attr name="asynchronous" boolvalue="true"/> -->
</file>

«Делегатом», зарегистрированным для Actions.alwaysEnabled и Actions.context, может быть любой ActionListener (через атрибут «newvalue») или метод в ActionListener (через атрибут «methodvalue»). Когда вы используете мастер New Action в IDE, создается ActionListener вместе с соответствующими записями слоя (как указано выше) для фабричного метода Actions. *, Относящегося к типу Action, который вы выбрали для создания.

Обычно все метаданные, такие как имя и значок, могут быть выражены в слое («displayName» и «iconBase»), но если вам нужно, например, динамически изменить отображаемое имя, сделайте «делегат» полным действием, вместо ActionListener. Затем, после первого выбора пункта меню, который загружает делегата, любые последующие изменения в метаданных действия, даже в состоянии включения, будут отображаться в обычном режиме.

Другое преимущество состоит в том, что описанные выше фабричные методы Actions. * Могут быть объявлены асинхронными, как описано здесь и продемонстрировано здесь .

Вы можете, как всегда, зарегистрировать любое действие напрямую (без использования фабричных методов Actions. *), Но тогда оно будет загружено. Это иногда необходимо, например, если ваше действие должно внести некоторые пользовательские изменения в статус включения, имя и т. Д., Даже если оно еще не было вызвано. Но такие регистрации немного замедляют запуск, поэтому их следует избегать, если это вообще возможно.

Устаревшие? Не совсем

Классы, основанные на SystemAction, как описано выше, больше не рекомендуется использовать. Тем не менее, они не были объявлены устаревшими. Почему это так? Поскольку в дополнение к тому, что они по-прежнему используются в IDE NetBeans, как указано выше, в соответствии с политикой совместимости NetBeans невозможно исключить то, что не имеет замены на 100%. Фабричные методы Actions. * Можно считать заменой на 95%. Вот две вещи, которые вы не можете сделать с этими фабричными методами, которые возможны с классами на основе SystemAction:

  • Мелкозернистая контекстная чувствительность. Прямо сейчас с помощью фабричных методов Actions. * Action можно включить контекстно-зависимо, основываясь на присутствии определенного объекта, как описано здесь , например. Но что, если вы хотите, чтобы включение было более детализированным, например, вам нужно свойство в объекте, чтобы определить, включено ли действие. Это не поддерживается фабричными методами Actions. *. Если вам нужно это поведение, вы можете напрямую работать с классом CookieAction и использовать его метод «enable (Node [])», чтобы обеспечить более детальный подход, который вам необходим в этом случае.
  • Несколько объектов в контексте. Если вы используете CookieAction, вы можете указать несколько объектов, которые должны быть доступны для включения действия. Например, вы можете указать, что только при наличии «SaveCapability» и «SelectCapability» действие будет активировано. Однако при использовании фабричных методов Actions. * Это невозможно. Вы можете требовать, чтобы только один объект (как видно из записей слоя выше) был доступен для включения действия.

Помимо действия Cookie

Имейте в виду, однако, что использование CookieActions не единственный способ реализовать два вышеупомянутых сценария. Вы также можете реализовать их с помощью простого действия, зарегистрированного непосредственно в вашем слое, т. Е. Без использования фабричных методов Actions. *. Посмотрите сценарий « Roll Your Own », как пример. В качестве другого примера, модуль «projectui» в NetBeans регистрирует действия с довольно сложной семантикой: «Запуск проекта» включается, если в текущем выделении есть основной проект, или точно один проект, или только один открытый проект, и (в каждом случае ) если этот проект имеет ActionProvider, который поддерживает COMMAND_RUN. Он также корректирует свое отображаемое имя, когда оно представлено в виде пункта меню в соответствии с выбором.

В чем недостаток этого подхода? Ну, вы не получаете ленивую загрузку Action, но это также верно в отношении использования CookieAction. Основная причина использования CookieAction для такого рода вещей заключается в том, что было бы проще реализовать использование такого подкласса, чем с нуля, по крайней мере для редкого второго сценария, описанного выше, т. Е. Сценария «множественных объектов». Реализация первого сценария, т. Е. «Мелкозернистого», более правдоподобна, но также труднее сделать правильно даже с CookieAction, поскольку, хотя вы можете переопределить «enable (Node [])» для выполнения дополнительных проверок, вы также нужно прикрепить и отсоединить слушателей в нужное время. 

Предстоящие улучшения

На самом деле есть предложенная замена для этих более экзотических сценариев, которые облегчили бы их написание, но, поскольку он еще не прошел рецензирование, он в настоящее время экспортируется только в несколько дружественных модулей. В то же время вы можете реализовать любое поведение, которое, по вашему мнению, вам нужно, в глобальном элементе меню или кнопке панели инструментов, используя действие Utilities.actionsGlobalContext () и следя за тем, чтобы обновлять свое состояние, используя правильную цепочку слушателей.

Реализовать элементы контекстного меню со сложной семантикой включения намного проще: если вы — ContextAwareAction, делегату с учетом контекста не нужно ничего слушать, поскольку он создается непосредственно перед использованием, а затем отбрасывается. В 6.9 также легко использовать DynamicMenuModel.HIDE_WHEN_DISABLED, чтобы пункт контекстного меню исчезал, а не отображался серым цветом.

Вывод

Как правило, разумнее не использовать сложную логику включения в глобальных докладчиках. Используйте некоторую простую логику, поддерживаемую Actions. *, Вместо того, чтобы делать действие включенным, а не нет, и если «actionPerformed» вызывается в неподходящем контексте, просто подайте звуковой сигнал или отобразите информативное диалоговое окно с сообщением об ошибке. Производительность будет лучше, и пользователям не придется гадать, почему пункт меню иногда отключается без видимой причины. 

Спасибо Джесси Глику и Ярославу Тулачу, которые предоставили большую часть текста выше.