Статьи

Создание пользовательских компонентов контекстного меню в Flex

Больше сообщений Flex можно найти на blog.flexdevelopers.com



Предоставляя возможность щелкнуть правой кнопкой мыши (или удерживая клавишу Ctrl на Mac) элемент списка и запустить действие,
контекстное меню представляет собой удобную встроенную функцию элемента управления List. Однако, когда содержимое и поведение контекстного меню в разных частях вашего приложения различаются, ваш код может быстро выйти из-под контроля.


Примечание: не только список имеет контекстное меню, но и любой компонент Flex, который расширяет класс InteractiveObject.

В этой записи блога я столкнулся с проблемой совместного использования настраиваемого компонента List в приложении, одновременно предоставляя другим разработчикам возможность изменять контекстное меню, не создавая кучу не поддерживаемого
кода спагетти .



Чтобы настроить контекстное меню Списка, наиболее распространенный подход, используемый неопытными разработчиками Flex, нарушает принцип открытого / закрытого ООП
и включает добавление кода непосредственно в пользовательский класс List.

Вот пользовательский компонент List (SuperList) перед любыми изменениями в контекстном меню:


Обязательно прочитайте комментарии в фрагментах кода.
Они могут дать некоторые разъяснения.

// SuperList.as

package components
{
 import spark.components.List;
 
 /**
  * This isn't your ordinary List control. However, it's not very special
  * outside of the canDeleteItems property. In practice, you may have a 
  * List with a bunch of fancy features and different parts of your 
  * application may want to use this fancy List component while customizing 
  * the context menu.
  */
 
 public class SuperList extends List
 { 
  private var _canDeleteItems:Boolean = false;
  
  public function get canDeleteItems():Boolean
  {
   return _canDeleteItems;
  }
  
  public function set canDeleteItems(value:Boolean):void
  {
   _canDeleteItems = value;
  }
    
  public function SuperList()
  {
   super();
   allowMultipleSelection = true;
   doubleClickEnabled = true;
   labelField = "name";
  }  
 }
}

Вот SuperList после того, как один разработчик решил, что ему нужно добавить
Enable ,
Disable и
Delete в контекстное меню:

package components
{
 import flash.events.ContextMenuEvent;
 import flash.events.Event;
 import flash.ui.ContextMenu;
 import flash.ui.ContextMenuItem;
 
 import spark.components.List;
 
 public class SuperList extends List
 { 
  private var _canDeleteItems:Boolean = false;
  
  public function get canDeleteItems():Boolean
  {
   return _canDeleteItems;
  }
  
  public function set canDeleteItems(value:Boolean):void
  {
   _canDeleteItems = value;
  }
    
  public function SuperList()
  {
   super();
   allowMultipleSelection = true;
   doubleClickEnabled = true;
   labelField = "name";
   createContextMenu();
  }
  
  protected function createContextMenu():void
  {
   contextMenu = new ContextMenu();
   contextMenu.hideBuiltInItems();
   
   var enableMenu:ContextMenuItem = new ContextMenuItem("");
   enableMenu.caption = "Enable item...";
   enableMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onEnableSelected);
   
   var disableMenu:ContextMenuItem = new ContextMenuItem("");
   disableMenu.caption = "Disable item..."
   disableMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onDisableSelected);
   
   var deleteMenu:ContextMenuItem = new ContextMenuItem("");
   deleteMenu.caption = "Delete item...";
   deleteMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onDeleteSelected);
   
   contextMenu.customItems.push(enableMenu);
   contextMenu.customItems.push(disableMenu);
   contextMenu.customItems.push(deleteMenu);    
  }
    
  protected function contextMenu_onEnableSelected(event:ContextMenuEvent):void
  {
   dispatchEvent(new Event("enable"));
  }
  
  protected function contextMenu_onDisableSelected(event:ContextMenuEvent):void
  {
   dispatchEvent(new Event("disable"));
  }
  
  protected function contextMenu_onDeleteSelected(event:ContextMenuEvent):void
  {
   dispatchEvent(new Event("delete"));
  }
 }
}

