I've written a JWindow that acts a bit like a fancy menu in my application, popping up when a button is pressed. However, I'd like it to disappear if the user clicks anywhere in the main window. I can of course add a mouse listener to the main window, but that doesn't add it to all the components on the window itself, and looping over all the components seems like a bit of a brute force solution (and can't be guaranteed to work if the components on the window change.)
What's the best way of going about doing something like this?
Try to use Toolkit.getDefaultToolkit().addAWTEventListener(listener, eventMask). Find eventMask that filters only mouse clicks. This AWT listener is global for whole application, so you can see all events that happen.
I'd like it to disappear if the user clicks anywhere in the main window
Add a WindowListener to the child window and then handle the windowDeactiveated() event and invoke setVisible(false) on the child window.
Working example:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class DialogDeactivated
{
public static void main(String[] args)
{
final WindowListener wl = new WindowAdapter()
{
public void windowDeactivated(WindowEvent e)
{
e.getWindow().setVisible(false);
}
};
JButton button = new JButton("Show Popup");
button.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
JButton button = (JButton)e.getSource();
JFrame frame = (JFrame) SwingUtilities.windowForComponent(button);
JDialog dialog = new JDialog(frame, false);
dialog.setUndecorated(true);
dialog.add( new JButton("Dummy Button") );
dialog.pack();
dialog.setLocationRelativeTo( frame );
dialog.setVisible( true );
dialog.addWindowListener( wl );
}
});
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(button, BorderLayout.NORTH);
frame.setSize(400, 400);
frame.setLocationRelativeTo( null );
frame.setVisible( true );
}
}
Related
I'm implementing an "in app" search engine with Swing and I want it to behave exactly like Windows 10's search box.
The search box should:
Open above and to the right of the search button, touching the button's edge.
Have the focus when open.
Close (if open) on a press of the search button.
Close (if open) when pressing with the mouse anywhere out of the search box.
It was perfect if JPopUpMenu could have JDialog as it's child but since it can't I need to implement the behaviors from scratch (or do I?).
This is my first time using Swing and I'm having difficulties implementing everything by myself.
I tried looking for examples online but I couldn't find much helpful information.
Is there a workaround to the fact that JPopUpMenu can't host JDialog?
Are there examples of implementing the behaviors I described?
Thanks
===============================Edit============================
Thanks for the comments so far. I've managed to get the behavior I wanted except one issue.
The following code creates a frame with a button:
public static void main(String[] args){
JFrame mainWindow = new JFrame();
mainWindow.setSize(420,420);
mainWindow.setVisible(true);
JFrame popUp = new JFrame();
popUp.setSize(210, 210);
JButton button = new JButton("button");
mainWindow.add(button);
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if(!button.isSelected()){
button.setSelected(true);
popUp.setVisible(true);
}
else{
button.setSelected(false);
popUp.setVisible(false);
}
}
});
popUp.addWindowFocusListener(new WindowAdapter() {
#Override
public void windowLostFocus(WindowEvent e) {
popUp.setVisible(false);
}
});
}
When I click the button, a pop-up window appears and if I click out of the main window the pop up disappear but then when I want to re-open the pop-up I need to press the button twice.
How can I get the button to operate correctly when the pop-up was closed due to lose of focus?
Your "solution" is very brittle. Try moving the main JFrame before left-clicking on the JButton.
Oracle has a helpful tutorial, Creating a GUI With Swing. Skip the Netbeans section. Study the rest of the tutorial.
I created a main JFrame that pops up a JDialog. I put the close JButton on the JDialog. The JDialog is modal, meaning you cannot access the main JFrame while the JDialog is visible.
You can place the JDialog anywhere you wish on the screen. Normally, you have a JDialog appear in the center of the parent JFrame. That's where users expect a dialog to appear. I placed the JDialog towards the upper left, just to show you how it's done.
Here's the complete runnable code.
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class PopupExample {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new PopupExample().createAndShowGUI());
}
private JFrame mainWindow;
public void createAndShowGUI() {
mainWindow = new JFrame("Main Window");
mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainWindow.add(createMainPanel(), BorderLayout.CENTER);
mainWindow.pack();
mainWindow.setLocationByPlatform(true);
mainWindow.setVisible(true);
}
private JPanel createMainPanel() {
JPanel panel = new JPanel(new FlowLayout());
panel.setBorder(BorderFactory.createEmptyBorder(200, 200, 200, 200));
JButton button = new JButton("button");
panel.add(button);
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
createAndShowDialog(mainWindow);
}
});
return panel;
}
private void createAndShowDialog(JFrame frame) {
JDialog dialog = new JDialog(frame, "Dialog", true);
dialog.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
dialog.add(createDialogPanel(dialog), BorderLayout.CENTER);
dialog.pack();
// Here's where you set the location of the JDialog relative
// to the main JFrame
Point origin = frame.getLocation();
dialog.setLocation(new Point(origin.x + 30, origin.y + 30));
dialog.setVisible(true);
}
private JPanel createDialogPanel(JDialog dialog) {
JPanel panel = new JPanel(new FlowLayout());
panel.setBorder(BorderFactory.createEmptyBorder(100, 100, 100, 100));
JButton button = new JButton("Close");
panel.add(button);
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
dialog.dispose();
}
});
return panel;
}
}
Good afternoon!
I have this code:
private static class ClickListener implements ActionListener {
public ClickListener() {
}
#Override
public void actionPerformed(ActionEvent e) {
JFrame frame = new JFrame();
JLabel label = new JLabel("Opção Indisponivel");
JPanel panel = new JPanel();
frame.add(label, BorderLayout.CENTER);
frame.setSize(300, 400);
JButton button = new JButton("Voltar");
button.addActionListener(new CloseWindowListener());
panel.add(button);
frame.add(panel, BorderLayout.SOUTH);
frame.setVisible(true);
}
}
private static class CloseWindowListener implements ActionListener {
public CloseWindowListener() {
}
#Override
public void actionPerformed(ActionEvent e) {
setVisible(false);
}
}
What I want to do is when i click on the button "voltar" (which is in another window, not on the "main" one as you can see) it closes the windows but not the app itselft. The setVisible line gives me an error about that it cannot be referenced by a static context which I understand because I need the reference of the frame. How do I solve this?
EDIT: Changed JFrame to JDialog but still no sucess. Both windows are shutdown.
Thanks in advance,
Diogo Santos
The setVisible line gives me an error about that it cannot be referenced by a static context which I understand because I need the reference of the frame. How do I solve this?
You can access the component that generated the event. Then you can find the window the component belongs to. This will give you generic code to hide any window:
//setVisible(false);
JButton button = (JButton)e.getSource();
Window window = SwingUtilities.windowForComponent(button);
window.setVisible(false);
You can also check out Closing an Application. The ExitAction can be added to your button. Now when you click the button it will be like clicking the "x" (close) button of the window. That is whatever default close operation your specify for the window will be invoked.
My application offers the ability to launch a long-running task. When this occurs a modeless JDialog is spawned showing the progress of the task. I specifically make the dialog modeless to allow the user to interact with the rest of the GUI whilst the task runs.
The problem I'm facing is that if the dialog becomes hidden behind other windows on the desktop it becomes difficult to locate: There is no corresponding item on the task bar (on Windows 7), nor is there an icon visible under the Alt+Tab menu.
Is there an idiomatic way to solve this problem? I had considered adding a WindowListener to the application's JFrame and use this to bring the JDialog to the foreground. However, this is likely to become frustrating (as presumably it will mean the JFrame then loses focus).
You can create a non-modal dialog and give it a parent frame/dialog. When you bring up the parent frame/dialog, it also brings the non-modal dialog.
Something like this illustrates this:
public static void main(String[] args) throws IOException {
JFrame frame = new JFrame();
frame.setTitle("frame");
JDialog dialog = new JDialog(frame, false);
dialog.setTitle("dialog");
final JButton button = new JButton("Click me");
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(button, "Hello");
}
});
final JButton button2 = new JButton("Click me too");
button2.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(button2, "Hello dialog");
}
});
frame.add(button);
dialog.add(button2);
frame.pack();
dialog.pack();
frame.setVisible(true);
dialog.setVisible(true);
}
I've experimented and searched and I can't seem to figure out what I thought would be something simple, which is having my START button have focus when my little GUI app launches I.e., so all the user has to do is press their Enter/Return key, which will have the same effect as if they had clicked the START button with their mouse. Here is my code. Thanks for your help :)
private void initialize() {
// Launch the frame:
frame = new JFrame();
frame.setTitle("Welcome!");
frame.setSize(520, 480);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Add the image:
ImageIcon heroShotImage = new ImageIcon("heroShot.jpg");
JPanel heroShotPanel = new JPanel();
JLabel heroShot = new JLabel(heroShotImage);
heroShotPanel.add(heroShot);
// Create a panel to hold the "Start" button:
JPanel submitPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
// Create the "Start" button, which launches business logic and dialogs:
JButton start = new JButton("Start");
start.setToolTipText("Click to use library");
start.setFocusable(true); // How do I get focus on button on App launch?
start.requestFocus(true); // Tried a few things and can't get it to work.
// Listen for user actions and do some basic validation:
start.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
// THE APP's LOGIC GOES HERE...
}
// Finish setting up the GUI and its components, listeners, and actions:
submitPanel.add(start);
frame.getContentPane().add(heroShotPanel, BorderLayout.NORTH);
frame.getContentPane().add(submitPanel, BorderLayout.SOUTH);
}
Try out this code.. All I have done is moving the requestFocus() method at the end.
Basically these are the two things you have to do for it to respond while pressing enter key and for it to be focused by default.
frame.getRootPane().setDefaultButton(start);
start.requestFocus();
package sof;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class TestFrame {
public static void main(String[] args) {
// Launch the frame:
JFrame frame = new JFrame();
frame.setTitle("Welcome!");
frame.setSize(520, 480);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Add the image:
ImageIcon heroShotImage = new ImageIcon("heroShot.jpg");
JPanel heroShotPanel = new JPanel();
JLabel heroShot = new JLabel(heroShotImage);
heroShotPanel.add(heroShot);
// Create a panel to hold the "Start" button:
JPanel submitPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
JButton start = new JButton("Start");
start.setToolTipText("Click to use library");
start.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("I AM PRESSED");
}
});
submitPanel.add(start);
frame.getContentPane().add(heroShotPanel, BorderLayout.NORTH);
frame.getContentPane().add(submitPanel, BorderLayout.SOUTH);
frame.setVisible(true);
frame.getRootPane().setDefaultButton(start);
start.requestFocus();
}
}
If I'm understanding you then you want to do a click event of Start Button when user hits Enter key. If this is the case then you can do it as follows:
jFrame.getRootPane().setDefaultButton(start);// 'start' will be your start button
And if you just want to get focus on start Button then shift your requestFocus() method at the end (after you make your frame visible) and no need to pass true in it. Also it is better to use requestFocusInWindow() then requestFocus() as stated in java doc.
move your focus line to the end of the method
and change it to
start.requestFocus(); // without params
If you want your start button to get the focus then do this at the end
//This button will have the initial focus.
start.requestFocusInWindow();
not easy job because Focus/Focus_SubSystem came from Native OS and is pretty asynchronous,
1) inside one Containers works by wraping that into invokeLater(),
2) manage Focus betweens two or more Top-Level Containers, by #camickr
I prepared a small test case below. My problem is when i right click on the window. JPopupMenu show up but if i click anywhere outside the JWindow menu does not disappear. I have to click somewhere on the window to get rid of it which is not the expected behavior.
EDIT:
after reading akf's answer i switched to JFrame, when frame is in focus and pop up menu is showing it disappears when you click on another window. but if the window does not have focus and you click somewhere menu does not disappear.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
class test {
static class window extends JWindow
implements MouseListener, MouseMotionListener{
JPopupMenu popMenu;
JPanel panel = new JPanel();
Point location;
MouseEvent pressed;
public window(){
addMouseListener( this );
addMouseMotionListener( this );
JLabel label = new JLabel("JWindow", JLabel.CENTER);
initPopMenu();
add(label);
setVisible(true);
setAlwaysOnTop(true);
setLocationRelativeTo(null);
pack();
}
public void initPopMenu(){
popMenu = new JPopupMenu();
JMenuItem item;
item = new JMenuItem( "Title" );
item.setEnabled(false);
popMenu.add(item);
popMenu.addSeparator();
item = new JMenuItem( "Item One" );
popMenu.add(item);
item = new JMenuItem( "Item 2" );
popMenu.add(item);
item = new JMenuItem( "Item 3" );
popMenu.add(item);
}
public void mousePressed(MouseEvent e)
{
pressed = e;
int nModifier = e.getModifiers();
if (((nModifier & InputEvent.BUTTON2_MASK) != 0)||
((nModifier & InputEvent.BUTTON3_MASK) != 0))
popMenu.show( this, e.getX(), e.getY() );
}
public void mouseClicked(MouseEvent e) {
}
public void mouseReleased(MouseEvent e) {}
public void mouseDragged(MouseEvent me){
}
public void mouseMoved(MouseEvent e) {}
public void mouseEntered(MouseEvent e) {}
public void mouseExited(MouseEvent e) {}
}
public static void main(String[] args) {
window dw = new window();
}
}
Take a look at the Java Doc for JWindow.isFocusableWindow
A JWindow cannot be the focused window unless it has an owner and the owner is visible.
You're using the default constructor, so your JWindow has the shared owner asn is not focusable. When it is not focusable, it cannot detect the loss of focus when you click somewhere else.
I changed JWindow to JFrame and added a call to setUndecorated(true); before the call to setVisible and it's working for me. If these changes do not make it work for you, please post the version of Java you are using: java -fullversion
In Java 6 on Windows, I cannot get the popup to even display with the code you have provided. On the other hand, if I change your superclass to JFrame, it works as desired, with the popup going away when I click outside of the window. Is there a reason why you are using JWindow as your superclass and not JFrame? If you wish to have a border-less/title-less window, you can call setUndecorated(true) on your JFrame (before you set visible and pack, of course.)
What about hiding the menu if it's visible from within the MouseExited method?