Содержание


Создание GUI приложений в Java с использованием библиотеки GTK+

Часть 3

Comments

Серия контента:

Этот контент является частью # из серии # статей: Создание GUI приложений в Java с использованием библиотеки GTK+

Следите за выходом новых статей этой серии.

Этот контент является частью серии:Создание GUI приложений в Java с использованием библиотеки GTK+

Следите за выходом новых статей этой серии.

Эта статья продолжает знакомить вас с библиотекой GTK+ для Java. Ранее мы рассмотрели основы создания приложений с использованием этих двух технологий, а также немного познакомились со средой Eclipse. В этой статье мы разовьем код из второй части цикла, т.е. подразумевается, что вы представляете, как создать окно, что такое контейнеры и как добавлять в них элементы управления. Если вы желаете освежить этот материал в памяти, вернитесь ко второй части статьи.

События

Большинство виджетов, независимо от того, кнопка это или диалоговое окно, должны реагировать на одни и те же события. Чтобы создавать качественные приложения, разработчик должен иметь возможность реагировать на нажатие не только кнопки, но и иконки, и даже переключение между вкладками. Механизм событий в GTK+ реализуется посредством привязки объекта, реализующего определенный интерфейс, к определенному типу событий. Как уже отмечалось, большинство элементов управления, или виджетов, реагируют на одни и те же события, поэтому логично, что за привязку этих самых обработчиков отвечает класс org.gnome.gtk.Widget. Естественно не все события «отлавливаются» в нем. Например, окно, кнопка и пункт меню — это все виджеты, однако у них есть свои специфические черты: окно можно закрывать, кнопку нажимать, а пункт меню активировать, но никто не пытается закрыть кнопку или меню. Поэтому соответствующие коннекторы для таких типов событий описаны прямо в элементе, которому они присущи.

Итак, в классе Widget есть несколько вариантов метода connect(). Например, public void connect(Widget.ButtonPressEvent handler) и public void connect(Widget.ScrollEvent handler). В этом же классе, как можно видеть из сигнатуры методов, описаны интерфейсы Widget.ButtonPressEvent и Widget.ScrollEvent. Первый интерфейс реализуется классами (возможно, анонимными), желающими обеспечить реакцию на нажатие кнопки мыши, а второй — на прокрутку ее колесика. Существуют также типы Widget.EnterNotifyEvent и Widget.LeaveNotifyEvent — мышь входит в виджет и выходит из него, Widget.FocusInEvent и Widget.FocusOutEvent — получение/потеря фокуса, Widget.KeyPressEvent/Widget.KeyReleaseEvent — нажатие-отпускание клавиши на клавиатуре, Widget.MapEvent/Widget.UnmapEvent — видимость/сокрытие виджета. Эти события генерируются, когда окно сворачивается/разворачивается, пользователь меняет рабочее место или виджет создается/разрушается. За видимость также отвечает Widget.VisibilityNotifyEvent.

Что касается специфических событий, то в качестве примера можно привести public void connect(DeleteEvent handler) класса org.gnome.gtk.Window. Этот метод позволяет связать любой код с нажатием кнопки закрытия в строке заголовка окна. Далее мы рассмотрим меню и связанный с ним тип событий MenuItem.Activate.

Давайте добавим немного кода в наше приложение и далее дадим небольшое пояснение. Допишем следующие строки:

Листинг 1. "Привязываемся" к нажатию на кнопку
        HBox hbox = new HBox(false, 1);
        Button button = new Button("Click me!");
        button.connect(new Button.Clicked() {
            public void onClicked(Button source) {
            	 System.out.println("button label: " + source.getLabel());
            	 System.out.println("label label: " + button.getLabel());
            }
        });
        hbox.add(button);
        vbox.add(hbox);

Горизонтальный контейнер HBox описывался в предыдущей статье. На данном этапе неважно, будет ли контейнер горизонтальным или вертикальным, потому что мы добавляем лишь один элемент. Кнопка получает надпись "Click me!" и обработчик нажатия. Метод connect() принимает в качестве параметра объект, реализующий интерфейс org.gnome.gtk.Button.Clicked. В данном случае это анонимный класс — методу все равно, что мы в него передадим, главное, чтобы у полученного параметра можно было вызвать метод onClicked(). В своем текущем виде обработчик практически бесполезен, но, глядя на него, проще понять, как происходит обработка событий. Во время щелчка по кнопке происходит вызов метода onClicked(), и в него передается объект класса Button. Это замечание может показаться излишним, но класс-обработчик не всегда является внутренним, как в данном примере. Это может быть полноценный самостоятельный класс, который, в том числе, может обрабатывать нажатия на несколько кнопок. Если посмотреть внимательно, то можно заметить, что передаваемый объект — это как раз объект, который мы создали строкой ранее. button.getLabel() здесь приведен для того, чтобы обратить внимание на область видимости переменных: при таком построении приложения можно легко иметь доступ, например, к содержимому поля ввода. Далее кнопка добавляется в горизонтальный контейнер hbox, который в свою очередь размещается внутри вертикального vbox.

