I'm trying to create a mouseOver visual effect for several JLabel elements filled with text. The idea is to make each label darker when mouse enters and then return it to normal when the mouse leaves its area. Also all the labels are placed over a panel that has a background image.
Though simple enough, I've encountered a nasty behavior I can't overcome.
Bug 1: The first time I move mouse over a label it shows me the upper-left corner of my main window as its background.
Bug 2: Then, every time I move mouse over one label once, and then move it over the second label, the second one changes its background to the "summ background" (panel image + semitransparent background) of first label. Above that it seems that even the first label's text contents are being "copied" to the second label's background. This only happened once per label change: if I move mouse over the same label twice, the second mouse over event is painted correctly.
I've already tried to use MouseMotionListener, a different element (JButton), played with component modification methods and eve tried to override paint methods. No result.
I've attached an animated GIF showing the described behavior:
Two JLabels copying backgrounds and contents from each other
I'm relatively new to Swing so I'm not familiar with its caveats. Any idea what might cause this?
Custom Panel class:
public class ImagePanel extends JPanel{
private static final long serialVersionUID = -3995745756635082049L;
private Image image = null;
public ImagePanel(Image image){
this.image = image;
}
public void paintComponent(Graphics g){
super.paintComponent(g);
if(image != null){
g.drawImage(image, 0, 0, this);
}
}
}
MouseListener class:
public class MouseHoverPiece implements MouseListener{
private static final Cursor CURSOR_HAND = new Cursor(Cursor.HAND_CURSOR);
private static final Cursor CURSOR_DEFAULT = new Cursor(Cursor.DEFAULT_CURSOR);
private static final Color HOVER_SHADOW = new Color(40, 80, 60, 50);
#Override
public void mouseEntered(MouseEvent e) {
JLabel component = (JLabel)e.getComponent();
component.setBackground(HOVER_SHADOW);
component.setCursor(CURSOR_HAND);
component.setOpaque(true);
component.repaint();
}
#Override
public void mouseExited(MouseEvent e) {
JLabel component = (JLabel)e.getComponent();
component.setBackground(null);
component.setCursor(CURSOR_DEFAULT);
component.setOpaque(false);
component.repaint();
}
MainWindow class:
Image background = ResourceLoader.loadImage("board.png");
ImagePanel panel = new ImagePanel(background);
panel.setBounds(10, 55, 480, 480);
panel.setLayout(null);
panel_main.add(panel);
final JLabel lblNewLabel1 = new JLabel("N");
lblNewLabel1.setHorizontalAlignment(SwingConstants.CENTER);
lblNewLabel1.setOpaque(false);
lblNewLabel1.setBounds(25, 24, 52, 52);
lblNewLabel1.setFont(lblNewLabel1.getFont().deriveFont(42f));
lblNewLabel1.addMouseListener(new MouseHoverPiece());
panel.add(lblNewLabel1);
final JLabel lblNewLabel2 = new JLabel("O");
lblNewLabel2.setHorizontalAlignment(SwingConstants.CENTER);
lblNewLabel2.setOpaque(false);
lblNewLabel2.setBounds(25+52+2, 24, 52, 52);
lblNewLabel2.setFont(lblNewLabel2.getFont().deriveFont(42f));
lblNewLabel2.addMouseListener(new MouseHoverPiece());
panel.add(lblNewLabel2);
private static final Color HOVER_SHADOW = new Color(40, 80, 60, 50);
Swing components have problems with transparent backgrounds because you are breaking the painting rules which state that an opaque component will completely paint the background.
Check out Backgrounds With Transparency for more information and a couple of solutions to the problem. You can either:
do custom painting on the label to manually paint the background
use a wrapper component and have that component do the painting for you.
I think I've found the solution. Both bugs had disappeared. What I did was to add parent container's (in my case the panel with the board background) repaint:
#Override
public void mouseEntered(MouseEvent e) {
JLabel component = (JLabel)e.getComponent();
component.setBackground(HOVER_SHADOW);
component.setCursor(CURSOR_HAND);
component.setOpaque(true);
Container container = component.getParent();
component.repaint();
container.repaint(); //fix
}
#Override
public void mouseExited(MouseEvent e) {
JLabel component = (JLabel)e.getComponent();
component.setBackground(null);
component.setCursor(CURSOR_DEFAULT);
component.setOpaque(false);
Container container = component.getParent();
component.repaint();
container.repaint(); //fix
}
Thanks everyone for your help ;)
Related
I have a board game (think Monopoly) where multiple game pieces can be located on a single tile. I want to be able to arbitrarily place game pieces on the any given tile. I want the tile to have a background (image or just flat color) and be able to place up to 4 game pieces on the tile in a grid. I am currently using this code but the circles do not display.
tank.png is a 135 x 135 pixel background.
GraphicsTile:
public class GraphicsTile extends JPanel {
public static final Dimension SIZE = new Dimension(135, 135);
public static final GridLayout MGR = new GridLayout(4, 4);
public GraphicsTile() {
super();
setLayout(MGR);
initGraphics();
setSize(SIZE);
add(new CirclePanel());
}
private void initGraphics() {
JLabel panel = null;
try {
Image image = ImageIO.read(new File("tank.png"));
panel = new JLabel(new ImageIcon(image));
panel.setSize(SIZE);
} catch (IOException e) {
e.printStackTrace();
}
add(panel);
}
}
CirclePanel:
public class CirclePanel extends JPanel {
public CirclePanel() {
setSize(33, 33);
}
#Override
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D)g;
g2d.setColor(Color.RED);
Ellipse2D.Float circle = new Ellipse2D.Float(50, 50, 0, 0);
g2d.draw(circle);
g2d.fill(circle);
}
}
public class GraphicsTile {
I don't know how your code compiles since your GraphicsTile doesn't extend any Swing component yet you use methods like setLayout(...) and setSize(...) which implies you are trying to use it like a JPanel.
You should not be using setSize(...). A Swing component should have a preferred size. Then the layout manager will set the size and location of the component based on the rules of the layout manager. I'm guessing you have a problem because the preferred size is (0, 0).
I also have no idea how you add the GraphicsTile to the parent component. Again it looks like you are using setSize() when you should let the layout manager position the tiles on the game board.
Also, if you want to have a background image with circles on top then you need a hierarchical structure. That is you need something like:
panel
background image
circle component.
So my suggestions are:
CirclePanel needs to implement the getPreferredSize(...) method to return the size of your custom painting.
Your GraphicsTile class needs to extend JPanel. You would then override the paintComponent(...) method to draw your background image. Now you can add the CirclePanel instances to the this panel which will use the GridLayout.
I have added webcam to my software using com.github.sarxos.webcam. It has a JPanel named WebcamPanel and has predefined webcam sizes while I need my custom size of pictures. I managed to crop the images taken from webcam at 640 x 480. I want to put a red rectangle over the WebcamPanel to show that this part of the image will be saved.
public class CardPanel {
Dimension panelDim = new Dimension(640, 480);
public Cardpanel(){
//....Button Defined earlier
btnTakePhoto.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
webcameFrame();
}
});
}
private void webcamFrame(){
imageFrame = new JFrame("Photo Capture");
// Did some calculations to put window at center
imageFrame.setBounds(screenSize.width / 2 - frameWidth / 2, screenSize.height / 2 - frameHeight / 2, frameWidth,
frameHeight);
imageFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
imageFrame.setContentPane(contentPane);
JPanel webcamWindow = new JPanel();
RedHighlighter redHighlighter = new RedHighlighter();
Webcam webcam = Webcam.getDefault();
webcam.setViewSize(WebcamResolution.VGA.getSize());
webcamPanel = new WebcamPanel(webcam);
webcamPanel.setFillArea(true);
webcamPanel.setMirrored(false);
webcamPanel.setPreferredSize(panelDim);
webcamWindow.add(webcamPanel);
webcamWindow.add(redHighlighter);
hBox.add(webcamWindow);
}
// Sub Class just for drawing the rectangle
public class RedHighlighter extends JPanel{
public RedHighlighter() {
// If you delete the following line, nothing will appear
setPreferredSize(new Dimension(400, 400));
}
#Override
public void paint(Graphics g) {
g.setColor(Color.RED);
g.drawRect(100, 100, 200, 200);
}
}
}
I used JLayeredPanes but no matter what you do it will cover whole size and will show only one item at a time.
Overriding paint method helped me draw the rectangle but it's on side and not on top.
As you can see the rectangle has pushed WebcamPanel towards left. I want webcamPanel to remain in it's position while the rectangle on top of it at center. Please suggest an efficient approach to this problem. Thanks!
The one JPanel is being pushed over due to the layout managers that you are using. If you want one JPanel to overly another, you'll want to consider using a JLayeredPane, with the video images in the lower level, perhaps the JLayeredPane.DEFAULT layer, and the drawing JPanel above it.
Other options and issues:
You could potentially draw in the same JPanel that the image is being displayed in by displaying the image in a paintComponent method as well as the drawing (in lines of code after the image is displayed.
Look into use of a JLayer as a way of adding a drawing "decoration" over your image.
Always override paintComponent, not paint
Always call the super's painting method within your override.
It worked!
public class MyWebcamPanel extends WebcamPanel {
/**
*
*/
private static final long serialVersionUID = 2808353446021354508L;
public MyWebcamPanel(Webcam webcam) {
super(webcam);
}
#Override
protected void paintComponent(Graphics g) {
int x = 180;
int y = 87;
super.paintComponent(g);
g.setColor(Color.RED);
g.drawRect(x, y, 640-2*x, 480-2*y);
}
}
The situation here is the following: I have a JFrame, in which I store a list of images. When the right-key is pressed, I display the next image in this fullscreen JFrame. I want a Exit JButton in the upper right corner of the screen, which is the problem. To achieve that, I create a new JPanel everytime the image is changed, remove the old JPanel from the frame, and add the JButton to the new JPanel. The issue is the following: when I start the program, the JButton is in the middle of the JFrame. Once I load the next Image by pressing the right key the JButton is on the right position. However, workarounds like calling the method to display the next Image fail. Even if I set the JButton invisible until the right key was pressed and the second image loaded, it still will be in the center of the screen instead of the upper right. The following code is used:
BufferedImage image = ImageIO.read(images.get(number));
JPanel panel = new JPanel() {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, 0, 0, null);
}
};
if (currentPanel != null) {
this.remove(currentPanel);
}
panel.setSize(Gallery.this.getSize());
currentPanel = this.add(panel);
jButton1.setBounds(Gallery.this.getWidth() - jButton1.getWidth(), 0, jButton1.getWidth(), jButton1.getHeight());
panel.add(jButton1);
currentPanel.repaint();
This code is executed once at startup. The JButton then is in the middle.
It is executed again when loading the next Image, JButton now is in correct position.
I already tried many things, like adding the JButton to the JFrame instead, setting JPanels layout to null (makes button invisible), repaint, pack, invalidate, nothing I try seems to work. Is anyone able to instruct Swing to place that JButton in the upper right corner of my JFrame? Thank you a lot!
After cleaning up the mess I programmed there, an easy solution proved to work:
I added a custom JPanel to the JFrame at startup and set a fixed width to the JButton. Here the JPanel:
public class ImagePanel extends javax.swing.JPanel {
Image image;
public Image getImage() {
return image;
}
public void setImage(Image image) {
this.image = image;
}
public ImagePanel() {
initComponents();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, 0, 0, this);
}
}
I set a fixed width to the JButton and added it to that JPanel:
jButton1 = new JButton();
jButton1.setText("Exit");
imagePanel = new ImagePanel();
imagePanel.setSize(Gallery.this.getSize());
add(imagePanel);
imagePanel.add(jButton1);
jButton1.setBounds(Gallery.this.getWidth() - 50, 0, 50, 30);
jButton1.addActionListener((ActionEvent e) -> exit());
displayImage(0);
The displayImage method consists of these lines:
BufferedImage image = ImageIO.read(images.get(number));
imagePanel.setImage(image);
imagePanel.repaint();
Here's an 11x11 grid of JTextField objects I've made.
The colors are nice, but Shapes would be better (or both):
I don't think there is any way to add an existing shape (e.g., .PNG) to a JTextField or a JPanel, is there?
What should I do? I haven't yet ventured into Graphics class and I'd rather not, just now. But if that's the only way, OK.
I'm assuming that the user will be typing in the single character of each text field. There are a couple of possibilities:
1) Create an IconBorder. This would simply paint your supplied Icon on top of the text field. Simple proof of concept:
import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;
import javax.swing.border.*;
public class IconBorder implements Border
{
private Icon icon;
private Insets borderInsets = new Insets(0, 0, 0, 0);
public IconBorder(Icon icon)
{
this.icon = icon;
}
//
// Implement the Border interface
//
#Override
public Insets getBorderInsets(Component c)
{
return borderInsets;
}
#Override
public boolean isBorderOpaque()
{
return false;
}
#Override
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height)
{
icon.paintIcon(c, g, x+1, y+1);
}
private static void createAndShowUI()
{
JPanel panel = new JPanel();
panel.add( createTextField( new Ellipse2D.Double(0, 0, 30, 30) ) );
JFrame frame = new JFrame("SSCCE");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add( panel );
frame.setLocationByPlatform( true );
frame.pack();
frame.setVisible( true );
}
private static JTextField createTextField(Shape shape)
{
JTextField textField = new JTextField(1);
textField.setFont( new Font("Serif", Font.PLAIN, 18) );
OutlineIcon icon = new OutlineIcon(shape, Color.RED, 2);
CompoundBorder inner = new CompoundBorder( textField.getBorder(), new EmptyBorder(5, 10, 5, 10) );
CompoundBorder border = new CompoundBorder(new IconBorder(icon), inner);
textField.setBorder( border );
return textField;
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowUI();
}
});
}
}
The above code uses the OutlineIcon class found in Playing With Shapes to provide the Icon for the Border. Or you can use any "transparent" Icon that you have lying around.
But as you can see there is a lot of playing around with insets to try to get the text aligned properly. If you want to go with the Border approach, it may be better to create a "CircleBorder" and a "SquareBorder", then you can just paint the shape directly in the paintBorder(...) method and do the painting based on the size of the parent component.
2) Another approach would be to use a JLabel with an Icon. You can set the properties of the JLabel such that the text is both horizontally and vertically centered so that it paints on top of the label. In order to support keyboard input, you would then need to make each label focusable and add a KeyListener to listen for the key pressed and then set the text of the label. (I like this approach as the sizing of the components will be done easily based on the size of the Icon that you use).
3) Or finally you could use a JLabel with an Icon like above. But then you could set the layout manager to a BorderLayout and then add your JTextField to the label. You would need to make the JTextField non-opaque.
You can add an image to a JLabel. There is a constructor that takes an Icon as a parameter. This Icon could be something like an ImageIcon, which has a constructor that takes a filename to display.
When spending a few minutes tweaking my desktop clock, I discovered a problem that I seem unable to resolve without help... I read some posts with similar problem but solutions did not work for me.
The clock (in typical java form with an Action Listener and Calendar) works just fine. The intended tweak: To set the Frame, ContentPane and Label backgrounds to transparent so only the time/text shows.
What happens is this: When the label background is transparent (or until it's opaque enough by setting the Alpha when Opaque is true), the underlying previous display stay's and does not clear.
To help figure this out, I put together the following code - the time and date Calendar stuff/etc is excluded. This code is just one version of many I tried with/without opaque, placement of calls...etc.
What does make a difference is use of the Action Listener - if the Action Listener is commented/deleted, label's display fine. Un-comment the Action Listener and the problem occurs.
See the images… Any help appreciated… Thanks!
fyi - below: the code sans imports and comments...
Screenshot of clock with black bg
Screenshot of the problem:
public class Clear extends JFrame {
private JPanel contentPane;
Color ppColor = new Color(255, 255, 0, 0); // r,g,b,a
Color lblColor = new Color(225, 200, 200, 0);
Color lbl2Color = new Color(225, 200, 200, 254);
int delay = 1000;
JLabel lblTime = new JLabel("TESTING");
JLabel lblTime2 = new JLabel("XXXXXX");
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
final Clear frame = new Clear();
frame.setVisible(true);
}
catch (Exception e) {
e.printStackTrace();
}
}
});
}
public Clear() {
setUndecorated(true);
setBackground(ppColor);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(1680, 975, 128, 74);
contentPane = new JPanel();
contentPane.setBackground(ppColor);
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
setContentPane(contentPane);
contentPane.setLayout(null);
lblTime.setOpaque(true);
lblTime.setBackground(lblColor);
lblTime.setBounds(0, 0, 125, 30);
contentPane.add(lblTime);
lblTime2.setOpaque(true);
lblTime2.setBackground(lbl2Color);
lblTime2.setBounds(0, 33, 125, 16);
contentPane.add(lblTime2);
ActionListener myTaskPerformer = new ActionListener() {
#Override
public void actionPerformed(ActionEvent evt) {
lblTime.setText("Does it");
lblTime2.setText("work? ");
}
};
new Timer(delay, myTaskPerformer).start();
}
}
Swing components do not work well with alpha based colors. They are either completely transparent or completely opaque.
If you specifiy that a component is isOpaque, but fill it using a translucent (alpha) color, the repaint manager won't update the area behind the component and the Graphics context will not be cleared properly. Remember, the Graphics context is shared resource, so everything that was painted before your component will still be painted
You can take a look at Java Swing Graphical Glitches Dealing with Transparency and Images for more details.
However. The simplest solution would be to create a TranslucentPane, the extends from something like JPanel, make it transparent (not opaque), override it's paintComponent method and paint the translucent (alpha) color from within it. Then add your label onto this.
Check out Performing Custom Painting and Painting in AWT and Swing for more details