Skip to Main Content

Java SE (Java Platform, Standard Edition)

Announcement

For appeals, questions and feedback about Oracle Forums, please email oracle-forums-moderators_us@oracle.com. Technical questions should be asked in the appropriate category. Thank you!

Interested in getting your voice heard by members of the Developer Marketing team at Oracle? Check out this post for AppDev or this post for AI focus group information.

conversion from awt to Swing, colored list widget, and awt update() method

RichFOct 15 2010 — edited Oct 27 2010
Now that my Interactive Color Wheel program/applet is in Swing, I guess I should continue my previous thread in here from the AWT forum ("list widget with different colors for each list item?"):

* 1557347

My current issue involves two canvas (well, JPanel) refresh issues likely linked to double buffering. You can see them by running the following file with "java -jar SIHwheel.jar":

* http://r0k.us/rock/Junk/SIHwheel.jar

[edit add]
(Heh, I just noticed Firefox and Chrome under Windows 7 will allow you to run thie .jar directly from the link. Cool.)
[edit]

If you don't trust me and would rather run it as an applet, use:

* http://r0k.us/rock/Junk/SIHwheel.html

(For some reason the first issue doesn't manifest when running as applet.)

1) The canvas goes "wonky-white" when the user first clicks on the wheel. What is supposed to happen is simply the user sees another dot on the wheel for his new selected color. Forcing a complete redraw via any of the GUI buttons at the bottom sets things right. The canvas behaves itself from then on, at least until minimized or resized, at which point one needs to click a GUI button again. I'll be disabling resizing, but minimizing will still be allowed.

2) A button image, and sometimes toolTip text, from an entirely different JPanel will appear in the ULC (0,0) of my canvas.

Upon first running the new Swing version, I had thought everything was perfect. I soon realized though that my old AWT update() method was never getting called. The desired case when the user clicks somewhere on the wheel is that a new dot appears on his selected color. This usually allows them to see what colors have been viewed before. The old paint(), and now paintComponent(), clear the canvas, erasing all the previous dots.

I soon learned that Swing does not call update(). I had been using it to intercept refresh events where only one of the components on my canvas needing updating. Most usefully, don't redraw the wheel (and forget the dots) when you don't need to. The way I chose to handle this is to slightly modify the update() to a boolean method. I renamed it partialOnly() and call it
at the beginning of paintComponent(). If it returns true, paintComponent() itself returns, and no clearing of the canvas occurs.

Since I first posted about these two issues, I've kludged-in a fix to #1. (The linked .jar file does not contain this kludge, so you can see the issue.) The kludge is included in the following code snippet:
    public void paintComponent(Graphics g)
    {
        Rectangle ulc;

	if (font == null)  defineFont(g);

	// handle partial repaints of specific items
	if (partialOnly(g))  return;

        ...  // follow with the normal, full-canvas refresh
    }

    private boolean partialOnly(Graphics g)
    {
	boolean	imDone = true;
	if (resized > 0)  // this "if { }" clause is my kludge
	{   // should enter on 1 or 2
	    imDone = false;
	    resized += 1;	// clock thru two forced-full paints
	    if (resized > 2)  resized = 0;
	}

        if (wedgeOnly)
        {
	    putDotOnWheel(g);
            paintWedge(g);
	    drawSnake(g);
	    drawSatSnake(g);
	    updateLumaBars(g);
            wedgeOnly = false;
        }
          else if (wheelOnly)
          {
            ...
            wheelOnly = false;
          }
          ...
          else
            imDone = false;  // was paint() when method was update() in the AWT version

        return(imDone);
    }
Forcing two initial full paintComponent()s does whatever magic the double-buffering infrastructure needs to avoid the "wonky-white" problem. This also happens on a minimize; I've disabled resizing other than minimization. Even though it works, I consider it a kludge.

The second issue is not solved. All I can figure is that the double buffers are shared between the two JPanels, and the artifact buttons and toolTips at (0,0) are the result. I tried simply clearing the top twenty lines of the canvas when partialOnly() returns true, but for some reason that causes other canvas artifacting further down. And that was just a second kludge anyway.

Sorry for being so long-winded. What is the right way to avoid these problems?

-- Rich

Edited by: RichF on Oct 15, 2010 8:43 PM

Comments

darrylburke
My current issue involves two canvas (well, JPanel) refresh issues likely linked to double buffering.
Swing components are double buffered by default. Get rid of any custom double buffering.

Have you started going through the Swing tutorials? It's time you went through the one on Performing Custom Painting, and it won't be long before you need to better understand Concurrency in Swing.

db
pietblok
Hi Rich,

Problem 1: I don't see the effect you describe (or I just don't understand the description)

Problem 2: I have seen the behavior you describe on one of my own fancy attempts to explore the depths of swing. I never found out why it happened, but it disappeared after some changes. I never found what those changes were exactly. Obviously I was doing something wrong.

The way you handle partial repaints:

Are you aware that repaint() can have a rectangle argument? And are you aware that the Graphics object has a clip depicting the area that will be affected by painting? You might use these for your partial painting.

In general, I don't think it is a good idea to not completely paint the clip area, in order to leave the results from previous paintings untouched. In contrast, you should keep state information that tells you how the requested clip must be painted.

Remember, you don't know why the paintComponent() method is invoked. Somewhere in the system it is decided that the current painting is not valid any more. It may be you who decided that, but it may as well be something else.

