Swing persistent popup - java

I need to display a swing popup with my custom component. The popup should stay visible, until I hide it myself, but shouldn't get focus.
I have a code written by some other developer that does it in the following way:
popupMenu = new JPopupMenu();
popupMenu.add(myCustomComponent, BorderLayout.CENTER);
popupMenu.setFocusable(false);
popupMenu.setVisible(true);
popupMenu.show(parentComponent, x, y);
This seems to work, but has a bug - when the popup is visible, first mouse click outside the component is consumed by the popup. So I need to click twice to set focus to another component.
How can I fix it? Or what is correct way to make the popup?
UPDATE
At last I've managed to reproduce my problem in short code fragment. Thanks to Guillaume Polet for giving me a starting point.
Here's the code:
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.*;
public class TestJPopup {
protected void initUI() {
JFrame frame = new JFrame(TestJPopup.class.getSimpleName());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JTextField textField = new JTextField("Some text field");
frame.add(textField, BorderLayout.WEST);
final JButton buttonToHit = new JButton("Hit me");
buttonToHit.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(buttonToHit, "You hit the button successfully");
}
});
frame.add(buttonToHit);
frame.setSize(200, 100);
frame.setVisible(true);
final JPopupMenu popup = new JPopupMenu();
popup.add(new JLabel("<html>Hey!<br>I'm the popup window!</html>"),
BorderLayout.NORTH);
popup.setFocusable(false);
popup.setVisible(true);
popup.show(textField, 60, 60);
// I want to activate popup when user clicks in the text field
textField.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
if (popup != null) {
popup.show(textField, 60, 60);
}
}
});
}
public static void main(String[] args) throws Exception {
Class lnfClass = Class.forName("com.sun.java.swing.plaf.windows.WindowsLookAndFeel", true,
Thread.currentThread().getContextClassLoader());
LookAndFeel feel = (LookAndFeel) lnfClass.newInstance();
UIManager.setLookAndFeel(feel);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new TestJPopup().initUI();
}
});
}
}
Two critical moments:
Windows look and feel used (with default not reproducible)
Mouse listener attached to text field in main frame

Not an answer, but just an example SSCCE in which I can't currently reproduce the behaviour you described. Maybe start from this code, try to reproduce the error and the edit your post with modified non-working code.
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
public class TestJPopup {
protected void initUI() {
JFrame frame = new JFrame(TestJPopup.class.getSimpleName());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JLabel leftLabel = new JLabel("Left");
frame.add(leftLabel, BorderLayout.WEST);
final JButton buttonToHit = new JButton("Hit me");
buttonToHit.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(buttonToHit, "You hit the button successfully");
}
});
frame.add(buttonToHit);
frame.setSize(500, 400);
frame.setVisible(true);
JPopupMenu popupMenu = new JPopupMenu();
popupMenu.add(new JLabel("<html>A Custom<br>component<br>made to<br> simulate <br>your custom component</html>"),
BorderLayout.NORTH);
JTextField textfield = new JTextField(30);
popupMenu.add(textfield);
popupMenu.setFocusable(false);
popupMenu.setVisible(true);
popupMenu.show(leftLabel, 20, 20);
// Let's force the focus to be in a component in the popupMenu
textfield.requestFocusInWindow();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new TestJPopup().initUI();
}
});
}
}

Not a solution, but:
Looks like a bug to me, even a plain componentPopup exhibits the same mis-behaviour (in winLAF and Nimbus, not in Metal):
JTextField field = new JTextField("some popup owner");
JPopupMenu menu = new JPopupMenu();
menu.add("dummy");
field.setComponentPopupMenu(menu);
Action action = new AbstractAction("hit me!") {
#Override
public void actionPerformed(ActionEvent e) {
LOG.info("got hit!");
}
};
JComponent content = new JPanel();
content.add(new JButton(action));
content.add(field);

for quick research and/or for future readers,
this issue is reproducible and presented for,
a) JPopup
b) JMenu
tested on jdk1.6.0_25 and jdk1.7.0_04,
same issue on WinXp and Win7,
for Look and Feel to SystemLookAndFeel / WindowsLookAndFeel,

