3

I am trying to build a JToggleButton that displays a JDialog (that contains a JList) when the JToggleButton is pressed. And have the JDialog disappear when the JToggleButton is pressed again, OR if the user clicks away or somewhere else in the frame (I simulated this via a FocusListener on the JList when the focus is lost).

Pressing the button in sequence will display and hide the JDialog correctly.

However, the problem is when the JDialog is visible, and I click somewhere else on the frame, the JDialog correctly disappears as the focus is lost. However, the state of the JToggleButton remains incorrectly set as selected. This meant that clicking on the JToggleButton now will not display the JDialog as the state of the JToggleButton is now out of sync. Instead, I will need to press the JToggleButton twice to get the JDialog visible again. My code example below demonstrates this issue.

I can't seem to get the lost focus of the JList to sync up with the state of the JToggleButton. It seems like a straightforward problem, but am stuck trying to find a solution. Can anyone help? Thanks.

See my code below:

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;

public class MultiComboBox extends JToggleButton
{
    public MultiComboBox(JFrame frame, String buttonText)
    {
        super(buttonText);

        JDialog dialog = new JDialog(frame, false);
        dialog.setLayout(new BorderLayout());

        Object[] items = new Object[] { "one", "two", "three" };
        JList list = new JList(items);
        list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

        JScrollPane listScrollPane = new JScrollPane(list,
            JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
            JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        listScrollPane.setPreferredSize(list.getPreferredSize());

        dialog.add(listScrollPane, BorderLayout.CENTER);
        dialog.pack();

        addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                final JToggleButton button = (JToggleButton) e.getSource();
                System.out.println("button clicked: " + button.isSelected());
                if (button.isSelected())
                {
                    Point p = button.getLocation();
                    p.setLocation(p.getX() + 300, p.getY());
                    SwingUtilities.convertPointToScreen(p, button);
                    dialog.setLocation(p);
                    dialog.setVisible(true);
                }
                else
                    dialog.setVisible(false);
            }
        });

        list.addFocusListener(new FocusListener()
        {
            @Override
            public void focusGained(FocusEvent e)
            {
            }

            @Override
            public void focusLost(FocusEvent e)
            {
                System.out.println("list focusLost, dialog: " + dialog.isVisible());
                dialog.setVisible(false);
            }
        });
    }

    public static void main(String[] args)
    {
        JFrame frame = new JFrame("Test");
        frame.setPreferredSize(new Dimension(300, 300));
        frame.setLayout(new BorderLayout());

        MultiComboBox mcb = new MultiComboBox(frame, "Toggle");

        JPanel buttonPanel = new JPanel(new BorderLayout());
        buttonPanel.setPreferredSize(new Dimension(80, 30));
        buttonPanel.add(mcb, BorderLayout.CENTER);

        JPanel blankPanel = new JPanel(new BorderLayout());
        frame.add(blankPanel, BorderLayout.CENTER);

        frame.add(buttonPanel, BorderLayout.PAGE_START);
        frame.pack();
        frame.setVisible(true);
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    }
}
malaccan
  • 61
  • 4

1 Answers1

1

Suggestions:

  • Don't add an ActionListener to the JToggleButton
  • but instead add an ItemListener. This will respond to changes in the toggle's selection state
  • Inside this listener, change the dialog's visible state.
  • In your FocusListener, don't change the dialog's visible state but rather change the toggle's selection state.
  • Use a WindowFocusListener added to the JDialog itself to be notified if it loses focus. This way the listener code can be outside of the code for the dialog components, a cleaner OOPs solution.

For example:

import javax.swing.*;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;

public class MultiComboBox2 extends JToggleButton {
    public MultiComboBox2(JFrame frame, String buttonText) {
        super(buttonText);

        JDialog dialog = new JDialog(frame, false);
        dialog.setLayout(new BorderLayout());

        Object[] items = new Object[] { "one", "two", "three" };
        JList list = new JList(items);
        list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

        JScrollPane listScrollPane = new JScrollPane(list, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
                JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        listScrollPane.setPreferredSize(list.getPreferredSize());

        dialog.add(listScrollPane, BorderLayout.CENTER);
        dialog.pack();

        addItemListener(new ItemListener() {

            @Override
            public void itemStateChanged(ItemEvent e) {
                final JToggleButton button = (JToggleButton) e.getSource();
                if (e.getStateChange() == ItemEvent.SELECTED) {
                    Point p = button.getLocation();
                    p.setLocation(p.getX() + 300, p.getY());
                    SwingUtilities.convertPointToScreen(p, button);
                    dialog.setLocation(p);
                    dialog.setVisible(true);
                } else {
                    dialog.setVisible(false);
                }
            }
        });
        // addActionListener(new ActionListener() {
        // @Override
        // public void actionPerformed(ActionEvent e) {
        // final JToggleButton button = (JToggleButton) e.getSource();
        // System.out.println("button clicked: " + button.isSelected());
        // if (button.isSelected()) {
        // Point p = button.getLocation();
        // p.setLocation(p.getX() + 300, p.getY());
        // SwingUtilities.convertPointToScreen(p, button);
        // dialog.setLocation(p);
        // dialog.setVisible(true);
        // } else
        // dialog.setVisible(false);
        // }
        // });

        list.addFocusListener(new FocusListener() {
            @Override
            public void focusGained(FocusEvent e) {
            }

            @Override
            public void focusLost(FocusEvent e) {
                System.out.println("list focusLost, dialog: " + dialog.isVisible());
                // dialog.setVisible(false);
                MultiComboBox2.this.setSelected(false);
            }
        });
    }

    public static void main(String[] args) {
        JFrame frame = new JFrame("Test");
        frame.setPreferredSize(new Dimension(300, 300));
        frame.setLayout(new BorderLayout());

        MultiComboBox2 mcb = new MultiComboBox2(frame, "Toggle");

        JPanel buttonPanel = new JPanel(new BorderLayout());
        buttonPanel.setPreferredSize(new Dimension(80, 30));
        buttonPanel.add(mcb, BorderLayout.CENTER);

        JPanel blankPanel = new JPanel(new BorderLayout());
        frame.add(blankPanel, BorderLayout.CENTER);

        frame.add(buttonPanel, BorderLayout.PAGE_START);
        frame.pack();
        frame.setVisible(true);
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    }
}
Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373
  • Thanks for the suggestions but unfortunately this still is an issue. Using your sample code, the `JDialog` will disappear once the focus is lost, but it introduces a new problem where clicking on the `JToggleButton` now makes the `JDialog` visible all the time; instead of the previous behaviour where the `JDialog` appears and disappears as the `JToggleButton` is selected/unselected each time. – malaccan Apr 16 '16 at 14:18