Piet
RichF
Darryl, I'm not doing any custom double buffering. My goal was to simply replicate the functionality of awt's update() method. And yes, I have started with the Swing tutorial. I believe it was there that I learned update() is not part of the Swing infrastructure.
Problem 1: I don't see the effect you describe (or I just don't understand the description)
Piet, were you viewing the program (via the .jar) or the applet (via the .html)? For whatever reason, problem 1 does not manifest itself as an applet, only a program. FTR I'm running JDK/JRE 1.6 under Windows 7. As a program, just click anywhere in the wheel. The whole canvas goes wonky-white, and the wheel doesn't even show. If it happens, you'll understand. ;)
Are you aware that repaint() can have a rectangle argument? And are you aware that the Graphics object has a clip depicting the area that will be affected by painting? You might use these for your partial painting.
Yes and yes. Here is an enumeration of most of the update regions:
enum AoI    // areas of interest
{
    LUMA_SNAKE, GREY_SNAKE, HUEBORHOOD, BULB_LABEL, LUMA_WEDGE,
    LAST_COLOR, BRIGHTNESS_BOX, INFO_BOX, VERSION,
    COLOR_NAME, EXACT_COLOR, LUMA_BUTTON, LUMA_BARS, GUI_INTENSITY,
    QUANTIZATION_ERROR
}
That list doesn't even include the large color intensity wedge to the right, nor the color wheel itself. I have a method that will return a Rectangle for any of the AoI's. One problem is that the wheel is a circle, and a containing rectangle will overlap with some of the other AoI's. I could build an infrastructure to handle this mess one clip region at a time, but I think it would add a lot of unnecessary complexity.

I think the bigger picture is that, though it is now updated to Swing, some of the original 1998 design decisions are no longer relevant. Back then I was running Windows 98 on a single-core processor clocked at significantly less than 1 GHz. You could actually watch the canvas update itself. The color wheel alone fills over 1000 arcs, and the color intensity wedge has over 75 update regions of its own. While kind of interesting to watch, it's not 1998 any more. My multi-core processor runs at over 2 GHz, and my graphic card is way, way beyond anything that existed last century. Full canvas updates probably take less than 0.1 sec, and that is with double-buffering!

So, I think you're right. Let the silly paintComponent() do it's thing unhindered. If I want to track old dots on the wheel, keep an array of Points, remembering maybe the last 10. As a final step in the repainting process, decide how many of those old dots to display, and do it.

Thanks, guys, for being a sounding board.

Oh, I'm moving forward on implementing the color list widget. I've already added a 3rd JPanel, which is a column to the left of the main paint canvas. It will contain 3 GUI items:

1) the color list widget itself, initially sorted by name
2) 3 radio buttons allowing user to resort the list by name, hue, or hex
3) a hex-entry JTextField (which is all that is there at this very moment), allowing exact color request

The color list widget will fill most of the column from the top, followed by the radio buttons, with hex-entry at bottom.

For weeks I had in mind that I wanted a pop-up color list widget. Then you shared your ColorList class, and it was so obvious the list should just be there all the time. :)

-- Rich
pietblok
RichF wrote:
Problem 1: I don't see the effect you describe (or I just don't understand the description)
Piet, were you viewing the program (via the .jar) or the applet (via the .html)? For whatever reason, problem 1 does not manifest itself as an applet, only a program. FTR I'm running JDK/JRE 1.6 under Windows 7. As a program, just click anywhere in the wheel. The whole canvas goes wonky-white, and the wheel doesn't even show. If it happens, you'll understand. ;)
I ran the jar file and did not see the effect. Windows XP and java 1.6 update 21
So, I think you're right. Let the silly paintComponent() do it's thing unhindered. If I want to track old dots on the wheel, keep an array of Points, remembering maybe the last 10. As a final step in the repainting process, decide how many of those old dots to display, and do it.
Good decision :-)

Piet
RichF
I ran the jar file and did not see the effect. Windows XP and java 1.6 update 21
Hmm, I wonder if Java takes advantage of hardware double buffering on one's graphic board? If so, that would explain the difference. FTR I have an ATI Radeon HD-5700 series board with a GByte of its own memory.

My CPU is a 2.6 GHz ATI Phenom II X6 1035T. I.e, it's fast and has 5 cores, and 8 GBytes RAM to boot. This likely puts it at least in the top 20% of current computers. But even most those folks who have computers ranking in the bottom 5% are ahead of where I was in 1998. (At 2 years per computer generation, that's 6 generations of computer power!)

PS: how the heck do you quote in these forums? Obviously
this
does not work.
RichF
Not that it matters any more, but:

* http://r0k.us/rock/junk/wonkyWhite.png
pietblok
RichF wrote:
PS: how the heck do you quote in these forums? Obviously
this
does not work.
I click the quote button above

And no, I didn't see anything like the picture in your last post.

Piet
RichF
Sorry to get this thread off track, but I must be officially blind and/or stupid. Here is an image of Piet's last post:

* http://r0k.us/rock/junk/pietPost.png

Where is the "quote button"? Neither the "Reply" text link, nor the icon link immediately to its left causes any quoting. They both bring up the same page with an empty reply pane.

In the event that I'm not blind, and for some reason I'm not actually getting a quote button, what appears in the text pane to indicate a quoted passage? On most fora, you use the BBCode markup, . I'm assuming there must be something similar here.
darrylburke
<li> The quote button looks like this:
<font face="Arial" size=6>&rdquo;</font>
<li> Other formatting codes are listed in the FAQ.

db
RichF
Other forms are listed in the FAQ
Ok! I plead stupid for not reading the FAQ. Use curly braces instead of square brackets, and the closing quote does not begin with slash. Got it, thanks! :) (Strange that square brackets work fine for the code /code markup, though.)

And twice stupid for not realizing the quote button was not on the thread page, but on the post page. Here I don't feel quite so bad. On every other forum I've used, you enter the reply page via a quote link or button. It never occurred to me to look for it once I was already replying.
RichF
Now that I've added the third JPanel, I am having trouble achieving my desired layout:
L = colorList pane, c = paint canvas, G = primary GUI

        LLLccccccccc            LLLccccccccc
        LLLccccccccc            LLLccccccccc
        LLLccccccccc            LLLccccccccc
        LLLccccccccc            LLLccccccccc
