GUI в Java c помощью JFace: Создание окна приложения. Улучшаем интерфейс Java-приложения

Вайбер на компьютер 16.06.2019
Вайбер на компьютер

В Java есть 2 основных пакета для создания графических интерфейсов (Graphics User Interface). Это Abstract Windows Toolkit (AWT) и Swing. AWT использует виджеты операционной системы, поэтому эта библиотека немного быстрее. Но на мой взгляд, Swing более хорошо спроектирован.

В данном туториале мы рассмотрим основные элементы библиотеки Swing и создадим простой интерфейс (GUI) в качестве примера.

Для группировки компонент интерфейса используются контейнеры (Container). Для создания основного контейнера для приложения чаще всего используется контейнер JFrame (есть еще JWindows и JApplet). Проще всего унаследоваться от JFrame тем самым получить доступ ко множеству методов, например:

setBounds(x, y, w, h) - указывает координаты верхней левой вершины окна, а также его ширину и высоту.

setResizable(bool) - указывает, можно ли изменять размер окна.

setTitle(str) - устанавливает название окна.

setVisible(bool) - собственно отображает окно.

setDefaultCloseOperation(operation) - указывает операцию, которая будет произведена при закрытии окна.

Основные элементы управления:

  • JLabel - элемент для отображения фиксированного текста;
  • JTextField - простой edit-box;
  • JButton - обычная кнопка (button);
  • JCheckBox - элемент выбора (аналог checkbox);
  • JRadioButton - радио кнопка

Как видите, все довольно просто и логично.

При отображении элементов управления используются специальные менеджеры - LayoutManager. У всех LayoutManager"ов есть методы для добавления у удаления элементов.

FlowLayout - используется для последовательного отображения элементов. Если элемент не помещается в конкретную строку, он отображается в следующей.

GridLayout - отображения элементов в виде таблицы с одинаковыми размерами ячеек.

BorderLayout - используется при отображении не более 5 элементов. Эти элементы располагаются по краям фрейма и в ценрте: North, South, East, West, Center.

BoxLayout - отображает элементы в виде рядка или колонки.

GridBagLayout - позволяет назначать месторасположение и размер каждого виджета. Это самый сложный, но и самый эффективный вид отображения.

Стоит еще обратить внимание на обработку событий. Для этого используются так называемые Event Listeners.

Ну все, довольно теории, перейдем к примеру GUI:

Import java.awt.*; import java.awt.event.*; import javax.swing.*; public class SimpleGUI extends JFrame { private JButton button = new JButton("Press"); private JTextField input = new JTextField("", 5); private JLabel label = new JLabel("Input:"); private JRadioButton radio1 = new JRadioButton("Select this"); private JRadioButton radio2 = new JRadioButton("Select that"); private JCheckBox check = new JCheckBox("Check", false); public SimpleGUI() { super("Simple Example"); this.setBounds(100,100,250,100); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); Container container = this.getContentPane(); container.setLayout(new GridLayout(3,2,2,2)); container.add(label); container.add(input); ButtonGroup group = new ButtonGroup(); group.add(radio1); group.add(radio2); container.add(radio1); radio1.setSelected(true); container.add(radio2); container.add(check); button.addActionListener(new ButtonEventListener()); container.add(button); } class ButtonEventListener implements ActionListener { public void actionPerformed(ActionEvent e) { String message = ""; message += "Button was pressed\n"; message += "Text is " + input.getText() + "\n"; message += (radio1.isSelected()?"Radio #1":"Radio #2") + " is selected\n"; message += "CheckBox is " + ((check.isSelected()) ?"checked":"unchecked"); JOptionPane.showMessageDialog(null, message, "Output", JOptionPane.PLAIN_MESSAGE); } } public static void main(String args) { SimpleGUI app = new SimpleGUI(); app.setVisible(true); } }

Примечания:

getContentPane возвращает контейнер верхнего уровня. ButtonGroup служит для создания группы взаимосвязанных радио-кнопок.

Внутренний класс ButtonActionListener реализует интерфейс ActionListener. Для этого необходимо предоставить имплементацию метода actionPerformed.

JOptionPane служит для отображения диалоговых окон.

Жду ваших вопросов и комментариев. Если вы хотите больше узнать о Swing, скажите об этом, и в скором времени я напишу еще одну статью с более сложными приемами и компонентами.

Графический интерфейс в Java прошел весьма тернистый путь развития и становления. Долгое время его обвиняли в медленной работе, жадности к ресурсам системы и ограниченной функциональности.

Java AWT

Первой попыткой Sun создать графический интерфейс для Java была библиотека AWT (Abstract Window Toolkit) - инструментарий для работы с различными оконными средами. Sun сделал прослойку на Java, которая вызывает методы из библиотек, написанных на С. Библиотечные методы AWT создают и используют графические компоненты операционной среды. С одной стороны, это хорошо, так как программа на Java похожа на остальные программы в рамках одной ОС. Но при запуске ее на другой платформе могут возникнуть различия в размерах компонентов и шрифтов, которые будут портить внешний вид программы.

Чтобы обеспечить мультиплатформенность AWT интерфейсы вызовов компонентов были унифицированы, вследствии чего их функциональность получилась немного урезанной. Да и набор компонентов получился довольно небольшой. Так например, в AWT нет таблиц, а в кнопках не поддерживается отображение иконок. Тем не менее пакет java.awt входит в Java с самого первого выпуска и его можно использовать для создания графических интерфейсов.

Таким образом, компоненты AWT не выполняют никакой "работы". Это просто «Java-оболочка» для элементов управления той операционной системы, на которой они работают. Все запросы к этим компонентам перенаправляются к операционной системе, которая и выполняет всю работу.

Использованные ресурсы AWT старается освобождать автоматически. Это немного усложняет архитектуру и влияет на производительность. Написать что-то серьезное с использованием AWT будет несколько затруднительно. Сейчас ее используют разве что для апплетов.

Основные концепции SWING

Вслед за AWT Sun разработала графическую библиотеку компонентов Swing , полностью написанную на Java. Для отрисовки используется 2D, что принесло с собой сразу несколько преимуществ. Набор стандартных компонентов значительно превосходит AWT по разнообразию и функциональности. Swing позволяет легко создавать новые компоненты, наследуясь от существующих, и поддерживает различные стили и скины.

Создатели новой библиотеки пользовательского интерфейса Swing не стали «изобретать велосипед» и в качестве основы для своей библиотеки выбрали AWT. Конечно, речь не шла об использовании конкретных тяжеловесных компонентов AWT (представленных классами Button, Label и им подобными). Нужную степень гибкости и управляемости обеспечивали только легковесные компоненты. На диаграмме наследования представлена связь между AWT и Swing.

Важнейшим отличием Swing от AWT является то, что компоненты Swing вообще не связаны с операционной системой и поэтому гораздо более стабильны и быстры. Такие компоненты в Java называются легковесными (lightweight), и понимание основных принципов их работы во многом объяснит работу Swing.

Swing контейнеры высшего уровня

Для создания графического интерфейса приложения необходимо использовать специальные компоненты библиотеки Swing, называемые контейнерами высшего уровня (top level containers). Они представляют собой окна операционной системы, в которых размещаются компоненты пользовательского интерфейса. К контейнерам высшего уровня относятся окна JFrame и JWindow, диалоговое окно JDialog, а также апплет JApplet (который не является окном, но тоже предназначен для вывода интерфейса в браузере, запускающем этот апплет). Контейнеры высшего уровня Swing представляют собой тяжеловесные компоненты и являются исключением из общего правила. Все остальные компоненты Swing являются легковесными.

Простой Swing пример создания оконного интерфейса JFrame .

