When does a JPanel paint (or repaint) its child components? - java

I've got a JButton which is painted using a custom UI delegate (CustomButtonUI extends BasicButtonUI). The CustomButtonUI's paint() method draws the button with rounded "antialiased" corners, to make the apperance as "smooth" as possible.
Somehow the "antialiased" edges of the button disappears each time i drag the mouse over the
button. This makes the button edges look "pixelized". However, once I add a line of code to repaint the parent of the button, the antialiasing kicks in even when i drag the mouse over the button.
Now, my question relates to wether this is a good idea? I do after all repaint the parent component from a child component. I wonder if this lead to a loop of repaints? If the parent
tries to repaint its children and the children tries to repaint its parent - then i assume we're talking about a loop.
I've attached my code as a reference. Any comments are very welcome!
public class JCustomButtonUI extends BasicButtonUI {
#Override
public void installUI(JComponent c) {
super.installUI(c);
AbstractButton b = (AbstractButton) c;
b.setBorderPainted(false);
}
#Override
public void paint(Graphics g, JComponent c) {
//Cast the Graphics instance to a Graphics2D instance.
Graphics2D g2d = (Graphics2D) g;
JButton b = (JButton) c;
//Repaint parent component to make sure that we get "antialiased"
//edges.
b.getParent().repaint();
//Get the component's height and width.
int w = (int) g.getClipBounds().getWidth();
int h = ((int) g.getClipBounds().getHeight());
//Make sure the button is drawn with "antialiased" edges.
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(Color.GRAY);
g2d.fillRoundRect(0, 0, w, h, w, h);
}
}
Update 1
Just to illustrate the alias and antialiased border, please have a look at the below two pictures. When i (from the ButtonUI's paint() method) manually invoke the parent JPanel's repaint method, all borders are perfectly antialiased all the time. However, when i do not manually invoke the parent JPanel's repaint method, then the borders are no longer antialiased once i hoover the mouse over the button.
Update 2
I have shared the entire "component" which consists of a JPanel, a JSlider and a couple of JButtons on Snipt. Please get it from http://snipt.org/wnllg.
Update 3
It seems that i have managed to get it working. Instead of painting the JPanel's background in its paintComponent() method, i created a JCustomPanelUI which i installed on the JPanel. I don't think that was the solution itself, but instead of using width and height from the Graphics instance, I tried using widht and height from the JPanel itself. I'm not quite sure why things go wrong when i use width and height from the Graphics instance. I thought the width and height from the Graphics instance was already "prepared" with regard to dimensions from the JPanel component. You can have a look at the final component here: http://snipt.org/wnlli,

I've reduced the example to just the anti-aliasing, and I am unable to reproduce the problem. It doesn't appear to be platform dependent. I'm not sure why you are using getClipBounds().
Addendum:
The JPanel background (a gradient) needs to shine through…
I've update the example to use a gradient background behind a transparent button; I've put anti-aliased (left) and aliased (right) examples side-by-side. I see no unexpected behavior.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.RenderingHints;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.plaf.basic.BasicButtonUI;
/** #see http://stackoverflow.com/questions/5169647 */
public class ButtonUITest extends JPanel {
public ButtonUITest() {
this.setLayout(new GridLayout(1, 0));
this.setPreferredSize(new Dimension(640, 480));
this.add(new CustomButton(true));
this.add(new CustomButton(false));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int w = this.getWidth();
int h = this.getHeight();
Graphics2D g2d = (Graphics2D) g;
g2d.setPaint(new GradientPaint(0, 0, Color.blue, w, h, Color.red));
g2d.fillRect(0, 0, w, h);
}
private void display() {
JFrame f = new JFrame("ButtonUITest");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
private static class CustomButton extends JButton {
public CustomButton(boolean antialiased) {
this.setOpaque(false);
this.setUI(new CustomButtonUI(antialiased));
}
}
private static class CustomButtonUI extends BasicButtonUI {
private boolean antialiased;
public CustomButtonUI(boolean antialiased) {
this.antialiased = antialiased;
}
#Override
public void paint(Graphics g, JComponent c) {
int w = c.getWidth();
int h = c.getHeight();
Graphics2D g2d = (Graphics2D) g;
if (antialiased) {
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
}
g2d.setColor(Color.LIGHT_GRAY);
g2d.fillOval(0, 0, w, 2 * h);
}
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new ButtonUITest().display();
}
});
}
}