Это прекрасное решение, ЕСЛИ для каждого экземпляра SuperList требуется
пункт контекстного меню «
Включить ,
отключить и
удалить» . К сожалению, эти пункты контекстного меню не применяются в большинстве случаев, и появляется ошибка. Вернуться к работе для вашего неопытного разработчика …

package components
{
 import flash.events.ContextMenuEvent;
 import flash.events.Event;
 import flash.ui.ContextMenu;
 import flash.ui.ContextMenuItem;
 
 import mx.events.FlexEvent;
 
 import spark.components.List;
 
 public class SuperList extends List
 { 
  // developer adds a public variable to turn his context menu off by default
  public var showContextMenu:Boolean = false; 
  
  private var _canDeleteItems:Boolean = false;
  
  public function get canDeleteItems():Boolean
  {
   return _canDeleteItems;
  }
  
  public function set canDeleteItems(value:Boolean):void
  {
   _canDeleteItems = value;
  }
  
  public function SuperList()
  {
   super();
   allowMultipleSelection = true;
   doubleClickEnabled = true;
   labelField = "name";
   /* we can't use the constructor anymore to build the context menu and junior developers 
      that don't understand the flex component lifecycle love the creationComplete event */
   addEventListener(FlexEvent.CREATION_COMPLETE, onCreationComplete);
  }
  
  protected function onCreationComplete(event:FlexEvent):void
  {
   // build the context menu based on the value of the showContextMenu variable
   if (showContextMenu)
    createContextMenu();
  }
  
  protected function createContextMenu():void
  {
   contextMenu = new ContextMenu();
   contextMenu.hideBuiltInItems();
   
   var enableMenu:ContextMenuItem = new ContextMenuItem("");
   enableMenu.caption = "Enable item...";
   enableMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onEnableSelected);
   
   var disableMenu:ContextMenuItem = new ContextMenuItem("");
   disableMenu.caption = "Disable item..."
   disableMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onDisableSelected);
   
   var deleteMenu:ContextMenuItem = new ContextMenuItem("");
   deleteMenu.caption = "Delete item...";
   deleteMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onDeleteSelected);
   
   contextMenu.customItems.push(enableMenu);
   contextMenu.customItems.push(disableMenu);
   contextMenu.customItems.push(deleteMenu);    
  }
  
  protected function contextMenu_onEnableSelected(event:ContextMenuEvent):void
  {
   dispatchEvent(new Event("enable"));
  }
  
  protected function contextMenu_onDisableSelected(event:ContextMenuEvent):void
  {
   dispatchEvent(new Event("disable"));
  }
  
  protected function contextMenu_onDeleteSelected(event:ContextMenuEvent):void
  {
   dispatchEvent(new Event("delete"));
  }
 }
}

Теперь он может «активировать» свое контекстное меню, не затрагивая другой экземпляр SuperList, используя новое свойство showContextMenu.

<components:SuperList dataProvider="{data}"
         canDeleteItems="true"
         showContextMenu="true"/>

Этот код работает, поэтому он остается в вашей кодовой базе довольно долго, пока следующий разработчик не захочет создать собственное контекстное меню. Заглянув внутрь класса SuperList, он видит работу предыдущего разработчика и, желая вернуться в Facebook как можно скорее, применяет быстрое решение. Беспорядок начинается …

