In the following reproducer, I am reproducing what I'm seeing in the graphical interface.
I'd like to repaint a dirty region of a canvas (a JPanel), because a full repaint introduces more latencies.
However when the repaint (dirty rectangle) function is invoked, it causes some issues with nested Graphics2D objects. In particular
This code may mis-use clip bounds, but I wasn't expecting this "artifact" upon the creation of the sub (nested) graphic context.
What would be the proper way to deal with clips, nested graphic contexts, and the dirty region (or rectangle) repaint in this situation.
My intuition tells me the code should compute its shape without using the clipping area.
import javax.swing.*;
import java.awt.*;
import java.awt.geom.Rectangle2D;
public class Reproducer {
public static void main(String[] args) {
var comp = new MyJPanel();
var timer = new Timer(2_000, e -> {
SwingUtilities.invokeLater(comp::bug);
});
timer.start();
timer.setRepeats(true);
SwingUtilities.invokeLater(() -> {
var frame = new JFrame("Reproducer");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(new Dimension(300, 300));
frame.setVisible(true);
frame.getContentPane().add(comp);
});
}
private static class MyJPanel extends JPanel {
public MyJPanel() {
setSize(600, 600);
setPreferredSize(getSize());
}
#Override
protected void paintComponent(Graphics g) {
var g2 = (Graphics2D) g;
super.paintComponent(g2);
g2.setColor(Color.cyan);
g2.fill(g2.getClip());
var graphicsBox = (Graphics2D) g2.create(10, 10, 290, 290);
var clipBounds = graphicsBox.getClipBounds();
graphicsBox.setColor(Color.LIGHT_GRAY);
graphicsBox.setStroke(new BasicStroke(2));
graphicsBox.drawRect(clipBounds.x, clipBounds.y, clipBounds.width, clipBounds.height);
graphicsBox.setColor(Color.red);
graphicsBox.fill(new Rectangle2D.Double(clipBounds.x + 2,
clipBounds.y + 2,
clipBounds.width - 2,
clipBounds.height - 2));
graphicsBox.dispose();
var graphicsOverlay = (Graphics2D) g2.create(100, 100, 100, 100);
graphicsOverlay.setColor(Color.YELLOW);
graphicsOverlay.fillOval(10, 10, 60, 60);
graphicsOverlay.dispose();
}
public void bug() {
getVisibleRect();
repaint(100, 100, 100, 100);
}
}
}
EDIT by moving the fill above drawRect
So, there are a couple of "mistakes"
I found this...
graphicsBox.drawRect(clipBounds.x, clipBounds.y, clipBounds.width, clipBounds.height);
graphicsBox.setColor(Color.red);
graphicsBox.fill(new Rectangle2D.Double(clipBounds.x + 2,
clipBounds.y + 2,
clipBounds.width - 2,
clipBounds.height - 2));
to be a little erroneous, which meant when a clip was applied, the background area was not been filled properly.
The call to super.paintComponent was filling the clip area with the background color of the component, but graphicsBox.fill wasn't filling the entire area, allow part of the background color to be seen.
I also found the result of Graphics2D#create(int, int, int, int) to be a little surprising
The new Graphics object has its origin translated to the specified point (x, y). Its clip area is determined by the intersection of the original clip area with the specified rectangle.
(emphasis added by me)
I mean, honestly, I shouldn't be surprised, but I've just never used these features before so it took me a little by surprise.
So, I corrected the graphicsBox rendering code so that the fill will fill the entire clipping area first and THEN the rectangle is drawn (within the bounding rectangle)
var graphicsBox = (Graphics2D) g2.create(10, 10, 290, 290);
var clipBounds = graphicsBox.getClipBounds();
graphicsBox.setColor(Color.red);
graphicsBox.fill(new Rectangle2D.Double(clipBounds.x,
clipBounds.y,
clipBounds.width,
clipBounds.height));
Collections.shuffle(colors);
graphicsBox.setColor(colors.get(0));
graphicsBox.setStroke(new BasicStroke(2));
graphicsBox.drawRect(clipBounds.x + 1, clipBounds.y + 1, clipBounds.width - 2, clipBounds.height - 2);
graphicsBox.dispose();
nb: The reference to colors is just a list of Colors which I randomise during the paint process, so I can "see" what's going on
Another trick might be to change the background color of the component to make it easier to "see" where things are been updated incorrectly.
When you have issues like this, it's really important to reduce the amount of clutter to see if you can narrow down the problem, I took out most of the paint code to figure out all these "little" things which were combining to give you issues.
Runnable example...
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class Main {
public static void main(String[] args) {
var comp = new MyJPanel();
var timer = new Timer(2_000, e -> {
SwingUtilities.invokeLater(comp::bug);
});
timer.start();
timer.setRepeats(true);
SwingUtilities.invokeLater(() -> {
var frame = new JFrame("Reproducer");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(new Dimension(300, 300));
frame.setVisible(true);
frame.getContentPane().add(comp);
});
}
private static class MyJPanel extends JPanel {
private List<Color> colors;
public MyJPanel() {
setSize(600, 600);
setPreferredSize(getSize());
colors = Arrays.asList(new Color[] {
Color.BLACK,
Color.BLUE,
Color.CYAN,
Color.DARK_GRAY,
Color.GRAY,
Color.GREEN,
Color.LIGHT_GRAY,
Color.MAGENTA,
Color.ORANGE,
Color.PINK,
Color.RED,
Color.WHITE,
Color.YELLOW
});
}
#Override
protected void paintComponent(Graphics g) {
var g2 = (Graphics2D) g;
super.paintComponent(g2);
g2.setColor(Color.cyan);
g2.fill(g2.getClip());
var graphicsBox = (Graphics2D) g2.create(10, 10, 290, 290);
var clipBounds = graphicsBox.getClipBounds();
graphicsBox.setColor(Color.red);
graphicsBox.fill(new Rectangle2D.Double(clipBounds.x,
clipBounds.y,
clipBounds.width,
clipBounds.height));
Collections.shuffle(colors);
graphicsBox.setColor(colors.get(0));
graphicsBox.setStroke(new BasicStroke(2));
graphicsBox.drawRect(clipBounds.x + 1, clipBounds.y + 1, clipBounds.width - 2, clipBounds.height - 2);
graphicsBox.dispose();
var graphicsOverlay = (Graphics2D) g2.create(100, 100, 100, 100);
graphicsOverlay.setColor(Color.YELLOW);
graphicsOverlay.fillOval(10, 10, 60, 60);
graphicsOverlay.dispose();
}
public void bug() {
getVisibleRect();
repaint(100, 100, 100, 100);
}
}
}
If you're having "performance" issues, you might consider using a BufferedImage as the primary canvas and update it, then simply paint the image.
Alternatively, it might be time to move over to using BufferStrategy, this is going to get you as close to the "metal" as Java/Swing allows
Related
println in the paintComponent prints out 497 971. in the view of JPanel, the red line's left-up point is supposed to be somewhere near JPanel's middle according that number pair, but in fact it's not. Is it caused by coordinate system conversion?
Thanks in advance.
Code shows below:
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Calendar;
import javax.imageio.ImageIO;
import javax.swing.*;
public class ClockFrame extends JFrame {
JPanel panel;
public ClockFrame(){
panel = new JPanel();
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
add(panel);
setSize(1000, 1000);
panel.setSize(getWidth(), getHeight());
//panel.setLayout(null);//!important
panel.setLayout(new GridLayout());
setVisible(true);
setResizable(false);
panel.setBackground(Color.BLACK);
Hand sHand=new Hand(panel);
panel.add(sHand);
}
class Hand extends JComponent{
private Timer timer;
public Hand(Object o){
setLocation(500,500);
((JPanel)o).add(this);
timer = new Timer(800, new ActionListener() {
public void actionPerformed(ActionEvent e) {
repaint();
}
});
timer.start();
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.RED);System.out.println(panel.getWidth()/2 +" "+panel.getHeight());
g2d.drawLine(panel.getWidth()/2, panel.getHeight()/2, 30, 30);
g2d.dispose();
}
public Dimension getPreferredSize() {
return new Dimension(600, 600);
}
}
public static void main(String[] a) {
ClockFrame c=new ClockFrame();
}
}
g2d.drawLine(0, 0, 100, 50);
g2d.setColor(Color.WHITE); // no good setting the color now!
Should be:
g2d.setColor(Color.WHITE); // correct
g2d.drawLine(0, 0, 100, 50);
Other tips:
Better to pack() the frame after all components are added. Then it will be the exact right size to display them. Setting a top-level container visible should be the last thing in the constructor (in the vast majority of cases).
setSize(1000, 1000);
The location of the component is best determined by the layout manager, padding and borders.
public Hand(Object o){
setLocation(500,500);
If the component needs to be added to a container, better to pass it as a container. Having said that, probably best not to pass the container in the constructor at all, but instead to add(..) it in the code immediately after where it is instantiated.
public Hand(Object o){
// ..
((JPanel)o).add(this);
Some of those concepts implemented in the code below. Be sure to run it to see the effect.
Note especially:
/* Note that this is translating further drawing operations
to the middle of the Hand container based on its preferred size,
which is (layout manager not withstanding) also its actual size.
All co-ordinates in custom painting are relative to the component
being painted. */
g2d.translate(middle, middle);
Code
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Line2D;
import java.util.Calendar;
import java.util.Date;
import javax.swing.*;
public class ClockFrame extends JFrame {
public ClockFrame() {
JPanel panel = new JPanel();
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
add(panel);
panel.setSize(getWidth(), getHeight());
//panel.setLayout(null);//!important
panel.setLayout(new GridLayout());
setResizable(false);
panel.setBackground(Color.BLACK);
Hand sHand = new Hand(panel);
panel.add(sHand);
pack();
setVisible(true);
}
class Hand extends JComponent {
private Timer timer;
private Dimension preferredSize = new Dimension(600, 600);
private Calendar currentTime;
public Hand(Object o) {
setLocation(500, 500);
((JPanel) o).add(this);
currentTime = Calendar.getInstance();
timer = new Timer(50, new ActionListener() {
public void actionPerformed(ActionEvent e) {
currentTime.setTime(new Date(System.currentTimeMillis()));
repaint();
}
});
timer.start();
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
System.out.println("x: " + this.getX() + " y: " + this.getY() + " w: " + this.getWidth() + " h: " + this.getHeight());
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.WHITE);
double angle = (currentTime.get(Calendar.SECOND)*2*Math.PI)/60d;
double middle = preferredSize.getWidth() / 2d;
/* Note that this is translating further drawing operations
to the middle of the Hand container based on its preferred size,
which is (layout manager not withstanding) also its actual size.
All co-ordinates in custom painting are relative to the component
being painted. */
g2d.translate(middle, middle);
Line2D.Double secondHand = new Line2D.Double(0, 0,
middle*.9*Math.cos(angle),
middle*.9*Math.sin(angle));
g2d.draw(secondHand);
g2d.dispose();
}
public Dimension getPreferredSize() {
return preferredSize;
}
}
public static void main(String[] a) {
ClockFrame c = new ClockFrame();
}
}
Edit: to include minute and hour hands
I find that after second hand if I add minute hand, my panel will be twice wide - I think it's because graphics cannot be superimposed...- and what I get is that the two graphics2D are separated far away from each other and repaint themselves..any good ideas to resolve this?
Because I was bored, I played around with the code a little more. I discovered the angle was off, so put an offset to correct it. Then I added minute and hour hands to that same custom component. I think the latter is the cause of the problem you describe (if not, show your latest code - though perhaps in a new question).
Try this version (note that second, minute and hour hands are all quantized to their respective time units):
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Line2D;
import java.io.IOException;
import java.net.URL;
import java.util.Calendar;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.*;
public class ClockFrame extends JFrame {
public ClockFrame() {
JPanel panel = new JPanel();
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
add(panel);
panel.setSize(getWidth(), getHeight());
panel.setLayout(new GridLayout());
setResizable(false);
panel.setBackground(Color.MAGENTA.darker().darker());
Hand sHand = new Hand(panel);
panel.add(sHand);
pack();
setVisible(true);
}
class Hand extends JComponent {
private Timer timer;
private Dimension preferredSize = new Dimension(600, 600);
private Calendar currentTime;
private Image clockFace;
public Hand(Object o) {
setLocation(500, 500);
((JPanel) o).add(this);
currentTime = Calendar.getInstance();
try {
clockFace = ImageIO.read(new URL(
"http://www.clipartbest.com/cliparts/LTK/kBp/LTKkBpRGc.png"));
} catch (IOException ex) {
Logger.getLogger(ClockFrame.class.getName()).log(Level.SEVERE, null, ex);
}
timer = new Timer(50, new ActionListener() {
public void actionPerformed(ActionEvent e) {
currentTime.setTime(new Date(System.currentTimeMillis()));
repaint();
}
});
timer.start();
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(
RenderingHints.KEY_DITHERING,
RenderingHints.VALUE_DITHER_ENABLE);
g2d.setColor(Color.LIGHT_GRAY);
int size = preferredSize.width;
g2d.fillOval((int)(size*.01), (int)(size*.01), (int)(size*.98), (int)(size*.98));
if (clockFace!=null) {
g2d.drawImage(clockFace, 0, 0, this);
}
double middle = size / 2d;
/* Note that this is translating further drawing operations
to the middle of the Hand container based on its preferred size,
which is (layout manager not withstanding) also its actual size.
All co-ordinates in custom painting are relative to the component
being painted. */
g2d.translate(middle, middle);
g2d.setColor(Color.CYAN.darker().darker());
double angleHour = ((currentTime.get(Calendar.HOUR)*2*Math.PI)/12d)-(Math.PI/2);
g2d.setStroke(new BasicStroke(6.5f));
Line2D.Double hourHand = new Line2D.Double(0, 0,
middle*.83*Math.cos(angleHour),
middle*.83*Math.sin(angleHour));
g2d.draw(hourHand);
g2d.setColor(Color.CYAN.darker());
double angleMin = ((currentTime.get(Calendar.MINUTE)*2*Math.PI)/60d)-(Math.PI/2);
g2d.setStroke(new BasicStroke(4.5f));
Line2D.Double minuteHand = new Line2D.Double(0, 0,
middle*.85*Math.cos(angleMin),
middle*.85*Math.sin(angleMin));
g2d.draw(minuteHand);
g2d.setColor(Color.CYAN);
double angleSec = ((currentTime.get(Calendar.SECOND)*2*Math.PI)/60d)-(Math.PI/2);
g2d.setStroke(new BasicStroke(2.5f));
Line2D.Double secondHand = new Line2D.Double(0, 0,
middle*.87*Math.cos(angleSec),
middle*.87*Math.sin(angleSec));
g2d.draw(secondHand);
g2d.dispose();
}
public Dimension getPreferredSize() {
return preferredSize;
}
}
public static void main(String[] a) {
ClockFrame c = new ClockFrame();
}
}
While I was making a 2D Game in Java, a (atleast to me) very strange problem occured. I've provided a runnable, shortened example which reproduces my problem. When the x-coordinate of the red square is between 100 and 120 it should draw the string "Sample Text" above the square. However, you will see if you run the code, the window freezes completely for a few seconds. After the lag you can go over the area without problems, and the text will be shown. This problem only occurs when the program draws a String above the square. If I change the code so only another square appears above the red one, there's no lag. (I commented that in my code)
Any help would be appreciated.
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JApplet;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JRootPane;
import javax.swing.KeyStroke;
public class MyExample extends JApplet {
int x = 10;
int y = 150;
public void init() {
setFocusable(true);
requestFocus();
Action right = new moveRight();
Action left = new moveLeft();
JRootPane rootPane = getRootPane();
rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), "right");
rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), "left");
rootPane.getActionMap().put("right", right);
rootPane.getActionMap().put("left", left);
getContentPane().add(new Paint());
}
protected class moveRight extends AbstractAction {
public void actionPerformed(ActionEvent e) {
x+=3;
repaint();
}
}
protected class moveLeft extends AbstractAction {
public void actionPerformed(ActionEvent e) {
x-=3;
repaint();
}
}
class Paint extends JPanel {
public void paintComponent(Graphics g) {
g.setColor(Color.RED);
g.fillRect(x,y,10,10);
g.setColor(Color.BLACK);
g.drawLine(100,100,100,200);
g.drawLine(129,100,129,200);
if(x>100&&x<120) {
g.setFont(new Font("TimesRoman", Font.PLAIN, 15));
g.setColor(Color.BLACK);
g.drawString("Sample Text",x-30,y-25);
//g.fillRect(x,y-15,10,10); - This work fine if you remove the g.setFont and the drawString
}
}
}
}
This has to do with the fact that you are trying to load the font within the paintComponent method AND the underlying API trying to load the font and it's details before it can paint them.
I did think you could just pre-cache the font using something like...
class Paint extends JPanel {
private Font paintFont;
public Paint() {
paintFont = new Font("TimesRoman", Font.PLAIN, 15);
setFont(paintFont);
}
But in my testing, this still didn't work, what I actually ended up doing was adding a call to getFontMetrics, which seems to force the API to load the font and it's properties into memory, making it render immediately, for example
class Paint extends JPanel {
private Font paintFont;
public Paint() {
paintFont = new Font("TimesRoman", Font.PLAIN, 15);
setFont(paintFont);
getFontMetrics(paintFont);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.RED);
g.fillRect(x, y, 10, 10);
g.setColor(Color.BLACK);
g.drawLine(100, 100, 100, 200);
g.drawLine(129, 100, 129, 200);
if (x > 100 && x < 120) {
System.out.println("...");
//g.setFont(paintFont);
g.setColor(Color.BLACK);
g.drawString("Sample Text", x - 30, y - 25);
//g.fillRect(x,y-15,10,10); - This work fine if you remove the g.setFont and the drawString
}
}
}
Now, this will make your application load slightly slower, but will allow it to run faster as you've moved the loading of the font out of the paint cycle
On my system TimesRoman doesn't exist.
I used Times New Roman with no problems. I also tried a few other fonts and they worked no problem. So I guess specifying an invalid font name causes a hiccup?
I also created the font once and cached it:
Font font = new Font("Times New Roman", Font.PLAIN, 15);
and then in the painting method used:
g.setFont( font );
Also, don't forget the super.paintComponent(g);
This question contains the code I used to list the fonts on my machine: Java JTextArea font
I am currently working on a project, I get this error Java.lang.NullPointerException, I undrestand that this error happen when you try to refer to a null object instance, but what I do not know, is how I can fix it.
This is my code:
public void paint(Graphics g) {
g.setColor(Color.red);
g.drawOval(150, 150, 10, 10);
}
/** Main Method **/
public static void main(String [] args) {
Run run = new Run();
run.paint(null);
}
Please help me with a solution and also explain it, so that I learn it. Many thanks in advance. Andre
You may not pass null to your paint method! Here is a small example how to do it:
import java.awt.Graphics;
import javax.swing.JComponent;
import javax.swing.JFrame;
class MyCanvas extends JComponent {
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawOval (10, 10, 200, 200);
}
}
public class DrawOval {
public static void main(String[] a) {
JFrame window = new JFrame();
window.setBounds(30, 30, 300, 300);
window.getContentPane().add(new MyCanvas());
window.setVisible(true);
}
}
You almost never call paint methods (including paintComponent) directly as this is what the JVM should be doing.
Don't override the paint(Graphics g) method of a JComponent or JComponent derived class such as a JPanel if you can avoid it. This method, paint, is responsible for not only painting the component but also its borders and its child components, and if not done carefully, overriding this method will not infrequently result in unwanted side effects.
Later when you want to do graphics animation, overriding paint(Graphics g) will result in jerky graphics since it does not do double buffering by default.
By overriding the paintComponent(Graphics g) method instead you fix these issues.
Don't forget to call the super's paintComponent(g) method in your override to erase any unwanted previously drawn images.
Read the Swing Graphics tutorials, both the basic and advanced tutorials. Links at the bottom.
Better code:
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JComponent;
import javax.swing.JFrame;
#SuppressWarnings("serial")
public class MyBetterCanvas extends JComponent {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawOval(10, 10, 200, 200);
}
public static void main(String[] a) {
MyBetterCanvas canvas = new MyBetterCanvas();
canvas.setPreferredSize(new Dimension(300, 300));
JFrame window = new JFrame("My Better Canvas");
window.getContentPane().add(canvas);
window.setLocationByPlatform(true);
window.pack();
window.setVisible(true);
}
}
Better Still:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.RenderingHints;
import javax.swing.JComponent;
import javax.swing.JFrame;
#SuppressWarnings("serial")
public class MyBetterStillCanvas extends JComponent {
private static final int PREF_W = 500;
private static final int PREF_H = 500;
private static final int OVAL_X = 10;
private static final int OVAL_Y = OVAL_X;
private static final Paint BG_PAINT = new GradientPaint(0, 20,
Color.black, 20, 0, Color.darkGray, true);
private static final Paint FILL_PAINT = new GradientPaint(0, 0,
Color.blue, 20, 20, Color.red, true);
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
// to smooth out graphics
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
// let's draw something funky
g2.setPaint(BG_PAINT);
g2.fillRect(0, 0, getWidth(), getHeight());
g2.setPaint(FILL_PAINT);
// avoid use of "magic" numbers
g.fillOval(OVAL_X, OVAL_Y, getWidth() - 2 * OVAL_X, getHeight() - 2
* OVAL_Y);
}
// a cleaner way to set the preferred size of a component
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
public static void main(String[] a) {
JFrame window = new JFrame("My Better Canvas");
window.getContentPane().add(new MyBetterStillCanvas());
window.setLocationByPlatform(true);
window.pack();
window.setVisible(true);
}
}
Which displays as:
Tutorials:
Java Tutorials, Really Big Index
Java Swing Tutorials
Basic Swing Graphics Tutorial: Lesson: Performing Custom Painting
More Advanced Graphics Article: Painting in AWT and Swing
You are not doing it the right way. In order to use graphics in java you need to build upon Swing/AWT components. Currently you are passing Graphics as null.
run.paint(null);
You need to implement this using JFrame and other swing components.
Since you are sending null to paint, Graphics g contains null (points to nowhere).
Then inside paint(...) you call setColor(...) on g, which is null. null.setColor(...) causes NullPointerException.
My question is a bit strange.
I want my created form (with JFrame) coloration should be like to this picture:
Should i use a special look and feel ?
Should i use a special look and feel ?
there is no such built-in Look&Feel available as much as i know. However for "Look&Feel", "Look" refers to the appearance of GUI widgets (more formally, JComponents) and "feel" refers to the way the widgets behave. If so, then we can always make our Swing component to be appeared as we want in the GUI.
Advanced responsive applications which are graphically rich ooze cool, sucking users in from the outset and hang onto them with a death grip of excitement. They make users tell their friends about the applications.
However, to develop graphically rich Swing application we must know how to render custom graphics on component, making them to appear as we want( with shiny color, beautiful texture, moving animation, nice typography). We need to learn how to layout the component properly to arrange them one relative to another. From answering your various question, i came to understand that you want to be a swing geek. Well:
First, Learn about the Swing JComponent(JPanel, JLabel, JButton, JList, JTable, JTextPane etc) and various type of Event listeners and how they responds with the component.
Second, Learn about the Layout Managers. There are other advanced level layout manager available which makes life easier but learn about the standard managers first.
Third, Learn about rendering custom graphics on swing components and swing painting mechanism. Then about the Font Concept.
Fourth, Learn about Graphics and Graphics2D class for rendering carious type of geometric object, image rendering, texturing, gradient painting etc.
Five, Learn about Concurrency in Swing. Swing is not thread safe, it maintain single threading rules. A good knowledge about threading is required, StackOverflow overflowed almost every day with the threading issues with Swing.
Six, collect the book Filthy Rich Clients and read it thoroughly when you will have nearly finished all of the above.
Now, A Demo Example for you:
I have tried to make the application as much short and simple as possible. But there are somethings i have done to achieve your requested GUI, which you should not do, if you don't know how they work, (for example overriding the paint() function). In this example i could not collect a texture as the image you have provided. But i have used a fabric texture. The application needs to load the texture image first. keep your eye on the console. It will show following message prior to run the application:
Please wait, Loading Texture :
http://www.brewsterwallcovering.com/data/default/images/catalog/original/289-5757.jpg
Loading finished. Starting the Demo!
I have used: GridBagLayout as the MainContainer's pane layout manager. Have a look at the code thoroughly and read about corresponding extension(extending JCompononent and JButton) and painting i have made to achieve a nicer GUI(Well not so much nicer to many critics, but for DEMO....)
Demo Source code:
import java.awt.*;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.util.logging.*;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.border.*;
/// creating the Button with custom look
class CButton extends JButton
{
BasicStroke basicStroke = new BasicStroke(2.0f);
public CButton(String txt) {
super(txt);
setForeground(Color.WHITE);
setFont(getFont().deriveFont(Font.BOLD, 13));
setContentAreaFilled(false);
setBorder(null);
setCursor(new Cursor(Cursor.HAND_CURSOR));
}
#Override
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(new Color(0xFFAA00));
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setStroke(basicStroke);
int archH = (getHeight()-4)/2;
g2d.drawRoundRect(3, 3, getWidth()-4, getHeight()-4, archH, archH);
if(getModel().isRollover())
{
g2d.fillRoundRect(3, 3, getWidth()-4, getHeight()-4, archH, archH);
setForeground(Color.black);
}
else
{
setForeground(Color.white);
}
g2d.dispose();
super.paintComponent(g); //To change body of generated methods, choose Tools | Templates.
}
}
/** creating the MainContainer panel with custom look **/
// custom painting to with paintComponent(Graphics g) and paint(Graphics g)
class MainContainer extends JPanel
{
public BufferedImage gradientImage = null;
public static BufferedImage textureImg; // made it static just for easyness
public static boolean loadingFinished = false;
public MainContainer() {
setBorder(new EmptyBorder(50, 50, 50, 50)); // setting the insets
// learn about GridBagLayout from the linked page about LayoutManager
setLayout(new GridBagLayout());
JLabel usrNameLabel = new JLabel("User Name");
changeCompFont(usrNameLabel);
JTextField usrNameFeild = new JTextField("user name");
changeCompFont(usrNameFeild);
// create compund border for text and password feild with left padding 5 px
Border compundBorder = BorderFactory.createCompoundBorder(
new LineBorder(Color.white, 2),
new EmptyBorder(2, 5, 2, 2));
usrNameFeild.setBorder(compundBorder);
usrNameFeild.setOpaque(false);
usrNameLabel.setLabelFor(usrNameFeild);
JLabel passwordLabel = new JLabel("Password");
changeCompFont(passwordLabel);
JPasswordField passFeild = new JPasswordField("Password");
changeCompFont(passFeild);
passFeild.setBorder(compundBorder);
passFeild.setOpaque(false);
passwordLabel.setLabelFor(passFeild);
// working with GridBagConstraints, please check out the linked online tutorial
GridBagConstraints labCnst = new GridBagConstraints();
GridBagConstraints txtCnst = new GridBagConstraints();
labCnst.insets = new Insets(0, 0, 5, 10);
txtCnst.insets = new Insets(0, 0, 5, 10);
labCnst.ipady = txtCnst.ipady = 10;
txtCnst.fill = labCnst.fill = GridBagConstraints.HORIZONTAL;
labCnst.gridx = 0;
txtCnst.gridx = 1;
labCnst.gridwidth = 1;
txtCnst.gridwidth = 2;
labCnst.weightx = 0.3;
txtCnst.weightx = 0.7;
txtCnst.gridy = labCnst.gridy = 0;
add(usrNameLabel, labCnst);
add(usrNameFeild, txtCnst);
txtCnst.gridy = labCnst.gridy = 1;
add(passwordLabel, labCnst);
add(passFeild, txtCnst);
labCnst.gridx = 2;
labCnst.gridy = 2;
labCnst.ipady = 13;
labCnst.insets = new Insets(0, 0, 0, 150);
JButton submitButt = new CButton("Log IN");
add(submitButt, labCnst);
}
public void changeCompFont(JComponent comp)
{
comp.setForeground(Color.WHITE);
comp.setFont(getFont().deriveFont(Font.BOLD, 13));
}
// To PAINT THE TEXTURE ABOVE THE COMPONENTS,
//DON'T DO IT UNTIL YOU UNDERSTAND PAINTING MECHANISM FULLY
#Override
public void paint(Graphics g) {
super.paint(g); //To change body of generated methods, choose Tools | Templates.
Graphics2D g2d = (Graphics2D)g.create(); // cloning to work, it is safer aproach
Rectangle2D txRect = new Rectangle2D.Double(0, 0, textureImg.getWidth(), textureImg.getHeight());
TexturePaint txPaint = new TexturePaint(textureImg, txRect);
g2d.setPaint(txPaint);
//make the texture transparent
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f));
g2d.fillRect(0, 0, getWidth(), getHeight());
g2d.dispose();// disposing the graphics object
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g); //To change body of generated methods, choose Tools | Templates.
Graphics2D g2d = (Graphics2D) g.create();
if(gradientImage==null || gradientImage.getHeight() != getHeight())
{
gradientImage = createGradientImg();
}
g2d.drawImage(gradientImage, 0, 0, getWidth(), getHeight(), this);
g2d.dispose();
}
public BufferedImage createGradientImg()
{
BufferedImage image = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
/// background gradient paint, linear gradient paint for the background
/// Gradient paint rendering could be made more optimized
LinearGradientPaint lgrPaint = new LinearGradientPaint(0.0f, 0.0f, getWidth(), getHeight(),
new float[] { 0.0f, 0.5f, 0.6f, 1.0f },
new Color[] { new Color(0x002AFF),
new Color(0x0CAAF9),
new Color(0x0CAAF9),
new Color(0x002AFF) });
Graphics2D g2d = (Graphics2D) image.getGraphics();
g2d.setPaint(lgrPaint);
//g2d.shear(0.2, 0);
g2d.fillRect(0, 0, getWidth(), getHeight());
g2d.dispose();
//g2d.drawImage(textureImg, 0, 0, getWidth(), getHeight(), null);
return image;
}
}
public class CustomApp {
public static void main(String[] args) throws IOException {
// load the texture resource image
System.out.println("Please wait, Loading Texture : http://www.brewsterwallcovering.com/data/default/images/catalog/original/289-5757.jpg");
MainContainer.textureImg = ImageIO.read(new URL("http://www.brewsterwallcovering.com/data/default/images/catalog/original/289-5757.jpg"));
System.out.println("Loading finished. Starting the Demo!");
MainContainer.textureImg = MainContainer.textureImg.getSubimage(0, 0, 200, 200);
// Starting the Swing GUI in the EDT, learn about it
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame("Demo: LogIn Dialogue");
// set frame size as Demo perposes, otherwise not recommended
frame.setSize(new Dimension(500, 300));
MainContainer container = new MainContainer();
frame.add(new MainContainer());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
});
}
}
You can use JavaFX which make you available some great look and feel.
I think it is better to go with JavaFX if you are looking for a great designs in Java Swing which provide better Look and Feels and animations also.
you can refer this links hope this will be helpful to you.
getting started with FXML
Swing with JavaFX
I want to change JButton gradient color,
i found this, http://java2everyone.blogspot.com/2009/01/set-jbutton-gradient-color.html, but i want to change gradient for only one button, not all button
You can override the paintComponent method of the JButton instance and paint its Graphics object with one of the following classes that implement the Paint interface:
GradientPaint.
LinearGradientPaint
MultipleGradientPaint
RadialGradientPaint
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public final class JGradientButtonDemo {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
createAndShowGUI();
}
});
}
private static void createAndShowGUI() {
final JFrame frame = new JFrame("Gradient JButton Demo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(new FlowLayout());
frame.add(JGradientButton.newInstance());
frame.setSize(new Dimension(300, 150)); // used for demonstration
//frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private static class JGradientButton extends JButton {
private JGradientButton() {
super("Gradient Button");
setContentAreaFilled(false);
setFocusPainted(false); // used for demonstration
}
#Override
protected void paintComponent(Graphics g) {
final Graphics2D g2 = (Graphics2D) g.create();
g2.setPaint(new GradientPaint(
new Point(0, 0),
Color.WHITE,
new Point(0, getHeight()),
Color.PINK.darker()));
g2.fillRect(0, 0, getWidth(), getHeight());
g2.dispose();
super.paintComponent(g);
}
public static JGradientButton newInstance() {
return new JGradientButton();
}
}
}
A little improvement over mre answer:
private static final class JGradientButton extends JButton{
private JGradientButton(String text){
super(text);
setContentAreaFilled(false);
}
#Override
protected void paintComponent(Graphics g){
Graphics2D g2 = (Graphics2D)g.create();
g2.setPaint(new GradientPaint(
new Point(0, 0),
getBackground(),
new Point(0, getHeight()/3),
Color.WHITE));
g2.fillRect(0, 0, getWidth(), getHeight()/3);
g2.setPaint(new GradientPaint(
new Point(0, getHeight()/3),
Color.WHITE,
new Point(0, getHeight()),
getBackground()));
g2.fillRect(0, getHeight()/3, getWidth(), getHeight());
g2.dispose();
super.paintComponent(g);
}
}
TL;DR: it's not possible directly, but can be done with a workaround like in Luca's answer, however his/her answer uses the incorrect gradient steps. The correct ones are listed below.
The way it works
In the Metal LAF there is a hardcoded exception. If the background property is a subclass of UIResource, it's ignored* and the button is instead painted with the (also hardcoded) gradient from the UI property Button.gradient. Otherwise, if background is not a UIResource, that background is painted as-is.
*unless the button is disabled, in which case there is no gradient and the color inside the UIResource is used for the background.
The gradient
Following the logic of MetalButtonUI, I found out the used gradient it uses comes from the UI property Button.gradient, which contains the ArrayList:
0 = {Float} 0.3
1 = {Float} 0.0
2 = {ColorUIResource} "[221,232,243]"
3 = {ColorUIResource} "[255,255,255]"
4 = {ColorUIResource} "[184,207,229]"
Following the logic even further, I ended up in MetalUtils.GradientPainter.drawVerticalGradient(). This implementation interprets the above data as*:
Gradient from 0% to 30%: color1 to color2
Gradient from 30% to 60%: color2 to color1
Gradient from 60% to 100%: color1 to color3
*assuming the second float is 0.0, otherwise more gradients are drawn.
Since this is a multi-stage gradient, it can't be done with a simple GradientPaint but can be done with a LinearGradientPaint. However the background property only accepts Color. It cannot even be spoofed/hacked because the actual value is eventually given to Graphics.setColor() and not Graphics2D.setPaint() (even though Metal is Swing-based and not AWT) Dead End. The only solution seems to subclass JButton altogether.