Block change action on controls

I have a form asking for some details for a user. There are two controls at the top of this form: JSpinner and JToggleButton.

If the user is using JSpinner, he can select the form number from 1 to 4, if he clicks on the JToggleButton, the counter is disabled, the form number is displayed 0 (if this button is toggled back, the counter is on back and the form loads with the number in the counter).

So far, no problem.

But now I would like to display a popup when the form is edited, not saved and that the user is using one of the two controls.

No problem for the popup, but I don't know how to undo the modification to the control that triggered the popup display.

Since I am using a ChangeListener for the counter and an ActionListener for the button, I am showing the popup AFTER modifying the control.

I'm actually looking for a way to notify the action on the controls, but with the ability to stop the modification (something like a notification listener where we need to return true or false to check for the modification).

How do you do it?

Thank.

Here's an example:

package test;

import java.awt.BorderLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.JToggleButton;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class SwingTest extends JFrame
{
    private static final long   serialVersionUID    = 1L;

    private JToggleButton       btnRecueilPermanent;
    private JSpinner            spinner;
    private JTextField          tf;
    private boolean             formChanged         = false;

    public SwingTest()
    {
        super();

        setLayout(new GridBagLayout());
        initComponents();

        loadForm(1);
    }

    private void initComponents()
    {
        JPanel panelGeneral = new JPanel();
        GridBagConstraints gbc_panelGeneral = new GridBagConstraints();
        gbc_panelGeneral.fill = GridBagConstraints.BOTH;
        gbc_panelGeneral.anchor = java.awt.GridBagConstraints.CENTER;
        gbc_panelGeneral.weightx = 100.0;
        gbc_panelGeneral.weighty = 100.0;
        gbc_panelGeneral.gridx = 0;
        gbc_panelGeneral.gridy = 0;
        add(panelGeneral, gbc_panelGeneral);
        panelGeneral.setLayout(new BorderLayout(0, 0));

        JPanel panelSelecteur = new JPanel();
        panelGeneral.add(panelSelecteur, BorderLayout.NORTH);

        JLabel lblChoixDuFormulaire = new JLabel("Choose form :");
        panelSelecteur.add(lblChoixDuFormulaire);

        spinner = new JSpinner(new SpinnerNumberModel(1, 1, 4, 1));
        spinner.addChangeListener(new ChangeListener()
        {
            public void stateChanged(final ChangeEvent e)
            {
                loadForm((Integer) spinner.getValue());
            }
        });
        panelSelecteur.add(spinner);

        btnRecueilPermanent = new JToggleButton("Permanent form");
        btnRecueilPermanent.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                if(btnRecueilPermanent.isSelected())
                {
                    loadForm(0);
                }
                else
                {
                    loadForm((Integer) spinner.getValue());
                }
            }
        });
        panelSelecteur.add(btnRecueilPermanent);

        final JPanel formPanel = new JPanel();
        tf = new JTextField(20);
        tf.addKeyListener(new KeyListener()
        {
            @Override
            public void keyTyped(KeyEvent e)
            {
                formChanged = true;
            }

            @Override
            public void keyPressed(KeyEvent e)
            {
            }

            @Override
            public void keyReleased(KeyEvent e)
            {
            }
        });
        formPanel.add(tf);
        panelGeneral.add(formPanel, BorderLayout.CENTER);
    }

    protected void loadForm(final int nbForm)
    {
        if(formChanged)
        {
            Object[] options =
            {
                "Continue", "Discard changes"
            };

            final int result = JOptionPane.showOptionDialog(this, "You have unsaved modifivations", "Beware !", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null, options, options[0]);
            if(result == 0)
            {
                // HERE WE DISCARD THE FORM CHANGE, BUT THE TOGGLEBUTTON or THE JSPINNER HAVED CHANGED
                return;
            }
        }

        if(nbForm == 0)
        {
            btnRecueilPermanent.setText("Normal form");
        }
        else
        {
            btnRecueilPermanent.setText("Permanent form");
        }

        tf.setText(String.valueOf(nbForm));

        spinner.setEnabled(nbForm != 0);

        formChanged = false;
    }

    public static void main(String[] args)
    {
        final SwingTest jf = new SwingTest();

        jf.pack();
        jf.setVisible(true);
    }
}

      

+3


source to share


1 answer


I'm actually looking for a way to know about the action on the controls, but with the option to stop the modification (something like a notification listener where we need to return true or false to confirm the modification).

Note: thanks to @mKorbel's comment / feedback below I fully understood your problem.