want:   LLLccccccccc    get:    LLLccccccccc
        LLLccccccccc            LLLccccccccc
        LLLccccccccc            LLLccccccccc
        LLLGGGGGGGGG            GGGGGGGGGGGG
        LLLGGGGGGGGG            GGGGGGGGGGGG
The problem is in the lower left corner. I want the primary GUI items to center themselves beneath the paint canvas, as they always have. The colorList pane begins at the top with a long list widget, followed by 3 radio buttons, followed by a hex text-entry field. I'd like the text-entry to be at the bottom, directly to the left of the pre-existing GUI items. I've read the Swing tutorial on BorderLayout, but to this point I have only been able to achieve either the second layout shown above, or each JPanel in its own row.

Here is my layout code:
public class SIHwheel extends JApplet
{
    public static SIHControls controls;
    public static SIHListPane colorList;
    public static SIHCanvas paintingCanvas;

    public void init()
    {
	paintingCanvas = new SIHCanvas(640, 640);
	setLayout(new BorderLayout());
        add(colorList = new SIHListPane(paintingCanvas), BorderLayout.WEST);
        add(controls = new SIHControls(paintingCanvas), BorderLayout.SOUTH);
        add(paintingCanvas, BorderLayout.CENTER);

	ntc.init();	// initialize name-that-color class
    }
I've tried myriad permutations of order, as well as BorderLayout.EAST for paintingCanvas. I've also messed with the BorderLayout.PAGE_START type specifications. It doesn't seem to want to give me a west pane that spans the full height of the frame. Is there a way?
RichF
Got it. Wheels within wheels:
public class SIHwheel extends JApplet
{
    public static SIHControls controls;
    public static SIHListPane colorList;
    public static SIHCanvas paintingCanvas;
    public static SIHGrouper grouper;

    public void init()
    {
	paintingCanvas = new SIHCanvas(640, 640);
	controls = new SIHControls(paintingCanvas);
	colorList = new SIHListPane(paintingCanvas);
	grouper = new SIHGrouper(paintingCanvas, controls);

	setLayout(new BorderLayout());
	add(colorList, BorderLayout.WEST);
	add(grouper, BorderLayout.EAST);

	ntc.init();	// initialize name-that-color class
    }
    ...
}

// a grouping class to allow colorList to span full west side of frame
class SIHGrouper extends JPanel
{
    public SIHGrouper(SIHCanvas canvas, SIHControls controls)
    {
	setLayout(new BorderLayout());
        add(canvas, BorderLayout.NORTH);
        add(controls, BorderLayout.SOUTH);
    }
}
Is there a more elegant way? The above works, but it seems somewhat tortured.
RichF
... and, what was probably obvious to most of you. Much cleaner nesting:
public class SIHwheel extends JApplet
{
    public  static SIHControls controls;
    public  static SIHListPane colorList;
    public  static SIHCanvas paintingCanvas;

    public void init()
    {
        paintingCanvas = new SIHCanvas(640, 640);
        controls = new SIHControls(paintingCanvas);
        colorList = new SIHListPane(paintingCanvas);

        // grouper allows colorList to span entire west of frame
        JPanel grouper = new JPanel(new BorderLayout());
        grouper.add(paintingCanvas, BorderLayout.NORTH);
        grouper.add(controls, BorderLayout.SOUTH);

        setLayout(new BorderLayout());
        add(colorList, BorderLayout.WEST);
        add(grouper, BorderLayout.EAST);

        ntc.init();    // initialize name-that-color class
    }
    ...
}
I'm slightly worried that the scope of grouper would seem to be within init() only. On the other hand, it's not used by me once the layout has occurred. As far as I'm concerned, it might as well auto-dissolve. Is there any reason to make grouper a class object?

-- Rich

Edited by: RichF on Oct 17, 2010 2:57 AM
darrylburke
Is there any reason to make grouper a class object?
No. Nor is there a reason to make controls, colorList and paintingCanvas static. The only reason to make any field static is in order to share one value across all instances of the class.

JApplet has a BorderLayout by default, there's no need to set another.

Also consider building your GUI in a JPanel which can then be added or set as contentPane for an Applet or a top level window such as JFrame or JDialog. That gives you better flexibility. In this case -
public class SIHwheel extends JApplet {

  private SIHControls controls;
  private SIHListPane colorList;
  private SIHCanvas paintingCanvas;

  public void init() {
    setContentPane(makeContent());


    ntc.init();    // initialize name-that-color class
  }

  public JPanel makeContent() {
    paintingCanvas = new SIHCanvas(640, 640);
    controls = new SIHControls(paintingCanvas);
    colorList = new SIHListPane(paintingCanvas);

    // inner allows colorList to span entire west of frame
    JPanel inner = new JPanel(new BorderLayout());
    inner.add(paintingCanvas, BorderLayout.NORTH);
    inner.add(controls, BorderLayout.SOUTH);

    JPanel outer = new JPanel(new BorderLayout());
    outer.add(colorList, BorderLayout.WEST);
    outer.add(inner, BorderLayout.EAST);

    return outer;
  }
  
  public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
      
      @Override
      public void run() {
        JFrame frame = new JFrame("Title goes here");
        frame.setContentPane(new SIHwheel().makeContent());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
      }
    });
  }
}
db
RichF
Darryl, thanks. I'll do as you suggest. Right now I'm fighting several issues in regards to layout, and the parent container you suggest could well help with some of them. I'm also fighting/learning the JTextField event handler, and I need to add an event handler for my radio buttons. The current state can be seen:

* http://r0k.us/rock/Junk/SIHwheel.jar (as a program)
* http://r0k.us/rock/Junk/SIHwheel.html (as an applet)

As a program, at least it compacts itself. As an applet, the pieces kind of spread apart or overlap themselves, depending on how big I specify the applet region to be in the HTML code.