Import java.awt.Dimension; import javax.swing.JFrame; import javax.swing.JLabel; public class JFrameTest { public static void createGUI() { JFrame frame = new JFrame("Test frame"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JLabel label = new JLabel("Test label"); frame.getContentPane().add(label); frame.setPreferredSize(new Dimension(200, 100)); frame.pack(); frame.setVisible(true); } public static void main(String args) { JFrame.setDefaultLookAndFeelDecorated(true); javax.swing.SwingUtilities.invokeLater(new Runnable() { public void run() { createGUI(); } }); } }

Конструктор JFrame() без параметров создает пустое окно. Конструктор JFrame(String title) создает пустое окно с заголовком title. Чтобы создать простейшую программу с пустым окном необходимо использовать следующие методы:

  • setSize(int width, int height) - определение размеров окна;
  • setDefaultCloseOperation(int operation) - определение действия при завершении программы;
  • setVisible(boolean visible) - сделать окно видимым.

Если не определить размеры окна, то оно будет иметь нулевую высоту независимо от того, что в нем находится. Размеры окна включают не только «рабочую» область, но и границы и строку заголовка.

Метод setDefaultCloseOperation определяет действие, которое необходимо выполнить при "выходе из программы". Для этого следует в качестве параметра operation передать константу EXIT_ON_CLOSE, описанную в классе JFrame.

По умолчанию окно создается невидимым. Чтобы отобразить окно на экране вызывается метод setVisible с параметром true. Если вызвать его с параметром false, окно станет невидимым.

Графический интерфейс java swing примера создания окна JFrame представлен на следующем рисунке.

Для подключения библиотеки Swing в приложении необходимо импортировать библиотеку javax.swing .

Каждый раз, как только создается контейнер высшего уровня, будь то обычное окно, диалоговое окно или апплет, в конструкторе этого контейнера создается корневая панель JRootPane . Контейнеры высшего уровня Swing следят за тем, чтобы другие компоненты не смогли "пробраться" за пределы JRootPane.

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

На следующем рисунке наглядно представлена структура корневой панели JRootPane .

Корневая панель JRootPane представляет собой контейнер, унаследованный от базового класса Swing JComponent. В этом контейнере за расположение компонентов отвечает специальный менеджер расположения, реализованный во внутреннем классе RootPaneLayout. Этот менеджер расположения отвечает за то, чтобы все составные части корневой панели размещались так, как им следует: многослойная панель занимает все пространство окна; в ее слое FRAME_CONTENT_LAYER располагаются строка меню и панель содержимого, а над всем этим располагется прозрачная панель.

Все составляющие корневой панели JRootPane можно получить или изменить. Для этого у нее есть набор методов get/set. Программным способом JRootPane можно получить с использованием метода getRootPane().

Кроме контейнеров высшего уровня корневая панель применяется во внутренних окнах JInternalFrame, создаваемых в многодокументных приложениях и располагающихся на "рабочем столе" JDesktopPane. Это позволяет забыть про то, что данные окна представляют собой обычные легковесные компоненты, и работать с ними как с настоящими контейнерами высшего уровня.

Многослойная панель JLayeredPane

В основании корневой панели (контейнера) лежит так называемая многослойная панель JLayeredPane , занимающая все доступное пространство контейнера. Именно в этой панели располагаются все остальные части корневой панели, в том числе и все компоненты пользовательского интерфейса.

JLayeredPane используется для добавления в контейнер свойства глубины (depth). To есть, многослойная панель позволяет организовать в контейнере третье измерение, вдоль которого располагаются слои (layers) компонента. В обычном контейнере расположение компонента определяется прямоугольником, который показывает, какую часть контейнера занимает компонент. При добавлении компонента в многослойную панель необходимо указать не только прямоугольник, занимаемый компонентом, но и слой, в котором он будет располагаться. Слой в многослойной панели определяется целым числом. Чем больше определяющее слой число, тем выше слой находится.

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

Возможности многослойной панели широко используются некоторыми компонентами Swing . Особенно они важны для многодокументных приложений, всплывающих подсказок и меню. Многодокументные Swing приложения задействуют специальный контейнер JDesktopPane («рабочий стол»), унаследованный от JLayeredPane , в котором располагаются внутренние окна Swing. Самые важные функции многодокументного приложения - расположение «активного» окна над другими, сворачивание окон, их перетаскивание - обеспечиваются механизмами многослойной панели. Основное преимущество от использования многослойной панели для всплывающих подсказок и меню - это ускорение их работы. Вместо создания для каждой подсказки или меню нового тяжеловесного окна, располагающегося над компонентом, в котором возник запрос на вывод подсказки или меню, Swing создает быстрый легковесный компонент. Этот компонент размещается в достаточно высоком слое многослойной панели выше в стопке всех остальных компонентов и используется для вывода подсказки или меню.

Многослойная панель позволяет организовать неограниченное количество слоев. Структура JLayeredPane включает несколько стандартных слоев, которые и используются всеми компонентами Swing, что позволяет обеспечить правильную работу всех механизмов многослойной панели. Стандартные слои JLayeredPane представлены на следующем рисунке.

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

Слой Palette предназначен для размещения окон с набором инструментов, которые обычно перекрывают остальные элементы интерфейса. Создавать такие окна позволяет панель JDesktopPane, которая размещает их в этом слое.

Слой Modal планировался для размещения легковесных модальных диалоговых окон. Однако такие диалоговые окна пока не реализованы, так что этот слой в Swing в настоящее время не используется.

Наиболее часто используемый слой, служащий для размещения всплывающих меню и подсказок.

Самый верхний слой. Предназначен для операций перетаскивания (drag and drop), которые должны быть хорошо видны в интерфейсе программы.

Небольшой пример JLayeredPane с многослойной панелью показывает, как добавлять компоненты в различные слои и как слои располагаются друг над другом:

Import javax.swing.*; import java.awt.*; // класс рисования двух типов фигур с текстом class Figure extends JComponent { private static final long serialVersionUID = 1L; private Color color; private int type; private String text; // параметры: цвет и тип фигуры Figure(Color color, int type, String text) { this.color = color; this.type = type; this.text = text; setOpaque(false); } public void paintComponent(Graphics g) { // прорисовка фигуры g.setColor(color); switch (type) { case 0: g.fillOval(0, 0, 90, 90); break; case 1: g.fillRect(0, 0, 130, 80); break; } g.setColor(Color.yellow); g.drawString(text, 10, 35); } } public class JLayeredPaneTest extends JFrame { private static final long serialVersionUID = 1L; public JLayeredPaneTest() { // создание окна super("Example LayeredTest"); // выход при закрытии окна setDefaultCloseOperation(EXIT_ON_CLOSE); // определение многослойной панели JLayeredPane lp = getLayeredPane(); // создание трех фигур Figure figure1 = new Figure(Color.red , 0, "Figure popup"); Figure figure2 = new Figure(Color.blue, 0, "Figure 1"); Figure figure3 = new Figure(Color.cyan, 1, "Figure 2"); // определение местоположения фигур в окне figure1.setBounds(10, 40, 120, 120); figure2.setBounds(60, 120, 160, 180); figure3.setBounds(90, 55, 250, 180); // добавление фигур в различные слои lp.add(figure1, JLayeredPane.POPUP_LAYER); lp.add(figure2, JLayeredPane.PALETTE_LAYER); lp.add(figure3, JLayeredPane.PALETTE_LAYER); // смена позиции одной из фигур lp.setPosition(figure3, 0); // определение размера и открытие окна setSize(280, 250); setVisible(true); } public static void main(String args) { JFrame.setDefaultLookAndFeelDecorated(true); new JLayeredPaneTest(); } }

В примере создается небольшое окно JFrame и в многослойную панель добавляется несколько компонентов Figure. Чтобы получить многослойную панель в любом контейнере Swing высшего уровня, достаточно вызвать метод getLayeredPane() .

Вспомогательный класс Figure наследует свойства базового класса JComponent и позволяет различными цветами рисовать фигуры двух типов (круги и прямоугольники). Параметры для прорисовки фигур задаются в конструкторе класса.

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

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

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

Панель содержимого ContentPane

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

Обратиться к панели содержимого можно методом getContentPane() класса JFrame. С помощью метода add(Component component) можно добавить на нее любой элемент управления. Заменить ContentPane любой другой панелью типа JPanel можно методом setContentPane()

Пример добавления кнопки в панель содержимого:

JButton newButton = new JButton(); getContentPane().add(newButton);

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

Панель содержимого можно полностью заменить. Рассмотрим следующий Swing пример использования панели содержимого ContentPane .

Import javax.swing.*; public class ContentPaneReplace extends JFrame { private static final long serialVersionUID = 1L; public ContentPaneReplace() { super("Test ContentPane"); setDefaultCloseOperation(EXIT_ON_CLOSE); // Создание панели с двумя кнопками JPanel contents = new JPanel(); contents.add(new JButton("Семья")); contents.add(new JButton("Школа")); // Замена панели содержимого setContentPane(contents); // Определение размера окна setSize(200, 100); // Открытие окна setVisible(true); } public static void main(String args) { JFrame.setDefaultLookAndFeelDecorated(true); new ContentPaneAdd(); } }

В примере создается небольшое окно и панель с двумя кнопками, которая затем методом setContentPane() заменяет панель содержимого окна. Таким образом была использована замена вместо более простого добавления - вызова метода add(). Интерфейс окна представлен на следующем скриншоте.

Панель содержимого ContentPane сама собой не представляет ничего особенного. Необходимо лишь помнить, что компоненты добавляются именно в нее.

Прозрачная панель JOptionPane

Прозрачная панель JOptionPane размещается корневой панелью выше всех элементов многослойной панели. За размещением JOptionPane следит корневая панель, которая размещает прозрачную панель выше многослойной панели, причем так, чтобы она полностью закрывала всю область окна, включая и область, занятую строкой меню.

JOptionPane используется в приложениях достаточно редко, поэтому по умолчанию корневая панель делает ее невидимой, что позволяет уменьшить нагрузку на систему рисования. Следует иметь в виду, что если вы делаете прозрачную панель видимой, нужно быть уверенным в том, что она прозрачна (ее свойство opaque равно false), поскольку в противном случае она закроет все остальные элементы корневой панели, и остальной интерфейс будет невидим.

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

Прозрачная панель JOptionPane может быть использована для эффектной анимации, «плавающей» поверх всех компонентов, включая строку меню, или для перехвата событий, если некоторые из них необходимо обрабатывать перед отправкой в основную часть пользовательского интерфейса.

Пример использования прозрачной панели Swing JOptionPane:

// Использование прозрачной панели JOptionPane import java.awt.Dimension; import java.awt.Font; import java.awt.event.WindowEvent; import java.awt.event.WindowListener; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.UIManager; public class JOptionPaneTest extends JFrame { private static final long serialVersionUID = 1L; public static final Font FONT = new Font("Verdana", Font.PLAIN, 11); public static void createGUI() { JFrame frame = new JFrame("Test JOptionPane"); frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); frame.addWindowListener(new WindowListener() { public void windowActivated(WindowEvent event) {} public void windowClosed(WindowEvent event) {} public void windowDeactivated(WindowEvent event) {} public void windowDeiconified(WindowEvent event) {} public void windowIconified(WindowEvent event) {} public void windowOpened(WindowEvent event) {} public void windowClosing(WindowEvent event) { Object options = { "Да", "Нет!" }; int rc = JOptionPane.showOptionDialog(event.getWindow(), "Закрыть окно?", "Подтверждение", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options); if (rc == 0) { event.getWindow().setVisible(false); System.exit(0); } } }); JLabel label = new JLabel("Использование прозрачной панели при закрытии окна"); frame.getContentPane().add(label); frame.setPreferredSize(new Dimension(350, 80)); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); } public static void main(String args) { javax.swing.SwingUtilities.invokeLater(new Runnable() { public void run() { UIManager.put("Button.font", FONT); UIManager.put("Label.font", FONT); JFrame.setDefaultLookAndFeelDecorated(true); JDialog.setDefaultLookAndFeelDecorated(true); createGUI(); } }); } }

Если методу setDefaultCloseOperation передать константу JFrame.EXIT_ON_CLOSE , то при закрытии окна приложение будет прекращать работу. В примере этому методу передается константа JFrame.DO_NOTHING_ON_CLOSE , чтобы при закрытии окна ничего не происходило. Выход из приложения в примере осуществляется из JFrame слушателя WindowListener в методе windowClosing . При закрытии окна вызывается метод windowClosing с параметром WindowEvent event, который в прозрачной панели Swing JOptionPane открывает диалоговое окно подтверждения.

На следующем скриншоте представлены два окна приложения. Верхнее главное окно. При закрытии данного окна открывается нижнее диалоговое окно подтверждения намерения.

Строка меню JMenuBar

Одной из важных особенностей использования корневой панели JRootPane в Swing, является необходимость размещения в окне строки меню JMenuBar . Серьезное приложение нельзя построить без какого-либо меню для получения доступа к функциям программы. Библиотека Swing предоставляет прекрасные возможности для создания удобных меню JMenuBar, которые также являются легковесными компонентами.

Строка меню JMenuBar размещается в многослойной панели в специальном слое FRAME_CONTENT_LAYER и занимает небольшое пространство в верхней части окна. По размерам в длину строка меню равна размеру окна. Ширина строки меню зависит от содержащихся в ней компонентов.

Корневая панель следит, чтобы панель содержимого и строка меню JMenuBar не перекрывались. Если строка меню не требуется, то корневая панель использует все пространство для размещения панели содержимого.

Примеры Swing

Исходные коды примеров, рассмотренных в тексте страницы, можно скачать .

Одно из важных достоинств Java состоит в том, что это не только язык, но и стандартизованная объектно-ориентированная среда выполнения. Любопытно проследить, как в рамках Java решаются традиционные программистские проблемы. Мы остановимся на оконном графическом интерфейсе.

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

Дело в том, что каждая оконная среда - это сложный мир, со своими законами, набором строительных блоков и приемов программирования. Motif не похож на MS-Windows и оконную систему Macintosh. По-разному представляются примитивные элементы интерфейса, по-разному обрабатываются внешние события, по-разному происжодит рисование на экране и т.д.

Вместе с тем, по своей сути оконная среда - просто идеальное поле деятельности для объектного программирования. Даже человеку, неискушенному в объектно-ориентированных методах проектирования, ясно, что такие вещи, как кнопки, текстовые поля, меню, вполне заслуживают названия объектов, как бы это слово ни понималось. Иначе говоря, вполне понятно, что такое “кнопка вообще”, “список вообще” и т.д.

Все это дает основания надеяться, что с помощью объектно-ориентированного подхода можно получить по-настоящему высокоуровневую и переносимую оконную среду, основанную на абстрактных типах данных.

Данная особенность оконных сред проявилась, в частности, в появлении довольно большого количества различных классовых библиотек, “обертывающих” оригинальные оконные системы. В качестве примеров можно привести MFC, OWL, Zink и многие другие.

Вот и среди стандартных Java-библиотек присутствует AWT или Abstract Windowing Toolkit - абстрактный оконный инструментарий.

AWT является системой классов для поддержки программирования в оконной среде. Его “абстрактность” проявляется в том, что все, зависящее от конкретной платформы, хорошо локализовано и спрятано. В AWT реализованы такие простые и понятные вещи, как кнопки, меню, поля ввода; простые и понятные средства организации интерфейса - контейнеры, панели, менеджеры геометрии.

Основы построения графического пользовательского интерфейса Компоненты и контейнеры

Если посмотреть на любое оконное приложение, то легко увидеть, что интерфейсная часть состоит из объектов, объединенных в группы. В AWT объекты называются компонентами (на самом деле они все являются наследниками класса Component), а группы объектов реализованы с помощью так называемых контейнеров. Отметим, что любой контейнер - это тоже компонента, поэтому группы объектов могут быть вложены друг в друга. Как обычно, меню стоят особняком.

К числу примитивных компонент относятся:

Основные контейнеры:

Взаимодействие интерфейсных компонент с пользователем реализовано с помощью аппарата событий, о котором будет рассказано ниже.

Библиотека Swing

Современные программы нуждаются в графическом интерфейсе пользователя (GUI). Пользователи отвыкли работать через консоль: они управляют программой и вводят входные данные посредством так называемых элементов управления (в программировании их также называют визуальными компонентами), к которым относятся кнопки, текстовые поля, выпадающие списки и т.д.

Каждый из современных языков программирования предоставляет множество библиотек для работы со стандартным набором элементов управления. Напомним, что под библиотекой в программировании набор готовых классов и интерфейсов, предназначенных для решения определенного круга задач.

В Java есть три библиотеки визуальных компонентов для создания графического интерфейса пользователя. Самая ранняя из них называется AWT. Считается, что при ее проектировании был допущен ряд недочетов, вследствие которых с ней довольно сложно работать. Библиотека Swing разработана на базе AWT и заменяет большинство ее компонентов своими, спроектированными более тщательно и удобно. Третья, самая новая библиотека, называется SWT.

Каждая библиотека предоставляет набор классов для работы с кнопками, списками, окнами, меню и т.д., но эти классы спроектированы по-разному: они имеют различный набор методов с разными параметрами, поэтому «перевести» программу с одной библиотеки на другую (например, с целью увеличения быстродействия) не так-то просто. Это почти как перейти с одного языка программирования на другой: все языки умеют делать одно и то же, но у каждого из них свой синтаксис, своя программная структура и свои многочисленные хитрости.

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

Окно JFrame

Каждая GUI-программа запускается в окне и по ходу работы может открывать несколько дополнительных окон.

В библиотеке Swing описан класс JFrame , представляющий собой окно с рамкой и строкой заголовка (с кнопками «Свернуть», «Во весь экран» и «Закрыть»). Оно может изменять размеры и перемещаться по экрану.

об окнах Swing

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

Конструктор JFrame() без параметров создает пустое окно. Конструктор JFrame(String title) создает пустое окно с заголовком title .

Чтобы написать простейшую программу, выводящую на экран пустое окно, нам потребуется еще три метода:

setSize(int width, int height) - устанавливает размеры окна. Если не задать размеры, окно будет иметь нулевую высоту независимо от того, что в нем находится и пользователю после запуска придется растягивать окно вручную. Размеры окна включают не только «рабочую» область, но и границы и строку заголовка.

setDefaultCloseOperation(int operation) - позволяет указать действие, которое необходимо выполнить, когда пользователь закрывает окно нажатием на крестик. Обычно в программе есть одно или несколько окон при закрытии которых программа прекращает работу. Для того, чтобы запрограммировать это поведение, следует в качестве параметра operation передать константу EXIT_ON_CLOSE , описанную в классе JFrame .

setVisible(boolean visible) - когда окно создается, оно по умолчанию невидимо. Чтобы отобразить окно на экране, вызывается данный метод с параметром true . Если вызвать его с параметром false , окно снова станет невидимым.

Теперь мы можем написать программу, которая создает окно, выводит его на экран и завершает работу после того, как пользователь закрывает окно.

import javax.swing.*; public class MyClass { public static void main (String args) { JFrame myWindow = new JFrame("Пробное окно" ); myWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); myWindow.setSize(400, 300); myWindow.setVisible(true ); } }

Обратите внимание, для работы с большинством классов библиотеки Swing понадобится импортировать пакет java.swing.*

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

В файле SimpleWindow.java:

public class SimpleWindow extends JFrame { SimpleWindow(){ super ("Пробное окно" ); setDefaultCloseOperation(EXIT_ON_CLOSE); setSize(250, 100); } }

В файле Program.java:

public class Program { public static void main (String args) { JFrame myWindow = new SimpleWindow(); myWindow.setVisible(true ); } }

Из примера видно, что окно описывается в отдельном классе, являющемся наследником JFrame и настраивающее свой внешний вид и поведение в конструкторе (первой командой вызывается конструктор суперкласса). Метод main() содержится в другом классе, ответственном за управление ходом программы. Каждый из этих классов очень прост, каждый занимается своим делом, поэтому в них легко разбираться и легко сопровождать (т.е. совершенствовать при необходимости).

Обратите внимание, что метод setVisible() не вызывается в классе SimpleWindow , что вполне логично: за тем, где какая кнопка расположена и какие размеры оно должно иметь, следит само окно, а вот принимать решение о том, какое окно в какой момент выводится на экран - прерогатива управляющего класса программы.

Панель содержимого

Напрямую в окне элементы управления не размещаются. Для этого служит панель содержимого, занимающая все пространство окна* . Обратиться к этой панели можно методом getContentPane() класса JFrame . С помощью метода add(Component component) можно добавить на нее любой элемент управления.

В примерах этого занятия мы будем использовать только один элемент управления - кнопку (не вдаваясь в подробности ее устройства). Кнопка описывается классом JButton и создается конструктором с параметром типа String - надписью.

Добавим кнопку в панель содержимого нашего окна командами:

JButton newButton = new JButton(); getContentPane().add(newButton);

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

Класс Container (контейнер)

Элементы, которые содержат другие элементы, называются контейнерами. Все они являются потомками класса Container и наследуют от него ряд полезных методов:

add(Component component) - добавляет в контейнер элемент component ;

remove(Component component) - удаляет из контейнера элемент component ;

removeAll() - удаляет все элементы контейнера;

getComponentCount() - возвращает число элементов контейнера.

Кроме перечисленных в классе Container определено около двух десятков методов для управления набором компонентов, содержащихся в контейнере. Как видно, они похожи на методы класса-коллекции. Это неудивительно, ведь по сути контейнер и является коллекцией, но коллекцией особого рода - визуальной. Кроме хранения элементов контейнер занимается их пространственным расположением и прорисовкой. В частности, он имеет метод getComponentAt(int x, int y) , возвращающий компонент, в который попадает точка с заданными координатами (координаты отсчитываются от левого верхнего угла компонента) и ряд других. Мы не будем подробно рассматривать абстрактный контейнер, а сразу перейдем к его наиболее часто используемому потомку - классу JPanel .

Класс JPanel (панель)

Панель JPanel - это элемент управления, представляющий собой прямоугольное пространство, на котором можно размещать другие элементы. Элементы добавляются и удаляются методами, унаследованными от класса Container .

В примере с кнопкой мы наблюдали, как добавленная на панель содержимого кнопка заняла все ее пространство. Это происходит не всегда. На самом деле у каждой панели есть так называемый менеджер размещения , который определяет стратегию взаимного расположения элементов, добавляемых на панель. Его можно изменить методом setLayout(LayoutManager manager) . Но чтобы передать в этот метод нужный параметр, необходимо знать, какими бывают менеджеры.

Менеджер последовательного размещения FlowLayout

Самый простой менеджер размещения - FlowLayout . Он размещает добавляемые на панель компоненты строго по очереди, строка за строкой, в зависимости от размеров панели. Как только очередной элемент не помещается в текущей строке, он переносится на следующую. Лучше всего пронаблюдать это на примере. Изменим конструктор класса SimpleWindow следующим образом:

SimpleWindow(){ super ("Пробное окно" ); setDefaultCloseOperation(EXIT_ON_CLOSE); JPanel panel = new JPanel(); panel.setLayout(new FlowLayout()); panel.add(new JButton("Кнопка" )); panel.add(new JButton("+" )); panel.add(new JButton("-" )); panel.add(new JButton("Кнопка с длинной надписью" )); setContentPane(panel); setSize(250, 100); }

Менеджеры расположения описаны в пакете java.awt. Не забывайте импортировать нужные классы.

Пронаблюдайте за поведением окна, появляющегося после запуска программы. Четыре кнопки в нем расположены как слова в текстовом редакторе (при выравнивании по центру). Эффект будет лучше заметен, если изменять размеры окна во время работы программы.

Проанализируем текст примера. Новый менеджер расположения FlowLayout создается конструктором без параметров. Обратите внимание, в программе не используется промежуточная переменная. То есть вместо двух команд:

FlowLayout newLayout = new FlowLayout(); panel.setLayout(newLayout);

Мы используем одну:

Panel.setLayout(new FlowLayout());

Это вполне допустимо в тех случаях, когда в дальнейшем нам не потребуется обращаться к создаваемому объекту (что справедливо для данного примера). Мы создаем менеджер расположения, тут же привязываем его к панели - и все. Теперь панель и менеджер сами найдут друг с другом общий язык.

о взаимоотношениях панели и ее менеджера

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

Кстати, класс JPanel кроме конструктора без параметров, имеет конструктор, в котором в качестве параметра задается менеджер расположения. Поэтому вместо команд

JPanel panel = new JPanel(); panel.setLayout(new FlowLayout());

можно написать:

JPanel panel = new JPanel(new FlowLayout());

Более того, по умолчанию вновь создаваемая панель имеет именно менеджер расположения FlowLayout . Поэтому в приведенном выше примере мы устанаваем менеджера скорее для наглядности, вообще же, делать это не обязательно.

Точно также мы добавляем на панель новые кнопки. Мы нигде больше не пытаемся обратиться к этим кнопкам в программе, поэтому заводить под них переменные нет смысла.

Метод setContentPane(JPanel panel) позволяет заменить панель содержимого окна.

Менеджер граничного размещения BorderLayout

Менеджер размещения BorderLayout разделяет панель на пять областей: центральную, верхнюю, нижнюю, правую и левую. В каждую из этих областей можно добавить ровно по одному компоненту, причем компонент будет занимать всю отведенную для него область. Компоненты, добавленные в верхнюю и нижнюю области, будут растянуты по ширине, добавленные в правую и левую - по высоте, а компонент, добавленный в центр, будет растянут так, чтобы полностью заполнить оставшееся пространство панели.

При добавлении элемента на панель с менеджером размещения BorderLayout , необходимо дополнительно указывать в методе add() , какая из областей имеется в виду. Для этого служат строки с названиями сторон света: "North" , "South" , "East" , "West" и "Center" . Но вместо них рекомендуется использовать константы, определенные в классе BorderLayout: NORTH , SOUTH , EAST , WEST и CENTER (поскольку в строке можно допустить ошибку и не заметить этого, а при попытке написать неправильно имя константы компилятор выдаст предупреждение). Если же использовать метод add() как обычно, с одним параметром, элемент будет добавлен в центр.

Панель содержимого имеет именно такое расположение, именно поэтому кнопка и занимала все окно целиком (она была добавлена в центральную область). Чтобы пронаблюдать эффект BorderLayout , добавим кнопки во все пять областей:

SimpleWindow(){ super("Пробное окно" ); setDefaultCloseOperation(EXIT_ON_CLOSE); getContentPane().add(new JButton("Кнопка" ), BorderLayout.NORTH); getContentPane().add(new JButton("+" ), BorderLayout.EAST); getContentPane().add(new JButton("-" ), BorderLayout.WEST); getContentPane().add(new JButton("Кнопка с длинной надписью" ), BorderLayout.SOUTH); getContentPane().add(new JButton("В ЦЕНТР!" )); setSize(250, 100); }

Эффект будет хорошо наблюдаться, если изменять размеры окна.

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

Менеджер табличного размещения GridLayout

GridLayout разбивает панель на ячейки одинаковой ширины и высоты (таким образом окно становится похожим на таблицу). Каждый элемент, добавляемый на панель с таким расположением, целиком занимает одну ячейку. Ячейки заполняются элементами по очереди, начиная с левой верхней.

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

SimpleWindow(){ super("Пробное окно" ); setDefaultCloseOperation(EXIT_ON_CLOSE); JPanel panel = new JPanel(); panel.setLayout(new GridLayout(2,3,5,10)); panel.add(new JButton("Кнопка" )); panel.add(new JButton("+" )); panel.add(new JButton("-" )); panel.add(new JButton("Кнопка с длинной надписью" )); panel.add(new JButton("еще кнопка" )); setContentPane(panel); setSize(250, 100); }

Менеджер блочного размещения BoxLayout и класс Box

Менеджер BoxLayout размещает элементы на панели в строку или в столбец.

Обычно для работы с этим менеджером используют вспомогательный класс Box , представляющий собой панель, для которой уже настроено блочное размещение. Создается такая панель не конструктором, а одним из двух статических методов, определенных в классе Box: createHorizontalBox() и createVerticalBox() .

Элементы, добавленные на панель с блочным размещением, выстраиваются один за другим. Расстояние между элементами по умолчанию нулевое. Однако вместо компонента можно добавить невидимую «распорку», единственная задача которой - раздвигать соседние элементы, обеспечивая между ними заданное расстояние. Горизонтальная распорка создается статическим методом createHorizontalStrut(int width) , а вертикальная - методом createVerticalStrut(int height) . Оба метода определены в классе Box , а целочисленный параметр в каждом из них определяет размер распорки.

Кроме того, на такую панель можно добавить еще один специальный элемент - своеобразную «пружину». Если размер панели будет больше, чем необходимо для оптимального размещения всех элементов, те из них, которые способны растягиваться, будут стараться заполнить дополнительное пространство собой. Если же разместить среди элементов одну или несколько «пружин», дополнительное свободное пространство будет распределяться и в эти промежутки между элементами. Горизонтальная и вертикальная пружины создаются соответственно методами createHorizontalGlue() и createVerticalGlue() .

Понять особенности работы этого менеджера лучше на наглядном примере. Мы расположим четыре кнопки вертикально, поставив между двумя центральными «пружину», а между остальными - распорки в 10 пикселов.

SimpleWindow(){ super("Пробное окно" ); setDefaultCloseOperation(EXIT_ON_CLOSE); Box box = Box.createVerticalBox(); box.add(new JButton("Кнопка" )); box.add(Box.createVerticalStrut(10)); box.add(new JButton("+" )); box.add(Box.createVerticalGlue()); box.add(new JButton("-" )); box.add(Box.createVerticalStrut(10)); box.add(new JButton("Кнопка с длинной надписью" )); setContentPane(box); setSize(250, 100); }

Особенности выравнивания элементов

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

Однако при разработке окна программы может понадобиться, чтобы какие-то элементы были выровнены иначе, например, по правому краю или по центру. Для того, чтобы установить выравнивание любого визуального компонента (например, кнопки или панели), используются методы setAlignmentX(float alignment) - выравнивание по горизонтали и setAlignmentY(float alignment) - выравнивание по вертикали. В качестве параметра проще всего использовать константы, определенные в классе JComponent . Для выравнивания по горизонтали служат константы LEFT_ALIGNMENT (по левому краю), RIGHT_ALIGNMENT (по правому краю) и CENTER_ALIGNMENT (по центру). Для выравнивания по вертикали - BOTTOM_ALIGNMENT (по нижнему краю), TOP_ALIGNMENT (по верхнему краю) и CENTER_ALIGNMENT (по центру).

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

Box.add(new JButton("-" ));

На три других:

JButton rightButton = new JButton("-" ); rightButton.setAlignmentX(JComponent.RIGHT_ALIGNMENT); box.add(rightButton);

Нам пришлось ввести переменную для обращения к этой кнопке, поскольку теперь нам нужно выполнить с ней не одно, а два действия: установка выравнивания по правому краю и добавление в панель. Прежний прием - одновременное создание кнопки и передача ее в качестве параметра в метод - здесь не сработает.

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

Объяснение просто. При выравнивании по правому краю объект не прижимается к правому краю компонента. Вместо этого он прижимается правым краем к невидимой линии выравнивания. Все остальные компоненты прижимаются к этой линии своим левым краем, поэтому и получается наблюдаемый эффект.

Единственная трудность для начинающего разработчика может оказаться в том, что не всегда легко понять, где именно пройдет эта линия. Ее положение зависит от размеров и выравнивания всех элементов контейнера. Однако легко запомнить простое правило: если все элементы в контейнере выровнены одинаково, мы получим привычное поведение (как это и было в предыдущем примере, когда все компоненты были выровнены влево и линия в результате прижалась к левому краю панели.

о выравнивании элементов

Параметр выравнивания на самом деле представляет собой вещественное число в диапазоне от 0 до 1. Он показывает, какая часть компонента окажется слева от линии выравнивания, т.е. в каких пропорциях компонент будет «разрезан». Константы LEFT_ALIGNMENT и TOP_ALIGNMENT на самом деле равны 0, RIGHT_ALIGNMENT и BOTTOM_ALIGNMENT равны 1, а CENTER_ALIGHNMENT - 0.5. Можно подставлять эти числа напрямую (хотя использование констант значительно повышает наглядность!), а можно выбрать любое другое число от 0 до 1 и настроить совершенно произвольное выравнивание.

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

Ручное размещение элементов

Если в качестве менеджера размещения панели установить null , элементы не будут расставляться автоматически. Координаты каждого элемента необходимо в этом случае указать явно, при этом они никак не зависят от размеров панели и от координат других элементов. По умолчанию координаты равны нулю (т.е. элемент расположен в левом верхнем углу панели). Размер элемента также необходимо задавать явно (в противном случае его ширина и высота будут равны нулю и элемент отображаться не будет).

Координаты элемента можно задать одним из следующих методов:

setLocation(int x, int y) ,

setLocation(Point point)

Эти методы работают аналогично, устанавливая левый верхний угол элемента в точку с заданными координатами. Разница в способе задания точки. Можно представить точку двумя целыми числами, а можно объектом класса Point . Класс Point по сути представляет собой ту же пару чисел, его конструктор имеет вид Point(int x, int y) . Получить доступ к отдельной координате можно методами getX() и getY() .

Можно задаться вопросом: зачем использовать класс Point , если можно просто передать пару чисел? Но дело в том, что многие полезные методы возвращают результат - координаты некоторой точки - в виде объекта этого класса. Например, метод getLocation() , возвращающий координаты элемента. Предположим, нам нужно поместить элемент b в точности в то место, которое занимает элемент a . Этого легко добиться одной строкой:

B.setLocation(a.getLocation());

Размер элемента задается одним из двух методов:

setSize(int width, int height) ,

setSize(Dimension size)

Эти методы работают одинаково - разница, как и в прошлый раз, в способе передачи параметра. Класс Dimension , аналогично классу Point , просто хранит два числа, имеет конструктор с двумя параметрами: Dimension(int width, int height) и позволяет получить доступ к своим составляющим - ширине и высоте - с помощью простых методов getWidth() и getHeigth() . Для того, чтобы получить текущий размер элемента, можно воспользоваться методом getSize() , возвращающего объект класса Dimension . Элемент b можно сделать точно такого же размера, как элемент a , выполнив команду:

B.setSize(a.getSize());

Создадим панель, с которой не будет связано никакого менеджера размещения и вручную разместим на ней две кнопки:

SimpleWindow(){ super("Пробное окно" ); setDefaultCloseOperation(EXIT_ON_CLOSE); JPanel panel = new JPanel(); panel.setLayout(null ); JButton button = new JButton("Кнопка" ); button.setSize(80, 30); button.setLocation(20,20); panel.add(button); button = new JButton("Кнопка с длинной надписью" ); button.setSize(120, 40); button.setLocation(70,50); panel.add(button); setContentPane(panel); setSize(250, 150); }

Мы используем одну и ту же переменную button для обращения к обеим кнопкам (причем, второй раз ее описывать не нужно). В самом деле, осуществив все необходимые операции с первой кнопкой и зная, что обращаться к ней нам больше не понадобится, мы используем «освободившуюся» переменную для манипуляций со второй.

Автоматическое определение размеров компонентов

Если у панели есть любой менеджер размещения, она игнорирует явно заданные размеры и координаты всех своих элементов. В этом легко убедиться, заменив в предыдущем примере команду panel.setLayout(null ) на panel.setLayout(new FlowLayout()) . Менеджер размещения сам определяет координаты и размеры всех элементов.

Способ определения координат элементов очевидным образом вытекает из алгоритмов работы каждого менеджера и, таким образом, детально рассмотрен нами выше.

Мы также отмечали, что в некоторых случаях компоненты стараются заполнить все доступное им пространство. Например, всю центральную область в случае менеджера BorderLayout или всю ячейку в менеджере GridLayout . А в панели с менеджером FlowLayout , напротив, элементы никогда не пытаются выйти за определенные границы. Рассмотрим, что это за границы.

Каждый визуальный компонент имеет три типа размеров: минимально допустимый, максимально допустимый и предпочтительный. Узнать, чему равны эти размеры для данного компонента можно с помощью соответствующих методов:

getMinimumSize() ,

getPreferredSize() ,

getMaximumSize() .

Методы возвращают результат типа Dimension . Они запрограммированы в соответствующем классе. Например, у кнопки минимальный размер - нулевой, максимальный размер не ограничен, а предпочтительный зависит от надписи на кнопке (вычисляется как размер текста надписи плюс размеры полей).

Менеджер FlowLayout всегда устанавливает предпочтительные размеры элементов. Менеджер BorderLayout устанавливает предпочтительную ширину правого и левого, а также предпочтительную высоту верхнего и нижнего. Остальные размеры подгоняются под доступное пространство панели. Менеджер GridLayout пытается подогнать размеры всех элементов под размер ячеек. Менеджер BoxLayout ориентируется на предпочтительные размеры.

Когда элемент старается занять все доступное ему пространство, он «учитывает» пожелания не делаться меньше своих минимальных или больше максимальных.

Всеми тремя размерами можно управлять с помощью соответствующим методов set:

setMinimumSize(Dimension size) ,

setPreferredSize(Dimension size) ,

setMaximumSize(Dimension size) .

Чаще всего используется простой прием, когда элементу «не рекомендуется» увеличиваться или уменьшаться относительно своих предпочтительных размеров. Это легко сделать командой:

Element.setMinimumSize(element.getPreferredSize());

«Упаковка» окна

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

Безусловно, наиболее подходящим будет вариант, при котором все элементы окна имеют предпочтительные размеры или близкие к ним* .

Если вместо явного указания размеров окна, вызвать метод pack() , они будут подобраны оптимальным образом с учетом предпочтений всех элементов, размещенных в этом окне.

SetSize(250, 100);

на команду

Заметьте, что когда панель не имеет метода размещения, эта команда не работает (поскольку панель не имеет алгоритма для вычисления своего предпочтительного размера).

Упражнение

Как уже отмечалось, элементом панели может быть другая панель. Создайте панель с тремя кнопками и менеджером размещения FlowLayout и панель с двумя кнопками и менеджером размещения BoxLayout (горизонтальным). Разместите обе панели в главном окне (не изменяя менеджера размещения у панели содержимого): одну в центр, а другую вдоль любой стороны окна.

Рамки

Когда панели служат не просто для размещения элементов в соответствии с алгоритмом некоторого менеджера, а для визуального отделения их друг от друга, они оформляются с помощью рамок.

Рамка панели устанавливается методом setBorder(Border border) . Параметром метода выступает рамка - объект класса Border . Это абстрактный класс, поэтому для создания рамки используются его наследники:

EmptyBorder - пустая рамка, позволяет создать отступы вокруг панели. Размеры отступов задаются в конструкторе четырьмя целыми числами.

TitledBorder - рамка с заголовком. Простейший конструктор имеет один параметр типа String (текст заголовка). Заголовок может размещаться вдоль любой стороны рамки, иметь различные начертания.

EtchedBorder - рамка с тиснением. Может быть вогнутой или выпуклой.

BevelBorder - объемная рамка (выпуклая или вогнутая). Можно настроить цвета, требуемые для получения объемных эффектов.

SoftBevelBorder - то же самое, что BevelBorder, но позволяет дополнительно скруглить углы.

LineBorder - простая рамка, нарисованная сплошной линией. Можно выбирать цвет и толщину линии, скруглить углы.

MatteBorder - рамка из повторяющегося рисунка.

CompoundBorder - объединяет две рамки, передаваемые в качестве параметров конструктору в одну новую рамку.

Все перечисленные классы описаны в пакете javax.swing.border.

Рассмотрим пример. В этом примере мы создадим шесть панелей с различными рамками и разместим их в виде таблицы. Чтобы не описывать шесть раз процедуру создания новой панели, вынесем ее в отдельный метод:

Private JPanel createPanel(Border border, String text) { JPanel panel = new JPanel(); panel.setLayout(new BorderLayout()); panel.add(new JButton(text)); panel.setBorder(new CompoundBorder(new EmptyBorder(12,12,12,12), border)); return panel; }

Метод createPanel() создает панель с кнопкой во весь свой размер. В качестве параметра передается надпись на кнопке и рамка, которую необходимо добавить к панели. Рамка добавляется не напрямую, а путем композиции с пустой рамкой. Этот прием часто используется, чтобы рамка не прилипала к краю панели.

Теперь шесть раз воспользуемся этим методом в конструкторе окна программы.

SimpleWindow(){ super("Пробное окно" ); setDefaultCloseOperation(EXIT_ON_CLOSE); JPanel panel = new JPanel(); panel.setLayout(new GridLayout(2,3,5,10)); panel.add(createPanel(new TitledBorder("Рамка с заголовком" ), "TitledBorder" )); panel.add(createPanel(new EtchedBorder(), "EtchedBorder" )); panel.add(createPanel(new BevelBorder(BevelBorder.LOWERED), "BevelBorder" )); panel.add(createPanel(new SoftBevelBorder(BevelBorder.RAISED), "SoftBevelBorder" )); panel.add(createPanel(new LineBorder(Color.ORANGE, 4), "LineBorder" )); panel.add(createPanel(new MatteBorder(new ImageIcon("1.gif" )), "MatteBorder" )); setContentPane(panel); pack(); }

Этот пример показывает, с помощью каких конструкторов создаются различные рамки и как они выглядят. В нем использованы два новых класса: Color и ImageIcon .

Класс Color предназначен для работы с цветом. В нем есть несколько констант, описывающих наиболее распространенные цвета. В частности, к таковым относится Color.ORANGE .

Класс ImageIcon описывает графическое изображение. Параметр его конструктора - это путь к файлу, из которого изображение может быть загружено. В примере используется относительное имя файла «1.gif». Чтобы объект ImageIcon был успешно создан, файл с таким именем должен быть помещен в папку проекта.

Пользовательский интерфейс на Java прошел весьма тернистый путь становления и развития. Долгое время его обвиняли в медленной работе, жадности к ресурсам системы, ограниченной функциональности. Появление.NET с более быстрыми графическими компонентами еще больше пошатнуло позиции Java. Но нет худа без добра - все эта движуха только подстегивала разработчиков Java к развитию и улучшению графических библиотек. Посмотрим, что из этого получилось.

Abstract Window Toolkit

AWT была первой попыткой Sun создать графический интерфейс для Java. Они пошли легким путем и просто сделали прослойку на Java, которая вызывает методы из библиотек, написанных на С. Библиотечные методы создают и используют графические компоненты операционной среды. С одной стороны, это хорошо, так как программа на Java похожа на остальные программы в рамках данной ОС. Но с другой стороны, нет никакой гарантии, что различия в размерах компонентов и шрифтах не испортят внешний вид программы при запуске ее на другой платформе. Кроме того, чтобы обеспечить мультиплатформенность, пришлось унифицировать интерфейсы вызовов компонентов, из-за чего их функциональность получилась немного урезанной. Да и набор компонентов получился довольно небольшой. К примеру, в AWT нет таблиц, а в кнопках не поддерживается отображение иконок.

Использованные ресурсы AWT старается освобождать автоматически. Это немного усложняет архитектуру и влияет на производительность. Освоить AWT довольно просто, но написать что-то сложное будет несколько затруднительно. Сейчас ее используют разве что для апплетов.

Достоинства:

  • часть JDK;
  • скорость работы;
  • графические компоненты похожи на стандартные.

Недостатки:

  • использование нативных компонентов налагает ограничения на использование их свойств. Некоторые компоненты могут вообще не работать на «неродных» платформах;
  • некоторые свойства, такие как иконки и всплывающие подсказки, в AWT вообще отсутствуют;
  • стандартных компонентов AWT очень немного, программисту приходится реализовывать много кастомных;
  • программа выглядит по-разному на разных платформах (может быть кривоватой).

заключение:

В настоящее время AWT используется крайне редко - в основном в старых проектах и апплетах. Oracle припрятал обучалки и всячески поощряет переход на Swing. Оно и понятно, прямой доступ к компонентам оси может стать серьезной дырой в безопасности.

Swing


Вслед за AWT Sun разработала набор графических компонентов под названием Swing. Компоненты Swing полностью написаны на Java. Для отрисовки используется 2D, что принесло с собой сразу несколько преимуществ. Набор стандартных компонентов значительно превосходит AWT по разнообразию и функциональности. Стало легко создавать новые компоненты, наследуясь от существующих и рисуя все, что душе угодно. Стала возможной поддержка различных стилей и скинов. Вместе с тем скорость работы первых версий Swing оставляла желать лучшего. Некорректно написанная программа и вовсе могла повесить винду намертво.

Тем не менее благодаря простоте использования, богатой документации и гибкости компонентов Swing стал, пожалуй, самым популярным графическим фреймворком в Java. На его базе появилось много расширений, таких как SwingX, JGoodies, которые значительно упрощают создание сложных пользовательских интерфейсов. Практически все популярные среды программирования Java включают графические редакторы для Swing-форм. Поэтому разобраться и начать использовать Swing не составит особого труда.

Достоинства:

  • часть JDK, не нужно ставить дополнительных библиотек;
  • по Swing гораздо больше книжек и ответов на форумах. Все проблемы, особенно у начинающих, гуглу досконально известны;
  • встроенный редактор форм почти во всех средах разработки;
  • на базе свинга есть много расширений типа SwingX;
  • поддержка различных стилей (Look and feel).

Недостатки:

  • окно с множеством компонентов начинает подтормаживать;
  • работа с менеджерами компоновки может стать настоящим кошмаром в сложных интерфейсах.

Заключение:

Swing жил, Swing жив, Swing будет жить. Хотя Oracle и старается продвигать JavaFX, на сегодняшний день Swing остается самым популярным фреймворком для создания пользовательских интерфейсов на Java.

Standard Widget Toolkit


Как
выглядит
SWT

SWT был разработан в компании IBM в те времена, когда Swing еще был медленным, и сделано это было в основном для продвижения среды программирования Eclipse. SWT, как и AWT, использует компоненты операционной системы, но для каждой платформы у него созданы свои интерфейсы взаимодействия. Так что для каждой новой системы тебе придется поставлять отдельную JAR-библиотеку с подходящей версией SWT. Это позволило более полно использовать существующие функции компонентов на каждой оси. Недостающие функции и компоненты были реализованы с помощью 2D, как в Swing. У SWT есть много приверженцев, но, положа руку на сердце, нельзя не согласиться, что получилось не так все просто, как хотелось бы. Новичку придется затратить на изучение SWT намного больше времени, чем на знакомство с тем же Swing. Кроме того, SWT возлагает задачу освобождения ресурсов на программиста, в связи с чем ему нужно быть особенно внимательным при написании кода, чтобы случайное исключение не привело к утечкам памяти.

Достоинства:

  • использует компоненты операционной системы - скорость выше;
  • Eclipse предоставляет визуальный редактор форм;
  • обширная документация и множество примеров;
  • возможно использование AWT- и Swing-компонентов.

Недостатки:

  • для каждой платформы необходимо поставлять отдельную библиотеку;
  • нужно все время следить за использованием ресурсов и вовремя их освобождать;
  • сложная архитектура, навевающая суицидальные мысли после тщетных попыток реализовать кастомный интерфейс.

Заключение:

Видно, что в IBM старались. Но получилось уж очень на любителя…

JavaFX


JavaFX можно без преувеличения назвать прорывом. Для отрисовки используется графический конвейер, что значительно ускоряет работу приложения. Набор встроенных компонентов обширен, есть даже отдельные компоненты для отрисовки графиков. Реализована поддержка мультимедийного контента, множества эффектов отображения, анимации и даже мультитач. Внешний вид всех компонентов можно легко изменить с помощью CSS-стилей. И самое прекрасное - в JavaFX входит набор утилит, которые позволяют сделать родной инсталлятор для самых популярных платформ: exe или msi для Windows, deb или rpm для Linux, dmg для Mac. На сайте Oracle можно найти подробную документацию и огромное количество готовых примеров. Это превращает программирование с JavaFX в легкое и приятное занятие.

Достоинства:

  • быстрая работа за счет графического конвейера;
  • множество различных компонентов;
  • поддержка стилей;
  • утилиты для создания установщика программы;
  • приложение можно запускать как десктопное и в браузере как часть страницы.

Недостатки:

  • фреймворк еще разрабатывается, поэтому случаются и падения и некоторые глюки;
  • JavaFX пока не получил широкого распространения.

Заключение:

Хорошая работа, Oracle. Фреймворк оставляет только позитивные впечатления. Разобраться несложно, методы и интерфейсы выглядят логичными. Хочется пользоваться снова и снова!

Визуальные библиотеки на практике

SWT: погодный виджет

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

И начнем, пожалуй, с самого популярного виджета - отображения текущей погоды, для реализации которого выберем SWT.

Любая программа на SWT начинается с создания объекта Display. Он служит своеобразным контекстом приложения, который содержит необходимые методы для обращения к ресурсам системы и обеспечивает цикл событий. Следующим шагом будет создание не менее важного объекта Shell. Shell представляет собой обычное окно операционной системы. В конструктор shell передается Display, чтобы создать окно верхнего уровня.

Display display = new Display(); shell = new Shell(display, SWT.NO_TRIM);

Так как мы создаем виджет, нам не нужно отображать стандартное обрамление окна и кнопки управления, для этого мы указали флаг NO_TRIM. Для фона мы будем использовать картинку - прямоугольник с закругленными углами. В принципе, окно SWT может принимать любые формы. Чтобы добиться такого эффекта, используем класс Region. Все, что нужно, - добавить в этот класс все видимые точки из картинки фона, пропуская прозрачные.

Загружаем картинку:

Image image = new Image(display, "images/bg.png#26759185");

В изображениях разных форматов прозрачность задается по-разному, поэтому и извлекается информация о прозрачных областях тоже не одинаково. Создаем область фона и добавляем туда все видимые точки:

Region region = new Region(); ImageData imageData = image.getImageData(); if (imageData.alphaData != null) { Rectangle pixel = new Rectangle(0, 0, 1, 1); for (int y = 0; y < imageData.height; y++) { for (int x = 0; x < imageData.width; x++) { if (imageData.getAlpha(x, y) == 255) { pixel.x = imageData.x + x; pixel.y = imageData.y + y; region.add(pixel); } } } } else { ImageData mask = imageData.getTransparencyMask(); Rectangle pixel = new Rectangle(0, 0, 1, 1); for (int y = 0; y < mask.height; y++) { for (int x = 0; x < mask.width; x++) { if (mask.getPixel(x, y) != 0) { pixel.x = imageData.x + x; pixel.y = imageData.y + y; region.add(pixel); } } } }

Устанавливаем форму окна:

Shell.setRegion(region);

Теперь нужно создать слушателя событий для окна. Нас будут интересовать события рисования окна, события мыши и нажатия клавиш, чтобы окно можно было передвигать по экрану.

Listener listener = new Listener() { int startX, startY; public void handleEvent(Event e) { if (e.type == SWT.KeyDown && e.character == SWT.ESC) { shell.dispose(); } if (e.type == SWT.MouseDown && e.button == 1) { startX = e.x; startY = e.y; } if (e.type == SWT.MouseMove && (e.stateMask & SWT.BUTTON1) != 0) { Point p = shell.toDisplay(e.x, e.y); p.x -= startX; p.y -= startY; shell.setLocation(p); } if (e.type == SWT.Paint) { e.gc.drawImage(image, imageData.x, imageData.y); } } };

Итак, по нажатию на клавишу Esc окно закроется. При нажатии левой клавиши мыши на области окна запомним координаты нажатия. При движении мыши с зажатой левой клавишей - передвигаем окно на экране соответственно движению. При событии перерисовки - рисуем картинку фона, используя графический контекст GC.

Назначим слушатель соответствующим событиям окна:

Shell.addListener(SWT.KeyDown, listener); shell.addListener(SWT.MouseDown, listener); shell.addListener(SWT.MouseMove, listener); shell.addListener(SWT.Paint, listener);

Устанавливаем размер окна равным размеру изображения:

Shell.setSize(imageData.x + imageData.width, imageData.y + imageData.height);

Открываем окно и запускаем цикл событий:

Shell.open(); while (!shell.isDisposed ()) { if (!display.readAndDispatch ()) display.sleep (); }

Не забываем в конце освободить использованные ресурсы:

Region.dispose(); image.dispose(); display.dispose();

Запустив программу на этом этапе, мы получим прямоугольничек, который можно двигать мышкой и закрывать по Esc.

Настало время добавить содержания. Будем отображать текущую погоду в виде иконки состояния (солнечно, дождь, снег…), показаний температуры и времени последнего обновления.

Для расположения графических компонентов в окне в нужном виде используются менеджеры компоновки. Менеджер компоновки занимается не только расположением компонентов, но и изменением их размеров при изменении размеров окна. Для нашего виджета будем использовать GridLayout. Этот менеджер располагает компоненты в ячейках воображаемой таблицы. Создаем GridBagLayout на две колонки с различной шириной колонок (флаг false в конструкторе), устанавливаем его в качестве менеджера компоновки окна:

GridLayout layout = new GridLayout(2, false); shell.setLayout(layout);

Для картинки статуса используем компонент Label. В качестве родителя передаем объект окна. Вторым параметром можно установить стиль компонента. Для каждого компонента набор возможных флагов стиля разный, их можно посмотреть в документации или прямо в исходниках компонента.

//draw status image Label imageLabel = new Label(shell, SWT.NONE); imageLabel.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, true, true, 1, 1));

Флаги в классе GridData означают, что метка будет располагаться слева вверху, будет растягиваться горизонтально и вертикально (флаги, установленные в true) при наличии свободного места и занимает одну строку и один столбец таблицы компоновки.

В SWT нет прозрачного фона компонентов, и позади картинки статуса будет красоваться белый фон, чего, конечно, не хотелось бы. Поэтому создадим объект Color с цветом фона окна:

Color bgColor = new Color(display, 0x2b, 0x2b, 0x2b);

В конце программы этот объект также необходимо очистить, вызвав метод dispose. Устанавливаем цвет фона и картинку статуса, которую можно загрузить из файла точно так же, как мы загрузили картинку фона вначале:

ImageLabel.setBackground(bgColor); Image statusImage = new Image(display, "images/1.png#26759185"); imageLabel.setImage(statusImage);

Теперь добавим Label с текущей температурой и расположим его в правой верхней части окна:

Label temperatureLabel = new Label(shell, SWT.NONE); temperatureLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.TOP, false, false, 1, 1));

Установим какую-нибудь температуру:

TemperatureLabel.setText("+1 \u2103");

Для записи температуры по Цельсию используется юникодный номер соответствующего символа со служебными символами \u.

Шрифт по умолчанию для текстовых меток слишком маленький. Так что создадим новый, побольше:

FontData fD = temperatureLabel.getFont().getFontData(); fD.setHeight(30); fD.setStyle(SWT.BOLD); Font newFont = new Font(display, fD); temperatureLabel.setFont(newFont); Шрифт, как и другие ресурсные объекты, нужно освобождать. Для этого воспользуемся слушателем события разрушения метки:

TemperatureLabel.addDisposeListener(new DisposeListener() { public void widgetDisposed(DisposeEvent e) { newFont.dispose(); } });

Наконец, добавим метку с описанием погодных условий:

Label descriptionLabel = new Label(shell, SWT.WRAP); descriptionLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true, 2, 1)); descriptionLabel.setText("Облачно с прояснениями, небольшой дождь"); descriptionLabel.setBackground(bgColor); descriptionLabel.setForeground(display.getSystemColor(SWT.COLOR_WHITE));

