2

So I have this login form and I have a "user photo." I'm trying to make it so that when you hover the mouse over the photo area, a transparent label with a colored background will appear (to give the effect of "selecting the photo"). It looks like this:

image1

And once you move your mouse off it, it goes back to being "deselected."

Now my problem is, if you hover your mouse over the login button first then move your mouse over the photo, a "ghost login button" appears. It looks like this:

image2

I don't know why this is happening. Can someone help? Here is the relevant code:

package com.stats;

public class Stats extends JFrame implements Serializable {

    private JLabel fader;

    public Stats() {

    try {
        Image image = ImageIO.read(new File(System.getenv("APPDATA")
                                   + "\\Stats\\Renekton_Cleave.png"));
        JLabel labelUserPhoto = new JLabel(new ImageIcon(image));
        fader = new JLabel();
        fader.setBounds(97, 44, 100, 100);
        fader.setOpaque(true);
        fader.setBackground(new Color(0, 0, 0, 0));
        labelUserPhoto.setBounds(97, 44, 100, 100);
        PicHandler ph = new PicHandler();
        contentPane.add(fader);
        contentPane.add(labelUserPhoto);
        fader.addMouseMotionListener(ph);
    } catch(Exception e) {
        e.printStackTrace();
    }
}

private class PicHandler implements MouseMotionListener {
    public void mouseDragged(MouseEvent e) { }
    public void mouseMoved(MouseEvent e) {
        int x = e.getX();
        int y = e.getY();

        System.out.println("x: " + x + ", y: " + y);

        if ((x > 16 && x < 80) && (y > 16 && y < 80)) {
            if (!fader.isOpaque()) {
                fader.setOpaque(true);
                fader.setBackground(new Color(0, 0, 0, 40));
                fader.repaint();
            }
        } else {
            if (fader.isOpaque()) {
                fader.setOpaque(false);
                fader.repaint();
            }
        }
    }
}
mKorbel
  • 109,525
  • 20
  • 134
  • 319
JoY
  • 53
  • 1
  • 5
  • I don't see your call to `super.paintComponent()`, as discussed [here](http://stackoverflow.com/q/7213178/230513). – trashgod Aug 06 '13 at 00:56
  • Yea, I read a thread about this, but I couldn't figure out where I needed to put it, let alone what it does. :S – JoY Aug 06 '13 at 01:26
  • Here's more on the [opacity](http://www.oracle.com/technetwork/java/painting-140037.html#props) property. – trashgod Aug 06 '13 at 01:30
  • When you say "super.paintComponent()", what class is the super referring to? The JFrame? JFrame has paintComponents but no paintComponent. Actually, now that I look, I don't think any of them have paintComponent (no s). I looked at the link and I kind of understand it better, but I'm still stumped. I'm looking through Google right now but... – JoY Aug 06 '13 at 01:47
  • Are you telling me I should change it so that it extends JPanel instead of JFrame? – JoY Aug 06 '13 at 01:49
  • "Swing programs should override `paintComponent()` instead of overriding `paint()`."—[*Painting in AWT and Swing: The Paint Methods*](http://www.oracle.com/technetwork/java/painting-140037.html#callbacks). – trashgod Aug 06 '13 at 02:08
  • 1
    Generally, Swing components don't support alpha colors, so I don't think using `fader.setBackground(new Color(0, 0, 0, 40));` is doing you any favors – MadProgrammer Aug 06 '13 at 06:22
  • 2
    Same as others already stated, just for emphasis (can't be repeated often enough :): a component with `isOpaque() == true` **must** fill each pixel in its area with a completely opaque (aka: alpha == 255) color. You are violating that contract, consequently you get painting artefacts – kleopatra Aug 06 '13 at 08:26

4 Answers4

2

I can see a number of issues with your example, but the most significant is the use of a color with an alpha value.

fader.setBackground(new Color(0, 0, 0, 40));

Swing doesn't render components with alpha based colors well (within this context). By making component opaque and then setting the background color to use an alpha value, you are telling Swing that it doesn't need to worry about painting what's underneath your component, which isn't true...

The Graphics context is also a shared resource, meaning that anything that was painted before your component is still "painted", you need to clear the Graphics context before painting.

enter image description hereenter image description here

This example uses a rather nasty trick to get it's work done. Because all the painting occurs within the UI delegate, if we were simply to allow the default paint chain to continue, we wouldn't be able to render underneath the icon. Instead, we take over control of the "dirty" details and paint the background on the behalf the parent.

This would be simpler to achieve if we simple extended from something like JPanel and painted the image ourselves

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class FadingIcon {

  public static void main(String[] args) {
    new FadingIcon();
  }

  public FadingIcon() {
    startUI();
  }

  public void startUI() {
    EventQueue.invokeLater(new Runnable() {
      @Override
      public void run() {
        try {
          UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
          ex.printStackTrace();
        }

        BufferedImage img = null;
        try {
          img = ImageIO.read(new File("C:\\Users\\swhitehead\\Documents\\My Dropbox\\Ponies\\SmallPony.png"));
        } catch (IOException ex) {
          ex.printStackTrace();
        }

        JFrame frame = new JFrame("Testing");
        frame.setLayout(new GridBagLayout());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(new FadingLabel(new ImageIcon(img)));
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
      }
    });
  }

  public class FadingLabel extends JLabel {

    private boolean mouseIn = false;
    private MouseHandler mouseHandler;

    public FadingLabel(Icon icon) {
      super(icon);
      setBackground(Color.RED);
      super.setOpaque(false)(
    }

    @Override
    public void setOpaque(boolean opaque) {
    }

    @Override
    public final boolean isOpaque() {
        return false;
    }

    protected MouseHandler getMouseHandler() {
      if (mouseHandler == null) {
        mouseHandler = new MouseHandler();
      }
      return mouseHandler;
    }

    @Override
    public void addNotify() {
      super.addNotify();
      addMouseListener(getMouseHandler());
    }

    @Override
    public void removeNotify() {
      removeMouseListener(getMouseHandler());
      super.removeNotify();
    }

    @Override
    protected void paintComponent(Graphics g) {
      if (mouseIn) {
        Graphics2D g2d = (Graphics2D) g.create();
        g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f));
        g2d.setColor(getBackground());
        g2d.fillRect(0, 0, getWidth(), getHeight());
        g2d.dispose();
      }
      getUI().paint(g, this);
    }

    public class MouseHandler extends MouseAdapter {

      @Override
      public void mouseEntered(MouseEvent e) {
        mouseIn = true;
        repaint();
      }

      @Override
      public void mouseExited(MouseEvent e) {
        mouseIn = false;
        repaint();
      }

    }

  }

}

I would also recommend that you take the time to learn how to use appropriate layout managers, they will save you a lot of hair pulling later

Check out A Visual Guide to Layout Managers and Laying Out Components Within a Container

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • hmm .. looks like your paintComponent is violating the opaqueness contract if opaque == true: you fill the background with a transparent color, thus _not_ making each pixel completely opaque. Am I missing something? – kleopatra Aug 06 '13 at 08:16
  • @kleopatra yeah, I figured that to, not quite sure what to do about – MadProgrammer Aug 06 '13 at 08:18
  • @kleopatra There, I've set it so it should almost always be transparent...:P – MadProgrammer Aug 06 '13 at 08:21
  • that'll be safe enough, except malicious sub-classes that override isOpaque To really be safe, override that as well and make it final .. just saying :-) – kleopatra Aug 06 '13 at 09:14
  • @kleopatra Agreed. Should have stuck with `JPanel` and `drawImage` :P – MadProgrammer Aug 06 '13 at 09:16
0

Since I can't comment I have to put this as an answer:

As trashgod mentioned, this problem can be caused by a missing super.paintComponent(g) call however you don't seem to be overriding the paintComponent method at all (at least you didn't show it here). If you do override the paintComponent method of a JFrame or any JPanels, you need to have:

public void paintComponent(Graphics g) {
    super.paintComponent(g);
    //rest of your drawing code....
}

But if you haven't used one at all then the problem may be caused by something else.

dukky
  • 80
  • 1
  • 7
  • I never overrided paintComponent. :( – JoY Aug 06 '13 at 01:57
  • Try adding the method in my answer as it is to the class that extends JFrame. From what I understand you're supposed to do all your drawing inside this method but you're technically just adding an image to a JLabel and not doing any drawing with a graphics object. – dukky Aug 06 '13 at 02:00
  • so super is supposed to refer to JFrame right? Well, I am using Eclipse and it shows that JFrame has a "paintComponents" method, not a paintComponent method. Also, does that mean I'm supposed to add my whole "image to a JLabel" code into the paintComponent method? – JoY Aug 06 '13 at 02:15
  • The paintComponent method is protected, meaning you can't call it from another class outisde the package, but a subclass can still call (and override) it. I'm not saying you should add the image to the JLabel inside the paintcomponent method, but I've had this same error before (a button appearing in a strange place where it shouldn't be) and it was due to the missing super.paintComponent(g) call. If you have sources linked to eclipse you can press f3 when your cursor is over JFrame, and look at the source and you will see that it indeed has a paintComponent method. – dukky Aug 06 '13 at 02:30
  • Ah sorry, you can't draw directly onto JFrames since they don't have that method. You should add a JPanel to the JFrame, and then add everything to this JPanel instead of the JFrame. It's possible that you not using the panel is what's causing this. – dukky Aug 06 '13 at 02:39
  • I actually have a contentPane that is of type JPanel which I add into the frame. Should I just make a new class then that extends JPanel and change my contentPane to that? – JoY Aug 06 '13 at 03:02
0

Since jdk7 there a new mechanism to apply visual decorations (and listening to events for child components): that's the JLayer/LayerUI pair.

In your case a custom layerUI would

