I'd like to create a group of two buttons (A and B), where switching from B to A is always allowed, but switching from A to B is dependent on a condition. The condition may only be checked in the latter case (A to B) and the check may only be done once per switch attempt. If the switch is prevented, there must be no ItemEvent.SELECTED events generated for either of the buttons.

Seems pretty straightforward, so I am baffled as to why I haven't been able to do this in a simple and concise way. I thought extending ButtonGroup was the way to do this but now I'm not sure anymore.

import java.awt.FlowLayout;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import javax.swing.ButtonGroup;
import javax.swing.ButtonModel;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JToggleButton;
import javax.swing.SwingUtilities;

public class ToggleGroup extends JFrame {

    private ButtonGroup group = new MyButtonGroup();
    private JToggleButton buttonA = new JToggleButton("A");
    private JToggleButton buttonB = new JToggleButton("B");

    public ToggleGroup() {
        setLayout(new FlowLayout());
        add(buttonA);
        add(buttonB);
        group.add(buttonA);
        group.add(buttonB);
        group.setSelected(buttonA.getModel(), true);
        pack();
        setLocationRelativeTo(null);

        ItemListener itemListener = new ItemListener() {
            public void itemStateChanged(ItemEvent e) {                
                if (e.getStateChange() == ItemEvent.SELECTED) {
                    System.out.println("-> " + (e.getSource() == buttonA ? "A" : "B") + " selected");
                }
            }
        };
        buttonA.addItemListener(itemListener);
        buttonB.addItemListener(itemListener);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                ToggleGroup test = new ToggleGroup();
                test.setVisible(true);
            }
        });
    }

    private class MyButtonGroup extends ButtonGroup {

        private boolean check() {
            int result = JOptionPane.showConfirmDialog(
                    ToggleGroup.this, "May I switch to B?",
                    "Important question", JOptionPane.YES_NO_OPTION,
                    JOptionPane.WARNING_MESSAGE);
            return result == JOptionPane.YES_OPTION;
        }

        @Override
        public void setSelected(ButtonModel m, boolean b) {      
            if (!b) {
                return;
            }
            if (m == buttonA.getModel() || m == buttonB.getModel() && check()) {
                super.setSelected(m, b);
            }
        }

    }
}

The problem with my code is obvious. The condition is checked multiple times, therefore the dialog is also shown multiple times.

So how can I "consume" a switch attempt when the condition fails?

EDIT:

The context in which I'm using these buttons is an implementation of switching between different modes of an application. One of the modes enables data to be changed and later committed. If uncommitted changes exist, switching modes might imply data loss. I'd like to make sure that the switch was intentional. Disabling either of the buttons until the condition is met is not an option.

有帮助吗?

解决方案

Interesting ... digging a bit turns out that the interaction of selected/armed/pressed is somewhat confused by the interrupted check (faintly remember some bug, but can't find it right now). The main issue is to not allow the re-entry into the setSelected method. A dirty (read: didn't to dig further) way out is to have toggle a flag, something like

private boolean isChecking;
@Override
public void setSelected(final ButtonModel m, boolean b) {   
    if (isChecking) return;
    isChecking = false;
    if (!b) {
        return;
    }
    if (m == buttonB.getModel()) {
        isChecking = true;
        final boolean select = check();
        if (select) {
            superSetSelected(m, select);
        }
        isChecking = false;
        return;
    } else {
        superSetSelected(m, b);
    }
}

protected void superSetSelected(ButtonModel m, boolean b) {
    super.setSelected(m, b);
}

其他提示

After consulting the implementation of ButtonGroup I realized that calling ButtonGroup.setSelected(ButtonModel, boolean) might spawn new calls of the same method, leading to my condition being checked up to three times. So I now guard against subsequent calls. Seems to work. A little iffy, though.

private class MyButtonGroup extends ButtonGroup {

    private Stack<ButtonModel> locked = new Stack<ButtonModel>();

    @Override
    public void setSelected(ButtonModel m, boolean b) {      
        if (!b || !locked.isEmpty() && locked.peek() == m) {
            return;
        }
        locked.push(m);
        if (m == buttonA.getModel() || m == buttonB.getModel() && check()) {
            super.setSelected(m, b);
        }
        locked.pop();
    }

}
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top