I asked this before, but only theoretically, without an SSCCE. Now, I've created one, and the problem persists. I'd like to know why paintComponent is not called on repaint(x, y, w, h), but is called on repaint().
Two classes:
SANDBOX
import java.awt.Dimension;
import java.awt.FlowLayout;
import javax.swing.JFrame;
public class Sandbox {
public static void main(String[] args) {
JFrame f = new JFrame();
f.setMinimumSize(new Dimension(800, 600));
f.setLocationRelativeTo(null);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setLayout(new FlowLayout());
// Add label
f.getContentPane().add(new TLabel());
f.setVisible(true);
}
}
and TLabel (with a little styling):
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JLabel;
import javax.swing.SwingConstants;
import javax.swing.border.LineBorder;
#SuppressWarnings("serial")
public class TLabel extends JLabel {
public TLabel() {
super("TEST LABEL, NON-STATIC");
this.setHorizontalAlignment(SwingConstants.CENTER);
TLabel.this.setPreferredSize(new Dimension(200, 50));
TLabel.this.setMaximumSize(new Dimension(200, 50));
TLabel.this.setMinimumSize(new Dimension(200, 50));
TLabel.this.setOpaque(true);
TLabel.this.setBackground(Color.cyan.darker().darker());
TLabel.this.setForeground(Color.white);
TLabel.this.setBorder(new LineBorder(Color.orange, 2));
this.addMouseListener(new MouseAdapter() {
#Override
public void mouseEntered(MouseEvent e) {
// EXPECTED BEHAVIOR HERE: This line will call paint and paintComponent.
//repaint();
// PROBLEM HERE: This line will not call paint or paintComponent.
repaint(TLabel.this.getBounds());
}
});
}
#Override
public void paint(Graphics g) {
// Note: This is called once when the label is realised.
// Note: This is called when the mouse enters the frame.
System.out.println("PAINT.");
super.paint(g);
}
#Override
public void paintComponent(Graphics g) {
// Note: This is called once when the label is realised.
// Note: This is called when the mouse enters the frame.
System.out.println("REPAINT.");
super.paintComponent(g);
}
}
You're calling this
repaint(TLabel.this.getBounds());
inside of the TLabel object. So repaint will try to paint a rectangle located relative to itself at the Bounds location, but getBounds() returns a rectangle located relative to this components containing object's location while repaint expects bounds relative to the component itself. So you're trying to paint a rectangle that has the width and height of your JLabel but which is located at x = 292 and y = 5 relative to the JLabel, when instead you want x and y to both be 0. In essence you're trying to draw way outside of this component.
Instead try this:
//!! repaint(TLabel.this.getBounds());
Dimension d = TLabel.this.getSize();
repaint(new Rectangle(0, 0, d.width, d.height));
Related
1. Need set 1024*768 the size of paintComponent Area.
2. Need set drawing orientation of line on (0, 0, 1366,1024) but not a center.
I try to change the size in getPreferredSize() method but It's doesn't help me or doing another effect. I can't do this.
I try to change the size in getPreferredSize() method but It's doesn't help me or doing another effect.
I try to change
This is code which I can't change for my needs!
This is code which I can't change for my needs!
package j;
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.RenderingHints;
import java.awt.geom.Path2D;
import javax.swing.JColorChooser;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class Letter {
LetterDraw letterDraw = new LetterDraw();
public Letter() {
JFrame frame = new JFrame();
JPanel letterDrawWrapper = new JPanel(new GridBagLayout());
letterDrawWrapper.add(letterDraw);
letterDrawWrapper.setSize(1024,760);
frame.add(createColorChooser(), BorderLayout.PAGE_END);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setSize(111, 111);
frame.setVisible(true);
}
private JColorChooser createColorChooser() {
JColorChooser colorChooser = new JColorChooser();
colorChooser.getSelectionModel().addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
letterDraw.setColor(colorChooser.getColor());
}
});
return colorChooser;
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new Letter();
}
});
}
}
class LetterDraw extends JPanel {
private Color color;
public void setColor(Color color) {
this.color = color;
repaint();
}
#Override
protected void paintComponent(Graphics graphics) {
super.paintComponent(graphics);
Graphics2D g = (Graphics2D) graphics;
g.setColor(color);
g.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setStroke(new BasicStroke(3));
//g.setStroke(new BasicStroke(4, BasicStroke.JOIN_BEVEL, 0));
g.setColor(color);
g.drawLine(11,11,1024,1024);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(150, 150);
}
}```
Need set 1024*768 the size of paintComponent Area.
You should NOT be hard coding size values. You don't know what the resolution will be.
Also, The resolution of your screen is NOT the space available for painting. The frame has a title bar and border which takes away space for custom painting.
Painting code should be dynamic based on the space available to your panel, so the painting code would use methods like getWidth() and getHeight() to determine the painting area.
I try to change the size in getPreferredSize()
Yes that will work to give a suggestion for the preferred size. Then you just pack() the frame. Don't use setSize() on the frame.
//frame.setSize(111, 111);
That statement is overriding the size determined by the pack() method. Get rid of it.
g.drawLine(11,11,1024,1024);
Don't hard code values. The size of the panel will change as the frame is resized. For example to draw a diagonal line on the panel the code should be:
g.drawLine(0, 0, getWidth(), getHeight());
Try manually resizing the frame to see how the size of the line changes.
Edit:
You didn't add your panel to the frame:
frame.add(letterDraw);
Hello and sorry for my imperfect English.
I have a class MyNode (draws a rectangle) that extends JComponent. I have mouse listener for mouseEntered and mouseExited to change its color.
The big problem is that i need to work with transforms (the main app, not this SSCE, it is a graph (nodes&edges) drawer.)
I read about this problem already but I want to ask you how can i make it work. If I apply a scale(2,2) on my panel i have two main problems:
The mouse position doesn't correspond anymore to the drew MyNode. I know i can apply inverseTransform and this works just well, but I must search in all node collection of the panel and check the contains method with the inversed point (inefficient imo). (hope you get what i am saying here). How i can make the mouseEntered, mouseExited etc. work by itself in the new scaled system.
If I scale the panel, the MyNode paintComponent() doesn't apply its owner (Panel) transforms. The node is drew with its initial size.
I am very curious if this can be resolved, and I wait any other question to help you understand what I am looking for. It seems even hard to explain the problem, can't imagine the solution :)
This is a demo of my problem (you can run it):
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class SSCE extends JPanel {
public SSCE() {
setLayout(null);
setBackground(Color.yellow);
setSize(new Dimension(500, 300));
setPreferredSize(new Dimension(500, 200));
add(new MyNode(0, 0));
setVisible(true);
}
#Override
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.scale(2, 2);
super.paintComponent(g2d);
paintComponents(g2d);
}
}
class MyNode extends JComponent {
private Color activeColor = Color.red;
public MyNode(int x, int y) {
this.setLocation(x, y);
this.setSize(50, 50);
this.addMouseListener(new MouseAdapter() {
#Override
public void mouseEntered(MouseEvent e) {
colorOnMouseHover();
}
#Override
public void mouseExited(MouseEvent e) {
colorOnMouseLeave();
}
});
}
private void colorOnMouseHover() {
this.activeColor = Color.blue;
repaint();
}
private void colorOnMouseLeave() {
this.activeColor = Color.red;
repaint();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(activeColor);
g.fillRect(0, 0, 50, 50);
}
}
class Run {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setSize(500, 200);
frame.add(new SSCE());
frame.setVisible(true);
}
}
I was trying to use XOR mode in Graphics to draw a 1bit texture in color against a flat background, when I encountered a behaviour in Graphics I don't understand.
Here is an example of what I mean, isolated:
package teststuff;
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JFrame;
public class XORTest extends JFrame {
public XORTest() {
super("Test");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(500, 400);
setIgnoreRepaint(true);
setResizable(false);
setVisible(true);
createBufferStrategy(2);
Graphics graphics = getBufferStrategy().getDrawGraphics();
graphics.setColor(Color.black);
graphics.fillRect(0, 0, getWidth(), getHeight());
graphics.setColor(Color.green);
graphics.fillRect(30, 40, 100, 200);
graphics.setXORMode(Color.white); // (*)
graphics.fillRect(60, 80, 100, 200);
graphics.dispose();
getBufferStrategy().show();
}
public static void main(String[] args) {
XORTest test = new XORTest();
}
}
If I uncomment the line marked with (*), two green rectangles are drawn as expected. If I leave it, nothing is drawn into the component, not even the black background or green rectangle that is drawn beforehand. Even more odd, it worked once. I had the color as Color.green instead of white before. After I changed it, it drew as expected. But when I closed the application and started it again, it didn't work anymore and it hasn't since.
Is this a bug in java? In my jre? Undocumented behaviour for Graphics? I'm on Windows and running the example on the jdk7.
Screenshots: Imgur album because it won't let me post 3 links
The third screenshot is the code as it is above, the first with (*) commented and the second is how it looked the one time it worked (I created that in GIMP because I didn't take a screenshot then).
Without a compelling reason to the contrary, it's easier and more reliable to override paintComponent() in JPanel, which is double buffered by default. With a compelling reason, follow the guidelines in BufferStrategy and BufferCapabilities. Also note,
Override getPreferredSize() to specify the preferred size of a component.
Swing GUI objects should be constructed and manipulated only on the event dispatch thread.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
/**
* #see https://stackoverflow.com/a/16721780/230513
*/
public class Test {
private void display() {
JFrame f = new JFrame("Test");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new XORPanel());
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
private static class XORPanel extends JPanel {
#Override
public Dimension getPreferredSize() {
return new Dimension(190, 320);
}
#Override
protected void paintComponent(Graphics graphics) {
super.paintComponent(graphics);
graphics.setColor(Color.black);
graphics.fillRect(0, 0, getWidth(), getHeight());
graphics.setColor(Color.green);
graphics.fillRect(30, 40, 100, 200);
graphics.setXORMode(Color.white);
graphics.fillRect(60, 80, 100, 200);
}
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new Test().display();
}
});
}
}
Write a program that fills the window with a larrge ellipse. The ellipse shoud touch the window boundaries, even if the window is resized.
I have the following code:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import javax.swing.JComponent;
public class EllipseComponent extends JComponent {
public void paintComponent(Graphics g)
{
Graphics2D g2 = (Graphics2D) g;
Ellipse2D.Double ellipse = new Ellipse2D.Double(0,0,150,200);
g2.draw(ellipse);
g2.setColor(Color.red);
g2.fill(ellipse);
}
}
And the main class:
import javax.swing.JFrame;
public class EllipseViewer {
public static void main(String[] args)
{
JFrame frame = new JFrame();
frame.setSize(150, 200);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
EllipseComponent component = new EllipseComponent();
frame.add(component);
frame.setVisible(true);
}
}
in your EllipseComponent you do:
Ellipse2D.Double ellipse = new Ellipse2D.Double(0,0,getWidth(),getHeight());
I'd also recommend the changes given by Hovercraft Full Of Eels. In this simple case it might not be an issue but as the paintComponent method grows in complexity you realy want as little as possible to be computed in the paintComponent method.
Do not resize components within paintComponent. In fact, do not create objects or do any program logic within this method. The method needs to be lean, fast as possible, do drawing, and that's it. You must understand that you do not have complete control over when or even if this method is called, and you certainly don't want to add code to it unnecessarily that may slow it down.
You should create your ellipse in the class's constructor. To resize it according to the JComponent's size and on change of size, use a ComponentListener.:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import javax.swing.JComponent;
public class EllipseComponent extends JComponent {
Ellipse2D ellipse = null;
public EllipseComponent {
ellipse = new Ellipse2D.Double(0,0,150,200);
addComponentListener(new ComponentAdapter() {
public void componentResized(ComponentEvent e) {
// set the size of your ellipse here
// based on the component's width and height
}
});
}
public void paintComponent(Graphics g)
{
Graphics2D g2 = (Graphics2D) g;
g2.draw(ellipse);
g2.setColor(Color.red);
g2.fill(ellipse);
}
}
Caveat: code not run nor tested
I want to draw the lines between 2 JScrollPanes (first scroll pane on the left side, second on the right). These JScrollPanes contain images. I want to draw lines between these 2 images (use some layers, use some trick etc.). I tried do it different ways, but i failed. Is it possible? (if not, i will have to make 2 images in one JScrollPane and it won't be nice).
EDIT
I want to draw between 2 images - throught components - get some points from images and draw lines between them. I apologize for poorly formulated question.
In order to accomplish this, I believe you'll need to make use of the Glass Pane. The Glass Pane sits on top of everything in the JRootPane and fills the entire view. This particular position allows two distinct capabilities:
Intercepting mouse and keyboard events
Drawing over the entire user interface
I believe your question is addressed by the second capability. The following is an example implementation, which you can later tailor to meet your own needs. Note that I've left out a lot of detail with regard to Glass Pane that you'll need to research on your own.
CODE
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class GlassPaneDemo {
private static BufferedImage bi;
public static void main(String[] args){
try {
loadImages();
SwingUtilities.invokeLater(new Runnable(){
#Override
public void run() {
createAndShowGUI();
}
});
} catch (IOException e) {
// handle exception
}
}
private static void loadImages() throws IOException{
bi = ImageIO.read(new File("src/resources/person.png"));
}
private static void createAndShowGUI(){
final JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.setGlassPane(new CustomGlassPane());
frame.getContentPane().add(getButtonPanel());
frame.pack();
frame.setLocationRelativeTo(null);
frame.getGlassPane().setVisible(true);
frame.setVisible(true);
}
private static final JPanel getButtonPanel(){
#SuppressWarnings("serial")
final JPanel panel = new JPanel(){
#Override
protected void paintComponent(Graphics g){
Graphics gCopy = g.create();
gCopy.setColor(Color.BLUE.darker());
gCopy.fillRect(0, 0, getWidth(), getHeight());
gCopy.dispose();
}
};
final JLabel labelOne = new JLabel();
labelOne.setIcon(new ImageIcon(bi));
final JLabel labelTwo = new JLabel();
labelTwo.setIcon(new ImageIcon(bi));
panel.add(labelOne);
panel.add(labelTwo);
return panel;
}
#SuppressWarnings("serial")
private static class CustomGlassPane extends JComponent{
private Point p1;
private Point p2;
private boolean lineDrawn;
public CustomGlassPane(){
addMouseListener(new MouseAdapter(){
#Override
public void mouseClicked(MouseEvent e){
if(p1 == null || lineDrawn){
if(lineDrawn){
p1 = null;
p2 = null;
lineDrawn = false;
}
p1 = e.getPoint();
}else{
p2 = e.getPoint();
repaint(); // not optimal
lineDrawn = true;
}
}
});
// Block all other input events
addMouseMotionListener(new MouseMotionAdapter(){});
addKeyListener(new KeyAdapter(){});
addComponentListener(new ComponentAdapter(){
#Override
public void componentShown(ComponentEvent e){
requestFocusInWindow();
}
});
setFocusTraversalKeysEnabled(false);
}
#Override
protected void paintComponent(Graphics g){
if(p1 != null && p2 != null){
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(Color.RED);
g2.drawLine((int)p1.getX(), (int)p1.getY(), (int)p2.getX(), (int)p2.getY());
g2.dispose();
}
}
}
}
OUTPUT
EXPLANATION
In this example, I clicked two arbitrary points within each JLabel, and then drew a connecting line.
This should be very possible. You will need to create a custom component that is aware of both vertical ScrollBars. It should add itself as an AdjustmentListener to each scroll bar in order to detect changes and repaint the lines between the two.
See:
addAdjustmentListener method in the API
You can use this
http://java-sl.com/connector.html
as an example of such code.