The good news is most functionality is in now.

Works
<li>Color List itself
<li>hex color text field (kind of)

Broke/Not Implemented/Hurting
<li>No event handler yet for radio buttons, so no resorting of Color List.
<li>both text fields function, but without cursors, and act funky. event handler issues I'm sure.
<li>layout of west panel gives 1/2 space to the single line hex entry, and only about 25% to Color List

I'm not asking for help yet, but I might soon. Still learning. Maybe Piet would like to see his color list in operation with 1567 names. :)

[below added after initial post]
Nor is there a reason to make controls, colorList and paintingCanvas static. The only reason to make any field static is in order to share one value across all instances of the class.
Heh, I just started on your suggestions, and this one bit me. Removing the "static" caused 10 errors of the form:
D:\progming\java\SIHwheel>javac SIHwheel.java
SIHwheel.java:111: non-static variable paintingCanvas cannot be referenced from
a static context
                paintingCanvas.notifyResized();
                ^
That's probably why I made them static in the first place. Just thought you'd like to know.

Edited by: RichF on Oct 17, 2010 10:21 AM
darrylburke
RichF wrote:
Nor is there a reason to make controls, colorList and paintingCanvas static. The only reason to make any field static is in order to share one value across all instances of the class.
Heh, I just started on your suggestions, and this one bit me. Removing the "static" caused 10 errors of the form:
D:\progming\java\SIHwheel>javac SIHwheel.java
SIHwheel.java:111: non-static variable paintingCanvas cannot be referenced from
a static context
paintingCanvas.notifyResized();
^
That's probably why I made them static in the first place. Just thought you'd like to know.
It's a very good reason to make the methods that use the variables non-static.

I repeat: The only reason to make any field static is in order to share one value across all instances of the class.

db
RichF
Ok, I've implemented Darryl's makeContent container, no problems. :) However, it doesn't prevent JPanels from separating when ran as an applet with non-exact size specified in the instantiating HTML. You can see this by clicking the SIHwheel.html link in previous post, and observing the white gap between the main painting canvas and the control area below it. There is not supposed to be any gap, and there is not when you run it as a program. What pack()'s an applet?

I have not yet modified my main() as Darryl suggests, mainly because I don't understand it. When I originally wrote this (~1998), applets were expected to have three standard methods, init(), start(), and stop(). If you wanted it to also run as a program, you could also add a main(). My current main() is:
    public static void main(String args[])
    {   // JFrame exists only when running as program, not applet
        JFrame f = new JFrame("SIHwheel");
        SIHwheel sihWheel = new SIHwheel();

        sihWheel.init();
        sihWheel.start();

        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        f.add("Center", sihWheel);
        f.setResizable(false);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }
Both Darryl's example and Piet's ColorList.java program use an overridden run() defined within main(), as part of an invokeLater(). I'm guessing it's a modern "thing to do"; I just don't understand what it accomplishes. It seems to run well every time when coded as above. I researched some, and the invoke stuff seems to involve the multithreading infrastructure. But once you are running, what difference does it make?

I think I'll end up doing it -- I just don't like adding complicated-looking stuff that I don't understand.
RichF
It's a very good reason to make the methods that use the variables non-static.
Well, unless the calling method is main() itself, which by definition has to be static. Until making my last post, main() also contained the following:
	f.addComponentListener(new ComponentAdapter()
	{
	    public void componentResized(ComponentEvent e)
	    {   // force clocking-in of two full paints
		paintingCanvas.notifyResized();
	    }
	});
(That includes the offending line I quoted earlier.) Once I abandoned my attempt to avoid "unnecessary" paintComponent()s, then that event handler was no longer necessary.

Does declaring them static cause problems, or is it simply considered bad form because it shouldn't be necessary?
darrylburke
RichF wrote:
Both Darryl's example and Piet's ColorList.java program use an overridden run() defined within main(), as part of an invokeLater(). I'm guessing it's a modern "thing to do"
Stop guessing. It almost never pays off.
; I just don't understand what it accomplishes. It seems to run well every time when coded as above.
More than 36 hours ago I advised you (emphasis added now)
Darryl Burke wrote:
Have you started going through the Swing tutorials? It's time you went through the one on Performing Custom Painting, and it won't be long before you need to better understand <font size="2">Concurrency in Swing</font>.
I researched some, and the invoke stuff seems to involve the multithreading infrastructure.
No. It has to do with Swing's single threaded rule.
But once you are running, what difference does it make?
Read the tutorials to know.
I think I'll end up doing it -- I just don't like adding complicated-looking stuff that I don't understand.
There's no justification and no satisfaction in not investing the time and effort to get the basics straight before moving on to more complicated stuff.

db
RichF
Darryl,

I appreciate your feedback, and I have modified my main() to use the invokeLater:run() paradigm. Believe it or not, I am learning a lot. But sometimes you don't know you don't know until you get bitten.

And I am accomplishing a fair amount in the free time I have to work on this. For example, the events from my radio buttons are handled, and the color list now can be resorted. :) As an example of what I'm learning, Piet's ColorList.java code used an immutable list. Pass the JList constructer a 2d array, and one had a functioning, colored list. To be able to handle a resortable list, I needed to learn about DefaultListModel() and restructure based on addElement() and removeElementAt(). I also needed to bypass the list event handler while it was being rebuilt.

At any rate, I've posted the current state:

<li> http://r0k.us/rock/Junk/SIHwheel.html (Applet still demonstrates the unpacked problem of the components. Amazingly it still works even though it looks like an exploded assembly drawing.
<li> http://r0k.us/rock/Junk/SIHwheel.jar (nice and pack()d. Still need to fix layout in the colorList pane, though.

