4

I've got a JButton which is painted using a custom UI delegate (CustomButtonUI extends BasicButtonUI). The CustomButtonUI's paint() method draws the button with rounded "antialiased" corners, to make the apperance as "smooth" as possible.

Somehow the "antialiased" edges of the button disappears each time i drag the mouse over the button. This makes the button edges look "pixelized". However, once I add a line of code to repaint the parent of the button, the antialiasing kicks in even when i drag the mouse over the button.

Now, my question relates to wether this is a good idea? I do after all repaint the parent component from a child component. I wonder if this lead to a loop of repaints? If the parent tries to repaint its children and the children tries to repaint its parent - then i assume we're talking about a loop.

I've attached my code as a reference. Any comments are very welcome!

public class JCustomButtonUI extends BasicButtonUI {

    @Override
    public void installUI(JComponent c) {
        super.installUI(c);

        AbstractButton b = (AbstractButton) c;
        b.setBorderPainted(false);
    }

    @Override
    public void paint(Graphics g, JComponent c) {

        //Cast the Graphics instance to a Graphics2D instance.
        Graphics2D g2d = (Graphics2D) g;
        JButton b = (JButton) c;

        //Repaint parent component to make sure that we get "antialiased"
        //edges.
        b.getParent().repaint();

        //Get the component's height and width.
        int w = (int) g.getClipBounds().getWidth();
        int h = ((int) g.getClipBounds().getHeight());

        //Make sure the button is drawn with "antialiased" edges.
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
            RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setColor(Color.GRAY);
        g2d.fillRoundRect(0, 0, w, h, w, h);       
    }
}

Update 1

Just to illustrate the alias and antialiased border, please have a look at the below two pictures. When i (from the ButtonUI's paint() method) manually invoke the parent JPanel's repaint method, all borders are perfectly antialiased all the time. However, when i do not manually invoke the parent JPanel's repaint method, then the borders are no longer antialiased once i hoover the mouse over the button.

Aliased Antialiased

Update 2

I have shared the entire "component" which consists of a JPanel, a JSlider and a couple of JButtons on Snipt. Please get it from http://snipt.org/wnllg.

Update 3

It seems that i have managed to get it working. Instead of painting the JPanel's background in its paintComponent() method, i created a JCustomPanelUI which i installed on the JPanel. I don't think that was the solution itself, but instead of using width and height from the Graphics instance, I tried using widht and height from the JPanel itself. I'm not quite sure why things go wrong when i use width and height from the Graphics instance. I thought the width and height from the Graphics instance was already "prepared" with regard to dimensions from the JPanel component. You can have a look at the final component here: http://snipt.org/wnlli,

sbrattla
  • 5,274
  • 3
  • 39
  • 63
  • Reformatted code; please revert if incorrect. – trashgod Mar 02 '11 at 16:10
  • Make sure you check out Devon's answer - it's very likely the correct one. – Lawrence Dol Mar 03 '11 at 01:43
  • To summarize: the problem was apparently that the painting of the JPanel's background was using dimensions from the Graphics instance instead of the component itself. Using the dimensions from the Graphics instance worked fine most of the time, but introduced a subtle bug. – sbrattla Mar 03 '11 at 09:20
  • Good summary; there's more about clip management [here](http://java.sun.com/products/jfc/tsc/articles/painting/index.html). – trashgod Mar 03 '11 at 12:07

5 Answers5

4

I've reduced the example to just the anti-aliasing, and I am unable to reproduce the problem. It doesn't appear to be platform dependent. I'm not sure why you are using getClipBounds().

Addendum:

The JPanel background (a gradient) needs to shine through…

I've update the example to use a gradient background behind a transparent button; I've put anti-aliased (left) and aliased (right) examples side-by-side. I see no unexpected behavior.

ButtonUITest.png

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.RenderingHints;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.plaf.basic.BasicButtonUI;

/** @see http://stackoverflow.com/questions/5169647 */
public class ButtonUITest extends JPanel {

    public ButtonUITest() {
        this.setLayout(new GridLayout(1, 0));
        this.setPreferredSize(new Dimension(640, 480));
        this.add(new CustomButton(true));
        this.add(new CustomButton(false));
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        int w = this.getWidth();
        int h = this.getHeight();
        Graphics2D g2d = (Graphics2D) g;
        g2d.setPaint(new GradientPaint(0, 0, Color.blue, w, h, Color.red));
        g2d.fillRect(0, 0, w, h);
    }

    private void display() {
        JFrame f = new JFrame("ButtonUITest");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.add(this);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    private static class CustomButton extends JButton {

        public CustomButton(boolean antialiased) {
            this.setOpaque(false);
            this.setUI(new CustomButtonUI(antialiased));
        }
    }

    private static class CustomButtonUI extends BasicButtonUI {

        private boolean antialiased;

        public CustomButtonUI(boolean antialiased) {
            this.antialiased = antialiased;
        }

        @Override
        public void paint(Graphics g, JComponent c) {
            int w = c.getWidth();
            int h = c.getHeight();
            Graphics2D g2d = (Graphics2D) g;
            if (antialiased) {
                g2d.setRenderingHint(
                    RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON);
            }
            g2d.setColor(Color.LIGHT_GRAY);
            g2d.fillOval(0, 0, w, 2 * h);
        }
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                new ButtonUITest().display();
            }
        });
    }
}
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • Thanks for your answer! Part of the problem is that the JButton is a child of a JPanel. The JPanel background (a gradient) needs to shine through the areas on the JButton which is not painted by the JButton. Now, the JButton is perfectly "antialiased" against the JPanel at first. However, once i hoover the mouse over the JButton the edges becomes pixelized. Only when i "manually" ask the JPanel to repaint whenever the JButton is repainted does the antialiasing work as I like. – sbrattla Mar 02 '11 at 21:11
  • Even though your examples themselves did not solve my issue, your comment about why i use getClipBounds() did. I decided to try and get dimensions details from the component itself instead of from getClipBounds(). Somehow this proved to be the solution. I don't know why, but I am happy it did! – sbrattla Mar 03 '11 at 09:15