Let's start with JSpinner . As suggested, you can use PropertyChangeListener and / or PropertyChangeSupport to listen for changes to value properties. Actually I would say that the simplest / best approach actually attaches PropertyChangeListener

to a text field that is part of the spinner editor, just because that component can be updated either by clicking the up / down buttons or manually entering the value, and updated (by contract ) by model change events.

That being said, please consider this snippet:

final JSpinner spinner = new JSpinner(new SpinnerNumberModel(1, 1, 4, 1));
JSpinner.NumberEditor editor = (JSpinner.NumberEditor) spinner.getEditor();
JFormattedTextField textField = editor.getTextField();
textField.addPropertyChangeListener("value", new PropertyChangeListener() {...});

      

Now you need a mechanism to store the last valid rollback value if the user regrets the issue (i.e. a simple fallback variable with enough scope to be accessed from propertyChange (PropertyChangeEvent evt) ). In addition, you have old and new values ​​available through the PropertyChangeEvent API to check and check if the new value is exactly the same value that was stored in the fallback variable. See the example below for a better understanding.

Now focus on the JToggleButton . In this case, I replaced the ActionListener with an ItemListener to count with the new button state (selected / deselected ) directly from the ItemEvent . Something like that:

final JToggleButton button = new JToggleButton("Toggle");
button.addItemListener(new ItemListener() {...});

      

Once again, you will need a mechanism to keep the last valid value of this toggle button and be able to rollback user changes if they think they are inappropriate. Beware of element changes being triggered twice: the first time to notify the old state and the second time to notify the new state. Therefore, you will have to ignore the first call just because it is the most current state. See the example below for a better understanding.

Example

Try this complete example. I think it suits your requirements.



import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.JToggleButton;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingUtilities;

/**
 * @author dic19
 */
public class Demo {

    private Object backupSpinnerValue;
    private Boolean backupButtonState;

    public void createAndShowGUI() {

        final JSpinner spinner = new JSpinner(new SpinnerNumberModel(1, 1, 4, 1));
        JSpinner.NumberEditor editor = (JSpinner.NumberEditor) spinner.getEditor();
        JFormattedTextField textField = editor.getTextField();
        textField.addPropertyChangeListener("value", new PropertyChangeListener() {
            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                Object oldValue = evt.getOldValue();
                Object newValue = evt.getNewValue();
                if (!evt.getNewValue().equals(backupSpinnerValue)) { // Just ignore if they're the same
                    if (Demo.this.confirmChanges(oldValue, newValue)) {
                        backupSpinnerValue = newValue;
                    } else {
                        spinner.setValue(backupSpinnerValue);
                    }
                }
            }
        });

        final JToggleButton button = new JToggleButton("Toggle");
        button.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent e) {
                Boolean isSelected = e.getStateChange() == ItemEvent.SELECTED;
                if (!isSelected.equals(backupButtonState)) { // Just ignore if they're the same
                    if (Demo.this.confirmChanges(backupButtonState, isSelected)) {
                        backupButtonState = isSelected;
                    }
                    button.setSelected(backupButtonState);
                }
            }
        });

        backupSpinnerValue = spinner.getValue();
        backupButtonState = button.isSelected();

        JPanel content = new JPanel();
        content.add(spinner);
        content.add(button);

        JFrame frame = new JFrame("Demo");
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.add(content);
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
    }

    private boolean confirmChanges(Object oldValue, Object newValue) {
        String message = String.format("Do you want to confirm changes from value '%1s' to '%2s'?", oldValue, newValue);
        int option = JOptionPane.showConfirmDialog(null, message, "Confirm changes", JOptionPane.OK_CANCEL_OPTION);
        return option == JOptionPane.OK_OPTION;
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new Demo().createAndShowGUI();
            }
        });
    }
}

      


Other comments

1) In these lines:

tf = new JTextField(20);
tf.addKeyListener(new KeyListener(){...});

      

Please note that KeyListener is not the right choice if you want to listen for a change in a text box. You have to use DocumentListener . See How to write a document listener

2) Swing components are intended to be used "as is", therefore composition is preferred over inheritance. Therefore, you should consider removing extends JFrame

in the class declaration and instead add all your components to the local frame. See also this thread: Extends a JFrame and creates it inside a program

3) Always include the default close operation in top level containers (windows):

public class SwingTest extends JFrame {
   ...
   private void initComponents() {
       ...
       setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
   }
}

      

4) If you are interested in a more elegant / complex approach than simple variables to get states to look at the Memento pattern . This template allows you to save not only one previous state, but the entire state changes history.

+1


source







All Articles