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
Related
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 ;)
When multiple JFrames are queued for repaint, the Swing Repaint Event paints the contents of all frames onto all frames, as though it is sharing the JPanels with the other frames.
In the example below, 'Frame 1' should only paint the square, and 'Frame 2' should only paint the oval. Instead, their graphics are somehow overlapping. The JPanels are not shared in any way, shape or form, so this should be impossible.
What I want to see is this:
For some reason, this only happens when the frames are being drawn in a loop (e.g. at 60 FPS).
I'm running Java 1.8.0_25 on Windows 10 x64.
I've been at it for hours now, and I couldn't find anything online about this... Does anyone know of a way to avoid this issue? Or if there is a work-around?
Here's the code:
JFrame frame1 = new JFrame("Frame 1");
frame1.setSize(256, 256);
frame1.setLocation(0, 0);
JPanel panel1 = new JPanel() {
public void paintComponent(Graphics g) {
g.fillRect(32, 32, 32, 32);
}
};
frame1.add(panel1);
frame1.setVisible(true);
JFrame frame2 = new JFrame("Frame 2");
frame2.setSize(256, 256);
frame2.setLocation(300, 0);
JPanel panel2 = new JPanel() {
public void paintComponent(Graphics g) {
g.fillOval(66, 32, 32, 32);
}
};
frame2.add(panel2);
frame2.setVisible(true);
while(true) {
Thread.sleep(16);
panel1.repaint();
panel2.repaint();
}
Solution (thanks MadProgrammer):
I did not add super.paintComponent(g) right after overriding the paintComponent() method. This fixed the issue. Unfortunately this also erases the previous graphics contents of the JPanel.
I am working on some application designed to be not 100% opaque, so it basically darkens the desktop of the user and my Swing interface is shown on top of this "dark veil".
It seems to me that, when some Swing components are being moved over that veil, my JFrame would need to be repainted for my moving components not to leave a trail behind them. The thing is that repainting the JFrame is too slow and my application wouldn't run smoothly anymore.
For your convenience, I created a SSCCE class that illustrates my issue, here it is:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Toolkit;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
#SuppressWarnings("serial")
public class TransparentFrameSSCCE extends JFrame {
private static final Dimension SCREEN_DIMENSIONS = Toolkit.getDefaultToolkit().getScreenSize();
private final JPanel movingPanel;
private TransparentFrameSSCCE() {
super();
this.setUndecorated(true);
this.setResizable(false);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setSize(TransparentFrameSSCCE.SCREEN_DIMENSIONS);
// This makes my JFrame transparent (its alpha component is set to 0)
this.setBackground(new Color(0, 0, 0, 0));
this.movingPanel = new JPanel();
this.movingPanel.setBounds(0, 0, 50, 50);
this.movingPanel.setBackground(Color.RED);
final JPanel contentPane = new JPanel();
// This makes my panel semi-transparent (its alpha component is set to 128)
contentPane.setBackground(new Color(0, 0, 0, 128));
contentPane.setLayout(null);
contentPane.add(this.movingPanel);
this.setContentPane(contentPane);
}
#Override
public void setVisible(final boolean isVisible) {
super.setVisible(isVisible);
new Thread(new Runnable() {
#Override
public void run() {
int x, y;
for(;;) {
x = TransparentFrameSSCCE.this.movingPanel.getLocation().x;
y = TransparentFrameSSCCE.this.movingPanel.getLocation().y;
TransparentFrameSSCCE.this.movingPanel.setLocation(x + 5, y);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
public static void main(final String args[]) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new TransparentFrameSSCCE().setVisible(true);
}
});
}
}
Would anyone know any other way to do so?
UPDATE: Following #MadProgrammer's directions about Swing components transparency behavior, this is how to deal with my "dark veil". It works perfectly. Many thanks to him :)
final JPanel contentPane = new JPanel() {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
final Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(new Color(0, 0, 0, 128));
g2d.fill(new Area(new Rectangle(new Point(0, 0), getSize())));
g2d.dispose();
}
};
contentPane.setOpaque(false); // Instead of: contentPane.setColor(new Color(0, 0, 0, 128)
Java components don't have a concept of transparency, they are either opaque or fully transparent (alright, the new transparency support for top level windows is an exception ;))
What you need to do is create a custom component that is fully transparent and the override it's paintComponent and fill the area of the component with your translucent color.
Also, don't modify the state of Swing components outside of the context of the Event Dispatching Thread, strange things begin to happen. A better solution might be to use a javax.swing.Timer
For example
Create rectangle with mouse drag, not draw
Java Swing: Transparent PNG permanently captures original background
How to make a transparent JFrame but keep everything else the same?
You may also want to take a look at Concurrency in Swing
Check out Backgrounds With Transparency for a simple explanation of the problem. Basically, you need to make sure your custom component paints the background.
Or instead of doing the custom painting you can take advantage of the AlphaContainer class which will do the painting for you:
//this.setContentPane( contentPane);
this.setContentPane( new AlphaContainer(contentPane) );
I'm working on a simple painting application in Java but Swing doesn't want to cooperate. At first I tried making a JTabbedPane that holds the DrawArea and a settings panel, but for some bizarre reason the second mouseDragged() (and by extension the drawArea.repaint() method) was triggered, the JTabbedPane seemed to duplicate. Imagine two identical tab panes stacked vertically. I thought this was just some bug in JTabbedPane, but I rewrote everything to a very simple custom menu using JButtons on a panel and the exact same thing happened. It's not like the GUI actually duplicated the top area; it's unusable and I can paint over it. Check it out:
Edit: I found a similar question. If I call super.paintComponent(g), the problem goes away (and drawArea.setBackground(color) actually works!) but the function called on super (a new keyword for me) basically repaints the drawArea so the paint trail is no longer saved.
If I paint a rectangle to fill the drawArea it overwrites the issue even though mouseDragged is still fired. Here's the rendering code:
#Override
public void mouseDragged(MouseEvent e) {
x = e.getX(); y = e.getY();
drawArea.repaint();
}
// (subclass):
class DrawArea extends JPanel {
public void paintComponent(Graphics g) {
g.setColor(Color.WHITE);
g.fillOval(x-3, y-3, 7, 7);
}
}
And here's the GUI code:
frame = new JFrame("Jadra");
frame.setSize(650, 600);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
menu = new JPanel();
settingsButton = new JButton("Settings");
clearButton = new JButton("Clear");
exitButton = new JButton("Quit");
menu.setLayout(new FlowLayout());
menu.add(settingsButton);
menu.add(clearButton);
menu.add(exitButton);
menu.setBackground(new Color(30, 90, 60));
drawArea = new DrawArea();
drawArea.setBackground(Color.red);
drawArea.setOpaque(true);
drawArea.addMouseMotionListener(this);
frame.getContentPane().add(menu, BorderLayout.NORTH);
frame.getContentPane().add(drawArea, BorderLayout.CENTER);
Thread pt = new Thread(new Painter());
frame.setVisible(true);
pt.start();
Please tell me I did something really stupid. Otherwise this looks like a really annoying bug. I really appreciate your help. Thanks!
Your DrawPanel.paintComponent() method should chain upward to the method it overrides from JPanel to allow default painting to occur. Simply add
super.paintComponent(g);
As the first line to this method.
I have a problem with java Xor method:
public class Okno extends JFrame {
public static void main(String[] args) {
Okno okno = new Okno();
}
Window()
{
this.setSize(300,300);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
JButton button= new JButton("Circle");
button.addActionListener(
new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
Graphics2D g = (Graphics2D)Window.this.getGraphics();
g.setXORMode(Color.red);
g.setStroke(new BasicStroke(10));
g.drawOval(100, 100, 100, 100);
}
});
this.add("South",button);
this.setVisible(true);
}
It paints circle after second click on button. On Graphic from Image it works fine...
If the code works the second time, odds are good you are calling the code incorrectly. For example, you may be requesting a paint callback and then improperly invalidating the screen area, which means that while the view has changed, the is no event to start the repainting routines.
On the second button click, the paint will then detect the first button click's action, which was to change what is drawn.
Swing painting has changed slightly over the years. You might be stuck with an old tutorial or text. Take a look at the latest online offerings to get a good idea of how it should be done.