Translucent JFrame leaves rendering artifacts when frame repacked - java

When I change a JLabel's font size or contents in a translucent JFrame, then call pack() on the frame, a ghosted version of the previous contents is visible. This only happens on macOS. The same code works fine on Windows.
Here's an enlarged example of what happens:
Here is code to reproduce the problem; make sure the JFrame is on top of a white background to better see the problem, then click the "Bigger" and "Smaller" buttons:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
public class ScratchSpace {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Test Transparent Frame");
frame.getRootPane().putClientProperty("apple.awt.draggableWindowBackground", true);
frame.setUndecorated(true);
frame.setBackground(new Color(0, 0, 0, 25));
JPanel contentPane = new JPanel(new FlowLayout());
contentPane.setOpaque(false);
contentPane.setBackground(new Color(0, 0, 0, 25));
JLabel label = new JLabel("Hello cruel world");
label.setForeground(Color.WHITE);
contentPane.add(label);
contentPane.add(new JButton(new AbstractAction("Bigger") {
#Override
public void actionPerformed(ActionEvent e) {
label.setFont(label.getFont().deriveFont(label.getFont().getSize() + 1f));
frame.pack();
}
}));
contentPane.add(new JButton(new AbstractAction("Smaller") {
#Override
public void actionPerformed(ActionEvent e) {
label.setFont(label.getFont().deriveFont(label.getFont().getSize() - 1f));
frame.pack();
}
}));
frame.setContentPane(contentPane);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setVisible(true);
});
}
}
I've tried changing all components to non-opaque. I've tried revalidating the content pane after packing. What do I need to do to solve this?

Swing doesn't know how to paint transparent backgrounds properly so you need to do the painting yourself.
Check out Background With Transparency for more information and a couple of solutions.
But to set a frame transparent you should be using the setOpacity(...) method of the frame. Check out the section from the Swing tutorial on How to Create Translucent Windows;

Related

Java Graphics AWT

When I run the below code, I am unable to see the background color as red. It's showing default one. Is there anything that I have to add to these lines?
import java.awt.*;
import java.awt.Graphics;
import javax.swing.*;
public class gfix extends JPanel {
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.red);
g.fillRect(80, 100, 150, 75);
}
public static void main(String[] args){
gfix gg=new gfix();
JFrame frame = new JFrame("RISK");
frame.setSize(800, 600);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel();
panel.setLayout(null);
frame.add(panel);
JButton button = new JButton("test");
button.setBounds(100, 100, 150, 150);
panel.add(button);
frame.setVisible(true);
}
}
Your are overriding painGraphics() in gfix class so add gfix class object into your frame not Java provided JPanel class object.
gfix gg=new gfix();
JFrame frame = new JFrame("RISK");
frame.setSize(800, 600);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//JPanel panel = new JPanel(); Not needed
//panel.setLayout(null);
frame.add(gg);
JButton button = new JButton("test");
button.setBounds(100, 100, 150, 150);
gg.add(button);
frame.setVisible(true);
for g.fillRect(80, 100, 150, 75); give proper panel bounds to fill complete panel background. OR use int width = getWidth();
int height = getHeight(); in paintGraphics to get actual height and width.
import java.awt.*;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
public class gfix {
public static void main(String[] args) {
JFrame frame = new JFrame("RISK");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel();
panel.setBackground(Color.RED);
frame.add(panel);
JButton button = new JButton("test");
// adjust numbers as needed
button.setMargin(new Insets(20,40,20,40));
panel.add(button);
// adjust numbers as needed
panel.setBorder(new EmptyBorder(10,40,50,200));
frame.pack();
frame.setVisible(true);
}
}
Other tips:
Java GUIs have to work on different OS', screen size, screen resolution etc. using different PLAFs in different locales. As such, they are not conducive to pixel perfect layout. Instead use layout managers, or combinations of them along with layout padding and borders for white space.
Provide ASCII art or a simple drawing of the intended layout of the GUI at minimum size, and if resizable, with more width and height - to show how the extra space should be used.
Please learn common Java nomenclature (naming conventions - e.g. EachWordUpperCaseClass, firstWordLowerCaseMethod(), firstWordLowerCaseAttribute unless it is an UPPER_CASE_CONSTANT) and use it consistently.

Java Swing Pixels Being Inaccurate

