Menu Popup MenuItem

Для создания главного меню ("main menu") или всплывающего окна в java-приложении с использованием SWT используется класс Menu. В качестве параметров конструктору класса Menu необходимо передать окно приложения Shell и стиль. В зависимости от значения стиля [SWT.BAR | SWT.POP_UP] меню может иметь вид "main menu" или вид "всплывающего окна" (контекстное меню).

// Создание main menu 
Menu menuBar = new Menu (shell, SWT.BAR);
// Создание контекстного меню в виде "всплывающего окна"
Menu popup = new Menu (shell, SWT.POP_UP);

В тексте страницы рассматривается пример MenuExample.java создания различных видов меню. Исходный код примера меню можно скачать здесь.

Главное меню, shell main menu

Для создания главного меню ("main menu") необходимо в конструкторе Menu использовать стиль SWT.BAR. В следующем коде создается "main menu" с двумя пунктами ("File", "Edit").

Menu menuBar = new Menu (shell, SWT.BAR);
shell.setMenuBar (menuBar);

MenuItem fileItem = new MenuItem (menuBar, SWT.CASCADE);
fileItem.setText ("File");
	
MenuItem editItem = new MenuItem(menuBar, SWT.CASCADE);
editItem.setText("Edit");

Пунктам главного меню MenuItem в качестве параметра в конструктор передается родительский объект Menu и устанавливается стиль SWT.CASCADE. Стиль SWT.CASCADE определяет раскрывающийся список подпунктов main меню.

Меню с акселераторами

В следующем коде для меню "File" создаются подпункты меню "Select All" и "Exit". Для этого формируется меню submenu со стилем SWT.DROP_DOWN, который подключается к пункту меню fileitem. К каждому подпункту меню "menu item" подключены акселераторы setAccelerator, позволяющие вызвать соответcтвующий MenuItem горячими клавишами.

// Подпункты меню
Menu submenu = new Menu (shell, SWT.DROP_DOWN);
fileItem.setMenu (submenu);

MenuItem item = new MenuItem (submenu, SWT.PUSH);
item.setText ("Select All\tCtrl+A");
item.setAccelerator (SWT.MOD1 + 'A');
item.addListener (SWT.Selection, new Listener () {
    @Override
    public void handleEvent (Event e) {
        System.out.println ("Select All");
    }
});
MenuItem item2 = new MenuItem (submenu, SWT.PUSH);
item2.setText ("Exit\tCtrl+Q");
item2.setAccelerator (SWT.MOD1 + 'Q');
item2.addListener (SWT.Selection, new Listener () {
    @Override
    public void handleEvent (Event e) {
        System.exit(0);
    }
});

На следующем скриншоте представлен интерфейс формы с открытыми пунктами меню.

Меню с обработчиками выделения

В следующем примере создаются подпункты меню, к которым подключаются обработчики выделения. При выделение меню "Edit" или одного из его подпунктов в статусной строке формы отображается наименование пункта меню.

// Список подпунктов меню "Edit"
String[] editStrings = { "Cut", "Copy", "Paste" };
// Менеджер статусной строки
private StatusbarManager  statusbar ;
...

// Создание статусной строки 
statusbar = new StatusbarManager(shell);
statusbar.setItems(new String[][]{{"left", "-1", "label"}});

// Обработчик выделение пункта меню
Listener armListener = new Listener() {
    @Override
    public void handleEvent(Event event) {
        MenuItem item = (MenuItem) event.widget;
        if (item != null)
            statusbar.setText(0, item.getText());
    }
};
// Обработчик представления пункта меню
Listener showListener = new Listener() {
    @Override
    public void handleEvent(Event event) {
        Menu menu = (Menu) event.widget;
        MenuItem item = menu.getParentItem();
        if (item != null)
            statusbar.setText(0, item.getText());;
    }
};
// Обработчик скрытия пункта меню
Listener hideListener = new Listener() {
    @Override
    public void handleEvent(Event event) {
        statusbar.setText(0, "");
    }
};

// Раздел меню «Edit»
Menu editMenu = new Menu(shell, SWT.DROP_DOWN);
// Подключение обработчиков
editMenu.addListener(SWT.Hide, hideListener);
editMenu.addListener(SWT.Show, showListener);
editItem.setMenu(editMenu);
// Создание подпунктов меню
for (int i = 0; i < editStrings.length; i++) {
    item = new MenuItem(editMenu, SWT.PUSH);
    item.setText(editStrings[i]);
    item.addListener(SWT.Arm, armListener);
}