2

For antialiasing to work consistently, your component needs to return false for isOpaque. Otherwise, the RepaintManager is free to skip painting the region behind your component.

I suspect that if you use a screen magnifier to look at the "unaliased" edges, you will find they really are antialiased. But it was done against against a black, unpainted background, not the parent's background.

Devon_C_Miller
  • 16,248
  • 3
  • 45
  • 71
  • Was just about to answer with this. The parent is almost certainly not repaint if the only area affected is that of a child which claims to be opaque. – Lawrence Dol Mar 03 '11 at 01:43
  • In my case, this was already taken care of. However, if anyone else should see this in the future then this is the first thing to check. – sbrattla Mar 03 '11 at 09:17
1

The Swing experts who know what they're talking about will be here shortly. In the meantime, let me comment on this:

Now, my question relates to wether this is a good idea? I do after all repaint the parent component from a child component. I wonder if this lead to a loop of repaints? If the parent tries to repaint its children and the children tries to repaint its parent - then i assume we're talking about a loop.

Once you try it out and see that it's not a problem on your machine, chances are that it will be true on all JVMs you try. That is to say, the proof is in the pudding, or random bumbling does generally lead to positive results in Swing. Recursive loops have a way of causing the program to halt pretty quickly in Java, so the answer is... if this were totally wrong you'd already know. Plus you can put sysouts in there to see if this is happening (it's obviously not).

That said, there may be a better way to deal with your problem, but if your answer works, stick with it for now.

Dan Rosenstark
  • 68,471
  • 58
  • 283
  • 421
  • 2
    Thanks for your comment. I do agree with you to a certain extent, but I somehow take this as a learning opportunity with regard to design patterns. I'd really like to make this work in a way which is coherent with how SWING are designed to make it work. Even though my own patchwork may do the job, it still feels a little dodgy :-) – sbrattla Mar 02 '11 at 21:22
  • 1
    @esbrattla point taken. This is exactly one of the great things about SO: some people (like your best answer, @trashgod) do know what they're talking about. – Dan Rosenstark Mar 05 '11 at 06:39
0

AFAIK, repaint() just tells the system to repaint that component in the next paint cycle, i.e. if you call repaint() in a paint() method the repaint might be done in the next cycle.

Generally, Swing runs in its own thread, so repeated repaint should not stop the application logic. However, it might be using processing power, if the system always repaints the ui even if there are no changes.

You could try and write a log message to see when and how often the button is painted. If that happens continously even if nothing happens that would cause a ui update, you might have to find a solution. If not, you should be fine.

Edit: there is a class called RepaintManager that you might find interesting.

Thomas
  • 87,414
  • 12
  • 119
  • 157
0

Doing a repaint in there is definitely a bad idea. Try adding b.setOpaque(false) in installUI(). Your button is no longer painting its entire bounds because of the antialiasing. You need to let the background show through to fill the gaps.

Alan Yackel
  • 500
  • 3
  • 6
  • I have invoked setOpaque(false) on the JButton to let the background show through. That works fine. The button edges are also perfectly "antialiased" against the background at first. However, once i hoover the mouse over the button does its edges become pixelized. The only remedy to this (so far) has been to request the JButton's parent component (JPanel) to repaint itself first. However, I'm not too satisfied with this as the ButtonUI now has been given the responsibility to make sure that the button's parent is repainted before the button itself is. A better solution must be out there... – sbrattla Mar 02 '11 at 21:19