I have a class MyPopupMenu that extends JPopupMenu. Inside this popup menu I add a JMenuItem with an ActionListener which calls a process that it takes some minutes to return. I would like to make the popup menu close right after this item is pressed. My method inside the MyPopupMenu class is this:
private JMenuItem newItem(){
JMenuItem item=new JMenuItem();
item.setText("One");
item.setToolTipText("One");
ActionListener mylistener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent event) {
MyPopupMenu.this.setVisible(false);
Class1 class1=new Class1(file);
try {
class1.start();
} catch (IOException e) {
e.printStackTrace();
}
}
};
item.addActionListener(mylistener);
return item;
}
This doesn't work. After I press the item "One" the process starts but the popup menu remains open until the process returns (for some minutes). Is it possible to make the the popup menu disappear but the process continue running?
You are performing an action on the Swing main thread. This blocks your Gui from updating. You should move the starting of you class into another Thread.
For more info see Here
Related
I have a gui class MyGUIClass that extends JFrame. I would like to accomplish following in java. I looked into EventQueue, but somehow could not get things right. I am not even sure if this is the correct approach.
In the main, I would like following sequence of events:
-> start JFrame
-> Keep JFrame active and wait until play button is pressed. when button pressed,
-> execute rest of the code in main, but keep gui alive to receive commands from JFrame.
try {
java.awt.EventQueue.invokeAndWait(new Runnable() {
public void run() {
new MyGUIClass().setVisible(true);
}
});
}
catch (Exception e) {
e.printStackTrace();
}
System.out.println("Portion to execute only after request from GUI");
}
But the above trial is not working and the println prints while gui is running without any commend from GUI.
You will have to provide a listener to the button on click of which you want to execute some code.
But if you want to put the code which you want to execute in main. You will need some anonymous class containing code or lambda etc.
I will show here how you can achieve what you want
class MyGUIClass extends JFrame {
...
// I am using Runnable to contain code you want to execute.
// You can use Function or any class/interface you want
Runnable codeToExecute;
MyGUIClass(Runnable codeToExecute){
this.codeToExecute = codeToExecute;
...
}
...
void yourGUImethod(){
...
JButton btn = new JButton(new AbstractAction(){
#Override
public void actionPerformed(ActionEvent e) {
codeToExecute.run(); // execute code on button press
}
});
yourPanel.add(btn); // Add this button to your panel
...
}
Now you can provide executing code from main
Runnable codeToExecute = () -> { // Using lambda
System.out.println("Portion to execute only after request from GUI");
}
java.awt.EventQueue.invokeAndWait(new Runnable() {
public void run() {
new MyGUIClass(codeToExecute).setVisible(true);
}
});
I am not even sure if this is the correct approach.
Well you haven't stated if this is the main window or a child window.
If this is a "child window" and you are prompting for information from the main window, then you should be using a modal JDialog for the child window.
If this is the "main window" then no it isn't the correct approach.
GUI's are event driven. This means you write code to respond to events.
So, when the frame is created you create all the components and add them to the frame.
For the "Play" button you need to add an ActionListener to the button. So when the button is pressed you invoke the code related to the play action.
Then the GUI will just sit there waiting for more events to be generated by the user.
I am already listening to MenuListener.menuSelected() to know when a JMenu is showing on the screen. Initially, the JMenu has 1 JMenuItem which says "Loading...". After a slow operation, I add JMenuItems to the JMenu. The JMenu continues to show "Loading...". If I select another JMenu and come back, then the JMenu shows the added JMenuItems. How do I cause the added JMenuItems to show up immediately?
Here's the code which reproduces what is happening.
public class AddMenuItem extends JFrame
{
private final JMenu m_menu = new JMenu("Edit");
public static void main(String args[])
{
new AddMenuItem().
setVisible(true);
}
public AddMenuItem()
{
JMenuBar bar;
bar = new JMenuBar();
bar.add(m_menu);
setJMenuBar(bar);
setSize(600, 400);
m_menu.addMenuListener(new MenuListener()
{
#Override
public void menuSelected(MenuEvent e)
{
JMenuItem loading;
loading = new JMenuItem("Loading...");
loading.setEnabled(false);
m_menu.removeAll();
m_menu.add(loading);
// This represents a long running task which updates the menu afterwards
new Timer(5 * 1000, event -> updateMenu()).start();
}
#Override
public void menuDeselected(MenuEvent e)
{
// nothing to do
}
#Override
public void menuCanceled(MenuEvent e)
{
// nothing to do
}
});
}
private void updateMenu()
{
m_menu.removeAll();
m_menu.add(new JMenuItem("1"));
m_menu.add(new JMenuItem("2"));
m_menu.add(new JMenuItem("3"));
m_menu.revalidate();
}
}
It seems that this code example does exactly what I want but I am not sure what they are doing to make the visible menu repaint on the screen.
Initially, the JMenu has 1 JMenuItem which says "Loading...".
Code invoked from a listener executes on the Event Dispatch Thread (EDT). When you execute a long running task, it prevents the EDT from responding to events and repainting the GUI.
So to solve the problem the task started by that menu items needs to execute in a separate Thread. You might want to consider using a SwingWorker. Read the section from the Swing tutorial on Concurrency for more information about the EDT and SwingWorker.
Edit:
The problem is that once I know what to add to the menu, I don't know how to get the menu to show the added JMenuItems
You get the JMenu from the MenuEvent of your MenuListener:
JMenu menu = (JMenu)e.getSource();
menu.add( new JMenuItem( "Loading..." ) );
Edit:
Based on the SSCCE you can do something like:
private void updateMenu()
{
m_menu.removeAll();
m_menu.add(new JMenuItem("1"));
m_menu.add(new JMenuItem("2"));
m_menu.add(new JMenuItem("3"));
JPopupMenu popup = m_menu.getPopupMenu();
popup.pack();
}
But remember because your long running task executes in a separate Thread, you would need to invoke the above code using a SwingUtiltities.invokeLater so that the code is executed on the EDT.
It seems that this code example does exactly what I want but I am not sure what they are doing to make the visible menu repaint on the screen.
I think they are just hiding and showing the menu again.
Instead of doing m_menu.revalidate(), do the following...
if (m_menu.isPopupMenuVisible())
{
m_menu.setPopupMenuVisible(false);
m_menu.setPopupMenuVisible(true);
}
I am trying to make my application minimize on taskbar and restore when I double click on the trayIcon. I also have a popup menu that has an item which restores the window when clicked.
trayIcon = new TrayIcon(image, "Anything", popup);
trayIcon.addActionListener(actionListener);
trayIcon.addMouseListener(mouseListener);
sysTray.add(trayIcon);
And here is the code of the actionListener and mouseListener:
private ActionListener actionListener = new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
if (e.getActionCommand().equals("Restore"))
{ // RIGHT CLICK -> RESTORE
// Do something
}
}
};
private MouseListener mouseListener = new MouseListener()
{
#Override
public void mouseClicked(MouseEvent e)
{
if (javax.swing.SwingUtilities.isLeftMouseButton(e) && e.getClickCount()>1)
{ // DOUBLE LEFT MOUSE CLICK
// Do something
}
}
// Rest of the code
}
The Restore option of the popup menu works fine, however when I am double-clicking on the trayIcon at the System Tray I get a Null Pointer Exception at line if(e.getActionCommand().equals("Restore"))
How can I eliminate this and if possible merge both listeners into one?
N.B. This answer is made from a conversation with the OP in the comments, and some of the solutions came from the OP
The TrayIcon does not populate the ActionCommand field when firing the event, hence the code dies with an NPE.
Since the tray icon only calls its ActionListener only when double clicked or in an analagous action (via keyboard), you can create a RestoreListener that does not check that condition at all, and is only used with the tray icon and the "Restore" menu item.
private ActionListener restoreListener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
// Do the actual restoration
}
};
And actually adding it to the items...
trayIcon = new TrayIcon(image, "Anything", popup);
trayIcon.addActionListener(restoreListener);
MenuItem restoreMenuItem = new MenuItem(...);
restoreMenuItem.addActionListener(restoreListener);
This seems to behave slightly differently from a MouseListener that it does not put the window on top, this can be remedied by calling toTop() on it.
I created a menu in the menu bar in which I'd like to create a JCheckBoxMenuItem to set a condition for highlighting remaining menu items.
Something like the following pseudo-code:
if login(true)
then highlight remaining menuitems
else
un-highlight the menuitems
I think for highlighting you mean enable/disable a JMenuItem. That's possible.
Use setEnabled:
JMenuItem item;
item.setEnabled(false); //to disable
Like suggested by kleopatra, the best way of doing that is to implement your own action for each JMenuItem, and let your action to enable/disable the button accordingly to the state:
For example:
public class AMenuAction extends AbstractAction {
#override
public void actionPerformed(ActionEvent e) {
//implement your action behavior here
}
}
Then construct your JMenuItem with such action:
AMenuAction afterLoginAction = new AMenuAction();
JMenuItem item = new JMenuItem(afterLoginAction );
When the user logged in/out call setEnabled method on the desired actions.
void Login()
{
afterLoginAction.setEnabled(true);
}
Enabling and disabling menu items is done in the same way as for any other JComponent by using the setEnabled( boolean ) method
Create a JCheckBoxMenuItem as userlogin menu item
JCheckBoxMenuItem jCheckBoxMenuItem = new JCheckBoxMenuItem();
then
add action listener to it
//unhighlite other menu items before login
jMenuFileOpen.setEnabled(false);
//...
jCheckBoxMenuItem.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e) {
if (login(true)){
jCheckBoxMenuItem.setSelected(true);
//highlite other menu items
jMenuFileOpen.setEnabled(true);
//...
} else {
jCheckBoxMenuItem.setSelected(false);
//unhighlite other menu items
jMenuFileOpen.setEnabled(false);
//...
}
}
});
once login(true) is successful the checkbox is checked on menu and other menu items are enabled.
I've created a drop-down menu on my Swing JToolBar. But it doesn't create behave the way I want. I'm aiming for it to work like Firefox's "Smart Bookmarks" button.
It disappears when the user selects a menu item: CORRECT!
It disappears when the user presses ESC: CORRECT!
It disappears when the user clicks somewhere in the main frame outside of the menu: CORRECT!
But it doesn't disappear when the user clicks a second time on the button that shows the drop-down menu: INCORRECT... :-(
My question is how can I add this behaviour, that it does disappear when the clicks on the button that shows the menu a second time.
Here's my current code, from Java 6 on the Mac:
import javax.swing.*;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
public class ScratchSpace {
public static void main(String[] arguments) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame("Toolbar with Popup Menu demo");
final JToolBar toolBar = new JToolBar();
toolBar.add(createMoreButton());
final JPanel panel = new JPanel(new BorderLayout());
panel.add(toolBar, BorderLayout.NORTH);
panel.setPreferredSize(new Dimension(600, 400));
frame.getContentPane().add(panel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
private static AbstractButton createMoreButton() {
final JToggleButton moreButton = new JToggleButton("More...");
moreButton.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() == ItemEvent.SELECTED) {
createAndShowMenu((JComponent) e.getSource(), moreButton);
}
}
});
moreButton.setFocusable(false);
moreButton.setHorizontalTextPosition(SwingConstants.LEADING);
return moreButton;
}
private static void createAndShowMenu(final JComponent component, final AbstractButton moreButton) {
JPopupMenu menu = new JPopupMenu();
menu.add(new JMenuItem("Black"));
menu.add(new JMenuItem("Red"));
menu.addPopupMenuListener(new PopupMenuListener() {
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
}
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
moreButton.setSelected(false);
}
public void popupMenuCanceled(PopupMenuEvent e) {
moreButton.setSelected(false);
}
});
menu.show(component, 0, component.getHeight());
}
}
Well, here is a potential solution that is not without it's drawbacks. Only you can decide if this is acceptable for your application. The issue is that the popup closing occurs before other mouse-handling events are fired so clicking on your More.. button again causes the popup to hide, thus resetting the buttons state to deselected BEFORE the button even gets told it was pressed.
The easy workaround is to add the following call within your main program:
UIManager.put("PopupMenu.consumeEventOnClose", Boolean.TRUE);
The result of this is that whenever a popup menu is closed because of a mouse-pressed event, that mouse event will be consumed at the time the menu is closed and won't be passed on to any other components under the mouse. If you can live with limitation, this is an easy solution.
What's happening is that when you click off the menu, it cancels the popup menu, so you deselect the button, but the next immediate event is clicking the button, and now its deselected so it shows the menu again.
I don't have the exact solution yet, but give me a little bit ...
I don't use Firefox so I don't know what the Smart Bookmarks button looks like, but maybe use a JMenu as the "button". You could try using the Border of a JButton to make it look more like a button.
Well, the listener on the button reacts only when it is pushed down, because you listen for ItemEvent.SELECTED events only. How about adding another if clause to listen for ItemEvent.DESELECTED events here:
moreButton.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() == ItemEvent.SELECTED) {
createAndShowMenu((JComponent) e.getSource(), moreButton);
}
}
});
You could either store a reference to the menu somewhere, or you could make the menu itself add another listener to the button. The latter solution could be more straightforward, since you already seem to send a button reference to the menu.