JDK-6308815 : JColorChooser not returning selected color from showDialog in separate Thread
  • Type: Bug
  • Component: client-libs
  • Sub-Component: javax.swing
  • Affected Version: 5.0
  • Priority: P4
  • Status: Resolved
  • Resolution: Fixed
  • OS: windows_xp
  • CPU: x86
  • Submitted: 2005-08-10
  • Updated: 2010-04-02
  • Resolved: 2005-08-31
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 6
6 b50Fixed
Related Reports
Relates :  
Description
FULL PRODUCT VERSION :
Running on: jre1.5.0_04

ADDITIONAL OS VERSION INFORMATION :
Windows XP Pro (SP 2)

A DESCRIPTION OF THE PROBLEM :
When developing a Swing application I came to a point in my program where there needed to be a lot of processing before coloring the cells of a JTable.  I decided to put this processing code in its own thread so I could maintain visability in the JFrame.  In the new thread, one portion of the code asks the user to select a color from a JColorChooser.  When I developed this program in Oracle's JDeveloper, the program runs fine, however when I throw the classes into an executable jar file to be run either locally or through a web browser as an applet I get an issue with the JColorChooser returning 'null' from the showDialog method.  In my original program, very rarely does it actually return a color; however, in my test program, it will return colors more often, but still randomly returning 'null', even though a color has been selected and the "OK" button is being pressed.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Create a small swing application.  I used an application provided from the tutorial section of Sun's website for using JColorChoosers titled ColorChooserDemo2 with the CrayonPanel removed.  Essentially you need only two classes, one class to hold the code for the new thread, and one to run the swing application.  Within the swing application start the new thread and have it popup a JColorChooser using JColorChooser.showDialog(...).  Check that the dialog actually returns the color you selected and not 'null' if the "OK" button is pressed.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The program should work the same as it normally would if the JColorChooser were called from within the event-dispatching thread and return any color selected from the dialog.
ACTUAL -
Randomly, the program will error.  Sometimes it will work just fine and return your selected color, and other times it will just return 'null.'  The problem does not seem to revolve around any particular color selected.  It seems like the problem may be stemming from the JDialog being destroyed before a color is returned.

ERROR MESSAGES/STACK TRACES THAT OCCUR :
No specific errors are thrown, just a 'null' value returned.

REPRODUCIBILITY :
This bug can be reproduced often.

---------- BEGIN SOURCE ----------
//Test Class 1
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.colorchooser.*;