I am designing a Java app with Swing, and I have trouble designing the GUI without a layout.
My purpose is to design a GUI with one JPanel and four JButtons. I've done the math to set buttons and panel on the right place and coded like the following:
import java.awt.*;
import javax.swing.*;
public class MainFrame extends JFrame {
public MainFrame() {
this.setTitle("Example Frame");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLayout(null);
JPanel randomPanel = new JPanel();
randomPanel.setOpaque(true);
randomPanel.setBackground(Color.RED);
randomPanel.setBounds(10, 10, 430, 530);
JButton addButton = new JButton("Add");
addButton.setBounds(10, 550, 100, 40);
addButton.setBackground(Color.GRAY);
JButton deleteButton = new JButton("Delete");
deleteButton.setBounds(120, 550, 100, 40);
deleteButton.setBackground(Color.GRAY);
JButton refreshButton = new JButton("Refresh");
refreshButton.setBounds(230, 550, 100, 40);
refreshButton.setBackground(Color.GRAY);
JButton devButton = new JButton("Developer");
devButton.setBounds(340, 550, 100, 40);
devButton.setBackground(Color.GRAY);
this.add(randomPanel);
this.add(addButton);
this.add(deleteButton);
this.add(refreshButton);
this.add(devButton);
this.setSize(900, 600);
this.setResizable(false);
this.setVisible(true);
}
public static void main(String[] args) {
new MainFrame();
}
}
Following to the code, the components are expected to be placed as following:
However, the actual form was displayed as following:
The components exceed the form, which does not match with the expected look.
What is the problem of this and what should be done for an accurate placement of components?
There are two main problems...
setLayout(null)
setSize
What you've not taken into account is the fact that the amount of space available to the content of the window, is the size of the window MINUS the frame decorations.
Pixel perfect layouts are an illusion in modern UI development and are best avoided.
You could have a look at:
What's wrong with the Null Layout in Java?
Why is it frowned upon to use a null layout in Swing?
Why null layout and absolute positions are bad practice in Java Swing?
for more details.
A better solution is to make use one or more available layout managers. The example below simply makes use of BorderLayout and GridLayout with the help of EmptyBorder to provide some padding
See Laying Out Components Within a Container for more details
Benefits
Adaptable layout:
The example uses pack to "pack" the window around the content, automatically, without you having to adapt your code to the currently running OS (or frame decorations provided by different look and feels)
The user can change the size of the window and the content will resize automatically - bonus to the user.
The layout will adapt to the user's system settings, so if they are using a font larger then you've designed for, it won't completely blow up in your face
Want to add more buttons? No worries, knock yourself out, just add more buttons, the layout will adapt automatically, no need to "pixel push" ever component on the screen
Runnable example...
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.EmptyBorder;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new BorderLayout());
setBorder(new EmptyBorder(10, 10, 10, 10));
add(new SizablePane(430, 530));
JPanel buttonPane = new JPanel(new GridLayout(1, 3, 20, 0));
buttonPane.setBorder(new EmptyBorder(10, 0, 0, 0));
buttonPane.add(new JButton("Add"));
buttonPane.add(new JButton("Delete"));
buttonPane.add(new JButton("Refresh"));
buttonPane.add(new JButton("Developer"));
add(buttonPane, BorderLayout.SOUTH);
}
}
public class SizablePane extends JPanel {
private Dimension size;
public SizablePane(int width, int height) {
size = new Dimension(width, height);
setBackground(Color.RED);
}
#Override
public Dimension getPreferredSize() {
return size;
}
}
}
Need to add more buttons? Easy...
JPanel buttonPane = new JPanel(new GridLayout(1, 0, 20, 0));
buttonPane.setBorder(new EmptyBorder(10, 0, 0, 0));
buttonPane.add(new JButton("Add"));
buttonPane.add(new JButton("Delete"));
buttonPane.add(new JButton("Refresh"));
buttonPane.add(new JButton("Developer"));
buttonPane.add(new JButton("Some"));
buttonPane.add(new JButton("More"));
buttonPane.add(new JButton("Buttons"));
I'm quite late, I don't think this will be helpful to OP anymore... But to anyone else in the same situation.
As others mentioned, when you setSize on a JFrame, that includes the title bar and borders. There's a way to get the size values for those, but... If you want to lay things out manually in your content pane, why not prepare a content pane first, then add it to the JFrame?
class MainPanel extends JPanel {
public MainPanel() {
setLayout(null);
setPreferredSize(new Dimension(900, 600));
// JFrame will have some layouting going on,
// it won't listen to setSize
JPanel randomPanel = new JPanel();
randomPanel.setOpaque(true);
randomPanel.setBackground(Color.RED);
randomPanel.setBounds(10, 10, 430, 530);
JButton addButton = new JButton("Add");
addButton.setBounds(10, 550, 100, 40);
addButton.setBackground(Color.GRAY);
JButton deleteButton = new JButton("Delete");
deleteButton.setBounds(120, 550, 100, 40);
deleteButton.setBackground(Color.GRAY);
JButton refreshButton = new JButton("Refresh");
refreshButton.setBounds(230, 550, 100, 40);
refreshButton.setBackground(Color.GRAY);
JButton devButton = new JButton("Developer");
devButton.setBounds(340, 550, 100, 40);
devButton.setBackground(Color.GRAY);
this.add(randomPanel);
this.add(addButton);
this.add(deleteButton);
this.add(refreshButton);
this.add(devButton);
}
public static void main(String[] args) {
JFrame mainFrame = new JFrame();
mainFrame.setTitle("Example Frame");
mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainFrame.setContentPane(new MainPanel());
mainFrame.pack();
mainFrame.setResizable(false);
mainFrame.setVisible(true);
}
}
If you mess with JFrame directly you're sort of bypassing the component system. Whereas this way, you're doing components just fine! Now, you have a JFrame fit to a single child panel, which has some things laid out manually.
This is how I normally do things, in such a situation.
P.S. "Don't lay things out manually, just use layout managers" is not something you can apply everywhere. You may need custom components sometimes, especially for something like a video game, where you have a game screen that you're custom rendering. Inside the game screen, you would be doing manual layout. They can coexist just fine, as long as you know which is which.
You need to override the getInsets() method of the underlying JFrame.
#Override
public Insets getInsets() {
return new Insets(0, 0, 0, 0);
}
Take a look at this question for more information.