package components
{
 import flash.events.ContextMenuEvent;
 import flash.events.Event;
 import flash.ui.ContextMenu;
 import flash.ui.ContextMenuItem;
 
 import mx.events.FlexEvent;
 
 import spark.components.List;
 
 public class SuperList extends List
 { 
  /* 2nd developer changes this variable to a String that he can key off of
     to determine which context menu to show. No context menu by default */
  public var contextMenuType:String = "none"; 
  
  private var _canDeleteItems:Boolean = false;
  
  public function get canDeleteItems():Boolean
  {
   return _canDeleteItems;
  }
  
  public function set canDeleteItems(value:Boolean):void
  {
   _canDeleteItems = value;
  }
  
  public function SuperList()
  {
   super();
   allowMultipleSelection = true;
   doubleClickEnabled = true;
   labelField = "name";
   addEventListener(FlexEvent.CREATION_COMPLETE, onCreationComplete);
  }
  
  protected function onCreationComplete(event:FlexEvent):void
  {
   // key off of the value of contextMenuType to determine which context menu to show if any
   if (contextMenuType == "foo")
   {
    createFooContextMenu();
   }
   else if (contextMenuType == "bar")
   {
    createBarContextMenu();
   }
  }
  
  protected function createFooContextMenu():void
  {
   contextMenu = new ContextMenu();
   contextMenu.hideBuiltInItems();
   
   var enableMenu:ContextMenuItem = new ContextMenuItem("");
   enableMenu.caption = "Enable item...";
   enableMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onEnableSelected);
   
   var disableMenu:ContextMenuItem = new ContextMenuItem("");
   disableMenu.caption = "Disable item..."
   disableMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onDisableSelected);
   
   var deleteMenu:ContextMenuItem = new ContextMenuItem("");
   deleteMenu.caption = "Delete item...";
   deleteMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onDeleteSelected);
   
   contextMenu.customItems.push(enableMenu);
   contextMenu.customItems.push(disableMenu);
   contextMenu.customItems.push(deleteMenu);    
  }
  
  protected function createBarContextMenu():void
  {
   contextMenu = new ContextMenu();
   contextMenu.hideBuiltInItems();
   
   var activateMenu:ContextMenuItem = new ContextMenuItem("");
   activateMenu.caption = "Activate item...";
   activateMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onActivateSelected);
   
   var deactivateMenu:ContextMenuItem = new ContextMenuItem("");
   deactivateMenu.caption = "Deactivate item..."
   deactivateMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onDeactivateSelected);
   
   var deleteMenu:ContextMenuItem = new ContextMenuItem("");
   deleteMenu.caption = "Delete item...";
   deleteMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onDeleteSelected);
   
   contextMenu.customItems.push(activateMenu);
   contextMenu.customItems.push(deactivateMenu);
   contextMenu.customItems.push(deleteMenu);    
  }
  
  protected function contextMenu_onEnableSelected(event:ContextMenuEvent):void
  {
   dispatchEvent(new Event("enable"));
  }
  
  protected function contextMenu_onDisableSelected(event:ContextMenuEvent):void
  {
   dispatchEvent(new Event("disable"));
  }
  
  protected function contextMenu_onActivateSelected(event:ContextMenuEvent):void
  {
   dispatchEvent(new Event("activate"));
  }
  
  protected function contextMenu_onDeactivateSelected(event:ContextMenuEvent):void
  {
   dispatchEvent(new Event("deactivate"));
  }
  
  protected function contextMenu_onDeleteSelected(event:ContextMenuEvent):void
  {
   dispatchEvent(new Event("delete"));
  }
 }
}

При установке значения нового свойства contextMenuType в SuperList отображается соответствующее контекстное меню.

<components:SuperList dataProvider="{data}"
         canDeleteItems="true"
         contextMenuType="foo"/>

Мне действительно нужно показать вам, как это получается? Через 2 года, 5 разработчиков и 10 запросов на изменение, SuperList содержит 1500 строк кода. Большая часть этого кода относится к одному небольшому аспекту компонента SuperList — контекстному меню. Истинная цель SuperList была потеряна.

Лучший путь

Два метода могут использоваться, чтобы изолировать пользовательское поведение каждого пользовательского контекстного меню. Первый метод очень распространен и включает в себя создание подклассов SuperList. Другой метод придерживается
принципа ООП, заключающегося в предпочтении композиции наследованию .

Использование наследования для создания пользовательского контекстного меню

Небольшое изменение SuperList требуется для использования наследования …

