Spring Boot 如何使用系统托盘图标

7

我正在设置一个Spring Boot应用程序,并希望主机可以访问一个系统托盘图标,该图标可以访问一些bean信息。

我目前尝试使用@Autowired细节为我的SystemTrayIcon创建一个Bean。然而,当我尝试添加SystemTrayIcon时,我会收到以下异常:

Caused by: java.awt.HeadlessException
    at java.awt.TrayIcon.<init>(Unknown Source)
    at java.awt.TrayIcon.<init>(Unknown Source)
    at java.awt.TrayIcon.<init>(Unknown Source)
    at hermes.subsrciber.systemTray.HermesTrayIcon.<init>(HermesTrayIcon.java:36)
    at hermes.subscriber.boot.AppStarter.trayIcon(AppStarter.java:83)
    at hermes.subscriber.boot.AppStarter$$EnhancerBySpringCGLIB$$c4f80849.CGLIB$trayIcon$3(<generated>)
    at hermes.subscriber.boot.AppStarter$$EnhancerBySpringCGLIB$$c4f80849$$FastClassBySpringCGLIB$$d62ab0bd.invoke(<generated>)
    at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228)
    at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:312)
    at hermes.subscriber.boot.AppStarter$$EnhancerBySpringCGLIB$$c4f80849.trayIcon(<generated>)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:166)
    ... 17 more

是否有办法使SpringBoot应用程序可以访问系统托盘图标?

我相信我可以在Main方法中简单地定义它(与SpringApplication.run并排),但我将无法自动装配属性。

这种设置是否可行?

3个回答

10
解决方案是,不使用SpringApplication.run(MyConfig.class,args),而使用以下设置:
    SpringApplicationBuilder builder = new SpringApplicationBuilder(MyConfig.class);
    builder.headless(false);
    ConfigurableApplicationContext context = builder.run(args);

为了添加系统图标,我添加了一个如下类型的bean:

实际上,我添加了一个以下类型的bean来添加系统图标:

public class MyTrayIcon extends TrayIcon {

    private static final String IMAGE_PATH = "/path/icon_16x16.png";
    private static final String TOOLTIP = "Text";

    private PopupMenu popup;
    private SystemTray tray;

    public MyTrayIcon(){
        super(createImage(IMAGE_PATH,TOOLTIP),TOOLTIP);
        popup = new PopupMenu();
        tray = SystemTray.getSystemTray();
    }

    @PostConstruct
    private void setup() throws AWTException{
        // popup.add(itemAbout);
        // here add the items to your popup menu. These extend MenuItem
        // popup.addSeparator();
        setPopupMenu(popup);
        tray.add(this);
    }

    protected static Image createImage(String path, String description){
        URL imageURL = MyTrayIcon.class.getResource(path);
        if(imageURL == null){
            System.err.println("Failed Creating Image. Resource not found: "+path);
            return null;
        }else {
            return new ImageIcon(imageURL,description).getImage();
        }
    }
}

你能提供一下添加系统托盘图标的代码片段吗? - yglodt
请查看以下链接,其中包含有关Java系统托盘的教程:https://docs.oracle.com/javase/tutorial/uiswing/misc/systemtray.html - mangusbrother
我实际上尝试了这个,但奇怪的是SystemTray.isSupported()返回false。有什么想法吗?(如果有影响,我设置了builder.headless(false);) - yglodt
@yglodt,你有找到解决这个问题的方法吗?我也遇到了一个java.awt.HeadlessException错误,是由于SystemTray.isSupported()返回false引起的,但我不知道为什么... - ChambreNoire
我的配置(MyConfig)类在哪里? - Shavkat Turakulov
7年前的问题,所以记不太清了。但我相信那只是你通用的Spring配置类,无论你在其中放了什么。 - mangusbrother

1
如果有人想要完整的代码...
import java.awt.AWTException;
import java.awt.CheckboxMenuItem;
import java.awt.Image;
import java.awt.Menu;
import java.awt.MenuItem;
import java.awt.PopupMenu;
import java.awt.SystemTray;
import java.awt.TrayIcon;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.URL;

import javax.annotation.PostConstruct;
import javax.swing.ImageIcon;

import org.springframework.boot.ExitCodeGenerator;
import org.springframework.boot.SpringApplication;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;

public class MyTrayIcon extends TrayIcon {

    private static final String IMAGE_PATH = "/PATH.png";
    private static final String TOOLTIP = "Running";
    private PopupMenu popup;
    final SystemTray tray;