  • trigger a repaint on rollover
  • implement the paint to apply the transparent color

Below is an example, similar to the WallPaperUI in the tutorial:

// usage: create the component and decorate it with the custom ui
JLabel label = new JLabel(myIcon);
content.add(new JLayer(label, new RolloverUI()));

// custom layerUI
public static class RolloverUI extends LayerUI<JComponent> {

    private Point lastMousePoint;

    private JLayer layer;

    /**
     * Implemented to install the layer and enable mouse/motion events.
     */
    @Override
    public void installUI(JComponent c) {
        super.installUI(c);
        this.layer = (JLayer) c;
        layer.setLayerEventMask(AWTEvent.MOUSE_MOTION_EVENT_MASK
                | AWTEvent.MOUSE_EVENT_MASK);
    }

    @Override
    protected void processMouseMotionEvent(MouseEvent e,
            JLayer<? extends JComponent> l) {
        updateLastMousePoint(e.getPoint());
    }

    @Override
    protected void processMouseEvent(MouseEvent e,
            JLayer<? extends JComponent> l) {
        if (e.getID() == MouseEvent.MOUSE_EXITED) {
            updateLastMousePoint(null);
        } else if (e.getID() == MouseEvent.MOUSE_ENTERED) {
            updateLastMousePoint(e.getPoint());
        }
    }