Here's a possible workaround with JWindow instead of JPopupMenu, that was proposed by mKorbel in comments:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class TestJPopup {
protected void initUI() {
final JFrame frame = new JFrame(TestJPopup.class.getSimpleName());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JTextField textField = new JTextField("Some text field");
frame.add(textField, BorderLayout.WEST);
final JButton buttonToHit = new JButton("Hit me");
buttonToHit.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(buttonToHit, "You hit the button successfully");
}
});
frame.add(buttonToHit);
frame.setSize(200, 70);
frame.setVisible(true);
final JWindow popup = new JWindow();
popup.getContentPane().add(new JLabel("<html>Hey!<br>I'm the popup window!</html>"),
BorderLayout.NORTH);
popup.setLocation(frame.getLocation().x + 60, frame.getLocation().y + 60);
popup.pack();
popup.setFocusable(false);
popup.setVisible(true);
// I want to activate popup when user clicks in the text field
textField.addMouseListener(new MouseAdapter() {
#Override
public void mouseReleased(MouseEvent e) {
if (popup != null) {
popup.setVisible(true);
popup.setLocation(frame.getLocation().x + 60, frame.getLocation().y + 60);
popup.toFront();
}
}
});
textField.addFocusListener(new FocusAdapter() {
#Override
public void focusLost(FocusEvent e) {
if (popup != null) {
popup.setVisible(false);
}
}
});
}
public static void main(String[] args) throws Exception {
Class lnfClass = Class.forName("com.sun.java.swing.plaf.windows.WindowsLookAndFeel", true,
Thread.currentThread().getContextClassLoader());
LookAndFeel feel = (LookAndFeel) lnfClass.newInstance();
UIManager.setLookAndFeel(feel);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new TestJPopup().initUI();
}
});
}
}

Here is the magic line that fixes the problem:
UIManager.put("PopupMenu.consumeEventOnClose", Boolean.FALSE);
I found this after looking into the source code for the BasicPopupMenuUI class. Apparently this behaviour is a deliberate design choice according to the following comments in the code, but it sure feels like a bug to me.
// Ask UIManager about should we consume event that closes
// popup. This made to match native apps behaviour.
By the way, it happens in Java 5 and 6 too.

Related

How to hide a JPopupMenu by pressing a button?

I'm making a program that has a popup menu with two buttons, one of which should close the popup menu, but I have no idea how to do that and googling hasn't gone too well.
I've tried using popup.hide() but then the menu wouldn't come back, despite doing so when I tried just moving the popup. It also required me to put a SuppressWarning in that case and it took a few seconds for it to close at all. Is there any better way of doing it?
I'm not sure what kind of code is relevant, but here's the relevant buttons and their roles in this(I skipped all the creating the GUI parts that didn't seem relevant, everything looks good and I know that the buttons are working):
package test;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
interface CustomButton {
JButton create();
void react(JPopupMenu popup, JFrame frame);
}
class ErrandsButton implements CustomButton {
private JButton errands = new JButton("Errands");
public JButton create() {
return errands;
}
public void react(JPopupMenu popup, JFrame frame) {
errands.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
popup.show(frame, 120, 65);
}
});
}
}
class Test {
static JFrame frame = new JFrame("List");
static CustomButton errands = new ErrandsButton();
static JButton cancelTask = new JButton("Cancel");
static JPopupMenu popup = new JPopupMenu();
static void cancelTask() {
cancelTask.addActionListener(new ActionListener() {
#SuppressWarnings("deprecation")
public void actionPerformed(ActionEvent e) {
popup.hide();
}
});
}
public static void main(String args[]) {
createInterface();
cancelTask();
errands.react(popup, frame);
}
static void createInterface() {
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(500, 500);
JPanel popup1 = new JPanel();
JPanel button = new JPanel();
popup1.add(cancelTask);
popup.add(popup1);
frame.add(popup);
button.add(errands.create());
frame.getContentPane().add(BorderLayout.CENTER, button);
frame.setVisible(true);
}
}
Use popup.setVisible(true) and popup.setVisible(false).
frame.add(popup); is the problem. Do not add a JPopupMenu to a Container. Instead, use setComponentPopupMenu.
Alternatively, you could do the work yourself by adding a MouseListener whose mousePressed, mouseReleased and mouseClicked methods call isPopupTrigger and show. (It is vital that you do this in all three of those methods—different platforms have different conditions for showing popup menus.)
But really, using setComponentPopupMenu is easier.

label.setText - change with another textarea?

Having an issue with some bits of my code.
label1.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
label1.setText(-Here i want the button to open up a new "update window, and it will update the label to the text i'll provide in a seperate window);
}
});
Is there any way to do it without without an additional form? Just wanted to add that i have several labels, and i'm not sure on how to start with it.
One approach is to return a result from your update dialog which you can then use to update the text in label1. Here's an example which updates the label text based on the result return from a JOptionPane
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class Test {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.setMinimumSize(new Dimension(200, 85));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new FlowLayout());
JLabel label = new JLabel("Original Text");
frame.add(label);
JButton button = new JButton("Click Me");
frame.add(button);
// to demonstrate, a JOptionPane will be used, but this could be replaced with a custom dialog or other control
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
int result = JOptionPane.showConfirmDialog(frame, "Should I update the label?", "Test", JOptionPane.OK_CANCEL_OPTION );
// if the user selected 'Ok' then updated the label text
if(result == JOptionPane.OK_OPTION) {
label.setText("Updated text");
}
}
});
frame.setVisible(true);
}
});
}
}
Another approach would be to use an Observer and Observable which would listen for updates and change the label text accordingly. For more on the Observer, take a look at this question: When should we use Observer and Observable

