参考教程
https://www.bilibili.com/video/BV1sJ41157VW
https://blog.csdn.net/MH_ANG/article/details/75907103
JavaFX的系统托盘是通过AWT实现的,样式过于简单,通过网上查询资料找到了自定义系统托盘的方法。
一. 思路如下:
1、自定义继承TrayIcon的类
2、将TrayIcon 的弹出菜单java.awt.PopupMenu 替换为JavaFX的组件(因为自己比较熟悉JavaFx)
3、通过TrayIcon的事件控制JavaFX的组件行为
二. 详细步骤如下:
1、自定义继承TrayIcon的类
public class TTrayIcon extends TrayIcon
2、重载构造方法,传入参数为java.awt.TrayIcon#TrayIcon(java.awt.Image, java.lang.String)原有的两个参数,再加上自定义的JavaFx组件
public TTrayIcon(Image image, String tooltip, Region menu) {
super(image, tooltip);
}
3、自定义类中维护一个弹窗面板,将传入的JavaFx组件menu绑定到自定义的面板上
//设置面板和布局为静态变量
private Stage stage = new Stage();
private StackPane pane = new StackPane();
{
stage.setScene(new Scene(pane));
//去掉面板的标题栏
stage.initStyle(StageStyle.TRANSPARENT);
}
public TTrayIcon(Image image, String tooltip, Region menu) {
super(image, tooltip);
//设置系统托盘图标为自适应
this.setImageAutoSize(true);
//添加组件到面板中
pane.getChildren().add(menu);
//设置面板的宽高
stage.setWidth(menu.getPrefWidth());
stage.setHeight(menu.getPrefHeight());
}
4、在构造函数中设置点击事件,让系统托盘图标点击时弹出弹窗
注意:在非JavaFx UI线程中调用JavaFx组件需要使用Platform.runLater
public TTrayIcon(Image image, String tooltip, Region menu) {
.
.
.
//添加鼠标事件
this.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
super.mouseClicked(e);
//getButton() 1左键 2中键 3右键
if (e.getButton() == 3) {
//在JavaFx的主线程中调用javaFx组件的方法
Platform.runLater(new Runnable() {
@Override
public void run() {
//设置dialog的显示位置
stage.setX(e.getX() - (stage.getWidth() / 2));
stage.setY(e.getY()-stage.getHeight());
//设置为顶层,否则在windows系统中会被底部任务栏遮挡
stage.setAlwaysOnTop(true);
//展示/隐藏
if (!stage.isShowing()) {
stage.show();
} else {
stage.hide();
}
}
});
}
}
});
}
5、此时点击系统托盘会弹出菜单面板,再次点击会隐藏面板,但是需要在点击非面板的区域后也能实现隐藏面板的效果,此时可以使用到面板的焦点监听,失去焦点后就隐藏
//对象代码块中
{
stage.setScene(new Scene(pane));
//去掉面板的标题栏
stage.initStyle(StageStyle.TRANSPARENT);
//监听面板焦点
stage.focusedProperty().addListener(new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if (!newValue) {
//如果失去焦点就隐藏面板
stage.hide();
}
}
});
}
6、在主进程中调用
SystemTray systemTray = SystemTray.getSystemTray();
java.awt.Image image = Toolkit.getDefaultToolkit().getImage(this.getClass().getResource("logo.png"));
//自定义想要的布局
VBox box=new VBox();
//布局中添加想要的组件
box.getChildren().addAll();
//设置宽高,可以根据内部组件的大小来计算
box.setPrefWidth(80);
box.setPrefHeight(100);
//设置样式
box.setStyle("-fx-background-color: #abcdef;-fx-padding: 0");
//新建自定义的系统托盘图标
TTrayIcon trayIcon = new TTrayIcon(image,str,box);
try {
//添加到系统托盘中
systemTray.add(trayIcon);
} catch (AWTException e) {
e.printStackTrace();
}
三. 完整代码
1、TTrayIcon.class
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
/**
* @author fearless
* @version v1.0
* @description:
* @date 2021/12/30 下午6:23
*/
public class TTrayIcon extends TrayIcon {
//设置面板和布局为静态变量
private Stage stage = new Stage();
private StackPane pane = new StackPane();
{
stage.setScene(new Scene(pane));
//去掉面板的标题栏
stage.initStyle(StageStyle.TRANSPARENT);
//监听面板焦点
stage.focusedProperty().addListener(new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if (!newValue) {
//如果失去焦点就隐藏面板
stage.hide();
}
}
});
}
public TTrayIcon(Image image, String tooltip, Region menu) {
super(image, tooltip);
//设置系统托盘图标为自适应
this.setImageAutoSize(true);
//添加组件到面板中
pane.getChildren().add(menu);
//设置面板的宽高
stage.setWidth(menu.getPrefWidth());
stage.setHeight(menu.getPrefHeight());
//添加鼠标事件
this.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
super.mouseClicked(e);
//getButton() 1左键 2中键 3右键
if (e.getButton() == 3) {
//在JavaFx的主线程中调用javaFx组件的方法
Platform.runLater(new Runnable() {
@Override
public void run() {
//设置dialog的显示位置
stage.setX(e.getX() - (stage.getWidth() / 2));
stage.setY(e.getY()-stage.getHeight());
//设置为顶层,否则在windows系统中会被底部任务栏遮挡
stage.setAlwaysOnTop(true);
//展示/隐藏
if (!stage.isShowing()) {
stage.show();
} else {
stage.hide();
}
}
});
}
}
});
}
}
四. 最终效果
最终效果为弹出用户自定义的面板样式
我稍微美化了一下按钮的样式最终开发效果如下
Deepin系统背景透明的logo区域显示为灰色不知道是什么原因
五. 总结
使用此方法会使托盘菜单能够完全按照自己的样子去设计,因为他本质就是一个窗口