    /**
     * Updates the internals and calls repaint.
     */
    protected void updateLastMousePoint(Point e) {
        lastMousePoint = e;
        layer.repaint();
    }

    /**
     * Implemented to apply painting decoration below the component.
     */
    @Override
    public void paint(Graphics g, JComponent c) {
        if (inside()) {
            Graphics2D g2 = (Graphics2D) g.create();

            int w = c.getWidth();
            int h = c.getHeight();
            g2.setComposite(AlphaComposite.getInstance(
                    AlphaComposite.SRC_OVER, .5f));
            g2.setPaint(new GradientPaint(0, 0, Color.yellow, 0, h,
                    Color.red));
            g2.fillRect(0, 0, w, h);

            g2.dispose();
        }
        super.paint(g, c);
    }

    protected boolean inside() {
        if (lastMousePoint == null || lastMousePoint.x < 0
                || lastMousePoint.y < 0)
            return false;
        Rectangle r = layer.getView().getBounds();
        r.grow(-r.width / 10, -r.height / 10);
        return r.contains(lastMousePoint);
    }
}
kleopatra
  • 51,061
  • 28
  • 99
  • 211
0

I had a similar problem with ghosted images after something moved or was resized. @MadProgrammer has some really good points, but in the end, they didn't work for me (possibly because I had multiple layers using 0.0 alpha value colors, some of which also had images in them, so I couldn't set a composite value for the whole component). In the end, what fixed it was a simple call to

<contentpane>.repaint()

after all the draw commands were executed.

mono código
  • 405
  • 1
  • 6
  • 10