Skip to content

Instantly share code, notes, and snippets.

@jonyfs
Forked from jewelsea/JavaFXTrayIconSample.java
Created October 27, 2016 20:54
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jonyfs/b279b5e052c3b6893a092fed79aa7fbe to your computer and use it in GitHub Desktop.
Save jonyfs/b279b5e052c3b6893a092fed79aa7fbe to your computer and use it in GitHub Desktop.
Demonstrate using the System Tray (AWT) to control a JavaFX application.
import javafx.application.*;
import javafx.geometry.Pos;
import javafx.scene.*;
import javafx.scene.control.Label;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.*;
import javax.imageio.ImageIO;
import java.io.IOException;
import java.net.URL;
import java.text.*;
import java.util.*;
// Java 8 code
public class JavaFXTrayIconSample extends Application {
// one icon location is shared between the application tray icon and task bar icon.
// you could also use multiple icons to allow for clean display of tray icons on hi-dpi devices.
private static final String iconImageLoc =
"http://icons.iconarchive.com/icons/scafer31000/bubble-circle-3/16/GameCenter-icon.png";
// application stage is stored so that it can be shown and hidden based on system tray icon operations.
private Stage stage;
// a timer allowing the tray icon to provide a periodic notification event.
private Timer notificationTimer = new Timer();
// format used to display the current time in a tray icon notification.
private DateFormat timeFormat = SimpleDateFormat.getTimeInstance();
// sets up the javafx application.
// a tray icon is setup for the icon, but the main stage remains invisible until the user
// interacts with the tray icon.
@Override public void start(final Stage stage) {
// stores a reference to the stage.
this.stage = stage;
// instructs the javafx system not to exit implicitly when the last application window is shut.
Platform.setImplicitExit(false);
// sets up the tray icon (using awt code run on the swing thread).
javax.swing.SwingUtilities.invokeLater(this::addAppToTray);
// out stage will be translucent, so give it a transparent style.
stage.initStyle(StageStyle.TRANSPARENT);
// create the layout for the javafx stage.
StackPane layout = new StackPane(createContent());
layout.setStyle(
"-fx-background-color: rgba(255, 255, 255, 0.5);"
);
layout.setPrefSize(300, 200);
// this dummy app just hides itself when the app screen is clicked.
// a real app might have some interactive UI and a separate icon which hides the app window.
layout.setOnMouseClicked(event -> stage.hide());
// a scene with a transparent fill is necessary to implement the translucent app window.
Scene scene = new Scene(layout);
scene.setFill(Color.TRANSPARENT);
stage.setScene(scene);
}
/**
* For this dummy app, the (JavaFX scenegraph) content, just says "hello, world".
* A real app, might load an FXML or something like that.
*
* @return the main window application content.
*/
private Node createContent() {
Label hello = new Label("hello, world");
hello.setStyle("-fx-font-size: 40px; -fx-text-fill: forestgreen;");
Label instructions = new Label("(click to hide)");
instructions.setStyle("-fx-font-size: 12px; -fx-text-fill: orange;");
VBox content = new VBox(10, hello, instructions);
content.setAlignment(Pos.CENTER);
return content;
}
/**
* Sets up a system tray icon for the application.
*/
private void addAppToTray() {
try {
// ensure awt toolkit is initialized.
java.awt.Toolkit.getDefaultToolkit();
// app requires system tray support, just exit if there is no support.
if (!java.awt.SystemTray.isSupported()) {
System.out.println("No system tray support, application exiting.");
Platform.exit();
}
// set up a system tray icon.
java.awt.SystemTray tray = java.awt.SystemTray.getSystemTray();
URL imageLoc = new URL(
iconImageLoc
);
java.awt.Image image = ImageIO.read(imageLoc);
java.awt.TrayIcon trayIcon = new java.awt.TrayIcon(image);
// if the user double-clicks on the tray icon, show the main app stage.
trayIcon.addActionListener(event -> Platform.runLater(this::showStage));
// if the user selects the default menu item (which includes the app name),
// show the main app stage.
java.awt.MenuItem openItem = new java.awt.MenuItem("hello, world");
openItem.addActionListener(event -> Platform.runLater(this::showStage));
// the convention for tray icons seems to be to set the default icon for opening
// the application stage in a bold font.
java.awt.Font defaultFont = java.awt.Font.decode(null);
java.awt.Font boldFont = defaultFont.deriveFont(java.awt.Font.BOLD);
openItem.setFont(boldFont);
// to really exit the application, the user must go to the system tray icon
// and select the exit option, this will shutdown JavaFX and remove the
// tray icon (removing the tray icon will also shut down AWT).
java.awt.MenuItem exitItem = new java.awt.MenuItem("Exit");
exitItem.addActionListener(event -> {
notificationTimer.cancel();
Platform.exit();
tray.remove(trayIcon);
});
// setup the popup menu for the application.
final java.awt.PopupMenu popup = new java.awt.PopupMenu();
popup.add(openItem);
popup.addSeparator();
popup.add(exitItem);
trayIcon.setPopupMenu(popup);
// create a timer which periodically displays a notification message.
notificationTimer.schedule(
new TimerTask() {
@Override
public void run() {
javax.swing.SwingUtilities.invokeLater(() ->
trayIcon.displayMessage(
"hello",
"The time is now " + timeFormat.format(new Date()),
java.awt.TrayIcon.MessageType.INFO
)
);
}
},
5_000,
60_000
);
// add the application tray icon to the system tray.
tray.add(trayIcon);
} catch (java.awt.AWTException | IOException e) {
System.out.println("Unable to init system tray");
e.printStackTrace();
}
}
/**
* Shows the application stage and ensures that it is brought ot the front of all stages.
*/
private void showStage() {
if (stage != null) {
stage.show();
stage.toFront();
}
}
public static void main(String[] args) throws IOException, java.awt.AWTException {
// Just launches the JavaFX application.
// Due to way the application is coded, the application will remain running
// until the user selects the Exit menu option from the tray icon.
launch(args);
}
}
@EasyG0ing1
Copy link