    public MyTrayIcon(){
        super(createImage(IMAGE_PATH,TOOLTIP),TOOLTIP);
        popup = new PopupMenu();
        tray = SystemTray.getSystemTray();
        try {
            setup();
        } catch (AWTException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }



    @PostConstruct
    private void setup() throws AWTException{
        // Create a pop-up menu components
        MenuItem exitItem = new MenuItem("Exit");
        popup.add(exitItem);
        exitItem.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent e) {
                final int exitCode = 0;
                ExitCodeGenerator exitCodeGenerator = new ExitCodeGenerator() {

                    @Override
                    public int getExitCode() {
                      return exitCode;
                    }

                  };

                  tray.remove(MyTrayIcon.this);
                  SpringApplication.exit(Application.context, exitCodeGenerator);
            }
        });
        // popup.addSeparator();
        setPopupMenu(popup);
        tray.add(this);



    }

    protected static Image createImage(String path, String description){

        URL imageURL = MyTrayIcon.class.getResource(path);
        if(imageURL == null){
            System.err.println("Failed Creating Image. Resource not found: "+path);
            return null;
        }else {
            return new ImageIcon(imageURL,description).getImage();
        }
    }
}

在你的Application.java文件中:

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
@EnableConfigurationProperties(StorageProperties.class)
public class Application {

    public static ConfigurableApplicationContext context;

    public static void main(String[] args) {
        //SpringApplication.run(Application.class, args);

        SpringApplicationBuilder builder = new SpringApplicationBuilder(Application.class);
        builder.headless(false);
        context = builder.run(args);

        MyTrayIcon m = new MyTrayIcon();
    }
}

0

正在使用的代码逻辑

/**
 * @author lotuso
 * @version 1.0 2021/2/21 14:55
 **/
@Slf4j
public class TrayIcon extends Application {
    private final AtomicInteger runCount = new AtomicInteger(0);
    private final RegistryTrayConfiguration configuration = new RegistryTrayConfiguration();
    
    @Override
    public void start(final Stage stage) throws Exception {
        //构建系统托盘图标
        if(configuration.isEnabled()) {
            var stream = TrayIcon.class.getClassLoader().getResource(configuration.getIcon());
            assert stream != null;
            var image = ImageIO.read(stream);
            var trayIcon = new java.awt.TrayIcon(image, configuration.getTip());
            trayIcon.setImageAutoSize(true);
            //添加托盘事件
            {
                trayIcon.addMouseListener(new MouseAdapter() {
                    //鼠标点击事件
                    @Override
                    public void mouseClicked(final MouseEvent event) {
                        //双击左键
                        if(event.getButton() == MouseEvent.BUTTON1 && event.getClickCount() == 2) {
                            log.info("托盘双击事件");
                            Platform.runLater(() -> {
                                //TODO 如果需要显示一个窗口,哪么在这里进行,暂不实现
                            });
                        }
                        else if(event.getButton() == MouseEvent.BUTTON3) {
                            log.info("托盘右击事件");
                            //TODO 可以做些其它的事
                        }
                    }
                });
            }
            
            //获取系统托盘
            var trayPanel = SystemTray.getSystemTray();
            trayPanel.add(trayIcon);
        }
    }
    
    public synchronized void run(String... args) {
        if(runCount.get() <= 0){
            runCount.set(runCount.get()+1);
            launch(args);
        }
    }
    
    public static class RegistryTrayConfiguration {
        
        public boolean isEnabled() {
            return !Objects.isNull(getIcon());
        }
        
        public String getIcon() {
            var icon = getYML("spring.application.tray.icon");
            return String.valueOf(icon);
        }
        
        public String getTip() {
            var tip = getYML("spring.application.tray.tip");
            return String.valueOf(tip);
        }
        
        public static Object getYML(Object key) {
            return getYML(key, "application.yml");
        }
        
        public static Object getYML(Object key, String yml) {
            var resource = new ClassPathResource(yml);
            Properties properties;
            try {
                var factory = new YamlPropertiesFactoryBean();
                factory.setResources(resource);
                properties = factory.getObject();
            }
            catch(Exception ex) {
                log.warn(ex.getLocalizedMessage());
                return null;
            }
            assert properties != null;
            return properties.get(key);
        }
    }
}

在带有@Configuration标签的类中定义以下代码:

@Bean
public Application trayIcon() {
    var trayIcon = new TrayIcon();
    //TODO 此处将托盘与SpringBoot线程分开
    new Thread(trayIcon::run).start();
    return trayIcon;
}

添加具有以下结构的资源文件。 将图像目录添加到项目的资源目录中,并将图标。PNG文件放在那里。

|_ src
  |_ main
    |_ java
    |_ resources
      |_ images
        |_ icon.png
      |_ application.yml

Application.yml需要以下设置

spring:
  application:
    tray:
      tip: Used to indicate the prompt when the mouse is moved over the icon
      icon: Directory path relative to resources, such as images/icon.png

这样,TrayIcon 就被交给 Spring 容器来管理。如果对您有帮助,请让我知道是否有更好的建议。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接