Being able to have a list sorted by hue is a really nice feature, and I believe it makes my color wheel unique among all the wheels on the web.
796367
RichF wrote:
Now that I've added the third JPanel, I am having trouble achieving my desired layout:
Rich, you need <tt>java.awt.GridBagLayout</tt> and <tt>java.awt.GridBagConstraints</tt> like so...
JPanel p = new JPanel(new GridBagLayout());
GridbagConstraints gbc = new GridBagConstraints();

gbc.fill = GridBagConstraints.BOTH;

gbc.gridx = 0;
gbc.gridy = 0;
gbc.gridheight = 2;
gbc.gridwidth = 1;
p.add(colorList, gbc);

gbc.gridx = 1;
gbc.gridy = 0;
gbc.gridheight = 1;
gbc.gridwidth = 1;
p.add(paintingCanvas, gbc);

gbc.gridx = 1;
gbc.gridy = 1;
gbc.gridheight = 1;
gbc.gridwidth = 1;
p.add(controls, gbc);

setContentPane(p);
... that should do it. Notice I put in all the redundant gbc statements to ease the difficulty associated with GridBag Layout and GridBagConstraints.
RichF
Pierrot, thanks for the tip! I made the change and it runs great as a program. It seems to pack all the major components together, even as an applet. In my first pass, I didn't realize that you were laying out a controls area that spanned the entire width, not a colorList what spanned the entire height. As an applet, something weird happened and only an overcrushed portion is visible:

* http://r0k.us/rock/junk/GridBagLayoutProblem.png

What was visible ran, though.

I reogranized the layout for a full-height colorList pane. It layed itself out as I wanted, and even looks right as an applet. Here is the code, which is prety much exactly like you, but with the inner/outer paradigm showing as a working alternative. (Working, but still not completely packed, that is.)
    public JPanel makeContent()
    {
	paintingCanvas = new SIHCanvas(640, 640);
	controls = new SIHControls();
	colorList = new SIHListPane();
/*
	// grouper allows colorList to span entire west of frame
	JPanel inner = new JPanel(new BorderLayout(0,0));
	inner.add(paintingCanvas, BorderLayout.NORTH);
	inner.add(controls, BorderLayout.SOUTH);

	JPanel outer = new JPanel(new BorderLayout(0,0));
	outer.add(colorList, BorderLayout.WEST);
	outer.add(inner, BorderLayout.EAST);
*/
	JPanel outer = new JPanel(new GridBagLayout());
	GridBagConstraints gbc = new GridBagConstraints();

	gbc.fill = gbc.BOTH;

	gbc.gridx = 1;
	gbc.gridy = 0;
	outer.add(paintingCanvas, gbc);

	gbc.gridx = 1;
	gbc.gridy = 1;
	outer.add(controls, gbc);

	gbc.gridx = 0;
	gbc.gridy = 0;
	gbc.gridheight = 2;
	outer.add(colorList, gbc);

	return(outer);
    }
Thanks to the help from you-all, I'm about ready to post this on my site. :) It is not perfect yet, but I think the improvements outweigh the remaining problems. Left to do are:

<li> figure out how to get fully functioning JTextField's, with visible, functionaing cursors
<li> figure out how to set the color of the "Sort by" title of my radio button box.
<li> ... sortButtons.setBorder(BorderFactory.createTitledBorder("Sort by"));
<li> the number of colorList rows displayed is currently hard-wired to 32. This looks good on my system, but it is likely to be too big or too small for folks with different fonts. I need to figure out how to have the BoxLayout manager (or any layout manager) automatically use the list widget to fill as much of the pane as it can, without hard-wiring a certain number of visible list rows.
<li> the HTML page needs to be updated to describe the new stuff. (at least for the folks who read directions ;) )

I just took the plunge, and v2.20 is live:

* http://r0k.us/graphics/SIHwheel.html
RichF
figure out how to get fully functioning JTextField's, with visible, functioning cursors
Duh! The cursor has always been there, and functioning. That's why I couldn't find any information about enabling it. My problem was the background color I was using. It was so close to the color of the carat, the carat could not be seen.

Fixed internally, but I'll be making several other changes before next release. I also found there were two "Red Violet"s, with noticeably different hex values. I renamed one to "Medium Red Violet", also internally, iaw a Wikipedia article.
796367
RichF wrote:
I just took the plunge, and v2.20 is live:
That's the way to do it Rich.
RichF
That's the way to do it Rich.
:)
RichF wrote:
<li> figure out how to set the color of the "Sort by" title of my radio button box.
<li> ... sortButtons.setBorder(BorderFactory.createTitledBorder("Sort by"));
<li> the number of colorList rows displayed is currently hard-wired to 32. This looks good on my system, but it is likely to be too big or too small for folks with different fonts. I need to figure out how to have the BoxLayout manager (or any layout manager) automatically use the list widget to fill as much of the pane as it can, without hard-wiring a certain number of visible list rows.
<li> the HTML page needs to be updated to describe the new stuff. (at least for the folks who read directions ;) )
I believe version 2.22 fixes the above issues. It's live now:

* http://r0k.us/graphics/SIHwheel.html

To set the silly "Sort by" color, one needs to use a long-form constructor for createTitledBorder() with 6 parameters. But it works!

To handle the number of colorlist rows, I did the following:
	// standardize how high each cell is
	list.setFixedCellHeight(20);  // defaults to 18 on my system,
				      // but varies for other folks
	list.setVisibleRowCount(29);  // had been 32 when height was variable