This is nice, but it seems that what you have done here is used the awt systemTrayIcon to create a text menuItem that launches the JavaFX Scene.

Is there any way to use the awt SystemTray icon so that when you click on it, what pops up is a JavaFX scene and not a text menuItem?

@dustinkredmond
Copy link

@EasyG0ing1 check out my FXTrayIcon library, it's pretty easy to implement the behavior you requested, and FXTrayIcon does it out of the box. Another benefit is that the library lets you "add" JavaFX MenuItems and Menus directly to the icon menu, so a developer doesn't have to worry with AWT's API, as it's not exactly friendly to folks used to JavaFX API.

@EasyG0ing1
Copy link

@dustinkredmond

@EasyG0ing1 check out my FXTrayIcon library, it's pretty easy to implement the behavior you requested, and FXTrayIcon does it out of the box. Another benefit is that the library lets you "add" JavaFX MenuItems and Menus directly to the icon menu, so a developer doesn't have to worry with AWT's API, as it's not exactly friendly to folks used to JavaFX API.

What I am wanting to do with a SystemTray Icon, is have a JavaFX container popup when the icon is clicked (like an AnchorPane for example) instead of a MenuItem. And so far, all I have seen is the ability to add MenuItems to the tray icon. Do you know of any way that I can have a JavaFX container popup when I click on the icon instead of a Menu or MenuItem?

@dustinkredmond
Copy link

@EasyG0ing1 FXTrayIcon supports this.

Simply instantiate the tray icon, then call the setOnAction method with your own event handler.

FXTrayIcon icon = new FXTrayIcon(myStage, myImageFile);
icon.setOnAction(e -> myStage.getScene().setRoot(yourAnchorPaneHere));

You can then add menu items that will popup on right-click if you wish, or have a tray icon without any menu items, the choice is yours and FXTrayIcon provides both options out of the box.

NOTE: setOnAction is only available from version 2.4.4 and up