How to hide a JFrame but then open it when the message box closes

I'm trying to create a MessageBox Creator.
I Have tried to hide the Creator when the Message Box Opens
and then show when the Message Box Closes. I am using the following plugins for Eclipse Neon:
WindowBuilder
Swing Designer
to help me create the program.
A similar one is here but it did not help: Click Me
The source code is here:
package org.us.me.****.messagebox.creator;
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JTextField;
import javax.swing.JProgressBar;
import javax.swing.JButton;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
public class MessageBoxCreator {
private JFrame frmD;
private JTextField txtMessageGoesHere;
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
MessageBoxCreator window = new MessageBoxCreator();
window.frmD.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the application.
*/
public MessageBoxCreator() {
initialize();
}
/**
* Initialize the contents of the frame.
*/
private void initialize() {
frmD = new JFrame();
frmD.setTitle("MessageBox: Creator");
frmD.setBounds(100, 100, 260, 113);
frmD.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frmD.getContentPane().setLayout(null);
JTextField MessageBox = new JTextField();
MessageBox.setText("Message goes here...");
MessageBox.setBounds(10, 11, 222, 20);
frmD.getContentPane().add(MessageBox);
MessageBox.setColumns(10);
JButton btnGenerate = new JButton("Generate");
btnGenerate.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(null, MessageBox);
}
});
btnGenerate.setBounds(10, 42, 86, 23);
frmD.getContentPane().add(btnGenerate);
}
}
Please Help.
Seems like you are trying to hide the Frame on button click and then on popup's button click you want the initial frame back as visible.
The easiest and straight way forward way to do so is to create your own popup box.
Try this out:
class CustomPopup extends JFrame
{
public CustomPopup()
{
frmD.setVisible(false);
this.setName("Popup");
this.setLayout(new BorderLayout());
JLabel l = new JLabel("Enter Message here");
JButton b = new JButton("Submit");
b.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
frmD.setVisible(true);
CustomPopup.this.setVisible(false);
CustomPopup.this.dispose();
}
});
this.add(l,BorderLayout.CENTER);
this.add(b,BorderLayout.SOUTH);
this.setSize(300, 150);
this.setResizable(false);
this.setDefaultCloseOperation(0);
this.setVisible(true);
}
}
Then in your button action listener do this:
btnGenerate.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
new CustomPopup();
}
});
Note: The above example is in with respect to your code. I have used Border layout for popup in the example as it is the simplest to implement. I believe you can customize the look and feel of your custom popup on your own. There are also more options to create popups with custom settings.
Refer to this for more info:http://docs.oracle.com/javase/tutorial/uiswing/components/dialog.html
I think the design is not good, but to make the frame hide and show change
this line:
JOptionPane.showMessageDialog(null, MessageBox);
To
frmD.setVisible(false);
JOptionPane.showMessageDialog(null, MessageBox);
frmD.setVisible(true);

Toggle visibility of a JComponent Swing

