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区域显示为灰色不知道是什么原因

五. 总结

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
禁止转载,如需转载请通过简信或评论联系作者。

推荐阅读更多精彩内容