@EasyG0ing1
Copy link

@dustinkredmond

FXTrayIcon icon = new FXTrayIcon(myStage, myImageFile);
icon.setOnAction(e -> myStage.getScene().setRoot(yourAnchorPaneHere));

OK, it sounds great, but when I try it, nothing happens.

Take this class for example:

import com.dustinredmond.fxtrayicon.FXTrayIcon;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import java.net.URL;

public class Main extends Application {

    private final URL url = getClass().getResource("images/Key.png");

    @Override
    public void start(Stage primaryStage) {
        FXTrayIcon icon = new FXTrayIcon(primaryStage,url);
        AnchorPane ap = new AnchorPane();
        ap.setPrefWidth(200);
        ap.setPrefHeight(100);
        Label label = new Label("Label");
        ap.getChildren().add(label);
        primaryStage.initStyle(StageStyle.TRANSPARENT);
        primaryStage.setScene(new Scene(ap));
        icon.setOnAction(e -> primaryStage.getScene().setRoot(ap));
        icon.showMinimal();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

It shows my icon just fine, but when I click on it, nothing happens.
I also tried the following variations of using the library:

        icon.setOnAction(e -> primaryStage.show());
        icon.setOnAction(e -> System.out.println("Hello World"));

And same results - nothing happens when I click on the icon...

Of course, if I just add the line

primaryStage.show();

at the bottom of the start method, it shows everything as expected ... but I can't get it to show from clicking on the icon.

@dustinkredmond
Copy link

@EasyG0ing1, the setOnAction method listens for double-click, this is why your code isn't working as expected. I can see how a user would want to be able to instead listen for single-click, so I've added this functionality in the setOnClick method. It is now available from version 2.4.5. I just released it to Maven central.

Please update your dependency if you wish to have this functionality available:

<dependency>
  <groupId>com.dustinredmond.fxtrayicon</groupId>
  <artifactId>FXTrayIcon</artifactId>
  <version>2.4.5</version>
</dependency>

Example:

icon.setOnAction(e -> System.out.println("I will run when user double-clicks the icon");
icon.setOnClick(e -> System.out.println("I will run when user single-clicks the icon");

@EasyG0ing1
Copy link

@dustinkredmond

icon.setOnAction(e -> System.out.println("I will run when user double-clicks the icon");
icon.setOnClick(e -> System.out.println("I will run when user single-clicks the icon");

OK, the setOnClick method works, the setOnAction still does nothing, even when double-clicked for some reason - just an FYI on that.

You've managed to give functionality to that system tray icon in JavaFX that I've not seen anywhere.

Do you have any thoughts on how I can kind of anchor the stage so that when the icon is clicked, it pops down underneath it as though it were connected to it? What I have going here is an app that has on the stage, an AnchorPane with potentially dozens of HBoxes all laid out in a scroll pane ... the desired effect is to click on the icon, and have that pop up just underneath it and the user can just use the scroll wheel on their mouse to scroll through the various objects bound in the scroll pane.

Without going into too much detail, hopefully, you can visualize what kind of interface I'm trying to achieve here.

Any thoughts on how I could make it look like the stage is anchored to that icon?

Nice work by the way! There has been an open request for native system tray icon capability in JavaFX since 2013 on the openJDK site where all the propeller heads hang out LOL ... and enough people voted for it that it was talked about as though it was going to happen but it never did for some reason. But your library is the closest thing I've seen yet to that native capability.

@dustinkredmond
Copy link

@EasyG0ing1

setOnAction continues to work on my machine running Windows 10. Which O.S. are you testing the functionality on, if Windows 10 can,
you share the exact code that you're using? I've tested mainly on Windows 10, but have yet to try other OS.

There may be a way of going about the design that you mention, but adding the implementation directly into FXTrayIcon may be slightly hacky. If I were trying to design the application you mention, I would "anchor" the pane based off of the OS and the relative location of the system tray. E.g. bottom-right on Windows 10, maybe top-left on Ubuntu with GNOME. I believe AWT's SystemTray exposes some API for size/width/height, but not position on the screen, so I will have to do some research.

Thanks! I got tired of waiting on a native implementation from JavaFX and Gluon, so I "attempted" to write it myself via abstracting AWT. Hope that you find it useful.

@EasyG0ing1
Copy link

EasyG0ing1 commented Jan 19, 2021

@dustinkredmond

@EasyG0ing1

setOnAction continues to work on my machine running Windows 10. Which O.S. are you testing the functionality on, if Windows 10 can,
you share the exact code that you're using? I've tested mainly on Windows 10, but have yet to try other OS.

I actually started thinking that you might be using Windows because obviously, it's working for you and the only logical explanation assuming everything else is the same, would be a different OS ... I'm using macOS Catalina (10.15.7).

There may be a way of going about the design that you mention, but adding the implementation directly into FXTrayIcon may be slightly hacky. If I were trying to design the application you mention, I would "anchor" the pane based off of the OS and the relative location of the system tray. E.g. bottom-right on Windows 10, maybe top-left on Ubuntu with GNOME. I believe AWT's SystemTray exposes some API for size/width/height, but not position on the screen, so I will have to do some research.

Yeah as I have been thinking about it, I think perhaps it might be just as well to pick up the coordinate of the mouse pointer at the moment the user clicks on the icon, then set the upper left corner of the stage to the left of the mouse, using the X coordinate and subtracting from it, half the width of the stage. Since the icon is so small, I don't think it would be too noticeable that the stage moves with the mouse when the icon is clicked.

Thanks! I got tired of waiting on a native implementation from JavaFX and Gluon, so I "attempted" to write it myself via abstracting AWT. Hope that you find it useful.

Based on the fact that the native implementation isn't getting any noise, I just might be the only one ... although I find that dumbfounding... writing apps that rest in the system tray has become quite popular these days.

I do sincerely appreciate your efforts in making this happen. This has been a long time coming for me personally and I lack the knowledge, apparently, to achieve what you did. I have zero experience with AWT - I started learning Java with FX only.

Thank you!

@dustinkredmond
Copy link

@EasyG0ing1 That makes sense, as MacOS is the only of the big three (Windows/Linux/Mac) that I haven't tested on. I will definitely look at incorporating more cross-platform behavior in future releases, I have a MacBook Air sitting around somewhere that I should be putting to use for this project. 😅

I'm glad that you like the library thus far, consider giving it a star or follow if you haven't already, as I plan to add more features in my free time going forward. I plan to add tray-related notifications and the like very soon.

@EasyG0ing1
Copy link

@dustinkredmond

@EasyG0ing1 That makes sense, as MacOS is the only of the big three (Windows/Linux/Mac) that I haven't tested on. I will definitely look at incorporating more cross-platform behavior in future releases, I have a MacBook Air sitting around somewhere that I should be putting to use for this project. 😅

I shouldn't even comment on this ... but ... the pure versatility, reliability and pain free experience of a Mac along with its capabilities that rival Windows ... just ... well ... ok ... no judgments here ... you use what makes you happy ... OF COURSE! :-)

Now why the double click works in Windows and not on a mac ... would be an AWT issue, would it not?

I'm glad that you like the library thus far, consider giving it a star or follow if you haven't already, as I plan to add more features in my free time going forward. I plan to add tray-related notifications and the like very soon.

So here's something I feel pretty stupid about ... I've been using github for years, and I had no idea that you could rate projects ... when I scrolled up, I see the star now ... and I'm sure I've seen it before, but obviously I never paid any attention to what it was about... thanks for educating me on that! lol. I will definitely rate and follow.

Mike

@EasyG0ing1
Copy link

@dustinkredmond

Dustin,

I can't find a way to send IMs in gist so if you would, shoot me an email: sims.mike@gmail.com I wanted to get your opinion on a couple of things that are OT for this forum.

Thanks,

Mike

@dustinkredmond
Copy link

@EasyG0ing1 Absolutely, I just sent you a message.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment