malaccan
malaccan

Reputation: 101

handling state and event sequences of JDialog and JToggleButton

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);
    }
}

Upvotes: 3

Views: 286

Answers (1)

Hovercraft Full Of Eels
Hovercraft Full Of Eels

Reputation: 285405

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);
    }
}

Upvotes: 1

Related Questions