package components
{
 import spark.components.List;
 
 /**
  * This isn't your ordinary List control. However, it's not very special
  * outside of the canDeleteItems property. In practice, you may have a 
  * List with a bunch of fancy features and different parts of your 
  * application may want to use this fancy List component while customizing 
  * the context menu.
  */
 
 public class SuperList extends List
 { 
  private var _canDeleteItems:Boolean = false;
  
  public function get canDeleteItems():Boolean
  {
   return _canDeleteItems;
  }
  
  public function set canDeleteItems(value:Boolean):void
  {
   _canDeleteItems = value;
  }
  
  public function SuperList()
  {
   super();
   allowMultipleSelection = true;
   doubleClickEnabled = true;
   labelField = "name";
   createContextMenu();
  }  
  
  protected function createContextMenu():void
  {
   // If you use SuperList, you'll get no context menu which may be OK
   // but if you want a context menu, extend SuperList and create one
   // by overriding this method
   return;
  }
 }
}

Создайте пользовательское контекстное меню для SuperList, расширив класс и переопределив метод createContextMenu.

package components
{
 import flash.events.ContextMenuEvent;
 import flash.events.Event;
 import flash.ui.ContextMenu;
 import flash.ui.ContextMenuItem;
 
 /**
  * This subclass only has one purpose - to create a context menu that
  * dispatches events based on what context menu item is selected
  */

 [Event(name = "enable")]
 [Event(name = "disable")]
 [Event(name = "delete")]
 
 public class MySuperList extends SuperList
 {
  public static const ENABLE:String = "enable";
  public static const DISABLE:String = "disable";
  public static const DELETE:String = "delete";
  
  public function MySuperList()
  {
   super();
  }
  
  override protected function createContextMenu():void
  {
   contextMenu = new ContextMenu();
   contextMenu.hideBuiltInItems();
   
   var enableMenu:ContextMenuItem = new ContextMenuItem("");
   enableMenu.caption = "Enable item...";
   enableMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onEnableSelected);
   
   var disableMenu:ContextMenuItem = new ContextMenuItem("");
   disableMenu.caption = "Disable item..."
   disableMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onDisableSelected);
   
   var deleteMenu:ContextMenuItem = new ContextMenuItem("");
   deleteMenu.caption = "Delete item...";
   deleteMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onDeleteSelected);
   
   contextMenu.customItems.push(enableMenu);
   contextMenu.customItems.push(disableMenu);
   contextMenu.customItems.push(deleteMenu);    
  }
  
  protected function contextMenu_onEnableSelected(event:ContextMenuEvent):void
  {
   dispatchEvent(new Event(ENABLE));
  }
  
  protected function contextMenu_onDisableSelected(event:ContextMenuEvent):void
  {
   dispatchEvent(new Event(DISABLE));
  }
  
  protected function contextMenu_onDeleteSelected(event:ContextMenuEvent):void
  {
   dispatchEvent(new Event(DELETE));
  }
 }
}

Теперь вы можете использовать простой старый SuperList, у которого нет контекстного меню …

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
      xmlns:s="library://ns.adobe.com/flex/spark" 
      xmlns:mx="library://ns.adobe.com/flex/mx"
      xmlns:components="components.*">
 
 <fx:Script>
  <![CDATA[
   import mx.collections.ArrayCollection;
   
   [Bindable]
   private var _data1:ArrayCollection = new ArrayCollection([{name: "foo"}, {name: "bar"}, {name: "moo"}, {name: "cow"}]);   
  ]]>
 </fx:Script>
  
 <components:SuperList id="superList" 
        width="200"
        canDeleteItems="true"
        dataProvider="{_data1}"/>

</s:Application>

… или вы можете использовать подкласс с контекстным меню в соответствии с вашими потребностями …

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
      xmlns:s="library://ns.adobe.com/flex/spark" 
      xmlns:mx="library://ns.adobe.com/flex/mx"
      xmlns:components="components.*">
 
 <fx:Script>
  <![CDATA[
   import mx.collections.ArrayCollection;
   import mx.controls.Alert;
   
   [Bindable]
   private var _data1:ArrayCollection = new ArrayCollection([{name: "foo"}, {name: "bar"}, {name: "moo"}, {name: "cow"}]);   

   protected function mySuperList_enableHandler(event:Event):void
   {
    Alert.show("Enable " + mySuperList.selectedItem.name);
   }

  ]]>
 </fx:Script>
    
 <components:MySuperList id="mySuperList"
       width="200"
       canDeleteItems="false"
       dataProvider="{_data1}"
       enable="mySuperList_enableHandler(event)"/> 

