Итак, поговорим о схлопывании окна в область системных задач. В Java6 появилась такая возможность, как добавление в эту область иконки. Со всеми сопутствующими приятными моментами - обработка на ней кликов, контекстные меню, всплывающие подсказки и т.п.
Реализуется это всё через классы
java.awt.SystemTray
и java.awt.TrayIcon
. Сначала выполняется проверка, поддерживается ли вообще системный трей - SystemTray.isSupported()
. Если да - создается иконка, к ней можно добавить обработчиков мыши, всплывающее меню, подсказку и т.п. Все используемые компоненты - из пакета AWT, а не SWING, но это в принципе логично - мы все-таки работаем не с окном приложения, а с системной частью.Дальше тоже вроде просто. По клику на кнопку закрытия окна мы прячем форму, по двойному клику на иконке - показываем.
Интереснее становится, когда возникает желание прятать окно и при его минимизации. Вот тут пришлось немного подумать. Нет, с тем, чтобы спрятать, проблем нет. А вот при показе - окно появляется в панели задач, однако на экране - нет. Кликнешь - показалось. Никакие
toFront()
, requestFocus()
и иже с ними ситуацию не спасают.Что оказалось? У окна есть несколько состояний. В частности - минимизированое оно или нормальное. Поскольку события в UI носят уведомительный характер - слушатель
windowIconified
срабатывает уже ПОСЛЕ того, как окно схлопнулось. И именно в этом состоянии окно прячется. И, соответственно, в этом состоянии и показывается. Таким образом, чтобы действительно показать окно, необходимо установить нормальное состояние - setState(JFrame.NORMAL)
.И вот тут возникает вторая тонкость. Окно может быть также и максимизировано перед тем, как его схлопнули. А если мы выставим нормальное состояние - оно вернется к первоначальным размерам. Что естественным образом нехорошо. К счастью, есть у Frame такая функция как setExtendedState (и, соответственно, getExtendedState для получения состояния). Причем состояния реализованы грамотно - в виде битовых флагов. Первый бит -
ICONIFIED
, второй - MAXIMIZED_HORIZ
, третий - MAXIMIZED_VERT
. Таким образом, когда мы схлопываем окно, просто устанавливается первый бит, а два оставшихся остаются неизменными. И если мы просто сбросим первый бит - состояние вернется точно к исходному. А сбросить его можно так: setExtendedState(getExtendedState() & (JFrame.ICONIFIED ^ 0xFFFF))
.Собственно, это все тонкости. Дальше всё прозрачно. Минимальный пример приложения, сворачивающегося в системный трей, приведен ниже. Для его запуска надо прописать имя иконки, которую вы будете использовать. Я использовал вот эту:
package ru.skipy.tests.ui; import java.awt.AWTException; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Image; import java.awt.MenuItem; import java.awt.PopupMenu; import java.awt.SystemTray; import java.awt.Toolkit; import java.awt.TrayIcon; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; /** * FormToTrayMinimizeSample * * @author Eugene Matyushkin aka Skipy */ public class FormToTrayMinimizeSample extends JFrame { /** * Constructs frame. Tray icon is constructed only if system tray is supported */ public FormToTrayMinimizeSample() { super("Form-to-tray minimize sample"); Image image = Toolkit.getDefaultToolkit().createImage("s.png"); setIconImage(image); JLabel lbl; ActionListener exitAL = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { System.exit(0); } }; if (SystemTray.isSupported()) { PopupMenu pm = new PopupMenu(); MenuItem miExit = new MenuItem("Exit"); miExit.addActionListener(exitAL); MenuItem miRestore = new MenuItem("Restore"); miRestore.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { restoreWindow(); } }); pm.add(miRestore); pm.addSeparator(); pm.add(miExit); lbl = new JLabel("<html><font color=\"blue\">System tray is supported</font></html>"); SystemTray st = SystemTray.getSystemTray(); TrayIcon ti = new TrayIcon(image, "Double click to restore window", pm); ti.addMouseListener(new TrayMouseListener()); try { st.add(ti); addWindowListener(new WindowMinimizeListener()); } catch (AWTException ex) { ex.printStackTrace(); } } else { lbl = new JLabel("<html><font color=\"red\">System tray is NOT supported</font></html>"); } lbl.setVerticalAlignment(JLabel.CENTER); lbl.setHorizontalAlignment(JLabel.CENTER); JButton btn = new JButton("Click to close application"); btn.addActionListener(exitAL); getContentPane().setBackground(Color.white); getContentPane().add(lbl, BorderLayout.CENTER); getContentPane().add(btn, BorderLayout.SOUTH); setSize(300, 100); setLocationRelativeTo(null); setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); } /** * Hides frame */ private void hideWindow() { setVisible(false); } /** * Shows frame. Restores frame state (normal or maximized) */ private void restoreWindow() { setVisible(true); setExtendedState(getExtendedState() & (JFrame.ICONIFIED ^ 0xFFFF)); requestFocus(); } /** * Mouse listener for tray icon. Restores frame on double click. */ class TrayMouseListener extends MouseAdapter { @Override public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 2) { restoreWindow(); } } } /** * Window event listener. Hides frame in iconfying and window closing events */ class WindowMinimizeListener extends WindowAdapter { @Override public void windowClosing(WindowEvent e) { hideWindow(); } @Override public void windowIconified(WindowEvent e) { hideWindow(); } } /** * Runs test * * @param args test arguments */ public static void main(String[] args) { new FormToTrayMinimizeSample().setVisible(true); } }
Как видите, всё просто. Всем спасибо!
С уважением,
Евгений aka Skipy
Комментарии? Дополнения?