Текст может быть довольно длинным, так что при создании метки указываем флаг WRAP, чтобы текст автоматически разбивался на несколько строк при нехватке места. Расположим компонент по центру и разрешим ему заполнить все горизонтальное пространство. Также укажем, что компонент занимает два столбца таблицы компоновки. Запускаем и получаем окошко с картинки «Виджет погоды».

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

Swing: всегда свежие новости

На Swing мы напишем виджет для отображения RSS-новостей. Начинаем, как и в прошлый раз, с создания окна. Класс, реализующий функционал стандартного окна в Swing, называется JFrame. По умолчанию закрытие окна приложения в Swing не приводит к остановке программы, так что лучше прописать, как должно себя вести окно при закрытии:

JFrame frame = new JFrame(); frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

Для представления новостей лучше всего подходит таблица. Swing построен на паттерне «Модель -представление - контроллер» (MVC). В архитектуре MVC модель предоставляет данные, представление отвечает за отображение данных (например, текст, поля ввода), а контроллер обеспечивает взаимодействие между моделью и представлением. Таблица хорошо демонстрирует этот подход. Для представления данных используется класс, реализующий интерфейс TableModel.

Для хранения информации о доступных новостях заведем класс FeedMessage c полями для названия статьи и даты выхода:

Public class FeedMessage { public String title; public Date publicationDate; }

