Керуємо java-програмою із системного трею


Якось ми вже робили просту клієнт-серверну програму на Java, суть якої полягала у можливості видавати клієнтові час із сервера. Бонусом вона могла видавати час і у вікно браузера у вигляді простого тексту (який сучасні браузери розуміють). Тепер же виникла потреба у тому, щоб сервер (а, можливо, і клієнт теж) міг не просто бути увесь час запущеним.

Тобто пропонується вивести у системний трей іконку, яка дозволить кількома кліками миші запустити сервер, зупинити його, або ж перезапустити. Роздуми про те, чи потрібна серверові графічна оболонка (навіть у вигляді іконки) я наводити не буду. Треба, значить треба 🙂

Розглянемо поки суто роботу серверної частини нашої софтини, бо клієнтом може бути і браузер. Фактично, файли SSocket.java та SServer.java можна взяти і з попередньої статті – вони не дуже змінилися. Єдине, що я доробив, це забрав із класу SServer точку входу в програму (вона тепер буде у класі, що реалізує іконку в системному треї), а сам клас SServer тепер імплементує інтерфейс Runnable, тому вміст main() просто перенесено у run().

Ще довелося задати таймаут для серверного сокета – ServerSocket ss.setSoTimeout(int TIME_OUT) – оскільки без цього сервер буде вічно чекати поки до нього постукає клієнт і не даватиме можливості себе коректно перезапустити. А ще додано закривання серверного сокета, яким раніше я просто знехтував.

Вихідний код обох класів можна взяти за наступними посиланнями:

http://code.google.com/p/pokemon-server/source/browse/SSocket.java

http://code.google.com/p/pokemon-server/source/browse/SServer.java

Тепер переходимо до, власне, того, як створити іконку в треї засобами Java. Тут я так само зробив 2 класи, один із точкою входу у програму просто, а інший – з усіма функціями. Почнемо з простішого.

import java.awt.AWTException;
import java.io.IOException;
public class TrayIconMgr {
public static void main(String[] args) throws Throwable {
TrayLauncher trayIcon = new TrayLauncher(); // Creating new TrayLauncher instanse
trayIcon.pack();// setting window size
trayIcon.setAlwaysOnTop(true);
// trayIcon.setLocationRelativeTo(null);//center window
trayIcon.setVisible(true);// show dialog
}
}

В принципі, коменти є і в самому коді, так що пояснювати особливо нема чого. Тут просто створюється інстанс класу TrayLauncher (про який далі), а ще задаються параметри вікна та іконки. Вікно треба для того, щоб зручніше було дивитися на стан сервера – там просто розміщено лейбл, текст у якому змінюється в залежності від того, що робить сервер. У більш зручному для сприйняття форматі текст класу наведено тут: http://code.google.com/p/pokemon-server/source/browse/TrayIconMgr.java

Останній клас у нас вийшов найбільшим за обсягом, хоча там вистачає копіпасту з одного місця у друге.

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
public class TrayLauncher extends JDialog {
JLabel myLabel;
Thread th1;
SServer mySrv;
private SystemTray systemTray = SystemTray.getSystemTray();// Getting system tray handle
public TrayLauncher() throws IOException, AWTException {
// Creating simple window with text label
myLabel = new JLabel();
myLabel.setText("Welcome to Server Launcher");
getContentPane().add(myLabel);
setDefaultCloseOperation(JDialog.HIDE_ON_CLOSE); // hide window if "Close" button pressed
TrayIcon trayIcon = new TrayIcon(ImageIO.read(new File("icon.jpg")),"Pokemon Server");// icon image and hint
trayIcon.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (!isVisible())
setVisible(true);
}
});
// creating right-click menu
PopupMenu popupMenu = new PopupMenu();
// "Exit" menu entry
MenuItem itemExit = new MenuItem("Exit");
itemExit.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
dispose();
System.exit(0);
}
});
// "Start" menu entry - will run the server
MenuItem itemStart = new MenuItem("Start");
itemStart.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
ServerStart();
}
});
// "Stop" menu entry - will stop the server
MenuItem itemStop = new MenuItem("Stop");
itemStop.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
ServerStop();
}
});
// "ReStart" menu entry - will stop the server and run it again after
// some time
MenuItem itemReStart = new MenuItem("Restart");
itemReStart.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
try {
ServerReStart();
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
});
// Adding items to menu
popupMenu.add(itemStart);
popupMenu.add(itemStop);
popupMenu.add(itemReStart);
popupMenu.addSeparator();// "Exit" entry will be just a little separated
popupMenu.add(itemExit);
trayIcon.setPopupMenu(popupMenu);// setting menu to tray icon
systemTray.add(trayIcon); // adding icon to tray
}

У цій частині спочатку отримуємо ідентифікатор системного трею, куди будемо пхати нашу іконку. Для цього є спеціальний об’єкт і метод SystemTray.getSystemTray(). Далі створюється вікно, у якому розміщуємо компонент JPanel із лейблом JLabel всередині. Використовуючи setDefaultCloseOperation(JDialog.HIDE_ON_CLOSE), робимо так, що при натисканні на хрестик у заголовку вікна воно просто ховається, але програма продовжує працювати.

Далі завантажуємо іконку із файлової системи (зображення розміром 16х16 точок) і встановлюємо її у якості картинки нашої іконки, а заодно прописуємо хінт (виринаючу підказку, чи як воно правильно називається українською). Створюємо меню, яке з’являється коли юзер клацне правою клавішею на іконку, у цьому меню додаємо 4 пункти: Start, Stop, Restart та Exit. На кожен пункт я причепив свій лістенер, який викликає один із приватних методів: ServerStart(), ServerStop(), ServerReStart(). При натисканні на “Exit” звільняємо ресурси та виходимо по System.exit(0);

private void ServerStart() {
// Method for starting server thread
myLabel.setText("Starting...");
// Server launch method call
mySrv = new SServer();
mySrv.isActive = true;
try {
th1 = new Thread(mySrv);
th1.start();
} catch (Throwable e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
} // End ServerStart();

Метод ServerStart() просто створює новий інстанс класу SServer і запускає його у “нитку” (thread). Публічний атрибут isActive в даному випадку служить для того, щоб можна було зупинити “нитку” зовні. Це робить наступний метод:

private void ServerStop() {
myLabel.setText("Stopping...");
// Server stop method call
mySrv.isActive = false;
}// End ServerStop()

Тут ми просто встановлюємо вищезгадану змінну у значення “неправда” і всередині “нитки” цикл зупиниться.

Останній метод служить для перезапуску сервера.
private void ServerReStart() throws InterruptedException {
// Server restart method call
ServerStop();// stop
Thread.sleep(400);// wait
ServerStart();// start
myLabel.setText("ReStarting...");
}// End ServerReStart()
}

Як бачимо, тут просто зупиняємо сервер, чекаємо трохи і запускаємо знову. Лістинг усього класу отут: http://code.google.com/p/pokemon-server/source/browse/TrayLauncher.java

Почитайте ще оце:

Залиште коментар

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *