JavaFx 自定义系统托盘

参考教程
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系统美化后的效果

Win10系统美化后的效果

Deepin系统背景透明的logo区域显示为灰色不知道是什么原因

五. 总结

使用此方法会使托盘菜单能够完全按照自己的样子去设计,因为他本质就是一个窗口

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容