Теперь давайте посмотрим на более интересные обработчики. Возьмем, например, org.gnome.gtk.Widget.ButtonPressEvent. В этом обработчике метод события выглядит уже следующим образом: public boolean onButtonPressEvent(Widget source, EventButton event). Первым параметром передается источник (например, виджет кнопки), а второй параметр содержит сведения о наступившем событии. В справке дается следующий пример использования такого обработчика:

w.connect(new Widget.ButtonPressEvent() {
        	boolean onButtonPressEvent(Widget source, EventButton event) {
        		if (event.getButton() == MouseButton.RIGHT) {
                         // popup menu
                }
                return false;
            }
        });

В статье невозможно перечислить все типы событий и их обработчики, но идея должна быть понятна. Все классы событий наследуются от org.gnome.gdk.Event. У этого класса есть метод getType(), который возвращает некий org.gnome.gdk.EventType. EventType — это, по сути, набор констант класса org.gnome.gdk.GdkEventType с пояснительной к каждому типу строкой. Типов всего около сорока. Наличие этого метода означает, что в любом обработчике можно узнать, что именно произошло. Исходя из этого, можно отметить, что теоретически достаточно написать всего один обработчик для всех событий и виджетов, а затем «на месте» разбирать, в зависимости от типа, к какому классу привести переменную event и затем обработать ее [Хотя поступать так настоятельно не рекомендуется — код быстро станет «макаронным». — Прим.ред.].

Меню

Давайте рассмотрим еще одну из составляющих WIMP (Window, Icon, Menu, Pointing device)-интерфейса. Строку меню с пунктами File, Edit, View, Help и т. д. можно видеть в большинстве приложений. Кстати, то, что в большинстве приложений главное меню имеет примерно одинаковую структуру, является заслугой Microsoft.

Строка главного меню в GTK+ представляется классом org.gnome.gtk.MenuBar. MenuBar пополняется пунктами меню org.gnome.gtk.MenuItem. Пункты меню являются потомками org.gnome.gtk.Bin (это контейнер с одним дочерним элементом) и предоставляют метод setSubmenu(Menu child) для установки выпадающего (дочернего) меню. org.gnome.gtk.Menu может также использоваться как контекстное меню: для этого существует метод popup(), который показывает содержимое меню в точке, находящейся под курсором мыши. Вызов этого метода обычно привязывается к нажатию правой кнопки. Menu и MenuBar наследуют org.gnome.gtk.MenuShell. Именно он предоставляет методы для добавления пунктов меню - append(Widget child), prepend(Widget child) и insert(Widget child, int position). В качестве параметра к этим методам принимается объект класса org.gnome.gtk.MenuItem. Таким образом, существует возможность создавать меню произвольной вложенности. Продемонстрируем вышесказанное на нашем «подопытном» приложении, добавив следующие строки сразу после создания контейнера VBox.

Листинг 2. Главное меню
        final Menu fileMenu = new Menu();
        Menu helpMenu = new Menu();
        MenuItem fileMenuItem = new MenuItem("_File");
        MenuItem helpMenuItem = new MenuItem("_Help");
        
        fileMenu.append(new MenuItem("_Close", new MenuItem.Activate() {
            public void onActivate(MenuItem source) {
                  Gtk.mainQuit();
            }
        }));
        
        helpMenu.append(new MenuItem("_Help", new MenuItem.Activate() {
            public void onActivate(MenuItem source) {
                  System.out.println("Some help");
            }
        }));
        helpMenu.append(new SeparatorMenuItem());
        helpMenu.append(new MenuItem("_About", new MenuItem.Activate() {
            public void onActivate(MenuItem source) {
                  final Window aboutWindow = new Window();
                  aboutWindow.setModal(true);
                  aboutWindow.setSizeRequest(200, 100);
            	
                  aboutWindow.add(new Label("UselessGUIApp v1.0"));
                  aboutWindow.showAll();
            }
        }));
        fileMenuItem.setSubmenu(fileMenu);
        helpMenuItem.setSubmenu(helpMenu);
        
        MenuBar menuBar = new MenuBar();
        menuBar.append(fileMenuItem);
        menuBar.append(helpMenuItem);
        
        vbox.add(menuBar);

Эти строки создадут главное меню с двумя пунктами — File и Help. В первом пункте присутствует только один подпункт, который завершает приложение. Код, вызываемый при активации пункта меню, передается через второй параметр конструктора MenuItem(). Этот механизм уже рассматривался нами выше. Странный знак подчеркивания позволяет использовать клавиши быстрого доступа. Например, если подчеркивание стоит перед буквой H, то пункт меню будет доступен по комбинации Alt+H.

Как можно видеть из примера, в меню можно добавлять не только объекты MenuItem. Существуют также несколько потомков этого класса. Это SeparatorMenuItem, CheckMenuItem и ImageMenuItem. Первый является разделителем логических групп меню. Второй и третий представляет собой различные виды пунктов меню. CheckMenuItem может хранить два значения: "true" и "false". Они переключаются "галочкой" ("check box"). CheckMenuItem позволяет добавить иконку слева от имени пункта. Такие меню выглядят приятнее.

Чтобы продемонстрировать различную вложенность меню, заменим строку menuBar.append(helpMenuItem); на fileMenu.append(helpMenuItem);. Теперь меню "Help" является подпунктом "File".

Заключение

Теперь посмотрим на полный код нашего эволюционировавшего приложения, так сказать, с высоты:

Листинг 3. Полный код демонстрационного приложения
package com.example;

import org.gnome.gdk.*;
import org.gnome.gtk.*;

public class UselessGUIApp {
    private UselessGUIApp(){
        final Window mainWindow;
        mainWindow = new Window();
        
        mainWindow.setTitle("GUI Application Title");
        mainWindow.setSizeRequest(300, 100);
        
        VBox vbox = new VBox(false, 1);
        
        final Menu fileMenu = new Menu();
        Menu helpMenu = new Menu();
        MenuItem fileMenuItem = new MenuItem("_File");
        MenuItem helpMenuItem = new MenuItem("_Help");
        
        fileMenu.append(new MenuItem("_Close", new MenuItem.Activate() {
    	      @Override
            public void onActivate(MenuItem source) {
                Gtk.mainQuit();
        	}
        }));
        
        helpMenu.append(new MenuItem("_Help", new MenuItem.Activate() {
        	@Override
            public void onActivate(MenuItem source) {
                System.out.println("Some help");
            }
        }));
        helpMenu.append(new SeparatorMenuItem());
        helpMenu.append(new MenuItem("_About", new MenuItem.Activate() {
        	@Override
            public void onActivate(MenuItem source) {
                final Window aboutWindow = new Window();
                aboutWindow.setModal(true);
                aboutWindow.setSizeRequest(200, 100);
            	
                aboutWindow.add(new Label("UselessGUIApp v1.0"));
                aboutWindow.showAll();
            }
        }));
        fileMenuItem.setSubmenu(fileMenu);
        helpMenuItem.setSubmenu(helpMenu);
        
        MenuBar menuBar = new MenuBar();
        menuBar.append(fileMenuItem);
        menuBar.append(helpMenuItem);
        
        vbox.add(menuBar);
        
        final Label label = new Label("Label #1");

        vbox.add(label);

        final Entry entry = new Entry("");
        entry.connect(new Widget.ButtonPressEvent() {
            @Override
            public boolean onButtonPressEvent(Widget source, EventButton event) {
                if(event.getButton() == MouseButton.RIGHT){
                    fileMenu.popup();
                }
                
                return true;
            }
        });
        vbox.add(entry);
       
        HBox hbox = new HBox(false, 1);
        Button button = new Button("Click me!");
        button.connect(new Button.Clicked() {
    	      @Override
            public void onClicked(Button source) {
                System.out.println("button label: " + source.getLabel());
                System.out.println("label label: " + label.getLabel());
            	
        	}
        });
        hbox.add(button);

        vbox.add(hbox);
        
        mainWindow.add(vbox);
        mainWindow.connect(new Window.DeleteEvent() {
        	@Override
            public boolean onDeleteEvent(Widget source, Event event) {
                Gtk.mainQuit();
                return false;
        	}
        });
        mainWindow.showAll();
    }

    public static void main(String[] args) {
        Gtk.init(args);
        
        new UselessGUIApp();
        
        Gtk.main();
    }
}

Следует обратить внимание, что строка меню добавляется в контейнер как обычный виджет. Иными словами, меню может быть добавлено в любое место на экране, но хороший тон предполагает, что оно будет именно вверху. Мы также добавили текстовое поле, чтобы продемонстрировать контекстное меню, для этого используется уже созданное меню. В том же обработчике демонстрируется, как распознать нажатие правой кнопки мыши. Щелчок правой кнопкой — это не отдельный тип события из org.gnome.gdk.EventType, поэтому определение точного типа события производится без участия вызова event.getType(). Может показаться немного странным, что, например, двойной клик определяется следующим образом: event.getType() == EventType.BUTTON_PRESS_DOUBLE. Но, чтобы определить двойное нажатие именно левой кнопкой, необходимо написать: event.getType() == EventType.BUTTON_PRESS_DOUBLE && event.getButton() == MouseButton.LEFT.


Ресурсы для скачивания


Похожие темы


Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, Open source
ArticleID=749793
ArticleTitle=Создание GUI приложений в Java с использованием библиотеки GTK+: Часть 3
publish-date=07292011