0

I am developing an app using Swing. While user interact with the app, help data may become available in a closed section of the screen, and I'd like to draw a small arrow pointing to that section when this happens.

To do this, I extended a JPanel and added it as the glasspane of the app jframe. The name of the custom glass pane class is AlertGlassPane.

The AlertGlassPane do this: waits until new help data is available. When this happens and the help section is closed, it finds the position of the help section on the screen and then draws an animated arrow at its side.

To draw the arrow I extended the method paintComponent of the glass pane.

To animate the arrow I created a small thread that loop every 100 ms, calling repaint on the glass pane.

The problem: Java ignores my drawing... if a trigger the start of the animation and stand still, nothing happens. No drawing is shown on the app.

I know that the loop is running and paintComponent is being called, but the custom paint is not taking effect.

But if I move the mouse over the region where the arrow should be rendered, it does! But just when the mouse is moving. If a stop move the mouse, the animation stops too, and the arrow freezes on the last position before the mouse stop (or leaves the area).

I tried a lot of things (as set the clip region of the drawing, for example), but nothing seems to work. The only thing that works was when I called by mistake repaint from inside the paintComponent.

At this moment I'm looking for a way to comunicate to the rendering system that a givem region of my glasspane needs to be repainted (repaint(x, y, w, h) didn't work...). Maybe moving the custom rendering to a JLabel and then adding this label to the glasspane? I don't like this approach...

I'll try to clean up the code before post a snippet here. Would it help?

Thanks in advance!

snnipet:

package br.com.r4j.test;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.image.BufferedImage;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.text.JTextComponent;

/**
 *
 * @author 
 */
public class TestGlassPaneAnimation extends JPanel
{
    private static TestGlassPaneAnimation gvp = new TestGlassPaneAnimation();



    public static void main(String args[])
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                JFrame f = new JFrame("Anitest in glass");
                f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                //f.setResizable(false);
                f.setLayout(new GridLayout(5, 3));

                f.add(new JLabel("First Name :"));
                f.add(new JTextField(20));
                f.add(new JLabel("Last Name :"));
                f.add(new JTextField(20));
                f.add(new JLabel("Phone Number :"));
                f.add(new JTextField(20));
                f.add(new JLabel("Email:"));
                f.add(new JTextField(20));
                f.add(new JLabel("Address :"));
                f.add(new JTextField(20));
                JButton btnStart = new JButton("Click me, please!");
                f.add(btnStart);

                btnStart.addActionListener(new ActionListener()
                {
                    public void actionPerformed(ActionEvent e)
                    {
                        gvp.startAnimation();
                    }
                });

                f.setGlassPane(gvp);

                f.pack();
                f.setVisible(true);
                gvp.setVisible(true);
            }

        });
    }

    private BufferedImage icon;
    private boolean animate = false;
    private long timeStart = 0;
    private Thread thrLastActive = null;

    public TestGlassPaneAnimation()
    {
        setLayout(null);//this is the exception to the rule case a layoutmanager might make setting Jlabel co-ords harder
        setOpaque(false);
        Icon icon1 = UIManager.getIcon("OptionPane.warningIcon");
        int imgW = icon1.getIconWidth();
        int imgH = icon1.getIconHeight();
        this.icon = ImageUtilities.getBufferedImageOfIcon(icon1, imgW, imgH);
        this.animate = false;
    }


    public void startAnimation()
    {
        this.animate = true;
        this.timeStart = (new Date()).getTime();

        if (this.thrLastActive != null)
            this.thrLastActive.interrupt();

        this.thrLastActive = new Thread(new Runnable() 
        {
            public void run()
            {
                try
                {
                    while (true)
                    {
                        // int x = 250, y = 250;
                        // int width = 60, height = 60;

                        Thread.currentThread().sleep(100);

                        // frmRoot.invalidate();
                        // repaint(x, y, width, height);
                        repaint(new Rectangle(x, y, width, height));
                        // repaint(new Rectangle(10, 10, 2000, 2000));
                        // repaint();
                        // paintComponent(getGraphics());
                    }
                }
                catch (Exception e)
                {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        });

        this.thrLastActive.start();
    }


    protected void paintComponent(Graphics g)
    {
      try
      {
            // enables anti-aliasing
            Graphics2D g2 = (Graphics2D) g;
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);


            java.awt.Composite composite = g2.getComposite();

            System.err.println("b1: " + g.getClipBounds());

            if (this.animate)
            {
                long timeSpent = (new Date()).getTime() - timeStart;

                int x = 10, y = 150;
                int width = 60, height = 60;
                float maxAlpha = 0.8f;
                x += (-100*Math.sin(5*2*Math.PI*timeSpent/10000)+50)/15;
                System.err.println("painting::x: " + x + ", y: " + y + ", sin: " + (Math.sin(6*2*Math.PI*timeSpent/10000)));

                // g.setClip(x-10, y-10, width, height);
                System.err.println("b2: " + g.getClipBounds());

                AlphaComposite alpha2 = AlphaComposite.SrcOver.derive(maxAlpha);
                g2.setComposite(alpha2);
                g2.drawImage(this.icon, x, y, null);
                g2.setComposite(composite);

                g2.setComposite(composite);
            }
      }
      catch (Throwable e)
      {
          System.err.println("Errr!");
          e.printStackTrace();
      }
    }

}