how do you customise position of a JPanel within a Jpanel

The class extends JPanel,
public void createDisplay(){
frame = new JFrame();
frame.setTitle(title);
frame.setSize(new Dimension(width, height));
frame.setVisible(true);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setPreferredSize(new Dimension(width, height));
this.setMaximumSize(new Dimension(width, height));
this.setMinimumSize(new Dimension(width, height));
this.setLayout(null); //have tried default and BorderLayout
this.setSize(new Dimension(width, height));
this.setBounds(0, 0, width, height);
//basically trying everything
frame.add(this);
frame.pack();
}
on startup this code works fine and the JPanel completely covers the size of the Parent frame
However my program later tries to add a new JPanel to the class's extend JPanel with:
public void gameOverWindow(){ ;
JPanel panel = new JPanel(new BorderLayout());
panel.setPreferredSize(new Dimension(200, 100));
JLabel label = new JLabel("Game Over");
label.setPreferredSize(new Dimension(40, 15));
//trying setPosition also doesn't work with BorderLayout or FlowLayout
JButton button_01 = new JButton("new");
button_01.setPreferredSize(new Dimension(100, 10));
JButton button_02 = new JButton("exit");
button_02.setPreferredSize(new Dimension(100, 10));
panel.add(label, BorderLayout.NORTH);
panel.add(button_01, BorderLayout.WEST);
panel.add(button_02, BorderLayout.EAST);
this.add(panel);
this.revalidate();
}
This new JPanel appears with the contents within the correct BorderLayout format, however the JPanel itself will remain at the top center of the extended JPanel, I know this is because the default Layout is set to FlowLayout, however setting this to BorderLayout will just cause the panel to take up the entire screen. Setting the Layout to null completely breaks the frame and nothing appears but the Minimize and Close buttons of the Frame. Trying to set the position or Bounds of this JPanel doesn't work with any Layout either. I have read a lot of other post online about this but they all seem to differ and become confusing, how do I gain control of the position of my new JPanel?
Normally I'd recommend using a CardLayout for switching between different views, but it's difficult to ascertain from the available information if that would help or not
Instead, you could make use of compounding layouts. That is wrap one container in another using different layouts.
In this example, I simply use a combination of BorderLayout and GridBagLayout
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new BorderLayout());
JButton gameOver = new JButton("Game over man");
gameOver.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
JPanel panel = new JPanel(new BorderLayout());
panel.add(new JLabel("Game Over Man", JLabel.CENTER), BorderLayout.NORTH);
panel.add(new JButton("New"), BorderLayout.WEST);
panel.add(new JButton("Exit"), BorderLayout.EAST);
removeAll();
JPanel inner = new JPanel(new GridBagLayout());
inner.add(panel);
add(inner);
revalidate();
repaint();
}
});
JPanel inner = new JPanel(new GridBagLayout());
inner.add(gameOver);
add(inner);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
}
what is the purpose of removing the components of a different panel instead of just directly adding it to GridBagLayout?
Because they interfere with the layout of other components.
i then want a small Jpanel to popup within and be unobtrusive
You could make use of the frame's glassPane or use a OverlayLayout
For example:
Floating JPanel above a JPanel with BorderLayout
Rectangle is not drawn on top
Placing a marker within the image
Display a message on the screen
Much of this information should have been in your original question, it would have wasted less of each other's time

JFrame loses transparency trait once components added