For antialiasing to work consistently, your component needs to return false for isOpaque. Otherwise, the RepaintManager is free to skip painting the region behind your component.
I suspect that if you use a screen magnifier to look at the "unaliased" edges, you will find they really are antialiased. But it was done against against a black, unpainted background, not the parent's background.

The Swing experts who know what they're talking about will be here shortly. In the meantime, let me comment on this:
Now, my question relates to wether
this is a good idea? I do after all
repaint the parent component from a
child component. I wonder if this lead
to a loop of repaints? If the parent
tries to repaint its children and the
children tries to repaint its parent -
then i assume we're talking about a
loop.
Once you try it out and see that it's not a problem on your machine, chances are that it will be true on all JVMs you try. That is to say, the proof is in the pudding, or random bumbling does generally lead to positive results in Swing. Recursive loops have a way of causing the program to halt pretty quickly in Java, so the answer is... if this were totally wrong you'd already know. Plus you can put sysouts in there to see if this is happening (it's obviously not).
That said, there may be a better way to deal with your problem, but if your answer works, stick with it for now.

AFAIK, repaint() just tells the system to repaint that component in the next paint cycle, i.e.
if you call repaint() in a paint() method the repaint might be done in the next cycle.
Generally, Swing runs in its own thread, so repeated repaint should not stop the application logic. However, it might be using processing power, if the system always repaints the ui even if there are no changes.
You could try and write a log message to see when and how often the button is painted.
If that happens continously even if nothing happens that would cause a ui update, you might have to find a solution. If not, you should be fine.
Edit: there is a class called RepaintManager that you might find interesting.

Doing a repaint in there is definitely a bad idea. Try adding b.setOpaque(false) in installUI(). Your button is no longer painting its entire bounds because of the antialiasing. You need to let the background show through to fill the gaps.

Related

How to add new Label(png) to GUI in the controller class? [duplicate]

I am new to making GUIs so I decided to try the the windows builder for eclipse, and while great I do have some doubts. I have been searching but I cannot seen to find a good way to add a background image to my "menu". For example I tried this:
public Menu() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(50, 50, 300, 250); //Dimensiones
contentPane = new JPanel() { //Imagen de Fondo
public void paintComponent(Graphics g) {
Image img = Toolkit.getDefaultToolkit().getImage(
Menu.class.getResource("/imgs/rotom.jpg"));
g.drawImage(img, 0, 0, this.getWidth(), this.getHeight(), this);
}
};
And adding the following classes:
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
But to no avail the window remains with its dull grey color, so far my code is just the standard one WindowsBuilder cooks for you plus 4 buttons but I doubt they're of importance here. Shouldn't the code I added override the paintComponent() method of the jPanel and draw the image in it?
The class for the menu is in a package within my project and the image is within a imgs package is within the same project as well.
Thanks a lot in advance.
A simple method, if you're not interested in resizing the background image or applying any effects is to use a JLabel...
BufferedImage bg = ImageIO.read(Menu.class.getResource("/imgs/rotom.jpg"));
JLabel label = new JLabel(new ImageIcon(bg));
setContentPane(label);
setLayout(...);
There are limitations to this approach (beyond scaling), in that the preferred size of the label will always be that of the image and never take into account it's content. This is both good and bad.
The other approach, which you seem to be using, is to use a specialised component
public class BackgroundPane extends JPanel {
private BufferedImage img;
public BackgroundPane(BufferedImage img) {
this.img = img;
}
#Override
public Dimension getPreferredSize() {
return img == null ? super.getPreferredSize() : new Dimension(img.getWidth(), img.getHeight());
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(img, 0, 0, this);
}
}
You should avoid trying to perform any task in the paintComponent method which may take time to complete as paintComponent may be called often and usually in quick succession....
Getting the image to scale when the component is resized is an entire question into of it self, for some ideas, you could take a look at...
Java: maintaining aspect ratio of JPanel background image
Java: JPanel background not scaling
Quality of Image after resize very low -- Java
Reading/Loading images
Oh, and, you should avoid extending directly from top level containers, like JFrame, they reduce the reusability for your components and lock you into a single container

Java rotation of a JLabel using arbitrary angles [duplicate]

I wonder if it is possible to implement a GUI panel (possibly JPanel) that is of square shape but rotated 90 degrees. Obviously, there will be a top-level container which contains this panel, and visually the main panel is this rotated square panel within.
More specifically, I would divide a panel (called 'A') into 4 equal square sub-panels, and fill these sub-panels with JLabels, for which I am thinking to use GridLayout. And lastly, I would rotate 'A' 90 degrees to give what I want.
From my reading of other similar questions, it seems that you cannot rotate JPanel itself, but you can rotate what is contained within. Is this applicable to my case here? Would appreciate if someone could point out. Thanks.
The critical thing seems to be painting the components after rotating the graphics context. Here's an example:
Addendum 1:As #Atreys comments, the rotated components are drawn, but interact poorly. If the components must remain usable, event coordinates should also be transformed. Compare this (considerably) more complex example that mirrors components.
Addendum 2: If you also need to transform the mouse coordinates, this example may be helpful.
Addendum 3: Alternatively, consider the drawString() examples examined here.
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
/** #see https://stackoverflow.com/questions/6333464 */
public class RotatePanel extends JPanel {
public RotatePanel() {
this.setPreferredSize(new Dimension(320, 240));
this.add(new JLabel("Hello World!", JLabel.CENTER));
}
#Override
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
int w2 = getWidth() / 2;
int h2 = getHeight() / 2;
g2d.rotate(-Math.PI / 2, w2, h2);
super.paintComponent(g);
}
private void display() {
JFrame f = new JFrame("RotatePanel");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new RotatePanel().display();
}
});
}
}
Check out JXTransformer in the SwingHelper project over at java.net. This class acts as a component decorator that allows you to apply an arbitrary affine transform to a component.
Yes, you would have to have the top-level container (JPanel or other container) be the item that rotates the contents. Really you aren't rotating the items, you are rotating to the painting of the items.
If all you need to do is rotate the text on a JLabel you could use a Rotated Icon, then you don't have to worry about rotating the panel.