class ImageUtilities {

    public static BufferedImage resize(BufferedImage image, int width, int height) {
        BufferedImage bi = new BufferedImage(width, height, BufferedImage.TRANSLUCENT);
        Graphics2D g2d = (Graphics2D) bi.createGraphics();
        g2d.addRenderingHints(new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY));
        g2d.drawImage(image, 0, 0, width, height, null);
        g2d.dispose();
        return bi;
    }

    public static BufferedImage getBufferedImageOfIcon(Icon icon, int imgW, int imgH) {
        BufferedImage img = new BufferedImage(imgW, imgH, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = (Graphics2D) img.getGraphics();
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        icon.paintIcon(null, g2d, 0, 0);
        g2d.dispose();
        return img;
    }
}
  • Are you calling `setVisible(true)` on the glass pane ? Glasspanes are invisible by default. – Extreme Coders Feb 15 '13 at 14:10
  • Seems like more of a threading issue. – Extreme Coders Feb 15 '13 at 14:17
  • Yes, I am! setVisible(true) and setOpaque(false). – Roberto Barra Feb 15 '13 at 15:07
  • I can draw a big shade rectangle right when the pane is added. It seems that the first call to paintComponent I am granted with the possibility to draw on the entire glass pane. After that, I can only draw where the engine thinks that a change happened (mouse moved, a component below the glass pane changed). – Roberto Barra Feb 15 '13 at 15:10
  • @RobertoBarra are you calling `setVisible(true)` on glasspane **AFTER** adding it to `JFrame` and **AFTER** `JFrame` is visible. And yes a code snippet would do the world of good because I have a working sample so something must be wrong in your code – David Kroukamp Feb 15 '13 at 15:10
  • Threading issue - maybe, bu I don't think so. Other thread problems that I faced before did not have a predictable behaviour as this one. I still think that I need to find a way to tell the rendering engine that I need to redraw a specific piece of my glasspane; and the setClip method of the Ghraphics object is not the way... – Roberto Barra Feb 15 '13 at 15:14
  • @RobertoBarra Have you tried [`repaint(Rectangle r)`](http://docs.oracle.com/javase/7/docs/api/javax/swing/JComponent.html#repaint(java.awt.Rectangle)) – David Kroukamp Feb 15 '13 at 15:15
  • @David Kroukamp - Ok, I'll work on the snippet right now! - but before I will test move around the setVisible call. I'm calling after adding to the frame, but I don't know if it is after the frame is visible. Thanks for the tip! – Roberto Barra Feb 15 '13 at 15:16
  • @David Kroukamp - yes, I tried repaint(Rectangle r). But I'll try again. – Roberto Barra Feb 15 '13 at 15:17
  • though unless its reeeeallly busy I think a simple `repaint()` would work just as effiecently. But its starting to sound like a threading issue (as @ExtremeCoders said) or the problem is elsewhere in the code – David Kroukamp Feb 15 '13 at 15:18
  • 3
    Please post an [SSCCE](http://sscce.org). – Guillaume Polet Feb 15 '13 at 15:30
  • Check out [this](http://stackoverflow.com/questions/14170257/drawing-warning-symbol-during-form-validation-in-swing/14170292#14170292) example. which might help. It shows an example of `GlassPane` being used to show a warning icon next to a `JTextField` whose input does not match a certain check. – David Kroukamp Feb 15 '13 at 15:44
  • Hi! A added a small code based on the sample that @David Kroukamp pointed. You can see what I meaning. After click the button, try to resize the window and place the icon over a textfield. Then start typing. – Roberto Barra Feb 15 '13 at 20:04
  • Stupid mistake! Code fixed. What was missing? To start the animation thread... well, at least this question generates a sample code of how one can draw directly to the glass pane. Tks for all the help! – Roberto Barra Feb 16 '13 at 05:20

1 Answers1

1

Hmm not sure exactly whats going on with the snippet you gave but as it looks like it was taken from mine I wrote another example (I had to change a 1 or 2 lines of code in showWarningIcon(Component c) and refreshLocations() methods but nothing major:

If anything except david is typed and the button (click me, please) clicked it will show this:

enter image description here

import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import java.util.Map;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;

public class TestGlassPaneAnimation {

    private static GlassValidationPane gvp = new GlassValidationPane();

    public static void main(String args[]) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame f = new JFrame("Anitest in glass");
                f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
                //f.setResizable(false);
                f.setLayout(new GridBagLayout());

                GridBagConstraints gc = new GridBagConstraints();
                gc.fill = GridBagConstraints.HORIZONTAL;
                gc.weightx = 1;
                gc.weighty = 1;
                gc.insets = new Insets(15, 15, 15, 15);//give some space so icon doesnt cover components when shown

                gc.gridx = 0;
                gc.gridy = 0;
                f.add(new JLabel("First Name:"), gc);

                final JTextField jtf = new JTextField(20);
                gc.gridx = 1;
                f.add(jtf, gc);

                gc.gridx = 0;
                gc.gridy = 1;
                f.add(new JLabel("Surname:"), gc);

                final JTextField jtf2 = new JTextField(20);
                gc.gridx = 1;
                f.add(jtf2, gc);

                JButton btnStart = new JButton("Click me, please!");
                gc.gridx = 2;
                f.add(btnStart, gc);

                btnStart.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        if (!jtf.getText().equalsIgnoreCase("david")) {
                            gvp.showWarningIcon(jtf);
                        }
                    }
                });

                f.addComponentListener(new ComponentAdapter() {//so wjen frame is resized icons follow
                    @Override
                    public void componentResized(ComponentEvent ce) {
                        super.componentResized(ce);
                        gvp.refreshLocations();
                    }
                });
                f.setGlassPane(gvp);

                f.pack();
                f.setVisible(true);
                gvp.setVisible(true);
            }
        });
    }
}