I want to make an overlay window that has a transparent frame with non transparent children. I was successful when making the transparent window and even was successful when adding a test child to see if it work. But as soon as I replace that test code with my paintComponent... I get a white background. Anyone know why?
package blahh;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;;
public class LoadingFrame {
public static void main(String[] args) {
LoadingFrame Class = new LoadingFrame();
Class.frameChar();
}
public void frameChar(){
JFrame frame = new JFrame();
B b = new B();
frame.setUndecorated(true);
frame.setBackground(new Color(0, 0, 0, 0));
frame.setAlwaysOnTop(true);
frame.setPreferredSize(new Dimension(500, 500));
frame.getContentPane().setLayout(new java.awt.BorderLayout());
frame.setLocationRelativeTo(null);
frame.setVisible(true);
//frame.getContentPane().add(new JLabel("test code");
frame.add(b);
frame.pack();
}
public class B extends JPanel{
public void paintComponent(Graphics g){
super.paintComponent(g);
g.drawRect(0, 0, 100, 100);
}
}
}
JPanel is opaque by default, combined with BorderLayout, this will make it cover the entire frame, making it appear opaque. You need to call setOpaque(false) on it to make it see through
JFrame frame = new JFrame();
B b = new B();
b.setOpaque(false);
frame.setUndecorated(true);
frame.setBackground(new Color(0, 0, 0, 0));
frame.setAlwaysOnTop(true);
// I'd prefer to override getPreferredSize of B
frame.setPreferredSize(new Dimension(500, 500));
frame.setLocationRelativeTo(null);
frame.add(b);
frame.pack();
frame.setVisible(true);
ps- You should also call setVisible last where possible, it just reduces the possibility of your components not showing up randomly

Strange JFrame size

So I set the setSize(500,500).. add some panels, the sum of panels Y is 500 like the JFrame but executing it shows an count Y of 525 am I missing something?
JPanel panel = new JPanel();
panel.setLayout(null);
getContentPane().add(panel);
//--------------------
JPanel top_panel = new JPanel();
top_panel.setLayout(null);
top_panel.setBackground(Color.blue);
top_panel.setBounds(0, 0, 500, 40);
panel.add(top_panel);
//------------------------------
JPanel middle_panel = new JPanel();
middle_panel.setLayout(null);
middle_panel.setBackground(Color.yellow);
middle_panel.setBounds(0, 40, 500, 385);
panel.add(middle_panel);
//-----------------------------
JPanel bottom_panel = new JPanel();
bottom_panel.setLayout(null);
bottom_panel.setBackground(Color.black);
bottom_panel.setBounds(0, 425, 500, 75);
panel.add(bottom_panel);
setSize(500,500);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setResizable(false);
setLocationRelativeTo(null);
40+385+75 = 500 but to show all the panels i must
setSize(500,525);
then it fits
here's an image:
The frame size is the light blue rectangle outside bounds including the title bar. Your panels are appearing in the inner bounds which is frame size less than the frame border and frame title bar. Do you see how your marked space at the bottom is strangely the same height as the title bar?
After adding your panels/component to the frame and just before calling frame.setVisible(true), call frame.pack().
It would be also preferable if you embrace a layout manager (such as FlowLayout) and when necessary call setPreferredSize and let the layout manager do the layout. Normally one would call setPreferredSize over setBound, setSize, setMininumSize, setMaximumSize.
import javax.swing.*;
import java.awt.*;
public class FrameSize {
private JFrame frame;
FrameSize create() {
frame = createFrame();
frame.getContentPane().add(createContent());
return this;
}
private JFrame createFrame() {
JFrame frame = new JFrame(getClass().getName());
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
return frame;
}
void show() {
// frame.setSize(500, 500);
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.pack();
frame.setVisible(true);
}
private Component createContent() {
JPanel panel = new JPanel(null);
JPanel topPanel = new JPanel(null);
topPanel.setBackground(Color.blue);
topPanel.setBounds(0, 0, 500, 40);
panel.add(topPanel);
JPanel middlePanel = new JPanel(null);
middlePanel.setBackground(Color.yellow);
middlePanel.setBounds(0, 40, 500, 385);
panel.add(middlePanel);
JPanel bottomPanel = new JPanel(null);
bottomPanel.setBackground(Color.black);
bottomPanel.setBounds(0, 425, 500, 75);
panel.add(bottomPanel);
panel.setPreferredSize(new Dimension(500, topPanel.getBounds().height + middlePanel.getBounds().height + bottomPanel.getBounds().height));
return panel;
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new FrameSize().create().show();
}
});
}
}
You shouldn't be setting size or calling setSize(...) or setBounds(...) as that's setting you up for similar problems in the future, or worse problems when you try to show your GUI on a different platform. Instead let the preferredSizes of your components and the layout managers do this work for you. If you absolutely must set the size of a component, then override getPreferredSize() and return a Dimension that is calculated to work for you. And yes, as per javajon, you should call pack() on the JFrame before displaying it.
For more discussions on the null layout, please read what one of the best Swing experts on this site, MadProgrammer, has to say in his answer here.

Categories

Resources