6

I'm trying to prevent the user from changing a tab when the current tab is not valid. So when he clicks on a tab, I want to check if the current one is "valid", and if not, stay on the current tab. I tried to use a VetoableChangeListener which didn't work, the code never goes inside the vetoableChange method:

jTabbedPane.addVetoableChangeListener(new VetoableChangeListener() {

  @Override
  public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException {
    if (!isCurrentTabValid()) {
      throw new PropertyVetoException("test", evt);
    }
  }
});

How can I do this properly?

Thanks!

Nanocom
  • 3,696
  • 4
  • 31
  • 46

3 Answers3

16

A VetoableChangeListener is useful only if the class it is registered with fires a vetoable propertyChange. Most (all? never came across one) properties on JComponents and subclasses are not vetoable. Plus the selection is handled by a SingleSelectionModel, not the component itself.

That model is the hook to support vetoable changes

  • implement a custom model that fires a vetoablePropertyChange on selection change
  • if none of its listeners object, go ahead with the change, otherwise do nothing
  • set the custom model to the tabbedPane
  • implement a VetoablePropertyChangeListener which contains the validation logic
  • register the vetoableListener to the model

in code, something like

public static class VetoableSingleSelectionModel extends
        DefaultSingleSelectionModel {

    private VetoableChangeSupport vetoableChangeSupport;

    @Override
    public void setSelectedIndex(int index) {
        if (getSelectedIndex() == index)
            return;
        try {
            fireVetoableChange(getSelectedIndex(), index);
        } catch (PropertyVetoException e) {
            return;
        }
        super.setSelectedIndex(index);
    }

    private void fireVetoableChange(int oldSelectionIndex,
            int newSelectionIndex) throws PropertyVetoException {
        if (!isVetoable())
            return;
        vetoableChangeSupport.fireVetoableChange("selectedIndex",
                oldSelectionIndex, newSelectionIndex);

    }

    private boolean isVetoable() {
        if (vetoableChangeSupport == null)
            return false;
        return vetoableChangeSupport.hasListeners(null);
    }

    public void addVetoableChangeListener(VetoableChangeListener l) {
        if (vetoableChangeSupport == null) {
            vetoableChangeSupport = new VetoableChangeSupport(this);
        }
        vetoableChangeSupport.addVetoableChangeListener(l);
    }

    public void removeVetoableChangeListener(VetoableChangeListener l) {
        if (vetoableChangeSupport == null)
            return;
        vetoableChangeSupport.removeVetoableChangeListener(l);
    }

}

// usage
JTabbedPane pane = new JTabbedPane();
VetoableSingleSelectionModel model = new VetoableSingleSelectionModel();
VetoableChangeListener validator = new VetoableChangeListener() {

    @Override
    public void vetoableChange(PropertyChangeEvent evt)
            throws PropertyVetoException {
        int oldSelection = (int) evt.getOldValue();
        if ((oldSelection == -1) || isValidTab(oldSelection)) return;

        throw new PropertyVetoException("change not valid", evt);

    }

    private boolean isValidTab(int oldSelection) {
        // implement your validation logic here
        return false;
    }
};
model.addVetoableChangeListener(validator);
pane.setModel(model);
pane.addTab("one", new JLabel("here we are and stay"));
pane.addTab("other", new JLabel("poor me, never shown"));
kleopatra
  • 51,061
  • 28
  • 99
  • 211
  • This works great for tab change, but how do you veto tab closing? `vetoableChange` gets invoked after the tab is closed and when the previous tab gets selected. I searched the internet for vetoableClose, but no hits. – Alex Burdusel Feb 28 '14 at 18:58
  • sounds unrelated/extended - please post a question with a SSCCE – kleopatra Feb 28 '14 at 22:58
  • It's ok, I solved it. I have a button component added on each tab to close them. It invokes `JTabbedPane.remove(tabIndex)`. I managed to work around it. I am selecting the previous tab before closing the current one so the `valueChanged` method gets invoked. – Alex Burdusel Mar 01 '14 at 16:15
1

Sounds like you want to disable the tab first. Then when the current page is valid enable the tab. Also sounds like you may want to consider a CardLayout instead of tabs. Then use a "Next" or "Continue" button when the current page is valid.

km1
  • 2,383
  • 1
  • 22
  • 27
  • 1
    Disabling the second tab may be not intuitive for the user. But card layout can do the job. Don't forget to add some tips for the user to see why he can't proceed to the next screen. – svz Sep 12 '12 at 14:15
  • I don't want to disable any tab. It would mean I need to check that the tab is valid after each action made by the user, which is not possible. – Nanocom Sep 12 '12 at 14:55
0

It looks like vetoableChange is part of the java.beans package. Try adding a javax.swing.event.ChangeListener.

bodyTabbedPane.addChangeListener(new javax.swing.event.ChangeListener() {
        public void stateChanged(javax.swing.event.ChangeEvent evt) {
            bodyTabbedPaneStateChanged(evt);
        }
    });


private void bodyTabbedPaneStateChanged(javax.swing.event.ChangeEvent evt) {
    if (!isCurrentTabValid()) {             
         throw new PropertyVetoException("test", evt);           
     }
}
kevin_s
  • 41
  • 1
  • 4
    guess you didn't you try that ;-) At the moment of the selection change notification the selection already _is_ changed, so that the only thingy you would be able to do is to revert the change - but the baby already is dead after falling into the well .. – kleopatra Sep 12 '12 at 14:03