Add GUI with Swing, but unsure of the sequence to take

I'm building a GUI for a data processing algorithm. I can instantiate the window, give it a background, title, etc., but when I try adding panels to it, I run into trouble. What I'm really looking for more than a proofreader is a suggestion for the sequence in which to build, configure, and add objects in Java Swing so that they behave correctly, in a generic sense. So, is this the best way to build a JFrame with a different-colored panel in it?
Declare JFrame
Set JFrame color (background color)
Declare JPanel (box to represent data graphically)
Set JPanel color (box color)
Add JPanel to JFrame
Set JFrame to visible = true
It makes sense intuitively but it doesn't seem to work, no matter what I do. I've found step-by-step instructions elsewhere but they tend to explain what to type more than why you're typing it, so you get a very narrow understanding of what's going on. Thanks for any help!
Below is the full code; I hesitated to post it because I'd begun experimenting with Graphics2D and it isn't well-commented, but if it helps:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import javax.swing.*;
import javax.swing.border.TitledBorder;
public class GUI extends JFrame
{
JFrame mainWindow = new JFrame();
JPanel backgroundPanel = new JPanel();
JPanel subPanel = new JPanel();
Color background = new Color(40,40,40);
Color subWindow = new Color(255, 255, 255);
TitledBorder title = BorderFactory.createTitledBorder("title");
Rectangle rect1 = new Rectangle(10, 10, 40, 40);
Graphics2D g;
public static void main (String[] args)
{
new GUI();
}
public GUI()
{
initializeGUI();
}
private void initializeGUI()
{
mainWindow.setSize(1340, 880);
backgroundPanel.setBackground(background);
subPanel.setBackground(subWindow);
subPanel.setBorder(title);
mainWindow.setTitle("Ed");
mainWindow.setLocationRelativeTo(null);
mainWindow.setDefaultCloseOperation(EXIT_ON_CLOSE);
mainWindow.add(backgroundPanel);
backgroundPanel.add(subPanel);
updateGUI();
}
public void updateGUI()
{
mainWindow.setVisible(false);
mainWindow.setVisible(true);
}
public void paintComponent(Graphics g)
{
this.g.setColor(subWindow);
this.g.fill(rect1);
this.g = (Graphics2D) g;
}
}
Let's break this down....
public class GUI extends JFrame {
JFrame mainWindow = new JFrame();
There is no need to extend from JFrame as you are neither using it nor are you adding any value to the class.
This...
public void paintComponent(Graphics g) {
this.g.setColor(subWindow);
this.g.fill(rect1);
this.g = (Graphics2D) g;
}
is doing nothing and will never be called, as nothing you've extended from implements a paintComponent method (that is, JFrame does not have a paintComponent methd) (and you class is not attached to anything displayed on the screen anyway). Also, you should NEVER maintain a reference to ANY Graphics context you did not create yourself.
The reason that subPanel is appearing so "small" is because it has not definable size, aside from the border.
You could rectify this in one of three ways...
You could change the layout manager of backgroundPanel to something like BorderLayout
You could override the getPreferredSize method of the subPanel to return a more suitable size or
You could add other components to it and let the layout manager figure it out...
In any case, you should have a look at Laying Out Components Within a Container.
You should also have a look at Painting in AWT and Swing and Performing Custom Painting for more details about how painting is done in Swing

Java Canvas Won't Paint

I want my program to display the canvas that is repainted once at the start and then whenever a change is made afterwards. I thought I had everything coded correctly, but for some reason nothing that is painted onto the canvas actually shows (I know it's repainting, I tested that).
Here are the code segments:
public TileMapCreator()
{
currentView = new BufferedImage(640, 640, BufferedImage.TYPE_INT_ARGB);
currentView.getGraphics().setFont(new Font("Arial", Font.BOLD, 100));
currentView.getGraphics().drawString("No Map Yet Open", currentView.getWidth()/2, currentView.getHeight()/2);
this.setJMenuBar(createMenuBar());
this.setContentPane(createMapPanel());
}
private JPanel createMapPanel()
{
JPanel p = new JPanel();
p.add(setUpCanvas());
p.setVisible(true);
return p;
}
private Canvas setUpCanvas()
{
mapCanvas = new Canvas(){
private static final long serialVersionUID = 1L;
public void repaint()
{
mapCanvas.getGraphics().drawImage(currentView, 0, 0, this);
}
};
mapCanvas.setIgnoreRepaint(true);
Dimension size = new Dimension(currentView.getWidth(), currentView.getHeight());
mapCanvas.setSize(size);
mapCanvas.setPreferredSize(size);
mapCanvas.setMaximumSize(size);
mapCanvas.setMinimumSize(size);
mapCanvas.setFocusable(true);
mapCanvas.addMouseListener(this);
mapCanvas.addMouseMotionListener(this);
mapCanvas.setVisible(true);
return mapCanvas;
}
Currently the area where the canvas should be painting is just the regular grey color of the Java GUI. Thanks for your help!
You appear to be mixing Swing with AWT components, and drawing in a very strange way, one that I've honestly never seen before (and I've seen a lot). Why not simply do your drawings in the paintComponent(Graphics g) method of a JPanel using the Graphics object given by the JVM, like you'll find in the Swing graphics tutorials and 98% of the Swing graphics answers on this site? Also for my money, I'd avoid using Canvas or trying to mix heavy and light weight components together. Stick with Swing all the way, and things should go more smoothly.
I'd be happy to give you more specific advice and perhaps some if you could create and post a minimal example program. Please have a look at the link and let us know if you need more information.
For example:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import javax.swing.*;
public class TestImagePanel extends JPanel {
private static final int BI_WIDTH = 640;
BufferedImage currentView = new BufferedImage(BI_WIDTH, BI_WIDTH, BufferedImage.TYPE_INT_ARGB);
public TestImagePanel() {
Graphics g = currentView.getGraphics();
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g.setColor(Color.black);
g.setFont(new Font("Arial", Font.BOLD, 60));
g.drawString("No Map Yet Open", 20, currentView.getHeight()/2);
g.dispose();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (currentView != null) {
g.drawImage(currentView, 0, 0, this);
}
}
#Override
public Dimension getPreferredSize() {
if (currentView != null) {
return new Dimension(BI_WIDTH, BI_WIDTH);
}
return super.getPreferredSize();
}
private static void createAndShowGui() {
TestImagePanel mainPanel = new TestImagePanel();
JFrame frame = new JFrame("TestImagePanel");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
I've found the Swing tutorials to be a great asset, and bet you would too. Please have a look at them.
Edit
You ask:
Hmm, so apparently getting the graphics object of my buffered image twice did not result in anything actually being painted... I had to get the graphics object, assign it to a variable, and then paint.
I wouldn't say that. I'd say that your way of getting the Graphics object should work the same as mine, but yours is not safe since the Graphics objects obtained in this way cannot be disposed of, and you risk running out of resources. I think that your image didn't show up due to your very convoluted and unusual way of trying to display your image. You override repaint(), put some weird code inside of it, tell the JVM to ignore repaint calls, never call the repaint super method inside of your override, so that repaint does in fact nothing. I have to wonder -- where did you get this code? Was it from a tutorial? If so, please share the link here. And never get advice from that site again.

Drawing graphics in java - NetBeans IDE

I created a new JApplet form in NetBeans:
public class UI extends javax.swing.JApplet {
//generated code...
}
And a JPanel in design mode named panou:
// Variables declaration - do not modify
private javax.swing.JPanel panou;
How do I get to draw a line on panou? I've been searching for this for 5 hours now so a code snippet and where to place it would be great. Using Graphics2D preferably.
Go to design mode
Right Click on the panel "panou"
Click "Costumize code"
In the dialog select in the first combobox "costum creation"
add after = new javax.swing.JPanel() this, so you see this:
 
panou = new javax.swing.JPanel(){
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g); // Do the original draw
g.drawLine(10, 10, 60, 60); // Write here your coordinates
}
};
Make sure you import java.awt.Graphics.
The line that you will see is always one pixel thick. You can make it more "line" by doing the following:
Create this method:
public static final void setAntiAliasing(Graphics g, boolean yesno)
{
Object obj = yesno ? RenderingHints.VALUE_ANTIALIAS_ON
: RenderingHints.VALUE_ANTIALIAS_OFF;
((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, obj);
}
And add after super.paintComponent(g); (in your costum creation) this:
setAntiAlias(g, true);
Edit:
What you are doing wrong is: you paint the line once (by creating the frame).
When you paint the line the frame is also invisible. The first draw is happening when the frame becomes visible. The frame will be Repainted, so everything from the previous paint will disappear.
Always you resize the frame, everything will be repainted. So you have to make sure each time the panel is painted, the line also is painted.
To do custom painting in a JPanel, one would need to make a subclass of a JPanel, and then overload the paintComponent method:
class MyPanel extends JPanel {
public void paintComponent(Graphics g) {
// Perform custom painting here.
}
}
In the example above, the MyPanel class is a subclass of JPanel, which will perform whatever custom painting is written in the paintComponent method.
For more information on how to do custom painting in Swing components, Lesson: Performing Custom Painting from The Java Tutorials have some examples.
If one wants to do painting with Java2D (i.e. using Graphics2D) then one could do some painting on a BufferedImage first, then draw the contents of the BufferedImage onto the JPanel:
class MyPanel extends JPanel {
BufferedImage image;
public MyPanel() {
Graphics2D g = image.createGraphics();
// Do Java2D painting onto the BufferedImage.
}
public void paintComponent(Graphics g) {
// Draw the contents of the BufferedImage onto the panel.
g.drawImage(image, 0, 0, null);
}
}
Further reading:
Painting in AWT and Swing
Trail: 2D Graphics

Categories

Resources