В этой главе собраны разнообразные сведения о библиотеке Swing, не вошедшие в предыдущие главы.
Свойства экземпляра компонента
Каждый экземпляр компонента Swing содержит небольшую хеш-таблицу, унаследованную от класса JComponent. Чаще всего она пуста, хотя при создании экземпляра компонента в нее могло быть занесено несколько значений конструктором класса.
Перед работой с таблицей ее надо заполнить методом putciientProperty(Object key, Object value). После того как таблица заполнена, любой объект может получить значение value ключа key методом getClientProperty(Object key), возвращающим значение value в виде объекта класса Object. Разумеется, метод getClientProperty(Object) может вернуть значение только уже определенного ранее ключа, иначе он вернет null.
Рменно через свойство экземпляра панель класса JToolBar определяет, надо ли показывать рамку Сѓ расположенных РЅР° ней инструментальных РєРЅРѕРїРѕРє (СЃРј. СЂРёСЃ. 14.10). Метод setRollover(boolean) этого класса определяет свойство JToolBar. isRollover. Р’РѕС‚ исходный текст данного метода:
public void setRollover(boolean rollover){ putClientProperty("JToolBar.isRollover",
rollover ? Boolean.TRUE : Boolean.FALSE);
}
Метод isRollover () в своей работе использует это свойство. Вот его исходный код:
public boolean isRollover(){
Boolean rollover =
(Boolean)getClientProperty("JToolBar.isRollover"); if (rollover != null)
return rollover.booleanValue(); return false;
}
Аналогично можно поступать с любым наследником comp класса JComponent: сначала определить какое-то свойство, например:
comp.putClientProperty("Number", new Integer(k++));
а затем воспользоваться им, скажем, чтобы отличить один экземпляр от другого:
Integer numb = (Integer)comp.getClientProperty("Number");
Подобная схема применена в листинге 19.1.
Прокрутка содержимого компонента
Содержимое компонентов Swing, находящихся на панели JScrollPane, легко прокручивать с помощью полос прокрутки или колесика мыши. Но возможность прокрутки заложена не только в компоненты, реализующие интерфейс Scrollable. Она есть у каждого компонента Swing.
Возможность прокрутки содержимого компонента активизируется методом setAutoscrolls (true). Если после его выполнения вывести курсор мыши за пределы компонента при нажатой кнопке мыши (drag), то начинает происходить событие мыши. Чтобы обработать это событие, надо присоединить к компоненту обработчик события мыши, обратившись в нем к методу scrollRectToVisible(Rectangle rect). Например:
JPanel p = new JPanel(); p.setAutoscrolls(true);
p.addMouseMotionListener(new MouseMotionAdapter(){ public void mouseDragged(MouseEvent e){
Rectangle r = new Rectangle(e.getX(), e.getY(), 1, 1);
((JPanel)e.getSource()).scrollRectToVisible(r);
}
});
Метод scrollRectToVisible (r) обращается к подобному методу объемлющего контейнера. Некоторые контейнеры, например JViewport, переопределяют этот метод таким образом, чтобы в окне появился прямоугольник rect.
Передача фокуса ввода
Каждый компонент Swing является контейнером и может содержать внутри себя другие компоненты. Только один из компонентов в контейнере обладает фокусом ввода — он реагирует на действия мыши и клавиатуры. На большинстве платформ по умолчанию передача фокуса ввода другому компоненту происходит при щелчке на нем кнопкой мыши или нажатии клавиши
Передача фокуса может быть временной, что означает возможность быстро вернуть фокус. Временная передача фокуса обычно производится при просмотре пунктов меню, передвижении бегунка по полосе прокрутки, перемещении окна.
Компонент класса Component и его наследники могут запросить фокус защищенными логическими методами requestFocus(boolean) и requestFocusInWindow(boolean). Значение параметра true означает временную передачу фокуса, false — постоянную передачу. Второй из этих методов может применяться компонентом, который расположен в контейнере, уже имеющем фокус. Он введен для того, чтобы запретить передачу фокуса из одного окна в другое окно, независимое от первого. Методы возвращают false, если запрос отвергнут, и true, если запрос обрабатывается.
РРЅРѕРіРґР° возникает необходимость изменить РїРѕСЂСЏРґРѕРє следования компонентов РІ контейнере. Так же как Рё обработку событий, систему передачи фокуса Swing наследует РѕС‚ AWT. Р’ пакете java.awt есть интерфейс KeyEventDispatcher, описывающий всего РѕРґРёРЅ логический метод dispatchKeyEvent(KeyEvent). Ртот метод должен возвращать false, если событие KeyEvent передается следующему компоненту, реализующему данный интерфейс, Рё true, если передача события РЅР° этом заканчивается. Событие поступает этому методу РґРѕ объекта-слушателя типа KeyListener, следовательно, метод dispatchKeyEvent(KeyEvent) может решить, передавать ли событие обработчику.
Второй интерфейс, KeyEventPostProcessor, тоже определяет один метод
postProcessKeyEvent (KeyEvent), к которому компонент обращается уже после передачи фокуса.
Оба интерфейса частично реализует абстрактный класс KeyboardFocusManager, расширенный классом DefaultKeyboardFocusManager.
Кроме своей основной работы — распределять события класса KeyEvent между компонентами — этот класс может назначить навигационные клавиши. В нем определены статические константы:
□ forward_traversal_key — обычно
□ backward_traversal_key — обычно
□ up_cycle_traversal_key — переход к первому компоненту, значения по умолчанию нет;
□ down_cycle_traversal_key — переход к последнему компоненту, значения по умолчанию нет.
Перечисленные константы используются в качестве первого параметра метода setDefaultFocusTraversalKeys (int, Set), применяемого для определения новых навигационных клавиш вместо клавиш
Аналогичный метод setFocusTraversalKeys (int, Set) есть и в классе Component.
Определение того, какой компонент будет следующим РїСЂРё применении навигационных клавиш, делается абстрактным классом FocusTraversalPolicy. Ртот класс содержит методы, возвращающие:
□ getComponentAfter (Container, Component) — следующий компонент и его контейнер;
□ getComponentBefore (Container, Component) — предыдущий компонент и его контейнер;
□ getFirstComponent (Container) — первый компонент в контейнере;
□ getLastComponent (Container) — последний компонент в контейнере;
□ getDefaultComponent (Container) - компонент, на который передается фокус при акти
визации контейнера;
□ getInitialComponent (Window) - компонент, на который передается фокус при активи
зации окна.
Два конкретных порядка следования компонентов определены двумя подклассами класса FocusTraversalPolicy.
Первый подкласс ContainerOrderFocusTraversalPolicy следует РїРѕСЂСЏРґРєСѓ, РІ котором компоненты расположены РІ массиве, возвращаемом методом getcomponents () класса container. Ртот класс добавляет логический метод accept(Component), проверяющий видимость Рё доступность компонента. Метод начинается так:
protected boolean accept(Component comp){
if (!(comp.isVisible() && comp.isDisplayable() &&
comp.isEnabled() && comp.isFocusable())) return false;
// Продолжение метода...
}
Как видно из этого начала, метод сразу отвергает компоненты, не выполняющие хотя бы одно из перечисленных четырех условий.
У класса ContainerOrderFocusTraversalPolicy есть расширение DefaultFocusTraversalPolicy, которое переопределяет метод accept(Component) таким образом, что четвертое условие, isFocusable (), учитывается, лишь если его значение отлично от значения по умолчанию.
Второй подкласс, InternalFrameFocusTraversalPolicy, относится уже Рє библиотеке Swing. Рто абстрактный класс, расположенный РІ пакете javax.swing. РћРЅ задает первый компонент РІРѕ внутреннем РѕРєРЅРµ методом getInitialComponent(JInternalFrame). Метод возвращает ссылку РЅР° объект класса Component.
Класс InternalFrameFocusTraversalPolicy расширен классом SortingFocusTraversalPolicy. Он пользуется порядком расположения компонентов, определенным некоторой реализацией интерфейса Comparator, сделанной разработчиком. Описание этого интерфейса и правила его реализации мы рассмотрели в главе 6. Конкретная реализация интерфейса задается в конструкторе класса SortingFocusTraversalPolicy(Comparator).
Класс SortingFocusTraversalPolicy, в свою очередь, расширен классом
LayoutFocusTraversalPolicy. РћРЅ определяет РїРѕСЂСЏРґРѕРє расположения компонентов, основываясь РЅР° РёС… размерах Рё положении РІ контейнере. Ртот класс используется РїРѕ умолчанию L&F класса BasicLookAndFeel Рё его расширениями.
После того как определен порядок следования компонентов, методом
setDefaultFocusTraversalPolicy(FocusTraversalPolicy) полученный экземпляр класса
LayoutFocusTraversalPolicy устанавливается в текущий экземпляр класса KeyboardFocusManager. Можно задать особенный порядок расположения компонентов в отдельном контейнере методом setFocusTraversalPolicy(FocusTraversalPolicy) класса Container.
После этого методами focusNextComponent() и focusPreviousComponent() класса
KeyboardFocusManager можно переходить к следующему или предыдущему компоненту. Для отдельного компонента или контейнера то же самое можно сделать методами
transferFocus(), transferFocusBackward() класса Component.
В листинге 19.1 приведен простой пример передачи фокуса от текстового поля к кнопке и обратно. Рисунок 19.1 демонстрирует протокол этой программы.
Рис. 19.1. Протокол передачи фокуса |
Листинг 19.1. Передача фокуса ввода
import java.awt.*; import java.awt.event.*; import javax.swing.*;
public class SimpFocus extends JFrame implements FocusListener{
DefaultKeyboardFocusManager myFocusmgr =
new DefaultKeyboardFocusManager();
JButton bt;
JTextField tf;
JTextArea ta;
SimpFocus(){
super(" Передача фокуса");
JPanel p = new JPanel(); add(p, BorderLayout.NORTH); bt = new JButton(" myButton "); bt.putClientProperty("Focus", "JButton bt");
tf = new JTextField(" myTextField "); tf.putClientProperty("Focus", "JTextField tf"); tf.addFocusListener(this); ta = new JTextArea(6, 40); ta.putClientProperty("Focus", "JTextArea ta");
add(ta); p.add(tf); p.add(bt);
pack();
setDefaultCloseOperation(EXIT ON CLOSE); setVisible(true);
printDefaultSettings(myFocusmgr); changeFocusOwner(myFocusmgr);
}
public void focusLost(FocusEvent e){
ta.append("\n myTextField: Фокус потерян."); ta.append("\n Передача фокуса компоненту " +
((JComponent)e.getOppositeComponent()). getClientProperty("Focus")+".");
}
public void focusGained(FocusEvent e){
ta.append("\n myTextField: Фокус получен.");
}
public void printDefaultSettings(
DefaultKeyboardFocusManager fm){
ta.append("\n Навигационные клавиши: "); ta.append("\n FORWARD_TRAVERSAL_KEYS: " +
fm.getDefaultFocusTraversalKeys(
KeyboardFocusManager.FORWARD TRAVERSAL KEYS)); ta.append("\n BACKWARD_TRAVERSAL_KEYS: " + fm.getDefaultFocusTraversalKeys(
KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS)); ta.append("\n UP_CYCLE_TRAVERSAL_KEYS: " + fm.getDefaultFocusTraversalKeys(
KeyboardFocusManager.UP_CYCLE_TRAVERSAL_KEYS)); ta.append("\n DOWN_CYCLE_TRAVERSAL_KEYS: " + fm.getDefaultFocusTraversalKeys(
KeyboardFocusManager.DOWN_CYCLE_TRAVERSAL_KEYS));
}
public void changeFocusOwner(DefaultKeyboardFocusManager fm){ ta.append("\n Фокус у " +
((JComponent)fm.getFocusOwner()).getClientProperty("Focus")); ta.append("\n Контейнер верхнего уровня: " +
(fm.getCurrentFocusCycleRoot()));
ta.append("\n Передача фокуса следующему компоненту."); fm.focusNextComponent(); ta.append("\n Сейчас фокус у " +
((JComponent)fm.getFocusOwner()).getClientProperty("Focus"));
ta.append("\n Передача фокуса предыдущему компоненту."); fm.focusPreviousComponent(); ta.append("\n Теперь фокус у " +
((JComponent)fm.getFocusOwner()).getClientProperty("Focus"));
}
public static void main(String[] args){ new SimpFocus();
}
}
Перенос данных Drag and Drop
В компоненты Swing JColorChooser, JFileChooser, JList, JTable, JTree, а также в компонент JTextComponent и в его потомки встроена возможность переноса данных с помощью мыши (Drag and Drop) или командных клавиш (Cut-Copy-Paste) через системный буфер обмена (clipboard). Обычно эта возможность выключена. Включается она методом
setDragEnabled(true).
РЎРІСЏР·СЊ СЃ системным буфером обмена РїСЂРѕРёСЃС…РѕРґРёС‚ через объект класса clipboard РёР· пакета j ava. awt. datatrans fer. Данные, предназначенные для переноса, должны удовлетворять интерфейсу Transferable, описывающему перенос данных, преобразованных РІ специально разработанный объект класса DataFlavor. Рнформация заносится РІ буфер методом
setContents(Transferable, ClipBoardOwner);
Р° извлекается методом getcontents (Obj ect) РІ РІРёРґРµ объекта, реализующего интерфейс Transferable. Рсточник данных регистрируется РІ буфере как объект, реализующий интерфейс ClipBoardOwner.
Возможность переноса данных заложена РІ класс TransferHandler. Ркземпляр этого класса установлен РІ перечисленные ранее компоненты Swing. Ркземпляр создается конструктором TransferHandler(String) , параметр которого определяет РёРјСЏ свойства JavaBean. Созданный экземпляр устанавливается РІ компонент методом
setTransferHandler(TransferHandler) класса JComponent.
После создания экземпляра метод
exportAsDrag(JComponent comp, InputEvent evt, int action);
приводит в готовность механизм переноса данных из компонента comp при наступлении события evt. Последний параметр action принимает значение copy или move. Независимо от значения этого параметра на большинстве платформ копирование происходит при нажатой клавише
РџСЂРё перетаскивании данных работает сложный внутренний механизм, заимствованный РёР· библиотеки AWT. Ртот механизм реализован интерфейсами Рё классами РґРІСѓС… пакетов: java.awt.dnd Рё java.awt.datatransfer. РћРЅ автоматически используется классом
Trans ferHandler.
Листинг 19.2 показывает простой пример переноса выделенного текста из поля ввода класса JTextField в надпись класса JLabel перетаскиванием мышью.
Листинг 19.2. Перетаскивание текста из поля ввода в надпись
import java.awt.*; import java.awt.event.*; import javax.swing.*;
public class LabelDnD extends JFrame{
public LabelDnD(){
super(" Перенос текста в надпись JLabel");
JTextField tf = new JTextField(100); tf.setDragEnabled(true);
JLabel tl = new JLabel("Перетащи сюда", JLabel.LEFT); tl.setTransferHandler(new TransferHandler("text")); tl.addMouseListener(new MouseAdapter(){ public void mousePressed(MouseEvent e){
JComponent c = (JComponent)e.getSource(); TransferHandler th = c.getTransferHandler(); th.exportAsDrag(c, e, TransferHandler.COPY);
}
});
getContentPane().add(tf, BorderLayout.NORTH);
getContentPane().add(tl);
tl.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
setDefaultCloseOperation(EXIT ON CLOSE); setSize(200, 100); setVisible(true);
}
public static void main(String[] args){ new LabelDnD();
}
}
Временная задержка Timer
Класс Timer играет роль будильника, генерирующего через заданное время событие класса ActionEvent. Задержка в миллисекундах и обработчик сгенерированного события задаются в единственном конструкторе класса
Timer(int millisec, ActionListener al);
Созданный этим конструктором "будильник" запускается методом start (). После этого стартует подпроцесс, в котором через заданное время millisec начнет генерироваться событие ActionEvent. Оно будет обрабатываться предварительно подготовленным объектом al.
Событие генерируется до тех пор, пока не будет выполнен метод stop (). Если надо сгенерировать событие только один раз, то следует обратиться к методу
setRepeats(false).
Задержку можно изменить методом setDelay(int). Допустимо отдельно задать начальную задержку методом setInitialDelay(int).
Если обратиться к методу setLogTimers (true), то при каждом событии в стандартный вывод System.out будет выводиться протокол, а именно сообщение "Timer ringing:" со ссылкой на объект Timer.
В листинге 19.3 приведен простой пример использования класса Timer. Сообщение о событии выводится в область ввода. Нажатие кнопки прекращает работу "будильника".
Листинг 19.3. Методы класса Timer
import java.awt.*; import java.util.*; import java.awt.event.*; import javax.swing.*;
public class SimpTimer extends JFrame implements ActionListener{ JButton bt;
JTextArea ta; javax.swing.Timer t;
SimpTimer(){
super(" Передача фокуса");
JPanel p = new JPanel(); add(p, BorderLayout.NORTH);
bt = new JButton("Останов"); bt.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent e){ t.stop(); ta.append("\nStop");
}
});
ta = new JTextArea(6, 30);
add(ta); p.add(bt);
t = new javax.swing.Timer(500, this); t.setInitialDelay(1000) ; t.setLogTimers(true) ; t.start();
pack();
setDefaultCloseOperation(EXIT ON CLOSE); setVisible(true);
}
public void actionPerformed(ActionEvent e){ ta.append("\n Время: " + new Date());
}
public static void main(String[] args){ new SimpTimer();
}
}
В листинге 19.3 использован класс Timer из пакета javax.swing. Еще один класс с таким же именем есть в пакете java.util. Он устроен сложнее и имеет больше возможностей, но класс Timer из пакета javax.swing оптимизирован под библиотеку Swing. Разработчики библиотеки Swing рекомендуют использовать этот класс Timer, а не его тезку из пакета j ava. util.
ГЛАВА 20