public class Test extends JPanel
                               implements ActionListener,
                                          ChangeListener
{
    public JLabel banner;
    public JColorChooser tcc;

    public Test()
    {
        super(new BorderLayout());

        //Set up banner to use as custom preview panel
        banner = new JLabel("Welcome to the Tutorial Zone!",
                            JLabel.CENTER);
        banner.setForeground(Color.yellow);
        banner.setBackground(Color.blue);
        banner.setOpaque(true);
        banner.setFont(new Font("SansSerif", Font.BOLD, 24));
        banner.setPreferredSize(new Dimension(100, 65));
        JPanel bannerPanel = new JPanel(new BorderLayout());
        bannerPanel.add(banner, BorderLayout.CENTER);
        bannerPanel.setBorder(BorderFactory.createTitledBorder("Banner"));

        //Set up color chooser for setting background color
        JPanel panel = new JPanel(); //use FlowLayout
        JButton bcc = new JButton("Show Color Chooser...");
        bcc.addActionListener(this);
        panel.add(bcc);
        panel.setBorder(BorderFactory.createTitledBorder(
                                "Choose Background Color"));

        //Set up color chooser for setting text color
        tcc = new JColorChooser();
        tcc.getSelectionModel().addChangeListener(this);
        tcc.setBorder(BorderFactory.createTitledBorder("Choose Text Color"));

        //Remove the preview panel
        tcc.setPreviewPanel(new JPanel());

        //Override the chooser panels with our own
        AbstractColorChooserPanel panels[] = { };
        tcc.setChooserPanels(panels);
        tcc.setColor(banner.getForeground());

        add(bannerPanel, BorderLayout.PAGE_START);
        add(panel, BorderLayout.CENTER);
        add(tcc, BorderLayout.PAGE_END);
    }
    
    public void setColor(Color newColor)
    {
      if (newColor != null)
      {
            banner.setBackground(newColor);
      }
    }


    public void actionPerformed(ActionEvent e)
    {
    
      ColorChooserDemo2 thread = new ColorChooserDemo2(this, banner.getBackground());
      Thread th = new Thread(thread);
      th.start();
    }

    public void stateChanged(ChangeEvent e) {
        Color newColor = tcc.getColor();
        banner.setForeground(newColor);
    }

    /**
     * Create the GUI and show it.  For thread safety,
     * this method should be invoked from the
     * event-dispatching thread.
     */
    private static void createAndShowGUI() {
        //Make sure we have nice window decorations.
        JFrame.setDefaultLookAndFeelDecorated(true);

        //Create and set up the window.
        JFrame frame = new JFrame("ColorChooserDemo2");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        //Create and set up the content pane.
        JComponent newContentPane = new Test();
        newContentPane.setOpaque(true); //content panes must be opaque
        frame.setContentPane(newContentPane);

        //Display the window.
        frame.pack();
        frame.setVisible(true);
    }

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

//Test Class 2
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.colorchooser.*;

public class ColorChooserDemo2 implements Runnable
{    
    private JColorChooser chooser;
    
    private Test parent;
    private Color defaultCol;

    public ColorChooserDemo2(Test prnt, Color bg)
    {
        parent = prnt;
        defaultCol = bg;
    }

    public void run()
    {
        Color newColor = JColorChooser.showDialog(
                                       parent,
                                       "Choose Background Color",
                                       defaultCol);
        if (newColor != null)
        {
            parent.setColor(newColor);
        }
        else
          JOptionPane.showMessageDialog(parent, "Null again.");
    }
}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Modify the color chooser thread class to use the createDialog(...) method and be sure to get the color before disposing the dialog.  Here is the code:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.colorchooser.*;

public class ColorChooserDemo2 implements Runnable, ChangeListener, ActionListener
{
    private JDialog dialog;
    private JColorChooser chooser;
    
    private Test parent;
    private Color defaultCol;

    public ColorChooserDemo2(Test prnt, Color bg)
    {
        parent = prnt;
        defaultCol = bg;
    }

    public void run()
    {
      createColorChooser();
    }
    
    private void createColorChooser()
    {
      chooser = new JColorChooser();
      dialog = JColorChooser.createDialog(parent, "Color Test", true, chooser, this, this);
      chooser.setColor(defaultCol);
      dialog.setVisible(true);
      Color color = chooser.getColor();
      dialog.dispose();
      if(color != null)
        parent.setColor(color);
      else
        JOptionPane.showMessageDialog(parent, "Null again.");
    }
    
    public void stateChanged(ChangeEvent evt)
    {  // Do Nothing, let Dialog handle all events at higher level
    }
    
    public void actionPerformed(ActionEvent evt)
    {  // Do Nothing, let Dialog handle all events at higher level
    }

}

Comments
EVALUATION First, a disclaimer: Swing components do not support being used on threads other than the event dispatch thread. Normally we would close out a bug like this as "Will Not Fix" since it is clearly doing something that we explicitly claim not to support. That being said, the fix is rather simple and would make this developer's life easier. As such, I'm going to provide a fix. The root cause of the problem is the ordering in which the internal color variable is set vs. when the caller gets unblocked from the hide() call. Internally, when the OK button is pressed, the code calls hide() and then sets this variable. When the JColorChooser is shown from the EDT, as it should be, even though the hide() is called first, AWT's modal logic is structured such that both happen before control is returned to the caller. When the JColorChooser is shown from another thread, AWT has different logic such that the hide() immediately returns control to the caller. As such, there is a race condition where sometimes the color is set and others it is null. Just one of the things that can be expected to happen when using Swing components off of the EDT. In any case, as AWT does support showing dialogs on other threads, a new bug is being filed on this inconsistency. I'll post the number here once it's been filed. To fix the problem in Swing, I'll simply ensure that the call to set the color variable occurs before hide().
10-08-2005