class GlassValidationPane extends JPanel {

    private HashMap<Component, JLabel> warningLabels = new HashMap<>();
    private ImageIcon warningIcon;

    public GlassValidationPane() {
        setLayout(null);//this is the exception to the rule case a layoutmanager might make setting Jlabel co-ords harder
        setOpaque(false);
        Icon icon = UIManager.getIcon("OptionPane.warningIcon");
        int imgW = icon.getIconWidth();
        int imgH = icon.getIconHeight();
        BufferedImage img = ImageUtilities.getBufferedImageOfIcon(icon, imgW, imgH);
        warningIcon = new ImageIcon(ImageUtilities.resize(img, 24, 24));
    }

    void showWarningIcon(Component c) {
        if (warningLabels.containsKey(c)) {
            return;
        }

        JLabel label = new JLabel();
        label.setIcon(warningIcon);

        //int x=c.getX();//this will make it insode the component
        int x = c.getX() - warningIcon.getIconWidth();//this makes it appear outside/next to component if space
        int y = c.getY();

        label.setBounds(x, y, warningIcon.getIconWidth(), warningIcon.getIconHeight());
        add(label);
        revalidate();
        repaint();
        warningLabels.put(c, label);
    }

    public void removeWarningIcon(Component c) {
        for (Map.Entry<Component, JLabel> entry : warningLabels.entrySet()) {
            Component component = entry.getKey();
            JLabel jLabel = entry.getValue();
            if (component == c) {
                remove(jLabel);
                revalidate();
                repaint();
                break;
            }
        }
        warningLabels.remove(c);
    }

    public void refreshLocations() {
        for (Map.Entry<Component, JLabel> entry : warningLabels.entrySet()) {
            Component c = entry.getKey();
            JLabel label = entry.getValue();
            //int x=c.getX();//this will make it insode the component
            int x = c.getX() - label.getIcon().getIconWidth();//this makes it appear outside/next to component
            int y = c.getY();

            label.setBounds(x, y, label.getIcon().getIconWidth(), label.getIcon().getIconHeight());
            revalidate();
            repaint();
        }
    }
}

class ImageUtilities {

    public static BufferedImage resize(BufferedImage image, int width, int height) {
        BufferedImage bi = new BufferedImage(width, height, BufferedImage.TRANSLUCENT);
        Graphics2D g2d = (Graphics2D) bi.createGraphics();
        g2d.addRenderingHints(new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY));
        g2d.drawImage(image, 0, 0, width, height, null);
        g2d.dispose();
        return bi;
    }

    public static BufferedImage getBufferedImageOfIcon(Icon icon, int imgW, int imgH) {
        BufferedImage img = new BufferedImage(imgW, imgH, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = (Graphics2D) img.getGraphics();
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        icon.paintIcon(null, g2d, 0, 0);
        g2d.dispose();
        return img;
    }
}

If you are looking for a more mature library have a look at JXLayer - Validation Overlays and Validation overlays using glass pane.

Also might want to have a read here which shows many ways of validating a textfields data (other than button press) like DocumentFilter and InputVerifier etc.

Community
  • 1
  • 1
David Kroukamp
  • 36,155
  • 13
  • 81
  • 138
  • Thanks for the code! I tried it out and almost surrender to the component-inside-glasspane technique, when I found the silly error on my snippet. I think that drawing directly to the glasspane is the best (more flexible) way to me. – Roberto Barra Feb 16 '13 at 05:24