Last active
July 7, 2024 03:30
-
-
Save wiverson/d2edf0d66ad195c96793d0d25290753b to your computer and use it in GitHub Desktop.
Starting point for implementing system native notifications with JavaFX.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.doublerobot; | |
import javafx.application.Platform; | |
import javafx.scene.control.Label; | |
import java.awt.*; | |
import java.awt.desktop.AppForegroundEvent; | |
import java.awt.desktop.AppForegroundListener; | |
import java.awt.event.ActionEvent; | |
/* | |
As of this writing, the underlying Java implementation has to add a SystemTray icon in order | |
for the notification to work. | |
In addition, at least on Big Sur, clicking on the notification doesn't actually post an event back. | |
As a workaround, if you only post notifications if the application is running in the background, you | |
can listen for application focus events. In this scenario, if the app is running in the foreground, | |
present the user with an application appropriate notification INSTEAD of using a system notification. | |
Then, if something happens in the background, the user will get a system tray icon AND a system native | |
notification. The user can then click on a) the app icon to bring it to the foreground, b) the system | |
native notification, or c) the system tray icon, all of which should then bring the desktop app to | |
the foreground. | |
One you are in the foreground, you can then present the application-specific notification and clear | |
the system tray icon and the notification. | |
For in-application notifications, check out | |
https://github.com/controlsfx/controlsfx/wiki/ControlsFX-Features#notifications | |
Between this snippet as a starter for native OS notifications and in-app notifications via ControlsFX | |
notifications, you have a reasonable starter. | |
It would be really, really nice for JavaFX to just create their own implementation of the current | |
java.awt.* desktop integration - hopefully that'll come at some point in the future. | |
Also, not shown here but important for cleanup, add: | |
stage.setOnCloseRequest(x -> notificationExamples.close()); | |
*/ | |
public class NotificationExample { | |
private Label target; | |
private ThreadLocal<TrayIcon> trayIcon; | |
NotificationExample(Label label) { | |
target = label; | |
Desktop.getDesktop().addAppEventListener(new AppFocusListener()); | |
} | |
public void NotificationClicked(ActionEvent actionEvent) { | |
System.out.println(actionEvent.getActionCommand()); | |
System.out.println("Notification clicked"); | |
Platform.runLater(() -> target.setText("Notification clicked!")); | |
SystemTray tray = SystemTray.getSystemTray(); | |
tray.remove(trayIcon.get()); | |
} | |
public void ShowSystemNotification() { | |
javax.swing.SwingUtilities.invokeLater(() -> | |
{ | |
if (trayIcon == null || trayIcon.get() == null) { | |
init(); | |
} | |
try { | |
SystemTray tray = SystemTray.getSystemTray(); | |
tray.add(trayIcon.get()); | |
} catch (AWTException e) { | |
e.printStackTrace(); | |
} | |
trayIcon.get().displayMessage("Hello, World", "Notification Example", | |
TrayIcon.MessageType.INFO); | |
}); | |
} | |
private void init() { | |
if (!SystemTray.isSupported()) | |
return; | |
// By default, we'll just use the standard app icon. | |
Image image = Taskbar.getTaskbar().getIconImage(); | |
//Alternative (if the icon is on the classpath): | |
//Image image = Toolkit.getDefaultToolkit().createImage(getClass().getResource("icon.png")); | |
trayIcon = new ThreadLocal<>(); | |
trayIcon.set(new TrayIcon(image, "Tray Demo")); | |
//Let the system resize the image if needed | |
trayIcon.get().setImageAutoSize(true); | |
//Set tooltip text for the tray icon | |
trayIcon.get().setToolTip("System tray icon demo"); | |
trayIcon.get().setActionCommand("notification-clicked"); | |
trayIcon.get().addActionListener(this::NotificationClicked); | |
PopupMenu popupMenu = new PopupMenu(); | |
MenuItem showNotification = new MenuItem("Show notification"); | |
popupMenu.add(showNotification); | |
showNotification.addActionListener(this::NotificationClicked); | |
trayIcon.get().setPopupMenu(popupMenu); | |
} | |
public void close() { | |
System.out.println("closing"); | |
javax.swing.SwingUtilities.invokeLater(() -> | |
{ | |
if (trayIcon.get() != null) { | |
SystemTray tray = SystemTray.getSystemTray(); | |
tray.remove(trayIcon.get()); | |
System.out.println(("Removed tray!")); | |
} | |
}); | |
} | |
private class AppFocusListener implements AppForegroundListener { | |
@Override | |
public void appRaisedToForeground(AppForegroundEvent e) { | |
javax.swing.SwingUtilities.invokeLater(() -> | |
{ | |
if (SystemTray.getSystemTray().getTrayIcons().length > 0) | |
SystemTray.getSystemTray().remove(trayIcon.get()); | |
}); | |
} | |
@Override | |
public void appMovedToBackground(AppForegroundEvent e) { | |
System.out.println("App moved to background"); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment