How to use SystemTray correctly in Java? - java

I tried to implement a small (test) programm using the SystemTray in java. I got problems and wrote a (M)WE and pubushed it with http://pastebin.com/nYBkaLSy (sorry for not doing any safty checks).
What happens (under Ubuntu/KDE with openJDK):
The program starts and the button is clickable. The SystemTray is registered (all right so far)
If I click (single left mouse) on the image in the tray the button is no more clickable. Instead it seems to be disabled. A right-click on the tray icon OR the frame content (!!!) opens the popup. After closing this (clicking anywhere) the button active again. (this bahavior I cannot explain. My code does not implie this as far as I see)
If I click on the minimize button of the window manager the frame disappears (correct)
On choosing show from the popup of the tray icon restores the frame and the frame content is clickable (ok so far).
Unfortunately the popup is disabled after that. I cannot open it again and if I minimize the window again I cannot bring the window up again. (also I do not see the point in the code)
Now I am unsure if I understood the principles wrongly or if there is some other bug around. SO please help me to clear my questions.
EDIT:
I inserted the code (a bit modifie) here.
Please note that the created image is just dummy code. In my implementation I load an external image (see comments) which produces the same result. So the iconToImage() hint in the comments seems not to be a problem.
import java.awt.AWTException;
import java.awt.Image;
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.awt.event.WindowEvent;
import java.awt.event.WindowStateListener;
import java.awt.image.BufferedImage;
import java.net.URL;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
public class TestTray {
public static void main(String[] args) {
// Create tmp image
Image image = new BufferedImage(32, 32, BufferedImage.TYPE_INT_RGB);
image.getGraphics().drawOval(2, 2, 30, 30);
// Alternative: Load PNG image
//URL imUrl = TestTray.class.getResource("clock.png");
//ImageIcon icon = new ImageIcon(imUrl);
//image = icon.getImage();
TrayIcon ti = new TrayIcon(image);
ti.setImageAutoSize(true);
try {
SystemTray.getSystemTray().add(ti);
} catch (AWTException e) {
e.printStackTrace();
}
// Create JFrame and set default things
final JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new JButton("Test-Button"));
frame.pack();
frame.setVisible(true);
// Add listener to hide window in case of minimization
frame.addWindowStateListener(new WindowStateListener() {
#Override
public void windowStateChanged(WindowEvent ev) {
if(ev.getNewState() == JFrame.ICONIFIED)
frame.setVisible(false);
}
});
// Create popup for System tray and register it
PopupMenu popup = new PopupMenu();
MenuItem menuItem;
menuItem = new MenuItem("Show");
menuItem.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
frame.setVisible(true);
frame.setExtendedState(JFrame.NORMAL);
}
});
popup.add(menuItem);
menuItem = new MenuItem("Exit");
menuItem.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
System.exit(0);
}
});
popup.add(menuItem);
ti.setPopupMenu(popup);
}
}

Related

Action event not firing for JMenuItem accelerator keystroke?

I have a simple java swing application. It's mostly used on Macos, so I'm trying to add a default menu bar to it through Desktop.getDesktop().setDefaultMenuBar(...). I defined a menu bar with a "File" menu and a "New" menu item with an action listener. Using the mouse to click on File->New calls the listener's actionPerformed() event as expected.
I tried to attach the standard accelerator to the menu item (Command-N on a mac). Clicking on the "File" menu now displays "New" with the expected accelerator next to it. However, when I actually type Command-N, the action listener isn't called. The only visible effect of typing Command-N is that the "File" menu item briefly flickers.
Edit: This seems to be related to the fact the menu is being set through Desktop.setDefaultMenuBar(). If I attach the menu to the JFrame, then accelerators work correctly. However, I'm using Desktop.setDefaultMenuBar() to define a menu that appears even when no other windows are open.
import java.awt.Desktop;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
public class Scratch {
static class MainMenu extends JMenuBar implements ActionListener {
public MainMenu() {
JMenu fileMenu = new JMenu("File");
fileMenu.setMnemonic(KeyEvent.VK_F);
int keyMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx();
JMenuItem item = new JMenuItem("New");
item.setMnemonic(KeyEvent.VK_N);
item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, keyMask));
item.addActionListener(this);
fileMenu.add(item);
this.add(fileMenu);
}
#Override
public void actionPerformed(ActionEvent e) {
System.err.println("actionPerformed " + e);
}
}
private static void createAndShowGUI() {
JFrame frame = new JFrame("Hello Stackoverflow!");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JLabel label = new JLabel("Hello Stackoverflow!");
frame.getContentPane().add(label);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
Desktop.getDesktop().setDefaultMenuBar(new MainMenu());
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
createAndShowGUI();
}
});
}
}
The call to Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx() is based on this blog entry.
Can anyone see what I'm doing wrong?
I'm testing with Amazon coretto 11 if it matters.

Changing focused window using java

I am currently programming a virtual keyboard using java. As you know, when the user click the buttons on my virtual keyboard, the text will appear in some other windows(say a word document). So how can I make this action? or to be clearer, how to change the window-in-focus when the user click the button and let the text appear in the word document?
Thanks!
I hope below example will help you to develop keyboard. Few points:
1. Used JWindow rather than JFrame or any other frame, to avoid your focusing issue.
2. Used Robot to transfer key press event to active cursor.
Run below application, and make sure your cursor is on textpad, press A button on window, character a is inserted at cursor position:
import java.awt.AWTException;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Robot;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JButton;
import javax.swing.JWindow;
public class Runningwindow extends JWindow
{
public static void main(String[] args) throws AWTException
{
Runningwindow window = new Runningwindow();
window.setBackground(Color.RED);
window.setPreferredSize(new Dimension(200, 200));
window.setLayout(new FlowLayout());
JButton button = new JButton("A");
Robot r = new Robot();
int keyCode = KeyEvent.VK_A; // the A key
button.addMouseListener(new MouseAdapter() {
public void mouseReleased(MouseEvent e)
{
r.keyRelease(keyCode);
}
public void mousePressed(MouseEvent e)
{
r.keyPress(keyCode);
}
});
window.add(button);
window.pack();
window.setVisible(true);
}
}

JPopupMenu Menu Not Appearing

This is the code for my JPopupMenu and how I added it, it is supposed to respond when I right click the table:
JMenuItem deleteRows = new JMenuItem("Delete Row");
popup.add(deleteRows);
personTable.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
if(e.getButton() == MouseEvent.BUTTON3) {
popup.show(personTable, e.getX(), e.getY());
}
}
});
I am not sure why the popup menu is not appearing when I right-click the table in the application. I would appreciate it if somebody told me what I am doing wrong.
The trigger for a popup is different for different OS's, you can't simply use mousePressed and your certainly shouldn't be using e.getButton() == MouseEvent.BUTTON3
From How to use Menus, Bringing up a PopupMenu
The exact gesture that should bring up a popup menu varies by look and feel. In Microsoft Windows, the user by convention brings up a popup menu by releasing the right mouse button while the cursor is over a component that is popup-enabled. In the Java look and feel, the customary trigger is either pressing the right mouse button (for a popup that goes away when the button is released) or clicking it (for a popup that stays up).
Instead, you should be checking for each of the mouse events, pressed, released and clicked. You should also be using MouseEvent#isPopupTrigger to determine if the event is a popup trigger for the OS.
Having said all that, it would be simpler to just us JComponent#setComponentPopupMenu and let it decide instead
personTable.setComponentPopupMenu(popup);
Runnable example...
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.table.DefaultTableModel;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
DefaultTableModel model = new DefaultTableModel(10, 10);
JTable table = new JTable(model);
JMenuItem mi = new JMenuItem("I'll be your menu for today");
JPopupMenu popup = new JPopupMenu();
popup.add(mi);
table.setComponentPopupMenu(popup);
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new JScrollPane(table));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}
Here is the simple way to create a popupMenu without using mouse listener:
JpopupMenu popMenu = new JpopupMenu() ;
JMenuItem item = new JMenuItem("my item") ;
popMenu.add(item);
myTable.setComponentPopupMenu(popMenu);

Java JDialog messes up JMenuBar on mac

I am having some problems regarding to the JMenuBar and I cant seem to figure it out.
I will start with an abriviation of the problem: The program consists of a JFrame, a JDialog and a JMenuBar. Initially, you will get to see a JFrame with the JMenuBar in the top. But at some point, the JDialog will pop up where a user can fill in some text fields. The problem that I'm having is that as soon as the focus goes to the JDialog, the JMenuBar disappears. What I want is that the JMenuBar stays in the top of the screen all the time, except if the whole program is NOT in focus. Here are 2 screenshots, in the first screen shot, the JFrame is selected and in the other one the JDialog is selected.
So what i actually want is instead of only seeing the JMenuBar when the focus is on the JFrame, i want to see the JMenuBar all the time. Since a JDialogs can not have the JMenuBar in the top, like a JFrame has, i decided not to have multiple JMenuBars, but just the one that should be visible all the time.
At last i will give a part of the code that is as small as possible (and still working) and also contains the problem:
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JRootPane;
import javax.swing.KeyStroke;
/**
* #author Guus Leijsten
* #created Oct 27, 2012
*/
public class MenuBarProblem extends JFrame {
public MenuBarProblem() {
super("Frame");
this.setMinimumSize(new Dimension(270, 200));
this.setPreferredSize(new Dimension(800, 530));
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
JRootPane root = this.getRootPane();
//Menu
JMenu fileMenu = new JMenu("File");
JMenuItem file_exit = new JMenuItem("Exit");
file_exit.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_W, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
file_exit.setToolTipText("Exit application");
file_exit.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
});
fileMenu.add(file_exit);
JMenuBar menu = new JMenuBar();
menu.add(fileMenu);
root.setJMenuBar(menu);
this.setVisible(true);
JDialog d = new JDialog(this, "Dialog");
d.setSize(200, 100);
d.setLocation(0, (int)root.getContentPane().getLocationOnScreen().getY());
d.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
d.setVisible(true);
}
public static void main(String[] args) {
String os = System.getProperty("os.name").toLowerCase();
if(os.indexOf("mac") >= 0) {
System.setProperty("apple.laf.useScreenMenuBar", "true");
}
new MenuBarProblem();
}
}
If I can be honoust, i think that the problem lies in the part of JRootPane. But we'll see ;)
Did anyone else encountered this problem and managed to solve it alrady, or is there anybody that wants to give it a shot?
Thanks in advance!
Added content:
In the following example I will show a version that gives some functionality to the play.
This is the program i'm making:
The second image shows the state in which the right menu is undocked.
Obviously the JMenuBar should still be visible and operational because without it, a lot of functionalities of the program will be disabled.
At this point i'm starting to think that it is impossible for the JMenuBar to stay visible when the dialog (undocked menu) is undocked, and focussed on.
I know that the JMenuBar on a JDialog can not be in the mac osx style (top of screen), so are there any other techniques i can use for undocking, which does give me a mac osx style JMenuBar?
One key to solving this problem, pun intended, is to let a key binding share a common menu action, as shown below. Note how a menu item, your dialog's content and an (otherwise superfluous) button can all use the same Action instance. A few additional notes:
Kudos for using getMenuShortcutKeyMask().
Swing GUI objects should be constructed and manipulated only on the event dispatch thread (EDT).
System properties should be set before starting the EDT.
Make the dialog's setLocation() relative to the frame after its geometry is known.
A common Mac idiom uses the following predicate:
if (System.getProperty("os.name").startsWith("Mac OS X") {…}
See also this example.
For local use in the dialog itself, also consider JToolBar.
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
/**
* #see https://stackoverflow.com/a/13100894/230513
*/
public class MenuBarProblem extends JFrame {
private static final int MASK =
Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
private static final String exitName = "Exit";
private static final KeyStroke exitKey =
KeyStroke.getKeyStroke(KeyEvent.VK_W, MASK);
private final ExitAction exitAction = new ExitAction(exitName);
public MenuBarProblem() {
super("Frame");
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
JMenu fileMenu = new JMenu("File");
JMenuItem fileExit = new JMenuItem(exitAction);
fileMenu.add(fileExit);
JMenuBar menu = new JMenuBar();
menu.add(fileMenu);
JDialog d = new JDialog(this, "Dialog");
JPanel p = new JPanel();
p.getInputMap().put(exitKey, exitName);
p.getActionMap().put(exitName, exitAction);
p.add(new JButton(exitAction));
d.add(p);
d.pack();
d.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
this.setJMenuBar(menu);
this.pack();
this.setSize(new Dimension(320, 240));
this.setLocationByPlatform(true);
this.setVisible(true);
d.setLocation(this.getRootPane().getContentPane().getLocationOnScreen());
d.setVisible(true);
}
private static class ExitAction extends AbstractAction {
public ExitAction(String name) {
super(name);
this.putValue(Action.MNEMONIC_KEY, exitKey.getKeyCode());
this.putValue(Action.ACCELERATOR_KEY, exitKey);
}
#Override
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
}
public static void main(String[] args) {
System.setProperty("apple.laf.useScreenMenuBar", "true");
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new MenuBarProblem();
}
});
}
}
Solved!
Using a JFrame with the use of setAlwaysOnTop(true) gives me the desired effect of having a JMenuBar when the focus changes.

Why do radio buttons render improperly in a java.awt.Dialog on the Mac when the dialog is modal?

I'm in the process of testing my Java application on the Mac and I've run into a very strange issue. Checkboxes that appear in a modal Dialog render incorrectly, though non-modal Dialogs work fine.
For example, say I have a window with 2 radio buttons. When the dialog opens the first one is selected. When I click on the second button it suddenly looks like both are selected. Clicking anywhere else within the dialog will cause the rendering to fix itself and only the selected button will be displayed.
The following code reproduces this for me:
package mactest;
import java.awt.Checkbox;
import java.awt.CheckboxGroup;
import java.awt.Dialog;
import java.awt.FlowLayout;
import java.awt.Frame;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
public class Main {
public static void main(String[] args) {
boolean modal = false;
if(args.length > 0) {
modal = args[0].toLowerCase().equals("true");
}
TestDialog dlg = new TestDialog(new Frame(), modal);
dlg.setVisible(true);
}
private static class TestDialog extends Dialog {
private Checkbox cb1;
private Checkbox cb2;
private CheckboxGroup cbg;
public TestDialog(Frame owner, boolean modal) {
super(owner);
cbg = new CheckboxGroup();
cb1 = new Checkbox("One", true, cbg);
cb2 = new Checkbox("Two", false, cbg);
this.setLayout(new FlowLayout());
this.add(cb1);
this.add(cb2);
this.setModal(modal);
this.pack();
this.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
TestDialog.this.setVisible(false);
System.exit(0);
}
});
}
}
}
If I call this like so:
java -cp MacTest.jar mactest.Main false
the dialog is not modal and everything works fine. However, If I tell it to be modal:
java -cp MacTest.jar mactest.Main true
then the rendering issues occur.
I've tried every trick I can think of to try to fix the problem (invalidate, doLayout, requesting focus, explicitly setting the state of every button when one is selected, etc)., but so far the only thing I've come up with that works is to make the dialog not be modal. Unfortunately, that's not an option in my application.
In case it matters, this is on a MacBook running OS X 10.5 running Java 1.5.0_16.
Are you asking about checkboxes or radiobuttons? You mention both.
To create a multiple-exclusion scope you have to use ButtonGroup class
I found something that seems to fix it, though it's an ugly hack. I added item listeners to each of the Checkboxes. When those trigger I resize the window by 1 pixel, then do an invokeLater() on pack() (my dialogs aren't resizable). Here's the modified test code:
package mactest;
import java.awt.Checkbox;
import java.awt.CheckboxGroup;
import java.awt.Dialog;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.Frame;
import java.awt.Rectangle;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
public class Main {
public static void main(String[] args) {
boolean modal = false;
boolean tweak = false;
if(args.length > 0) {
modal = args[0].toLowerCase().equals("true");
}
if(args.length > 1) {
tweak = args[1].toLowerCase().equals("true");
}
TestDialog dlg = new TestDialog(new Frame(), modal, tweak);
dlg.setVisible(true);
}
private static class TestDialog extends Dialog {
private Checkbox cb1;
private Checkbox cb2;
private CheckboxGroup cbg;
public TestDialog(Frame owner, boolean modal, boolean tweak) {
super(owner);
cbg = new CheckboxGroup();
cb1 = new Checkbox("One", true, cbg);
cb2 = new Checkbox("Two", false, cbg);
this.setLayout(new FlowLayout());
this.add(cb1);
this.add(cb2);
this.setModal(modal);
this.pack();
if(tweak) {
cb1.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
onSelection();
}
});
cb2.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
onSelection();
}
});
}
this.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
TestDialog.this.setVisible(false);
System.exit(0);
}
});
}
private void onSelection() {
Rectangle bounds = this.getBounds();
this.setBounds(bounds.x, bounds.y, bounds.width + 1, bounds.height);
EventQueue.invokeLater(new Runnable() {
public void run() {
TestDialog.this.pack();
}
});
}
}
}
For resizable components you'd need to store the size pre-tweak and restore that instead of calling pack().
In windows I could see the dialog flicker every once in a while when I changed the selection, though on the mac it wasn't noticeable at all.

Categories

Resources