</s:Application>

Когда требуется другое контекстное меню с другим поведением, SuperList может быть снова расширен …

package components
{
 import flash.events.ContextMenuEvent;
 import flash.ui.ContextMenu;
 import flash.ui.ContextMenuItem;
 
 import mx.controls.Alert;
 
 /**
  * This subclass is built a little different. It has different context menu items
  * than MySuperList and it doesn't dispatch events. Instead it acts on the context
  * menu item selection immediately. 
  */
 
 public class YourSuperList extends SuperList
 {  
  public function YourSuperList()
  {
   super();
  }
  
  override protected function createContextMenu():void
  {
   contextMenu = new ContextMenu();
   contextMenu.hideBuiltInItems();
   
   var activateMenu:ContextMenuItem = new ContextMenuItem("");
   activateMenu.caption = "Activate item...";
   activateMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onActivateSelected);
   
   var deactivateMenu:ContextMenuItem = new ContextMenuItem("");
   deactivateMenu.caption = "Deactivate item..."
   deactivateMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onDeactivateSelected);
   
   var deleteMenu:ContextMenuItem = new ContextMenuItem("");
   deleteMenu.caption = "Delete item...";
   deleteMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onDeleteSelected);
   
   contextMenu.customItems.push(activateMenu);
   contextMenu.customItems.push(deactivateMenu);
   contextMenu.customItems.push(deleteMenu);    
  }
  
  protected function contextMenu_onActivateSelected(event:ContextMenuEvent):void
  {
   Alert.show("Activate " + selectedItem.name);
  }
  
  protected function contextMenu_onDeactivateSelected(event:ContextMenuEvent):void
  {
   Alert.show("Deactivate " + selectedItem.name);
  }
  
  protected function contextMenu_onDeleteSelected(event:ContextMenuEvent):void
  {
   Alert.show("Delete " + selectedItem.name);
  }
 }
}

… и используется как таковой …

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
      xmlns:s="library://ns.adobe.com/flex/spark" 
      xmlns:mx="library://ns.adobe.com/flex/mx"
      xmlns:components="components.*">
 
 <fx:Script>
  <![CDATA[
   import mx.collections.ArrayCollection;
   
   [Bindable]
   private var _data1:ArrayCollection = new ArrayCollection([{name: "foo"}, {name: "bar"}, {name: "moo"}, {name: "cow"}]);   
  ]]>
 </fx:Script>
     
 <components:YourSuperList id="yourSuperList"
       width="200"
       canDeleteItems="false"
       dataProvider="{_data1}"/>
 
</s:Application>

Использование композиции для создания пользовательского контекстного меню

Для меня это кажется пустой тратой на расширение SuperList только для добавления пользовательского контекстного меню. Вместо этого я бы предпочел «внедрить» объект в SuperList и делегировать обязанности контекстного меню этому объекту. Походит на
образец Стратегии , правильно?

Для этого я начну с создания интерфейса. Все, что реализует этот интерфейс, будет содержать контекстное меню.

package interfaces
{
 import flash.ui.ContextMenu;
 
 /**
  * This interface guarantees that anything that 
  * implements this interface is composed of a contextmenu 
  */
 public interface ICustomContextMenu
 {  
  function get contextMenu():ContextMenu 
 }
}

Опять же, небольшая модификация SuperList требуется. Сеттер используется для добавления пользовательского контекстного меню в SuperList. Этот установщик принимает любой объект, который реализует интерфейс ICustomContextMenu, определенный ранее. Это гарантирует, что внедренный объект содержит контекстное меню. SuperList будет использовать это контекстное меню как свое собственное. Давайте рассмотрим изменения в SuperList.