В примере используется менеджер статусной строки StatusbarManager, полное описание и пример использование которого можно увидеть на странице Статусная строка StatusbarManager.

На скриншоте представлен интерфейс формы с открытыми пунктами меню.

Всплывающие окна, shell menu popup

Для создания всплывающего окна (menu popup) необходимо в конструкторе Menu использовать стиль SWT.POP_UP. В следующем коде создается всплывающее меню, которое подключается к окну приложения Shell. Всплывающее окно (меню) с одним пунктом вызывается нажатием на правую клавишу мыши.

// Подключение к shell всплывающего меню
shell.addListener (SWT.MenuDetect, new Listener () {
    @Override
    public void handleEvent (Event event) {
        Menu menu = new Menu (shell, SWT.POP_UP);
        MenuItem item = new MenuItem (menu, SWT.PUSH);
        item.setText ("Menu Item (shell)");
        item.addListener (SWT.Selection, new Listener () {
            @Override
            public void handleEvent (Event e) {
                System.out.println ("Popup menu of shell is selected");
            }
        });
        // Позиционирование всплывающего окна
        menu.setLocation (event.x, event.y);
        menu.setVisible (true);
        while (!menu.isDisposed () && menu.isVisible ()) {
            if (!display.readAndDispatch ()) 
                display.sleep ();
        }
        while (display.readAndDispatch());
                menu.dispose ();
        }
    });

Всплывающее окно позиционируется по месту положения указателя мыши.

Контекстное меню, Menu

Контекстное меню, с точки зрения программирования, это класс Menu со стилем SWT.POP_UP. С точки зрения визуального интерфейса - это всплывающее окно с одним или несколькими пунктами меню.

Можно подключить контекстное меню только к визуальным компонентам SWT. Продолжим развитие примера и создадим два визуальных компонента типа панели Composite и дерева со списком Tree. К каждому из компонентов подключим свое контекстное меню.

Composite composite = new Composite (shell, SWT.BORDER);
composite.setBounds (10, 10, 100, 180);
		
Tree tree = new Tree (shell, SWT.BORDER | SWT.MULTI);
tree.setBounds (350, 10, 100, 180);
for (int i = 0; i < 9; i++) {
    TreeItem treeItem = new TreeItem (tree, SWT.NONE);
    treeItem.setText ("Item " + i);
}

Интерфейс формы с визуальными компонентами представлен на следующем скриншоте.

Создание контекстного меню

Чтобы создать контекстное меню необходимо в конструктор класса Menu передать в качестве родительского класса Shell формы и определить стиль SWT.POP_UP. После этого можно формировать структуру контекстного меню.

Контекстное меню, также как и главное меню ("main menu"), может быть представлено не только в виде линейного списка, но и иметь иерархическую структуру. В следующем коде создается контекстное меню, имеющее вложенные подпункты в виде "радиокнопок". Контекстное меню подключается к панели и по нажатию на один из пунктов меню изменяет цвет панели.

// Создание контекстного меню
Menu menu = new Menu (shell, SWT.POP_UP);
MenuItem item1 = new MenuItem (menu, SWT.PUSH);
item1.setText ("Color : yellow");
item1.addListener (SWT.Selection, new Listener () {
    @Override
    public void handleEvent (Event e) {
        composite.setBackground(new Color (display,  255,  255,  64));
    }
});

Menu subMenu2 = new Menu (menu);
MenuItem item2 = new MenuItem (menu, SWT.CASCADE);
item2.setText ("Cascade Item");
item2.setMenu (subMenu2);

MenuItem subItem21 = new MenuItem (subMenu2, SWT.PUSH);
MenuItem subItem22 = new MenuItem (subMenu2, SWT.PUSH);
subItem21.setText ("Color : red");
subItem22.setText ("Color : blue");

subItem21.addListener (SWT.Selection, new Listener () {
    @Override
    public void handleEvent (Event e) {
        composite.setBackground(new Color (display,  255,  32,  32));
    }
});

subItem22.addListener (SWT.Selection, new Listener () {
    @Override
    public void handleEvent (Event e) {
        composite.setBackground(new Color (display,  32,  32,  255));
    }
});
// Создание вложенных подпунктов меню
Menu subMenu3 = new Menu (menu);
MenuItem item3 = new MenuItem (menu, SWT.CASCADE);
item3.setText ("Radio Item");
item3.setMenu (subMenu3);