Чтобы упростить и ускорить разработку, наследуем нашу модель данных от класса AbstractTableModel, который предлагает готовую реализацию почти всех методов интерфейса TableModel.

Public class RssFeedTableModel extends AbstractTableModel { private List entries = new ArrayList<>(); public void updateData(List entries) { this.entries = entries; fireTableDataChanged(); } public int getRowCount() { return entries.size(); } public int getColumnCount() { return 2; } public Object getValueAt(int rowIndex, int columnIndex) { switch (columnIndex) { case 0: return entries.get(rowIndex).title; case 1: return entries.get(rowIndex).publicationDate; } return null; } }

Метод fireTableDataChanged сообщает представлению, что модель данных изменилась и необходима перерисовка.

Создаем таблицу и немного изменяем ее вид, чтобы она была больше похожа на виджет. Убираем линии между строками и столбцами, увеличиваем высоту строки и убираем заголовок таблицы с названиями колонок:

JTable table = new JTable(new RssFeedTableModel()); table.setShowGrid(false); table.setIntercellSpacing(new Dimension(0, 0)); table.setRowHeight(30); table.setTableHeader(null);

Теперь займемся внешним видом ячеек. Swing позволяет назначать отдельные классы представления для разных типов данных. За отрисовку отдельных ячеек таблицы отвечает класс, наследующий интерфейс TableCellRenderer. По умолчанию используется DefaultTableCellRenderer, который представляет собой текстовую метку.

