Прочие свойства Swing
В этой главе собраны разнообразные сведения о библиотеке 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 является контейнером и может содержать внутри себя другие компоненты. Только один из компонентов в контейнере обладает фокусом ввода — он реагирует на действия мыши и клавиатуры. На большинстве платформ по умолчанию передача фокуса ввода другому компоненту происходит при щелчке на нем кнопкой мыши или нажатии клавиши <Tab>, для текстовых полей — <Ctrl>+<Tab>. После нажатия клавиш фокус передается следующему компоненту, причем следующим, как правило, считается компонент, помещенный в контейнер после данного компонента. Обычно это расположение компонентов слева направо и сверху вниз. Обратная передача фокуса ввода происходит при нажатии комбинации клавиш <Shift>+<Tab>, для текстовых полей — <Ctrl>+<Shift>+<Tab>.
Передача фокуса может быть временной, что означает возможность быстро вернуть фокус. Временная передача фокуса обычно производится при просмотре пунктов меню, передвижении бегунка по полосе прокрутки, перемещении окна.
Компонент класса 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 — обычно <Tab> или <Ctrl>+<Tab>;
? backward_traversal_key — обычно <Shift>+<Tab>;
? up_cycle_traversal_key — переход к первому компоненту, значения по умолчанию нет;
? down_cycle_traversal_key — переход к последнему компоненту, значения по умолчанию нет.
Перечисленные константы используются в качестве первого параметра метода setDefaultFocusTraversalKeys (int, Set), применяемого для определения новых навигационных клавиш вместо клавиш <Tab> и <Shift>. Второй параметр содержит описание новых навигационных клавиш в виде множества Set объектов класса AWTKeyStroke.
Аналогичный метод 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. Передача фокуса ввода
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(" myTextField: Фокус потерян."); ta.append(" Передача фокуса компоненту " +
((JComponent)e.getOppositeComponent()). getClientProperty("Focus")+".");
}
public void focusGained(FocusEvent e){
ta.append(" myTextField: Фокус получен.");
}
public void printDefaultSettings(
DefaultKeyboardFocusManager fm){
ta.append(" Навигационные клавиши: "); ta.append(" FORWARD_TRAVERSAL_KEYS: " +
fm.getDefaultFocusTraversalKeys(
KeyboardFocusManager.FORWARD TRAVERSAL KEYS)); ta.append(" BACKWARD_TRAVERSAL_KEYS: " + fm.getDefaultFocusTraversalKeys(
KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS)); ta.append(" UP_CYCLE_TRAVERSAL_KEYS: " + fm.getDefaultFocusTraversalKeys(
KeyboardFocusManager.UP_CYCLE_TRAVERSAL_KEYS)); ta.append(" DOWN_CYCLE_TRAVERSAL_KEYS: " + fm.getDefaultFocusTraversalKeys(
KeyboardFocusManager.DOWN_CYCLE_TRAVERSAL_KEYS));
}
public void changeFocusOwner(DefaultKeyboardFocusManager fm){ ta.append(" Фокус у " +
((JComponent)fm.getFocusOwner()).getClientProperty("Focus")); ta.append(" Контейнер верхнего уровня: " +
(fm.getCurrentFocusCycleRoot()));
ta.append(" Передача фокуса следующему компоненту."); fm.focusNextComponent(); ta.append(" Сейчас фокус у " +
((JComponent)fm.getFocusOwner()).getClientProperty("Focus"));
ta.append(" Передача фокуса предыдущему компоненту."); fm.focusPreviousComponent(); ta.append(" Теперь фокус у " +
((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. Независимо от значения этого параметра на большинстве платформ копирование происходит при нажатой клавише <Ctrl>, но это зависит от используемого L&F.
При перетаскивании данных работает сложный внутренний механизм, заимствованный из библиотеки 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(" Stop");
}
});
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(" Время: " + 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