// Вложенные подпункты меню в виде "радиокнопок"
MenuItem subItem31 = new MenuItem (subMenu3, SWT.RADIO);
MenuItem subItem32 = new MenuItem (subMenu3, SWT.RADIO);
subItem31.setText ("Color : magenta");
subItem32.setText ("Color : cyan");

subItem31.addListener (SWT.Selection, new Listener () {
    @Override
    public void handleEvent (Event e) {
        composite.setBackground(new Color (display,  255,  32,  255));
    }
});

subItem32.addListener (SWT.Selection, new Listener () {
    @Override
    public void handleEvent (Event e) {
        composite.setBackground(new Color (display,  32,  255,  255));
    }
});
// Подключение контекстного меню к панели
composite.setMenu (menu);

Интерфейс иерархического контекстного меню представлен на скриншоте.

Динамическая блокировка пунктов контекстного меню

Изменим немного код и подключим контекстное меню к визуальному компоненту дерева Tree. При вызове контекстного меню динамически будут блокироваться все пункты контекстного меню, за исключением выделенной записи в дереве.

// контекстное меню создать
final Menu menu = new Menu (shell, SWT.POP_UP);
// создать визуальный компонент дерева
Tree tree = new Tree (shell, SWT.BORDER | SWT.MULTI);
tree.setBounds (350, 10, 100, 180);
// подключить контекстное меню
tree.setMenu (menu);
for (int i = 0; i < 9; i++) {
    TreeItem treeItem = new TreeItem (tree, SWT.NONE);
    treeItem.setText ("Item " + i);
    MenuItem menuItem = new MenuItem (menu, SWT.PUSH);
    menuItem.setText (treeItem.getText ());
}
// обработчик события открытия контекстного меню
menu.addListener (SWT.Show, new Listener () {
    @Override
    public void handleEvent (Event event) {
        MenuItem [] menuItems = menu.getItems ();
        TreeItem [] treeItems = tree.getSelection ();
        for (int i = 0; i < menuItems.length; i++) {
            String text = menuItems [i].getText ();
            int index = 0;
            while (index < treeItems.length) {
                if (treeItems [index].getText ().equals (text))
                    break;
                    index++;
                }
            menuItems [i].setEnabled (index != treeItems.length);
        }
    }
});

Интерфейс контекстного меню с динамической блокировкой пунктов представлен на скриншоте.

Модуль создания меню MenuCreater

Описание вышеприведенного примера MenuExample, демонстрирует возможности библиотеки SWT по созданию различного вида меню. Но на практике, как правило, разработчики используют наработки, которые изменяются незначительно. Но каждый раз приходится «таскать за собой» из проекта в проект свои коды.

Мы предлагаем готовый модуль формирования меню MenuCreater, который позволяет создавать иерархическое локализованное меню, подключать акселераторы, маркировать подпункты меню различными иконками согласно описанию во внешнем файле в формате JSON. Файл с описанием структуры меню размещается в ресурсах, т.е. настраивается отдельно, но распространяется вместе с приложением внутри jar-модуля.

Основные особенности модуля MenuCreater заключаются в том, что

  • модуль получает родительский класс, реализующий интерфейс IMenuBar, для Callback'ов;
  • пункты меню локализуются - ресурсный файл передается в метод createMenu вместе с описанием в формате JSON;
  • к каждому пункту меню подключается слушатель selectListener, который передает родителю наименование выделенного пункта меню;
  • для маркировки пункта меню иконкой, модуль вызывает родительскую функцию getImage.

Листинг модуля MenuCreater

MenuCreater включает внутренний класс MenuDesc, который описывает пункт меню.

import java.util.ResourceBundle;

import labir.utils.interfaces.IMenuBar;

import net.sf.json.JSONArray;
import net.sf.json.JSONObject;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;

import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.MenuItem;

public class MenuCreater
{
    private         Shell     shell           = null;
    private         IMenuBar  parent          = null;
    private  final  String    STYLE_CASCADE   = "CASCADE";
    private  final  String    STYLE_SEPARATOR = "SEPARATOR";
    private  final  String    STYLE_PUSH      = "PUSH"   ;

    private  final  String    POSTFIX_TEXT  = ".text";
    private  final  String    ACCELERATOR   = ".accelerator";
    private  final  String    TAB           = "\t";