Назначим свой отрисовщик ячейки для данных типа String. Изменим стандартный цвет шрифта и сделаем чередующийся цвет фона, чтобы улучшить читаемость.

Table.setDefaultRenderer(String.class, new DefaultTableCellRenderer() { Color oddColor = new Color(0x25, 0x25, 0x25); Color evenColor = new Color(0x1a, 0x1a, 0x1a); Color titleColor = new Color(0x3a, 0xa2, 0xd7); public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); setBackground(row % 2 == 0 ? oddColor: evenColor); setForeground(titleColor); setFont(font); return this; } });

Чтобы таблица начала использовать наш отрисовщик, необходимо добавить метод, который возвращает тип данных для каждой ячейки, в модель данных:

Public Class getColumnClass(int columnIndex) { switch (columnIndex) { case 0: return String.class; case 1: return Date.class; } return Object.class; }

Новостей может быть много, поэтому поместим таблицу на панель прокрутки и сделаем ползунок прокрутки невидимым, чтобы он не портил нам дизайн виджета:

JScrollPane scrollPane = new JScrollPane(table); table.setFillsViewportHeight(true); scrollPane.getVerticalScrollBar().setPreferredSize (new Dimension(0,0));

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

Frame.getContentPane().add(scrollPane, BorderLayout.CENTER);

