2

I'm currently working on a project that requires the state of a JRadioButton to change when the record being viewed is updated.

We've had a few clients complain to us that when the record changes, if the JRadioButton is off-screen, it won't be updated until the screen is shown. This behavior seems to be a result of using the Windows Look and Feel, as it doesn't seem to happen when it is not set.

The code example below demonstrates this. The default selected JRadioButton is 'Cat', so by selecting the 'Dog' JButton and then changing tab to 'Question', you can see the JRadioButton transition occur.

import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JTabbedPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;

/**
 * Example of JRadioButton not updating until it's parent panel becomes visible.
 */
public class RadioButtonExample extends JPanel implements ActionListener {
    
    public static final String CAT  = "Cat";
    public static final String DOG  = "Dog";
    
    private final JRadioButton radCat;
    private final JRadioButton radDog;
    private final ButtonGroup grpAnimal;
    
    public RadioButtonExample() {
        super(new BorderLayout());
        
        JLabel lblQuestion = new JLabel("Are you a cat or dog person?");
        
        radCat = new JRadioButton(CAT);
        radCat.setActionCommand(CAT);
        radCat.setSelected(true);
        
        radDog = new JRadioButton(DOG);
        radDog.setActionCommand(DOG);
        
        grpAnimal = new ButtonGroup();
        grpAnimal.add(radCat);
        grpAnimal.add(radDog);
        
        JPanel pnlQuestion = new JPanel(new GridLayout(0, 1));
        pnlQuestion.add(lblQuestion);
        pnlQuestion.add(radCat);
        pnlQuestion.add(radDog);
        
        JButton btnSetCat = new JButton(CAT);
        btnSetCat.setActionCommand(CAT);
        btnSetCat.addActionListener(this);
        
        JButton btnSetDog = new JButton(DOG);
        btnSetDog.setActionCommand(DOG);
        btnSetDog.addActionListener(this);
        
        JPanel pnlButtons = new JPanel(new GridLayout(0, 1));
        pnlButtons.add(new JLabel("Update your choice of animal"));
        pnlButtons.add(btnSetCat);
        pnlButtons.add(btnSetDog);
        
        JTabbedPane tabPane = new JTabbedPane();
        tabPane.addTab("Buttons", pnlButtons);
        tabPane.addTab("Question", pnlQuestion);
        
        add(tabPane, BorderLayout.LINE_START);
        setBorder(BorderFactory.createEmptyBorder(20,20,20,20));
    }
    
    public void actionPerformed(ActionEvent evt) {
        SwingUtilities.invokeLater(new Runnable(){
            public void run() {
                grpAnimal.clearSelection();
                if (CAT.equals(evt.getActionCommand())) {
                    grpAnimal.setSelected(radCat.getModel(), true);
                }
                else if (DOG.equals(evt.getActionCommand())) {
                    grpAnimal.setSelected(radDog.getModel(), true);
                }
            }
        });
        
    }
    
    /**
     * Create the GUI and show it.  For thread safety,
     * this method should be invoked from the
     * event-dispatching thread.
     */
    private static void createAndShowGUI() {
        //Create and set up the window.
        JFrame frame = new JFrame("RadioButtonExample");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 
        //Create and set up the content pane.
        JComponent newContentPane = new RadioButtonExample();
        newContentPane.setOpaque(true);
        frame.setContentPane(newContentPane);
 
        //Display the window.
        frame.pack();
        frame.setVisible(true);
    }
    
    public static void main(String[] args) {
        // Comment out the line below to run using standard L&F
        setLookAndFeel();
        
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }
    
    private static void setLookAndFeel() {
        try {
            // Set Windows L&F
            UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
        } 
        catch (Exception e) {
           // handle exception
        }
    }
}

Is there a way to prevent this behavior or even speed it up to make it less noticeable for our users?

TylerH
  • 20,799
  • 66
  • 75
  • 101
Kuronai
  • 23
  • 1
  • 4
  • Observation: Only the selected component is visible (all other tab components are "hidden" (`setVisible(false)`). This means that they won't respond to paint requests, nice and efficient. This does, however mean, that when they are made visible, they must repaint there state, in this case, going from been selected to unselected and visa versa - that seems to be the problem, the solution might be a little complicated – MadProgrammer Nov 26 '15 at 01:06
  • I'd assumed that this might have been the case, but was unsure as the standard Look and Feel does not exhibit the same behavior... possibly because the Windows 'feel' has a transition effect opposed to the standard. – Kuronai Nov 26 '15 at 01:24
  • Windows L&F is the only one (that I can find) which has the "deselect" animation, this is actually a side effect of this issue. So when the component is made visible, the radio buttons are rendered from there previous states to there new states, but because of the animation in Windows L&F, it's really obvious of the state change – MadProgrammer Nov 26 '15 at 01:32
  • I've trying to dig through the Windows L&F code looking to see if there was some kind of flag, but all the functionality surrounding the animation is private :P – MadProgrammer Nov 26 '15 at 01:42
  • I've been doing the same without much luck. Suppressing the animation when the change occurs would probably be enough to satisfy the clients though. – Kuronai Nov 26 '15 at 02:04
  • The "idea" I had (I've not tested this), is basically, have a custom class which represents you form. When the user moves away from the tab, you remove the current "form" from the tab (placing inside another proxy `JPanel` for example). When the use moves to that tab, you create a new instance of the "form" and place within the proxy `JPanel` ... seems like a lot of work to overcome this simple visual glitch :P – MadProgrammer Nov 26 '15 at 02:27

1 Answers1

0

You might be able to disable the animation by specifying the swing.disablevistaanimation Java system property:

java -Dswing.disablevistaanimation="true" your-cool-application.jar

In the com.sun.java.swing.plaf.windows.AnimationController class, there is a VISTA_ANIMATION_DISABLED field that is initialized to the swing.disablevistaanimation property. This field determines whether the paintSkin method calls the skin.paintSkinRaw method if animations are disabled or else starts to get into the animations.

It works on my Windows 8.1 laptop with Java 8 (jdk1.8.0_65), so its effect does not seem to be limited to Windows Vista only.

Freek de Bruijn
  • 3,552
  • 2
  • 22
  • 28
  • I've accepted this as the answer as this is what we ended up getting the affected clients to do, as it looked as though the effort required to suppress the animations was going to be too great to justify (thanks to MadProgrammer for his help with that too). – Kuronai Dec 08 '15 at 23:03