So instead of merely hard-wiring the row count to 32 (like I had been doing), I first set the height of each cell. Then hopefully, the reduced row count of 29 will work for everyone. I just hope a cell height of 20 works for everyone. Is this a solution other folks use?
796367
Rich, override the<tt> JComponent#getPreferredSize() </tt>method for<tt> controls, paintingCanvas, and ColorList</tt>...
+--------+----------------+
|        |                |
| w=150  | w=500          |
| h=550  | h=500          |
|        |                |
|        |                |
|        |                |
|        |                |
|        |                |
|        +----------------+
|        | w=500 h=50     |
+--------+----------------+
... so that the total widths and heights add up. When you do this, your components will fit squarely on the contentPane. AND, you need to take into account that your<tt> paintComponent() </tt>methods do not paint into the border areas -- in case you decide to add a border to that component.
RichF
Pierrot, sounds like good idea. With my paintingCanvas the first thing I do is:
class SIHCanvas extends JPanel
{
    ...
    public SIHCanvas(int width, int height)
    {
	Dimension size = new Dimension(width, height);
	this.setPreferredSize(size);
        ...
    }
    ...
}
(It is created at 640x640). Other than setting list height and visible row count, that has been the only hardwired size I used. Well, I also define 3 fonts for use within the paintingCanvas. I've depended on the layout manager to handle everything to do with the two traditional GUI panes. Since I specifically set a size for paintingCanvas, do I need to specifically override its getPreferredSize() ?

Did you see anything on your system specifically off that led you to offer this suggestion, or is it just considered good programming practice for multi-pane apps?

It's probably obvious I'm not using an IDE. I've held off on borders and setting a look&feel. I wanted to get it running well in vanilla Swing before getting "fancy". (The "Sort by" area has a border simply because the radio button example I found used it, and I thought it looked nice. The example didn't tell me how to set title color though...) The flat, oversize buttons below the paintingCanvas have been a bit annoying to look at, but they fit, and Swing let me add toolTips, so I was happy. Whoever thought it was a good idea to make a [+] button look like [&nbsp;&nbsp;&nbsp;+&nbsp;&nbsp;&nbsp;] should be, um, required to answer dumb questions on these forums for a month!

Anyway, thanks for the heads up about borders. In a way, I have been doing that all along, with a 2-line black bar between my original 2 panes. Here is a snippit from paintComponent():
	r = getBounds();

	// for clean double-buffering
	super.paintComponent(g);
        g.setColor(background);
        g.fillRect(0, 0, r.width, r.height);

	// delineate the pane division with a black line
	g.setColor(Color.black);
	g.fillRect(0, r.height - 2, r.width, 2);
	r.height -= 2;		// and lie about the height from now on
I should be ready to handle more complicated borders, even if they extend into the getBounds() area. :) It's nice to know I should be ready for it, though.
pietblok
Instead of mimicking a border, you might just set a proper one with setBorder(). That border will be painted automatically.

Then, in paintComponent, query the inner area (that is the area inside the border) with SwingUtilities.calculateInnerArea(...). Everything is done for you, no need at all to do calculations yourself.

Piet

PS: I noted something problematic in your color wheel. When selecting a different sort for the color list, the gui freezes for quite a while. But the sort select buttons are not disabled. A user (like myself) that initially just does not understand what is happening, might attempt to click another sort button. A request for that new sort is queued and executed as soon as the first sort is ready, freezing the gui again.

So:
1) disable the sort buttons when clciked and enable them again when sorting is ready.
2) think of a way to do a quicker sort (for example have three sorted arrays ready for immediate use, or do the sorting in some background thread)
RichF
pietblok wrote:
Instead of mimicking a border, you might just set a proper one with setBorder(). That border will be painted automatically.

Then, in paintComponent, query the inner area (that is the area inside the border) with SwingUtilities.calculateInnerArea(...). Everything is done for you, no need at all to do calculations yourself.
Cool. Guess I'll experiment with borders and have some fun.
PS: I noted something problematic in your color wheel. When selecting a different sort for the color list, the gui freezes for quite a while. But the sort select buttons are not disabled. A user (like myself) that initially just does not understand what is happening, might attempt to click another sort button. A request for that new sort is queued and executed as soon as the first sort is ready, freezing the gui again.

So:
1) disable the sort buttons when clciked and enable them again when sorting is ready.
Good catch! I knew about the sort delay, but I didn't realize about the event queuing. I see that it is actually queuing multiple events -- during the freeze you can click multiple GUI buttons at the bottom and they seem to all eventually get executed. Heh, paintingCanvas is active too. Guess I'll figure out how to disable everything.
2) think of a way to do a quicker sort (for example have three sorted arrays ready for immediate use, or do the sorting in some background thread)
The sorts aren't slow. In fact if you (well "if I" -- I don't know about you yet) click a sort button before doing anything else with the list, it takes less than 0.5 sec. You can re-sort multiple times very quickly. However, once one makes a selection, then any further sorts do take a few seconds. I added a toolTip notice concerning this, but I imagine most users just click a button before seeing the toolTip.

There are actually three phases to the "sort":
    // sort, and rebuild the GUI list.  sortCmds are
    //	-1: sort by hex
    //  -2: sort by name
    //	-3: sort by hue
    private void getSortedNames(int sortCmd)
    {
	colorName worker;
	String hexName[];
// phase 1: actual sort
	worker = colorName.findColorName(sortCmd);
// phase 2: clear old elements from list widget
	// clear old model
	imWorkingHere = true;	// disable list event handler
	while (model.getSize() > 0)
	    model.removeElementAt(model.getSize() - 1);
// phase 3: add newly sorted elements to list widget
	for (int i = 0; i < ntc.names.length; i++)
	{
	    hexName = new String[2];
	    worker = colorName.findColorName(i);
	    hexName[0] = worker.getHex();
	    hexName[1] = worker.getName();
	    model.addElement(hexName);
	}

	imWorkingHere = false;	// re-enable list event handler
    }
If I don't depopulate the list in step 2, then new elements are added on top of those previously there. (I'm glad I get to show Piet this, since having a dynamic list is the main change to the colored list example he provided me. :) )

The imWorkingHere thing is a class-level boolean I use within the list event handler:
	list.addListSelectionListener(new ListSelectionListener()
	{
	    @Override
	    public void valueChanged(ListSelectionEvent e)
	    {
		// ignore events while rewriting list
		if (imWorkingHere)  return;

		if (!e.getValueIsAdjusting())
		{   // avoid duplicated calls
		    String[] selected = (String[]) list.getSelectedValue();
		    SIHwheel.paintingCanvas.redraw(selected[0], selected[1]);
		}
	    }
	});
That is the infrastructure Piet provided, with two changes. The first is the early exit when imWorkingHere. Apparently, "selected value doesn't exist any more" is considered a "ListSelection", and it caused problems. The second is the e.getValueIsAdjusting() check. Without it the event handler was getting called multiple times per click. Just yesterday, though, I added
	list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
to the panel constructor, and I may not need the getValueIsAdjusting() check any more.

Edited by: RichF on Oct 20, 2010 10:55 AM
RichF
However, once one makes a selection, then any further sorts do take a few seconds.
!!!
	// clear old model
	imWorkingHere = true;	// disable list event handler
	list.clearSelection();
... Well phooey. I thought I was on to something, but clearing the selection before depopulating and repopulating the list didn't help. Sorting still takes a few seconds once a selection has been made.
RichF
... but this did the trick!
    // clear old model
    imWorkingHere = true;    // disable list event handler
    list.clearSelection();

//    while (model.getSize() > 0)
//        model.removeElementAt(model.getSize() - 1);
    model.removeAllElements();
It doesn't go into "slow sort" mode any more. :)
796367
>
Rich, you are doing fine so I am not going to answer every question. However, for future custom components consider the following:
public class MyCustomComponent extends JComponent
{
  Dimension size = new Dimension(200, 80);

  public Dimension getPreferredSize(){
    Insets i = getInsets();
    int width = size.width + i.left + i.right;
    int height = size.height + i.top + i.bottom;

    return new Dimension(width, height);
  }

  public void paintComponent(Graphics g){
    Insets i = getInsets();

    g.translate(i.left, i.top);
    g.setClip(0, 0, size.width, size.height);

    // custom painting here...

    g.translate(-i.left, -i.top);
    g.setClip(0, 0, getWidth(), getHeight());
  }
}
This Component sets its own size internally -- see<tt> size</tt>. It also resizes slightly larger if a Border is set -- see getPrefferedSize() and getInsets(). When the Component is painted, it stays clear of the Border -- see paintComponent, getInsets(), translate(), and setClip(). If the Border is not set, the Component goes on like nothing ever happened. This component keeps a paintable area designated by<tt> size</tt> -- see clipRect(). If you have a strategy like this, then you can change the behavior to suit your needs. For example, the above code resizes the outside Dimensions slightly when a border is added. But what if you don't want the added Border to change the overall size? What if you want the paintable area to shrink instead? Just change the code slightly to get the behavior you want.

Also note this class extends JComponent. Once you do that, you can start talking like it is a Component, and not a Container. It is hard to have conversations about a component when in the back of your mind you are thinking JPanel -- because then layouts come into play, revalidate and a slew of other obnoxtious terms and subjects that are associated with JPanels and Containers in general.
darrylburke
pierrot_2 wrote:
note this class extends JComponent. Once you do that, you can start talking like it is a Component, and not a Container. It is hard to have conversations about a component when in the back of your mind you are thinking JPanel -- because then layouts come into play, revalidate and a slew of other obnoxtious terms and subjects that are associated with JPanels and Containers in general.
Nuts. <tt>JComponent</tt> extends <tt>Container</tt>. Every &nbsp;Swing component is-a <tt>Container</tt>.

db
796367
Darryl Burke wrote:
Nuts. <tt>JComponent</tt> extends <tt>Container</tt>. Every Swing component is-a <tt>Container</tt>.
I totally realize that. But when you do extend JComponent, the developer has the proper mental mindset that he/she is working with a component. IF the developer needs two or more views for one component, then he is free to add those other views.

Take JScrollPane as an example; it extends JComponent, even though it contains three JViewports, two JScrollBars, and four Components, and in turn, JViewPort and JScrollBar both extend JComponent too. Just because WE make custom components, doesn't really make them different than JScrollPane -- Architecturally speaking... And the big thing here is that everyone who uses JScrollPane, treats it like a component, not a container -- for example, I don't think anyone is going to reset the layout to a GridLayout.. I've tried it and it screws everything up. And one more thing about JScrollPane, it does not let you use the add() method successfully. In other words, JScrollPane has been written to behave like a component, not like a container. JFrame and JApplet do the same kinds of things.

Another example of extending the right Object is<tt> java.awt.Point. </tt>It used to extend Object, but with the advent of Graphics2D, and Point2D, Point now extends Point2D instead of Object. Now Graphics2D can take advantage of the interface of Shape's faculties. No one seems to mind that Point has been beefed up with Shape morphing capabilities and all, but we still handle Point like it's a basic run-of-the-mill object. Note: it doesn't matter who does the extending of these objects, a java core library technician or us, extending is extending.

Mr. Burke, the main point in my last reply was (and worded here slightly different), "When you *extend the correct Object*, then people and developers can have the *right conversations* about what's going on... and if you *extend the wrong object*, then the conversation or discussion is *filled with non-trivial, but unneccessary verbiage.*"

Edited by: pierrot_2 on Oct 21, 2010 11:15 PM -- corrections made after more fact checking.
RichF
pietblok wrote:
Instead of mimicking a border, you might just set a proper one with setBorder(). That border will be painted automatically.

Then, in paintComponent, query the inner area (that is the area inside the border) with SwingUtilities.calculateInnerArea(...). Everything is done for you, no need at all to do calculations yourself.
I've added a 2-pixel wide black border to the two control panes, and darkened the background on the paintingCanvas. Is there any reason to also add a border to it? Other than being able to say, "it has a border", I cannot think of a reason to do so.