Как и в прошлый раз, уберем стандартное обрамление окна. А в качестве заголовка окна будем использовать стилизованную текстовую метку, которую разместим вверху окна.

JLabel titleLabel = new JLabel("Xakep RSS"); Font titleFont = new Font("Arial", Font.BOLD, 20); titleLabel.setFont(titleFont); titleLabel.setHorizontalAlignment(SwingConstants.CENTER); titleLabel.setForeground(Color.WHITE); titleLabel.setPreferredSize(new Dimension(0, 40)); frame.getContentPane().add(titleLabel, BorderLayout.NORTH);

В отличие от SWT, объекты «цвет» и «шрифт» освобождаются автоматически, так что можно больше не переживать за утечки памяти.

Добавляем слушатели мыши, чтобы окно можно было двигать по экрану.

MouseAdapter listener = new MouseAdapter() { int startX; int startY; public void mousePressed(MouseEvent e) { if (e.getButton() == MouseEvent.BUTTON1) { startX = e.getX(); startY = e.getY(); } } public void mouseDragged(MouseEvent e) { Point currCoords = e.getLocationOnScreen(); frame.setLocation(currCoords.x - startX, currCoords.y - startY); } }; titleLabel.addMouseListener(listener); titleLabel.addMouseMotionListener(listener);