package components
{
 import interfaces.ICustomContextMenu;
 
 import mx.collections.ArrayCollection;
 
 import spark.components.List;
 
 /**
  * This isn't your ordinary List control. However, it's not very special
  * outside of the canDeleteItems property and the ability to accept a custom
  * context menu. However, in practice, you may have a List with a bunch 
  * of fancy features and different parts of your application may want to 
  * use this fancy List component while customizing the context menu.
  */
 
 public class SuperList extends List
 { 
  private var _canDeleteItems:Boolean = false;
  
  public function get canDeleteItems():Boolean
  {
   return _canDeleteItems;
  }
  
  public function set canDeleteItems(value:Boolean):void
  {
   _canDeleteItems = value;
  }
  
  private var _customContextMenu:ICustomContextMenu;
  private var _customContextMenuChanged:Boolean = false;
  
  public function set customContextMenu(value:ICustomContextMenu):void
  {
   // pass in an ICustomContextMenu anytime you like and SuperList will use its context menu
   _customContextMenu = value;
   _customContextMenuChanged = true;
   invalidateProperties();
  }
  
  public function SuperList()
  {
   super();
   allowMultipleSelection = true;
   doubleClickEnabled = true;
   labelField = "name";
  }
  
  override protected function commitProperties():void
  {
   super.commitProperties();
   
   if (_customContextMenuChanged)
   {
    contextMenu = _customContextMenu.contextMenu;
    _customContextMenuChanged = false;
   }
  }
  
 }
}

Далее я создам собственный класс для каждого пользовательского контекстного меню. Этот класс должен реализовывать интерфейс ICustomContextMenu, определенный ранее, чтобы он содержал контекстное меню и мог использоваться с SuperList.

// MyCustomContextMenu.as

package components.contextMenus
{
 import components.SuperList;
 
 import flash.events.ContextMenuEvent;
 import flash.events.Event;
 import flash.ui.ContextMenu;
 import flash.ui.ContextMenuItem;
 
 import interfaces.ICustomContextMenu;
 
 /**
  * This class implements ICustomContextMenu therefore guaranteeing
  * the existence of a context menu. However, we can customize the 
  * context menu as we please. We can add whatever ContextMenuItems
  * we like and we can handle clicks however we please.
  */
 public class MyCustomContextMenu implements ICustomContextMenu
 {
  public static const ENABLE:String = "enable";
  public static const DISABLE:String = "disable";
  public static const DELETE:String = "delete";

  private var _owner:SuperList; // with this I can dispatch events targeted at the SuperList
  
  private var _enableMenu:ContextMenuItem;
  private var _disableMenu:ContextMenuItem;
  private var _deleteMenu:ContextMenuItem;
 
  private var _contextMenu:ContextMenu;
  
  public function get contextMenu():ContextMenu
  {
   // this satisfies the interface requirements
   return _contextMenu;
  }
  
  public function MyCustomContextMenu(owner:SuperList)
  {
   super();
   
   _owner = owner;
   _contextMenu = new ContextMenu();
   
   _contextMenu.hideBuiltInItems();
   
   _enableMenu = new ContextMenuItem("");
   _enableMenu.caption = "Enable item...";
   _enableMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onEnableSelected);
   
   _disableMenu = new ContextMenuItem("");
   _disableMenu.caption = "Disable item..."
   _disableMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onDisableSelected);
   
   _deleteMenu = new ContextMenuItem("");
   _deleteMenu.caption = "Delete item...";
   _deleteMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onDeleteSelected);
   
   _contextMenu.customItems.push(_enableMenu);
   _contextMenu.customItems.push(_disableMenu);
   _contextMenu.customItems.push(_deleteMenu);
   
   _contextMenu.addEventListener(ContextMenuEvent.MENU_SELECT, contextMenu_onMenuSelect);
  }
  
  public function contextMenu_onMenuSelect(event:ContextMenuEvent):void
  {
   _enableMenu.enabled = true;
   _disableMenu.enabled = true;
   // this property (canDeleteItems) of the SuperList is being used by the context menu 
   _deleteMenu.enabled = _owner.canDeleteItems;
  }
  
  protected function contextMenu_onEnableSelected(event:ContextMenuEvent):void
  {
   _owner.dispatchEvent(new Event(ENABLE));
  }
  
  protected function contextMenu_onDisableSelected(event:ContextMenuEvent):void
  {
   _owner.dispatchEvent(new Event(DISABLE));
  }
  
  protected function contextMenu_onDeleteSelected(event:ContextMenuEvent):void
  {
   _owner.dispatchEvent(new Event(DELETE));
  }
 }
}