I am trying to toggle visibility of a JTextField with a checkbox. If the checkbox is selected I want the JTextField to be displayed and vice-versa. My program works fine until I add the line that initializes the JTextField to be invisible at the start. If I remove this the segment works fine! Can you help me?
final JCheckBox chckbxNewCheckBox_1 = new JCheckBox("New Folder");
panel_3.add(chckbxNewCheckBox_1);
final JTextField textField_3 = new JTextField();
panel_3.add(textField_3);
textField_3.setColumns(20);
//textField_3.setVisible(false); if a comment it in.. it never becomes visible
chckbxNewCheckBox_1.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent arg0) {
if(chckbxNewCheckBox_1.isSelected()){
textField_3.setVisible(true);
}
else
textField_3.setVisible(false);
}
});
Try with ActionListener instead of MouseListener
checkBox.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
textField_3.setVisible(checkBox.isSelected());
}
});
--EDIT--
call panel_3.revalidate(); after changing its visibility.
When an element is invisible during container initialization, it never gets its dimensions initialized. You can check it by calling getWidth() and getHeight() on the text area after you set it to visible. They're both zero. So follow #Braj edit and call panel.revalidate() after you change element visibility to let layout manager know that it's time to reposition/recalculate some elements and give them proper size.
You will do better with ItemListener
chckbxNewCheckBox_1.addItemListener(new ItemListener() {
#Override
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() == ItemEvent.DESELECTED))
textField_3.setVisible(false);
else if (e.getStateChange() == ItemEvent.SELECTED))
textField_3.setVisible(true);
textField_3.revalidate();
}
});
Note: pelase follow naming conventions and use underscores only for constants.
Consider calling pack() method
Below is the complete code I experimented with:
import java.awt.FlowLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
public class Test {
public static void main(String[] args) {
final JFrame frame = new JFrame();
frame.setLayout(new FlowLayout());
final JCheckBox chckbxNewCheckBox_1 = new JCheckBox("New Folder");
final JPanel panel_3 = new JPanel();
frame.add(panel_3);
panel_3.add(chckbxNewCheckBox_1);
final JTextField textField_3 = new JTextField();
panel_3.add(textField_3);
textField_3.setColumns(20);
textField_3.setVisible(false); //if a comment it in.. it never becomes visible
chckbxNewCheckBox_1.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent arg0) {
if (chckbxNewCheckBox_1.isSelected()) {
textField_3.setVisible(true);
} else
textField_3.setVisible(false);
frame.pack();
}
});
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}

JComboBox getSelectedIndex() method

I was making a simple text editor where you can set font style,font size, clear all etc. To set font size I added JComboBox and implemented ItemListener. Here is my MainWindow class:
import javax.swing.*;
public class MainWindow extends JFrame{
Editor e = new Editor();
public MainWindow(){
super(".:My Text Editor:.");
getContentPane().add(e);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack();
setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable(){
public void run() {
new MainWindow();
}
});
}
}
Here is my Editor class:
import javax.swing.*;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
public class Editor extends JPanel{
JPanel optionPanel = new JPanel();
JTextArea editArea = new JTextArea();
JButton boldBtn = new JButton("Bold");
JButton italicBtn = new JButton("Italic");
JButton plainBtn = new JButton("Plain");
JButton clearBtn = new JButton("Clear all");
String [] fontSizes = {"10","11","12","13","14","15","16","17","18","19","20"};
int fontSize;
JComboBox combo = new JComboBox(fontSizes);
public Editor(){
createUI();
addEvents();
}
public void createUI(){
optionPanel.add(boldBtn);
optionPanel.add(italicBtn);
optionPanel.add(plainBtn);
optionPanel.add(combo);
optionPanel.add(clearBtn);
setLayout(new BorderLayout());
add(optionPanel,BorderLayout.NORTH);
add(new JScrollPane(editArea),BorderLayout.CENTER);
setPreferredSize(new Dimension(640,480));
}
public void addEvents(){
boldBtn.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
editArea.setFont(new Font("Sans Serif",Font.BOLD,fontSize));
}
});
italicBtn.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
editArea.setFont(new Font("Sans Serif",Font.ITALIC,fontSize));
}
});
plainBtn.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
editArea.setFont(new Font("Sans Serif",Font.PLAIN,fontSize));
}
});
combo.addItemListener(new ItemListener(){
public void itemStateChanged(ItemEvent e){
int ind = combo.getSelectedIndex();
System.out.println(ind);
fontSize = Integer.parseInt(fontSizes[ind]);
editArea.setFont(new Font("Sans Serif",Font.PLAIN,fontSize));
}
});
clearBtn.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
editArea.setText("");
}
});
}
}
Now, weird thing what happened is when I put System.out.println(ind); line just to see what index the getSelectedIndex() method returns me. Depending on which item I click, it returns me this:
1
1
0
0
2
2
3
3
Why is this happening? Shouldn't return me just 1 0 2 3? Thanks in advance.
JCombobox fire itemStateChanged twice for SELECTED and DESELECTED that you differentiate with ItemEvent.getStateChanged(). So wrap your code in an if like this:
public void itemStateChanged( ItemEvent event ) {
if( event.getStateChanged() == ItemEvent.SELECTED ) {
// code here
}
}
Whenever you change the selection in a JComboBox, the itemStateChanged event is triggered twice, once for DESELECT of the old selected item and once for SELECT for the new selected item.
If you only want your code to be executed once, just do:
if (e.getStateChange() == ItemEvent.SELECTED) {
...
}
Seems that itemStateChanged is triggered twice. I think that the ItemEvent parameter is not the same each time. Perhaps you should check the event type before doing something.
Sorry I can't check now, but I will do it later if you still need help.

Categories

Resources