* http://r0k.us/graphics/SIHwheel.html

The other feature I added was sextant boundaries in the name list when the user sorts by hue. Not sure how generally useful that is, but I wanted to see them. It also gathers and prints out statistics of how many named colors per sextant:
color, l = #be6424, 120  --  h,s,b =  25°,  81.0%,  190
color, l = #be9418, 146  --  h,s,b =  44°,  87.3%,  190
-- Hue Breakdown --
116 --- Greys and Near-Greys
616 --- Red Sextant
164 --- Yellow Sextant
161 --- Green Sextant
214 --- Cyan Sextant
120 --- Blue Sextant
176 --- Magenta Sextant
If this seems like "feature-itus", it probably is. Unless someone thinks the paintingCanvas should have a border, or has other suggestions or bug reports, I could well be "done" for a while. The only other useful feature I can think of is to offer a popup text panel and transfer the info currently going to the Java Console go to the panel instead. Each selection could take up two lines, adding a color block and the color name for each selection.

... useful, or more feature-itus?

-- Rich
pietblok
RichF wrote:
pietblok wrote:
Instead of mimicking a border, you might just set a proper one with setBorder(). That border will be painted automatically.

Then, in paintComponent, query the inner area (that is the area inside the border) with SwingUtilities.calculateInnerArea(...). Everything is done for you, no need at all to do calculations yourself.
I've added a 2-pixel wide black border to the two control panes, and darkened the background on the paintingCanvas. Is there any reason to also add a border to it? Other than being able to say, "it has a border", I cannot think of a reason to do so.
1) Educational :-) Now you know how to use borders
2) Maintenance: you may decide to use a different border in the future. Since you are using borders already, this has become very easy (for instance when using some titled or compound border and you would have a hard time calculating the needed boundaries for, let's say, a text).
The other feature I added was sextant boundaries in the name list when the user sorts by hue. Not sure how generally useful that is, but I wanted to see them. It also gathers and prints out statistics of how many named colors per sextant:
color, l = #be6424, 120  --  h,s,b =  25°,  81.0%,  190
color, l = #be9418, 146  --  h,s,b =  44°,  87.3%,  190
-- Hue Breakdown --
116 --- Greys and Near-Greys
616 --- Red Sextant
164 --- Yellow Sextant
161 --- Green Sextant
214 --- Cyan Sextant
120 --- Blue Sextant
176 --- Magenta Sextant
Unfortunately, being not an expert on colors, I have no idea what this means. I didn't see any change in your applet, possibly because I didn't know where to look at.
If this seems like "feature-itus", it probably is. Unless someone thinks the paintingCanvas should have a border, or has other suggestions or bug reports, I could well be "done" for a while. The only other useful feature I can think of is to offer a popup text panel and transfer the info currently going to the Java Console go to the panel instead. Each selection could take up two lines, adding a color block and the color name for each selection.

... useful, or more feature-itus?
Well, there is one feature that I'm wondering why it is absent.
When clicking somewhere on the wheel, almost anything seems to get updated, except for the color list. I would expect when a new color is selected on the wheel (or elsewhere) the selected item on the list would be changed to the new value as well (and scrolled to visible!!). But I'm not sure if this fits in your concept (knowing nothing about colors). But it might be a good exercise :-)

Piet
RichF
Thanks for your continued feedback, Piet! I hope you like what I've done with your example. :)
Unfortunately, being not an expert on colors, I have no idea what this means. I didn't see any change in your applet, possibly because I didn't know where to look at.
If you sort by hue, and have the scrollbar all the way to the top, the first item in the list is:

--- Greys and Near-Greys

It is displayed with a red foreground on white background. The actual color names have either a white or black foreground on a background of their color. After 116 greyish colors, there is another section label:

--- Red Sextant

Again, it uses red foreground on white background. After 616 more names there is a "Yellow Sextant" label. Etc. Like I said, it might not be generally useful, but it does allow folks to relate the hue-sorted list to what they see on the wheel.
Well, there is one feature that I'm wondering why it is absent.
When clicking somewhere on the wheel, almost anything seems to get updated, except for the color list. I would expect when a new color is selected on the wheel (or elsewhere) the selected item on the list would be changed to the new value as well (and scrolled to visible!!). But I'm not sure if this fits in your concept (knowing nothing about colors). But it might be a good exercise
I had thought about that, but decided against it. With the default, alphabetic, sort, what value would it offer? Does anyone really want to see "names alphabetically near 'Mahagony'"? The feature would arguably be more useful with the hex and especially hue sorts. At least for those, it would show some "close" colors. I'll consider making the list "live" for those cases. Does anyone else have an opinion?
RichF
If someone mentioned this, I missed it.
<h3>In Swing applets, always set look and feel!</h3>

I was having problems with Macintosh users seeing the applet. Part of the problem was likely they didn't have Java 1.6. (I'm targeting 1.4 now.) The other part of the problem was the horrendous button size the app was using on their systems when no look and feel was specified. What should have easily fit in 900 width was almost 1200 wide!! One of the Mac users was gracious enough to run the .jar file directly, and here is what she saw:

* http://r0k.us/rock/Junk/SIHwheel_SwingDefaultLaF_mac.png

Once I switched to declaring the Motif look and feel, the Mac's size went sane, and we were both looking at pretty much the same thing:

* http://r0k.us/rock/Junk/SIHwheel_motifOnAMac.png
* http://r0k.us/rock/Junk/SIHwheel_v227_motif.png (my Windows 7 system)

Most of you probably know this, but for me it was an important lesson. I hope that no new can of worms opens because of the use of Motif LaF, but I brought it [url http://www.r0k.us/graphics/SIHwheel.html]live.

-- Rich
1 - 38
Locked Post
New comments cannot be posted to this locked post.

Post Details

Locked on Nov 24 2010
Added on Oct 15 2010
38 comments
7,847 views