Теперь поменяем форму окна на прямоугольник с закругленными углами. Лучше всего это делать в слушателе компонента, так как, если размер окна изменится, форма окна будет правильно пересчитана:

Frame.addComponentListener(new ComponentAdapter() { public void componentResized(ComponentEvent e) { frame.setShape(new RoundRectangle2D.Double(0, 0, frame.getWidth(), frame.getHeight(), 20, 20)); } });

Устанавливаем размер окна, убираем обрамление и делаем окно полупрозрачным.

Frame.setSize(520, 300); frame.setUndecorated(true); frame.setOpacity(0.85f);

Наконец, открываем окно в графическом потоке. SwingUtilities.invokeLater(new Runnable() { public void run() { frame.setVisible(true); } });

Осталось дописать загрузку данных в отдельном потоке, и получим такой вот виджет с последними новостями твоего любимого журнала:).

JavaFX: послушаем музычку

И наконец, гвоздь сезона - JavaFX. Воспользуемся его мультимедийными возможностями и компонентом для построения графиков и сделаем простенький эквалайзер.

Для начала наследуем класс виджета от Application. Это основной класс приложения в JavaFX. Application содержит основные методы жизненного цикла приложения. Компоненты формы создаются в методе start, аргументом которому служит класс Stage. Stage представляет собой окно программы. Изменим стиль окна на TRANSPARENT, чтобы убрать обрамление и кнопки. В Stage помещается класс Scene, в котором задаются размеры окна и цвет фона. В Scene, в свою очередь, передаем класс Group, в который будем помещать дочерние компоненты:

Public void start(Stage primaryStage) { primaryStage.initStyle(StageStyle.TRANSPARENT); Group root = new Group(); Scene scene = new Scene(root, 400, 200, Color.TRANSPARENT); primaryStage.setScene(scene);

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

CategoryAxis xAxis = new CategoryAxis(); NumberAxis yAxis = new NumberAxis(0,50,10); BarChart bc = new BarChart(xAxis,yAxis); bc.setPrefSize(400, 200); bc.setLegendVisible(false); bc.setAnimated(false); bc.setBarGap(0); bc.setCategoryGap(1); bc.setVerticalGridLinesVisible(false); bc.setHorizontalGridLinesVisible(false); xAxis.setLabel("Частота"); yAxis.setLabel("Мощность"); yAxis.setTickLabelFormatter(new NumberAxis.DefaultFormatter(yAxis, null, "dB"));

Заполняем диаграмму начальными данными:

XYChart.Series series1 = new XYChart.Series(); series1Data = new XYChart.Data; String categories = new String; for (int i=0; i(categories[i], 50); series1.getData().add(series1Data[i]); } bc.getData().add(series1);

Создаем прямоугольник с закругленными углами, чтобы придать виджету соответствующую форму:

Rectangle rectangle = new Rectangle(0, 0, 400, 200); Stop stops = new Stop { new Stop(0, new Color(0, 0, 0, 0.8)), null}; LinearGradient lg2 = new LinearGradient(0, 0, 0, 0, false, CycleMethod.NO_CYCLE, stops); rectangle.setFill(lg2); rectangle.setArcHeight(20); rectangle.setArcWidth(20);

Добавляем оба компонента к группе:

Root.getChildren().addAll(rectangle, bc);

Назначаем слушателей мыши к группе, чтобы двигать окно по экрану:

Root.setOnMousePressed(new EventHandler() { public void handle(MouseEvent me) { initX = me.getScreenX() - primaryStage.getX(); initY = me.getScreenY() - primaryStage.getY(); } }); root.setOnMouseDragged(new EventHandler() { public void handle(MouseEvent me) { primaryStage.setX(me.getScreenX() - initX); primaryStage.setY(me.getScreenY() - initY); } });

Загружаем песню в плеер:

File file = new File("выпусти меня отсюда.mp3"); Media audioMedia = null; audioMedia = new Media(file.toURI().toURL().toString()); audioMediaPlayer = new MediaPlayer(audioMedia);

Добавляем слушатель, который будет обновлять столбиковую диаграмму:

AudioMediaPlayer.setAudioSpectrumListener(new AudioSpectrumListener() { public void spectrumDataUpdate(double timestamp, double duration, float magnitudes, float phases) { for (int i = 0; i < series1Data.length; i++) { series1Data[i].setYValue(magnitudes[i] + 60); } } });

Делаем сцену видимой и запускаем песню:

PrimaryStage.show(); audioMediaPlayer.play();

Запускаем приложение:

Public static void main(String args) { launch(args); }

И наслаждаемся такой вот красотой.



Рекомендуем почитать

Наверх