Освоюємо Java/Графічний інтерфейс користувача
Робота з графікою (Swing та AWT)
[ред.]Для реалізації графічного інтерфейсу (GUI) в Java існують два основні пакети класів[1]:
- Abstract Window Toolkit (AWT)
- Swing
Перевагами першого є простота використання, інтерфейс подібний до інтерфейсу операційної системи та дещо краща швидкодія, оскільки базується на засобах ОС, щоправда має обмежений набір графічних елементів. Другий пакет Swing реалізує власний Java інтерфейс. Даний пакет створювався на основі AWT, і має набагато більше можливостей та більшу кількість графічних елементів. [2]. Swing-компоненти ще називають полегшеними (англ. lightweight), оскільки вони написані повністю на Java і, через це, платформонезалежні.
Принципи роботи із обома пакетами є схожі і освоївши роботу з одним, робота з іншим не матиме труднощів. В даному підручнику ми зосередимось на роботі із Swing.
Створення фреймів (вікон)
[ред.]Вікно верхнього рівня (таке що не містить в середині іншого вікна) в мові Java називається фреймом (frame - каркас). В бібліотеці AWT для цього вікна призначений клас Frame, а в бібліотеці Swing — JFrame.
Наступний код демонструє, як можна створити фрейм розміром 300х200 пікселів:
import javax.swing.JFrame;
public class SimpleFrame
{
public static void main(String[] args)
{
JFrame frame = new JFrame(); // створити фрейм
frame.setSize(300, 200); // задаємо ширину і висоту фрейму
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // задаємо дії при закритті фрейму
frame.setVisible(true); // показати фрейм на екрані (зробити видимим)
}
}
Проте розміщувати увесь код в main() поганий стиль, оскільки, наприклад, коли Вам доведеться додавати нові фрейми та інші елементи, код може стати доволі заплутаним. Крім того метод main статичний і потрібно враховувати деякі особливості робити у статичному контексті. Тому в main() часто намагаються залишити лише саме необхідне. І взагалі, якщо якийсь графічний елемент потребує значного коду, то бажано роботу з ним розмістити окремо. В нашому випадку усю роботу з фреймом можна доручити класу, який розширюватиме клас JFrame.
Наступний переписаний код робить теж саме, що і попередній. На перший погляд може здатися, що такий спосіб є доволі незручним, насправді ж у великих проектах - це виправдовується.
import javax.swing.*;
public class SimpleFrame
{
public static void main(String[] args)
{
MyFrame frame = new MyFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
class MyFrame extends JFrame
{
public MyFrame()
{
setSize(FRAME_WIDTH, FRAME_HEIGHT);
}
public static final int FRAME_WIDTH = 350;
public static final int FRAME_HEIGHT = 200;
}
Дана програма складається з двох класів, хоча метод MyFrame() можна б було розмістити і в класі SimpleFrame. Проте, на думку ряду програмістів, краще відділяти клас який запускає програму на виконання від класу, в якому описується інтерфейс користувача. Одною з причин такого є більш краща читабельність коду програми.
Розглянемо детальніше нашу програму.
Перший рядок import javax.swing.*; робить доступними класи бібліотеки Swing. Дальше в методі main() ми задаємо розмір нашому фреймові викликаючи конструктор класу MyFrame. Метод setSize() встановлює розмір вікна 350x200 пікселів. По замовчуванню фрейм буде 0х0 пікселів. Рядок frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); — вказує програмі, що потрібно зробити, якщо користувач захоче закрити фрейм. В даному випадку програма просто завершить свою роботу. І насамкінець ми робимо наш фрейм видимим викликаючи метод setVisible().
Робота з фреймами
[ред.]Фрейм - це контейнер в який поміщають всі інші компоненти (меню, кнопки, прапорці та інші елементи графічного елементу). Сам клас JFrame, який реалізує фрейм складається з чотирьох областей (pane), що накладаються одна на одну: коренева область (root pane), область шару (layered pane), прозора область (glass pane) та область вмісту (content pane). Перші три застосовують для створення та обслуговування меню. Для роботи з графічними елементами застосовується область вмісту, в яку і додають компоненти. Додати компонент можна наступним чином:
Container contentPane = frame.getContentPane();
Component c=....;
contentPane.add (c)
// з версії JDK 1.5 реалізований метод JFrame.add()
// що переадресовує виклик методу області вмісту contentPane.add()
// і тепер можна просто писати: add (c)
Щоправда напряму у фреймі не прийнято вставляти компоненти. Для цього використовується спеціальний компонент-контейнер - панель (panel), що добавляється до фрейму. Після цього у панель можна додавати різні графічні компоненти.
Для початку спробуємо намалювати прямокутник. Щоб додати відповідну панель, в якій буде здійснюватись малювання, необхідно:
- Визначити клас, що розширює клас JPanel;
- Замістити (перевизначити) в цьому класі метод paintComponent().
class MyPanel extends JPanel{
public void paintComponent(Graphics g){
.....// код, що здійснює малювання
}
}
Програмний код:
import javax.swing.*;
import java.awt.*;
public class SimpleFrame2
{
public static void main(String[] args)
{
MyFrame frame = new MyFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
class MyFrame extends JFrame
{
public MyFrame()
{
setSize(FRAME_WIDTH, FRAME_HEIGHT);
PaintPanel panel=new PaintPanel();
add(panel);
}
public static final int FRAME_WIDTH = 350;
public static final int FRAME_HEIGHT = 200;
}
class PaintPanel extends JPanel
{
@Override // вказує, що даний метод заміщується (не обов'язково)
public void paintComponent(Graphics g)
{
super.paintComponent (g);
g.drawString("Малюємо прямокутник:", 10, 20);
g.drawRect(10, 40, 300, 100);
}
}
Обробка подій
[ред.]Натиснення кнопки, закриття вікна, клацання мишкою – все це є прикладами подій, які отримує операційна система і передає відповідній програмі на обробку. Програміст повинен передбачити як потрібно обробляти дані події. Розглянемо як обробка подій реалізовується в Java.
В Java запропонована, так звана, модель делегування подій (event delegation model). Джерело події (event source) породжує подію, після чого вона передається в обробник подій (event listener – дослівно слухач події). При цьому будь-який об’єкт може бути призначеним як обробник деякої події. Така модель доволі гнучка, оскільки кожен програміст може вибрати зручний для нього спосіб обробки події (де саме її обробляти), проте інколи текст програми може бути дещо заплутаним для тих хто не звик до такої моделі.
Інформація про подію інкапсулюється у об’єкті події (event object). Всі події описуються підкласами java.util.EventObject. Як приклади, можна навести підкласи ActionEvent та WindowEvent. Перші об’єкти породжують кнопки, а другі вікна.
Джерела подій містять методи, які дозволяють зв’язати його з обробниками подій. Коли виникає подія, джерело повідомляє про неї усіх зареєстрованих обробників. Обробники подій на основі інформації у об’єкті події визначає як реагувати на ту чи іншу подію.
При розробці графічного інтерфейсу розробнику необхідного здійснити наступне:
- Створити клас, який оброблятиме подію чи ряд подій і, який реалізовуватиме відповідний інтерфейс
- Створити джерела подій (вікно, кнопки, смугу прокрутки тощо)
- Зв’язати джерела подій з обробниками подій
Сказане демонструє наступний фрагмент програми:
ActionListener listener = . . .; // створити обробник подій
JButton button = new JButton("Ok"); // створюємо кнопку
button.addActionListener(listener); // зв'язуємо кнопку з обробником подій
Клас, який реалізовуватиме інтерфейс ActionListener повинен мати метод actionPerformed() який в якості параметру отримуватиме об’єкт ActionEvent.
class MyListener implements ActionListener
{
. . .
public void actionPerformed(ActionEvent event)
{
// тут відбувається реакція на натиснення кнопки
. . .
}
}
Реалізувати такий механізм можна декількома способами. Розглянемо поступово на прикладах все вище сказане.
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JButton;
import java.awt.event.*;
import javax.swing.JOptionPane;
public class EventTest {
public static void main(String[] args) {
SimpleFrame frame = new SimpleFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
class SimpleFrame extends JFrame
{
public SimpleFrame()
{
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); // задаємо розміри фрейму
JPanel panel = new JPanel(); // створюємо панель
this.add(panel); // додаємо панель у фрейм
JButton button = new JButton("OK!"); //створюємо кнопку ОК!
panel.add(button); // додаємо кнопку на панель
EventListener buttonAction = new EventListener(); // створюємо екземпляр обробника подій
button.addActionListener(buttonAction); // зв'язуємо обробника подій з кнопкою "ОК!"
}
public static final int DEFAULT_WIDTH = 300;
public static final int DEFAULT_HEIGHT = 200;
}
class EventListener implements ActionListener{
@Override // не обов'язково; вказуємо, що ми перевизначаємо метод actionPerformed
public void actionPerformed(ActionEvent event)
{
// Виводимо повідомлення у діалоговому вікні
// зверніть увагу, що для цього ми створюємо новий фрейм
JOptionPane.showMessageDialog(new JFrame(), "Ви натиснули кнопку <<OK!>>");
}
}
Можна також зробити, що один і той же обробник буде обслуговувати декілька кнопок. В такому разі необхідно передбачити механізм розрізнення, яка кнопка натиснута. Найпростіше це зробити через конструктор обробника. Наступна програма виводитиме в фреймі дві кнопки і при нажатті на якусь з них буде виводитись повідомлення, яку кнопку натиснуто.
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JButton;
import java.awt.event.*;
import javax.swing.JOptionPane;
public class EventTest2 {
public static void main(String[] args) {
SimpleFrame frame = new SimpleFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
class SimpleFrame extends JFrame
{
public static final int DEFAULT_WIDTH = 300;
public static final int DEFAULT_HEIGHT = 200;
public SimpleFrame()
{
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); // задаємо розміри фрейму
JPanel panel = new JPanel(); // створюємо панель
this.add(panel); // додаємо панель у фрейм
JButton button1 = new JButton("Кнопка 1"); //створюємо кнопку 1
panel.add(button1); // додаємо кнопку на панель
EventListener button1Action = new EventListener(1); // створюємо екземпляр обробника подій
button1.addActionListener(button1Action); // зв'язуємо обробника подій з кнопкою 1
JButton button2 = new JButton("Кнопка 2"); //створюємо кнопку 2
panel.add(button2); // додаємо кнопку на панель
EventListener button2Action = new EventListener(2); // створюємо екземпляр обробника подій
button2.addActionListener(button2Action); // зв'язуємо обробника подій з кнопкою 2
}
}
class EventListener implements ActionListener{
private int buttonNumber;
public EventListener (int number){
buttonNumber=number;
}
@Override // не обов'язково, вказуємо, що ми перевизначаємо метод actionPerformed
public void actionPerformed(ActionEvent event)
{
// Виводимо повідомлення у діалоговому вікні, яку кнопку натиснуто
if (buttonNumber==1) JOptionPane.showMessageDialog(new JFrame(), "Ви натиснули кнопку 1");
else JOptionPane.showMessageDialog(new JFrame(), "Ви натиснули кнопку 2");
}
}
Часто також замість створення окремого класу обробника використовують внутрішні класи і навіть внутрішні безіменні класи. Також в якості обробника події може виступати і сам клас, в якому описується графічний інтерфейс користувача.
(Даний розділ незавершений. Необхідно описати використання внутрішніх класів при створенні обробника подій, обробку WindowEvent та роботу з мишкою)
Примітки
[ред.]- ↑ Графічний інтерфейс на Java (Swing GUI Tutorial)
- ↑ SWT, Swing or AWT: Which is right for you? (21.02.2006)