Мы можем создать другое пользовательское контекстное меню с его собственным уникальным поведением, если оно реализует ICustomContextMenu.

// YourCustomContextMenu.as 

package components.contextMenus
{
 import flash.events.ContextMenuEvent;
 import flash.ui.ContextMenu;
 import flash.ui.ContextMenuItem;
 
 import interfaces.ICustomListContextMenu;
 
 import mx.controls.Alert;
 
 /**
  * This class implements ICustomContextMenu therefore guaranteeing
  * the existence of a context menu. However, we can customize the 
  * context menu as we please. We can add whatever ContextMenuItems
  * we like and we can handle clicks however we please.
  */
 public class YourCustomContextMenu implements ICustomListContextMenu
 {  
  private var _activateMenu:ContextMenuItem;
  private var _deactivateMenu:ContextMenuItem;
  private var _deleteMenu:ContextMenuItem;
 
  private var _contextMenu:ContextMenu;
  
  public function get contextMenu():ContextMenu
  {
   return _contextMenu;
  }
  
  public function YourCustomContextMenu()
  {
   super();
   
   _contextMenu = new ContextMenu();
   
   _contextMenu.hideBuiltInItems();
   
   _activateMenu = new ContextMenuItem("");
   _activateMenu.caption = "Activate item...";
   _activateMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onActivateSelected);
   
   _deactivateMenu = new ContextMenuItem("");
   _deactivateMenu.caption = "Deactivate item..."
   _deactivateMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onDeactivateSelected);
   
   _deleteMenu = new ContextMenuItem("");
   _deleteMenu.caption = "Delete item...";
   _deleteMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onDeleteSelected);
   
   _contextMenu.customItems.push(_activateMenu);
   _contextMenu.customItems.push(_deactivateMenu);
   _contextMenu.customItems.push(_deleteMenu);
  }
    
  protected function contextMenu_onActivateSelected(event:ContextMenuEvent):void
  {
   Alert.show("Item Activated");
  }
  
  protected function contextMenu_onDeactivateSelected(event:ContextMenuEvent):void
  {
   Alert.show("Item Deactivated");
  }
  
  protected function contextMenu_onDeleteSelected(event:ContextMenuEvent):void
  {
   Alert.show("Item Deleted");
  }
 }
}

Наконец, мы можем добавить любое пользовательское контекстное меню (если оно есть) в наш новый улучшенный SuperList:

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
      xmlns:s="library://ns.adobe.com/flex/spark" 
      xmlns:mx="library://ns.adobe.com/flex/mx"
      xmlns:components="components.*">
 
 <fx:Script>
  <![CDATA[
   import components.contextMenus.MyCustomContextMenu;
   import components.contextMenus.YourCustomContextMenu;
   
   import mx.collections.ArrayCollection;
   
   [Bindable]
   private var _data:ArrayCollection = new ArrayCollection([{name: "foo"}, {name: "bar"}, {name: "moo"}, {name: "cow"}]);
  ]]>
 </fx:Script>
 
 <s:layout>
  <s:VerticalLayout/>
 </s:layout>
  
 <components:SuperList id="superListWithMyCustomContextMenu" 
        width="200"
        canDeleteItems="true"
        dataProvider="{_data}"
        customContextMenu="{new MyCustomContextMenu(superListWithMyCustomContextMenu)}"/>
  
 <components:SuperList id="superListWithYourCustomContextMenu" 
        width="200"
        canDeleteItems="false"
        dataProvider="{_data}"
        customContextMenu="{new YourCustomContextMenu()}"/>
  
 <components:SuperList id="superListWithNoCustomContextMenu" 
        width="200"
        canDeleteItems="false"
        dataProvider="{_data}"/>
 
</s:Application>