    private  final  String    JSON_name     = "name";
    private  final  String    JSON_style    = "style";
    private  final  String    IMAGE         = "image";
    private  final  String    JSON_menus    = "menus";
    private  final  String    JSON_submenus = "submenus";

    //---------------------------------------------------------------
    /**
     * Класс описания пункта меню, включает параметры 
     * name, text, style, accelerator, image
     */
    class MenuDesc
    {
        String name        = null; 
        String text        = null; 
        String style       = null;
        String accelerator = null;
        String image       = null;
        /**
         * Конструктор класса
         * @param name наименование пункта меню
         * @param text заголовок пункта меню 
         * @param style стиль пункта меню
         * @param accelerator управляющие клавиши
         * @param image иконка
         */
        MenuDesc(String name, String text, String style,
                 String accelerator, String image)
        {
            this.name        = name       ; 
            this.text        = text       ; 
            this.style       = style      ;
            this.accelerator = accelerator;
            this.image       = image      ;
        }
    }
    //---------------------------------------------------------------
    /**
     * Конструктор
     * @param shell окно
     */
    public  MenuCreater (final Shell shell)
    {
        this.shell = shell;
    }
    //---------------------------------------------------------------
    /**
     * Конструктор
     * @param shell окно
     * @param parent родитель
     */
    public MenuCreater (final Shell shell, final IMenuBar parent)
    {
        this(shell);
        this.parent = parent;
    }
    //---------------------------------------------------------------
    /**
     * Функция чтения ресурсов
     * @param resource ресурсный файл
     * @param key ключ
     * @return строка ресурсов
     */
    private String getResourceString(ResourceBundle resource,String key)
    {
        try {
            return resource.getString(key);
        } catch (MissingResourceException e) {
            return null;
        } catch (NullPointerException e) {
            return null;
        }			
    }
    //---------------------------------------------------------------
    /**
     * Функция чтения параметров пункта меню
     * @param json описание пункта меню
     * @return описание меню в виде объекта MenuDesc
     * @see MenuDesc 
     */
    private MenuDesc getMenuDesc(final ResourceBundle resource,
                                 final JSONObject json)
    {
        String name  = (String) ((json.containsKey(JSON_name)) ? 
                                  json.get(JSON_name) : null); 
        String style = (String) ((json.containsKey(JSON_style)) ? 
                                  json.get(JSON_style) : STYLE_CASCADE);
        String image = (String) ((json.containsKey(IMAGE)) ? 
                                  json.get(IMAGE) : null);
        String accelerator = StringUtil.getResourceString(resource,
                                             name + ACCELERATOR);
        String text = ((name != null) && 
                       (name.trim().length() > 0)) ?
                 StringUtil.getResourceString(resource,
                                             name + POSTFIX_TEXT) : null;
        return new MenuDesc(name, text, style, accelerator, image);
    }
    //---------------------------------------------------------------
    private MenuItem createMenuItem(Menu menu, MenuDesc desc,
                                    final ResourceBundle resource)
    {
        MenuItem item = null;
        if ((desc.name != null) && (desc.text != null) && 
            (desc.name.trim().length() != 0) && 
            (desc.text.trim().length() != 0)) {
            int style = SWT.PUSH;
            if (STYLE_CASCADE.equalsIgnoreCase(desc.style))
                style = SWT.CASCADE;
            item = new MenuItem (menu, style);
            // Определение акселератора
            if ((desc.accelerator != null) &&
                (desc.accelerator.trim().length() > 0)) {
                int idx = desc.accelerator.indexOf("+");
                if (idx > 0) {
                    String[] list = new String[2];
                    list[0] = desc.accelerator.substring(0, idx);
                    list[1] = desc.accelerator.substring(idx + 1);
                    if ((list.length == 2) &&
                        (list[0].equalsIgnoreCase("CTRL") || 
                         list[0].equalsIgnoreCase("MOD1"))) {
                        item.setAccelerator(SWT.CTRL + list[1].charAt(0));
                        desc.text += TAB + desc.accelerator;

                    }
                }
            }
            item.setText (desc.text);
            item.setData (desc.name);
        }
        return item;
    }
    //---------------------------------------------------------------
    private void createSubmenus(final ResourceBundle resource,
                                MenuItem item, JSONArray submenus)
    {
        Menu menu = new Menu(shell, SWT.DROP_DOWN);
        item.setMenu(menu);
        menu.addListener(SWT.Show, showListener);
        menu.addListener(SWT.Hide, hideListener);
        for (int i = 0; i < submenus.size(); i++) {
            JSONObject json = submenus.getJSONObject(i);
            MenuDesc desc = getMenuDesc(resource, json);
            if (STYLE_SEPARATOR.equalsIgnoreCase(desc.style)) {
                new MenuItem (menu, SWT.SEPARATOR);
            } else {
                MenuItem menuItem = createMenuItem(menu, desc, resource);
                menuItem.addSelectionListener(selectListener);
                if ((desc.image.length() > 0) && 
                                                (parent != null))
                    menuItem.setImage(parent.getImage(desc.image));

                menuItem.addListener(SWT.Show, showListener);
                if (json.containsKey(JSON_submenus)) {
                    JSONArray subs = json.getJSONArray(JSON_submenus);
                    if (subs.size() > 0)
                        createSubmenus(resource, menuItem, subs);
                }
            }
        }
    }
    //---------------------------------------------------------------
    /*
     * Процедура создания текстового меню с подсветкой выделяемого пункта 
     * @param resource ресурсный файл
     * @jsonMenu описание меню
     */
    public Menu createMenu(final ResourceBundle resource, JSONObject jsonMenu)
    {
        Menu menu = new Menu (shell, SWT.BAR);
        shell.setMenuBar (menu);
        
        JSONArray menus = jsonMenu.getJSONArray(JSON_menus);

        for (int i = 0; i < menus.size(); i++) {
            JSONObject json = menus.getJSONObject(i);

            MenuDesc desc = getMenuDesc(resource, json);
            MenuItem menuItem = createMenuItem(menu, desc, resource);
            if (json.containsKey(JSON_submenus)) {
                JSONArray submenus = json.getJSONArray(JSON_submenus);
                createSubmenus(resource, menuItem, submenus);
            }
        }
        return menu;
    }
    //---------------------------------------------------------------
    /**
     * Слушатель нажатия на пункт меню
     */
    SelectionListener selectListener = new SelectionListener()
    {
        @Override
        public void widgetDefaultSelected(SelectionEvent e) {}

        @Override
        public void widgetSelected(SelectionEvent e) 
        {
            // Если определен родитель, то передать ему обработку события
            if (parent != null) {
                // Кнопка панели инструментов
                MenuItem item = (MenuItem) e.getSource();
                parent.selectMenuItem((String)item.getData());
            }
        }
    };
    //---------------------------------------------------------------
    /**
     * Слушатель выделения пункта меню
     */
    Listener showListener = new Listener()
    {
        @Override
        public void handleEvent(Event event)
        {
            // Если определен родитель, то передать ему обработку события
            if (parent != null) {
                Menu menu = (Menu) event.widget;
                MenuItem item = menu.getParentItem();
                parent.showSubmenus((String)item.getData());
            }
        }
    };
    //---------------------------------------------------------------
    /**
     * Слушатель выделения пункта меню
     */
    Listener hideListener = new Listener()
    {
        @Override
        public void handleEvent(Event event)
        {
            // Если определен родитель, то передать ему обработку события
            if (parent != null) {
                Menu menu = (Menu) event.widget;
                MenuItem item = menu.getParentItem();
                parent.hideSubmenus((String)item.getData());
            }
        }
    };
}

Пример тестирования MenuCreaterTest включает набор изображений для меню, ресурсные файлы для локализации, и реализует интерфейс IMenuBar.

import org.eclipse.swt.graphics.Image;

public interface IMenuBar
{
	public void  selectMenuItem (final String menuName );
	public void  showSubmenus   (final String menuName );
	public void  hideSubmenus   (final String menuName );
	public Image getImage       (final String imageName);
}

На следующем скриншоте представлен интерфейс примера MenuCreaterTest с открытыми пунктами меню.

Скачать SWT примеры использования меню

Исходные коды рассмотренных примеров MenuExample, MenuCreaterTest в виде проекта Eclipse можно скачать здесь (5.80 Мб). Используемая в примере библиотека labir.utils.jar включает класс IconCache, полное описание которого (поля, методы) и пример использования представлены на странице Toolbar, ToolItem, IconCache, и класс StatusbarManager, полное описание которого (поля, методы) и пример использования представлены на странице Статусная строка StatusbarManager.

  Рейтинг@Mail.ru