Swing Java Rotate JLabel but text be erased - java

I'm using swing Java to try to do something with java. Now I want to rotate JLabel and I did that. But unfortunelately, a part of my text in JLabel is erased (as in the image below). I have tried search but seem no one has problems as same as mine. I guess it's occured caused JLabels they overlaped.
and this is my code
serviceName[j] = new JLabel(name){
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
AffineTransform aT = g2.getTransform();
Shape oldshape = g2.getClip();
aT.rotate(Math.toRadians(300));
g2.setTransform(aT);
g2.setClip(oldshape);
super.paintComponent(g);
}
};
Can you give me the way to solved it

You should restore original transform and clip after your painting. Like this
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
AffineTransform aT = g2.getTransform();
Shape oldshape = g2.getClip();
g2.rotate(Math.toRadians(300));
super.paintComponent(g);
g2.setTransform(aT);
g2.setClip(oldshape);
}

Your JLabel subclass should also override getPreferredSize() to report the size it will be when it is rotated; otherwise the any layout manager that uses asks your component for its preferred size will use JLabel's version, which assumes the text is drawn horizontally.

Instead of attempting to rotate the component, another approach would be to create a Text Icon and add the Icon to a JLabel.
Once you have created the TextIcon you can then create a Rotated Icon to add to the label. The RotatedIcon will calculate the proper size of the Icon so therefore the size of the label will also be correct and no custom painting is required.
So the basic code would be something like:
JLabel label = new JLabel();
TextIcon textlIcon = new TextIcon(label, "Rotated Text");
label.setIcon( new RotatedIcon(textIcon, 300) );
Edit:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.*;
import javax.swing.border.*;
import javax.swing.table.*;
import java.io.*;
public class SSCCE extends JPanel
{
public SSCCE()
{
OverlapLayout layout = new OverlapLayout(new Point(20, 0));
setLayout( layout );
addLabel("one");
addLabel("two");
addLabel("three or more");
addLabel("four");
}
private void addLabel(String text)
{
JLabel label = new JLabel();
TextIcon textIcon = new TextIcon(label, text);
label.setIcon( new RotatedIcon(textIcon, 300) );
label.setVerticalAlignment(JLabel.BOTTOM);
add(label);
}
private static void createAndShowGUI()
{
JFrame frame = new JFrame("SSCCE");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new SSCCE());
frame.setLocationByPlatform( true );
frame.pack();
frame.setVisible( true );
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowGUI();
}
});
}
}
This example also uses the Overlap Layout so the labels can be painted over top of one another.

You may find some hints from this small program. Experiment on the values of setPrefferedSize to have more ideas. If you still can't solve the problem, please edit and add more codes in your question above.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import javax.swing.*;
public class InclinedLabels extends JFrame{
/** Creates a new instance of InclinedLabels */
public InclinedLabels() {
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
setLayout(new BorderLayout());
JPanel jPanel1 = new JPanel();
jPanel1.setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(0, 0, 0)));
add(jPanel1);
JPanel jPanel2 = new JPanel();
jPanel2.setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(0, 0, 0)));
jPanel2.setPreferredSize(new Dimension(10, 100));
add(jPanel2, BorderLayout.NORTH);
jPanel1.setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(0, 0, 0)));
jPanel1.setPreferredSize(new java.awt.Dimension(200, 200));
java.awt.Dimension screenSize = java.awt.Toolkit.getDefaultToolkit().getScreenSize();
setBounds((screenSize.width-400)/2, (screenSize.height-352)/2, 300, 352);
String str = "The quick brown fox jumps over the lazy dog";
String[] word = str.split(" ");
JLabel[] serviceName = new JLabel[str.length()];
String name;
for (int j=0; j<word.length; j++) {
name = word[j];
serviceName[j] = new JLabel(name){
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
AffineTransform aT = g2.getTransform();
Shape oldshape = g2.getClip();
aT.rotate(Math.toRadians(300));
g2.setTransform(aT);
g2.setClip(oldshape);
super.paintComponent(g);
}
};
serviceName[j].setPreferredSize(new Dimension(50,20));
serviceName[j].setBorder(BorderFactory.createLineBorder(Color.RED));
jPanel1.add(serviceName[j]);
}
}
public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new InclinedLabels().setVisible(true);
}
});
}
}
Update:
I found a much closer hint that may solve this problem. The big factor here is the component layout. The null layout allows overlapping of JLabel components so it is the most appropriate layout to be used here. Then you have to customize the location and size of the labels through the setBounds method. In the code below there is serviceName[j].setBounds(xOffset + j*20,180, 170, 15); So in every loop iteration, the x location of the label is increased by 20. The size of all labels is 170 by 15. I also placed temporary borders to the components to help in understanding the output.
import java.awt.*;
import java.awt.geom.AffineTransform;
import javax.swing.*;
public class InclinedLabels extends JFrame{
/** Creates a new instance of InclinedLabels */
public InclinedLabels() {
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
setLayout(new BorderLayout());
java.awt.Dimension screenSize = java.awt.Toolkit.getDefaultToolkit().getScreenSize();
setBounds((screenSize.width-360)/2, (screenSize.height-352)/2, 360, 352);
JPanel jPanel1 = new JPanel();
jPanel1.setBorder(BorderFactory.createLineBorder(new java.awt.Color(0, 0, 0)));
jPanel1.setLayout(null); // null layout allows overlapping of components
add(jPanel1);
JPanel jPanel2 = new JPanel();
jPanel2.setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(0, 0, 0)));
jPanel2.setPreferredSize(new Dimension(10, 100));
add(jPanel2, BorderLayout.NORTH);
String str = "The quick brown fox jumpsssssssssssss123456 over the lazy dogssssssssssssss123456";
String[] word = str.split(" ");
JLabel[] serviceName = new JLabel[str.length()];
String name;
int xOffset = 30;
for (int j=0; j<word.length; j++) {
name = word[j];
serviceName[j] = new JLabel(name){
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
AffineTransform aT = g2.getTransform();
Shape oldshape = g2.getClip();
aT.rotate(Math.toRadians(300));
g2.setTransform(aT);
g2.setClip(oldshape);
super.paintComponent(g2);
}
};
serviceName[j].setBounds(xOffset + j*20,180, 170, 15); // experiment here
serviceName[j].setBorder(BorderFactory.createLineBorder(Color.RED));
jPanel1.add(serviceName[j]);
}
}
public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new InclinedLabels().setVisible(true);
}
});
}
}
The limitation that I found in the code above is the width of the parent panel. In the example, the label having the text dogssssssssssssss123456 was not printed in whole. This can be overcome by increasing the width of the frame which in turn increases the width of jPanel1.

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
public class Test {
public static void main(String[] args) {
// Create the first label, which will be rotated later.
Test.RotateLabel one = new Test.RotateLabel( "Rotated", 100, 100 );
one.setRotation( 270 );
JLayeredPane pane = new JLayeredPane();
pane.setLayer( one, JLayeredPane.DEFAULT_LAYER );
pane.add( one );
pane.setBorder(new javax.swing.border.LineBorder(Color.BLACK,1));
// Put the container pane in a frame and show the frame.
JFrame frame = new JFrame();
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
frame.add( pane );
frame.setSize( 500, 500 );
frame.setLocationRelativeTo( null );
frame.setVisible( true );
}
static class RotateLabel extends JLabel {
private static final long serialVersionUID = 1L;
private int angle = 0;
public RotateLabel( String text, int x, int y ) {
super( text );
setBorder( new javax.swing.border.CompoundBorder(
new javax.swing.border.LineBorder( Color.red, 1), getBorder() ) );
int width = getPreferredSize().width;
int height = getPreferredSize().height;
setBounds(x, y, width, height);
}
#Override
public void paintComponent( Graphics g ) {
Graphics2D gx = (Graphics2D) g;
Shape old = gx.getClip();
gx.rotate(-Math.toRadians(45), getWidth() / 2, getHeight() / 2);
gx.setClip(old);
super.paintComponent(gx);
}
public void setRotation( int angle ) { this.angle = angle; }
}

Related

Add new data to Graphics2D panel in a Java Swing application

I am writing a Java Swing application for data processing.
one of the functions I need to add is to visualize the data in a graphical way.
For this I want to use the Graphics2D class.
I have a GUI created, integrated my program and also a panel that draws the graphics using the Graphics2D class.
But my problem is that I can't figure out how to call the drawLine method after selecting and loading a file from the GUI
Below is short code example, showing my issue.
It just contains a basic GUI with 2 panels and a menu with load option to explain my problem:
In de MyFrame.java file, I put a comment at line 87 to show exactly where I am stuck.
The appl is based on 3 files:
main: here it creates an instance of MyFrame of the GUI
Myframe: creates the GUI and further process of data
MyPanel: makes a Jpanel of the Graphics2D with a base blue rectangle frame as start view.
If Anyone could give me a hint on how to call this drawLine method from outside the MyFrame() constructor...
I still don't fully understand the whole point on how to interact between classes...
here is a picture of what the GUI looks:
Thank you for helping me on this
public class main {
public static void main(String[] args) {
new MyFrame();
}
}
MyFrame.java:
public class MyFrame extends JFrame {
JTextComponent tc;
String fileName;
MyFrame() {
this.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
this.setLayout(null);
this.setBounds(0,0,464,312);
tc = new JTextPane();
tc.setBounds(0,520,450,50);
tc.setPreferredSize(new Dimension(450,50));
JScrollPane sp = new JScrollPane(tc);
JMenuBar mb = new JMenuBar();
JMenu fm = new JMenu("File");
JMenuItem loadItem = new JMenuItem("Load file");
loadItem.addActionListener(e -> {tc.setText("loading"+"\n");
SDprocess();});
fm.add(loadItem);
mb.add(fm);
MyPanel p1 = new MyPanel();
p1.setBounds(0,0,450,200);
p1.setPreferredSize(new Dimension(450,200));
JPanel p2 = new JPanel();
p2.setBounds(0,200,450,50);
p2.setPreferredSize(new Dimension(450,50));
p2.add(sp);
this.setJMenuBar(mb);
this.add(p1);
this.add(p2);
this.setResizable(false);
this.setVisible(true);
}
public void SDprocess() {
File fr = null;
JFileChooser fc = new JFileChooser();
int result = fc.showOpenDialog(this);
if (result == JFileChooser.APPROVE_OPTION) {
fr = fc.getSelectedFile();
fileName=fr.getName();
System.out.println(fileName);
}
try {
Scanner sc = new Scanner(fr);
tc.setText(fileName +" loading\n");
while (sc.hasNextLine()) {
String line = sc.nextLine();
// ...
// rest of code to get the x and y data for drawing
// lines using drawLine(x1,y1,x2,y2) method.
//
// at this point I need to call this drawLine method but how ???
// i just don't know how to call this method from this point and how to
// and update the graphics panel p1 after adding the data....
}
sc.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
MyPanel.java:
public class MyPanel extends JPanel {
Graphics2D g2D;
MyPanel() {
this.setPreferredSize(new Dimension (450,200));
}
public void paint (Graphics g) {
g2D = (Graphics2D) g;
g2D.setStroke(new BasicStroke(1));
g2D.setPaint(Color.blue);
g2D.drawLine(5, 5, 445,5);
g2D.drawLine(445, 5, 445,195);
g2D.drawLine(445, 195, 5,195);
g2D.drawLine(5, 195, 5,5);
}
}
Oracle has a helpful tutorial, Creating a GUI With Swing. Skip the Netbeans section.
Here's the revised GUI before "reading the file".
Here's the revised GUI after "reading the file".
I created an application model to hold the line segments. This model is passed to the drawing JPanel so that the line segments can be drawn in the paintComponent method of the drawing JPanel.
I cleaned up your GUI. I used Swing layout managers to create the GUI. I separated the creation of the JPanels from the creation of the JFrame so the code is easier for people to read and understand.
Here's the complete runnable code. I made the additional classes inner classes so I could post this code as one block.
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.Point;
import java.util.ArrayList;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.text.JTextComponent;
public class ExampleDrawingGUI {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new ExampleDrawingGUI().new MyFrame());
}
public class MyFrame extends JFrame {
private static final long serialVersionUID = 1L;
private ExampleDrawingModel model;
JTextComponent tc;
MyPanel p1;
public MyFrame() {
super("My Frame");
this.model = new ExampleDrawingModel();
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setJMenuBar(createMenuBar());
p1 = new MyPanel(model);
this.add(p1, BorderLayout.CENTER);
this.add(createTextPanel(), BorderLayout.SOUTH);
this.pack();
this.setLocationByPlatform(true);
// this.setResizable(false);
this.setVisible(true);
}
private JMenuBar createMenuBar() {
JMenuBar mb = new JMenuBar();
JMenu fm = new JMenu("File");
JMenuItem loadItem = new JMenuItem("Load file");
loadItem.addActionListener(e -> {
tc.setText("loading" + "\n");
model.readFile();
p1.repaint();
});
fm.add(loadItem);
mb.add(fm);
return mb;
}
private JPanel createTextPanel() {
JPanel panel = new JPanel(new BorderLayout());
panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
tc = new JTextPane();
tc.setPreferredSize(new Dimension(450, 50));
JScrollPane sp = new JScrollPane(tc);
panel.add(sp, BorderLayout.CENTER);
return panel;
}
public void repaint() {
p1.repaint();
}
}
public class MyPanel extends JPanel {
private static final long serialVersionUID = 1L;
private ExampleDrawingModel model;
public MyPanel(ExampleDrawingModel model) {
this.model = model;
this.setPreferredSize(new Dimension(450, 200));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2D = (Graphics2D) g;
paintBorder(g2D);
for (LineSegment line : model.getLines()) {
Point startPoint = line.getStartPoint();
Point endPoint = line.getEndPoint();
g2D.drawLine(startPoint.x, startPoint.y, endPoint.x, endPoint.y);
}
}
private void paintBorder(Graphics2D g2D) {
int margin = 5;
int x1 = margin;
int x2 = getWidth() - margin;
int y1 = margin;
int y2 = getHeight() - margin;
g2D.setStroke(new BasicStroke(3f));
g2D.setPaint(Color.blue);
g2D.drawLine(x1, y1, x1, y2);
g2D.drawLine(x1, y1, x2, y1);
g2D.drawLine(x2, y1, x2, y2);
g2D.drawLine(x1, y2, x2, y2);
}
}
public class ExampleDrawingModel {
private List<LineSegment> lines;
public ExampleDrawingModel() {
this.lines = new ArrayList<>();
}
public void readFile() {
this.lines.clear();
// Here's where you'd read a file and create a list of lines.
lines.add(new LineSegment(new Point(100, 100), new Point(100, 150)));
}
public List<LineSegment> getLines() {
return lines;
}
}
public class LineSegment {
private final Point startPoint, endPoint;
public LineSegment(Point startPoint, Point endPoint) {
this.startPoint = startPoint;
this.endPoint = endPoint;
}
public Point getStartPoint() {
return startPoint;
}
public Point getEndPoint() {
return endPoint;
}
}
}
First off, some problems with your code:
class MyFrame extends JFrame {
//....
MyFrame() {
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLayout(null); // !! Good God, no don't do this!
this.setBounds(0, 0, 464, 312); // and don't do this
Avoid null layouts and setBounds like the plague as this makes for very inflexible GUI's that while they might look good on one platform look terrible on most other platforms or screen resolutions and that are very difficult to update and maintain. Instead you will want to study and learn the layout managers and then nest JPanels, each using its own layout manager to create pleasing and complex GUI's that look good on all OS's.
Also, don't forget to call pack() on your JFrame after adding components and before setting visible, in order for the layout managers to do their things.
And then:
class MyPanel extends JPanel {
// ...
Graphics2D g2D; //!! -- no, don't do this
If you create a Graphics or Graphics2D field, you are tempted to use it outside of a painting method, and this is a recipe for disaster since any Graphics obtained from a component is short lived and doing this risks creating a brittle graphic or throwing a NullPointerException
public void paint (Graphics g) {
g2D = (Graphics2D) g;
g2D.setStroke(new BasicStroke(1));
g2D.setPaint(Color.blue);
g2D.drawLine(5, 5, 445,5);
g2D.drawLine(445, 5, 445,195);
g2D.drawLine(445, 195, 5,195);
g2D.drawLine(5, 195, 5,5);
}
Don't override paint but rather paintComponent since this is less risky (paint has greater responsibilities that you don't want to mess with) and smoother animations if needed since paintComponent uses double buffering by default.
Also, you almost always should call the super's painting method in your own override, and so instead do:
#Override
protected void paintComponent(Graphics g) {
// first call the super's method:
super.paintComponent(g);
Graphics2D g2D = (Graphics2D) g.create();
Now as for your actual problem, creating images and drawing after the GUI has been rendered, probably the easiest way to do this is to create a BufferedImage and draw with it in your GUI. You can do this easily by calling Graphics method drawImage(...). And you can pass a BufferedImage into your drawing JPanel any time it is needed. So for instance, your code could look something like...
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.Rectangle;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import javax.swing.*;
public class Foo {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("GUI");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
MainPanel mainPanel = new MainPanel();
MyMenu myMenu = new MyMenu();
myMenu.setMyPanel(mainPanel.getMyPanel());
myMenu.setMainPanel(mainPanel);
frame.add(mainPanel);
frame.setJMenuBar(myMenu);
frame.pack();
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
class MainPanel extends JPanel {
private static final int GAP = 5;
private JTextArea textArea = new JTextArea(4, 40);
private MyPanel myPanel = new MyPanel();
public MainPanel() {
textArea.setFocusable(false);
JScrollPane scrollPane = new JScrollPane(textArea);
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
setBorder(BorderFactory.createEmptyBorder(GAP, GAP, GAP, GAP));
setLayout(new BorderLayout(GAP, GAP));
add(myPanel);
add(scrollPane, BorderLayout.PAGE_END);
}
public MyPanel getMyPanel() {
return myPanel;
}
public void appendTextAreaText(String text) {
textArea.append(text);
}
public void setBuffImg(BufferedImage bImage) {
myPanel.setBuffImg(bImage);
}
}
class MyMenu extends JMenuBar {
private MainPanel mainPanel;
private MyPanel myPanel;
public MyMenu() {
JMenu fm = new JMenu("File");
JMenuItem loadItem = new JMenuItem("Load file");
loadItem.addActionListener(e -> {
// Emulate reading file here in a background thread
if (myPanel != null) {
int width = MyPanel.MY_WIDTH;
int height = MyPanel.MY_HEIGHT;
BufferedImage bImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = bImg.createGraphics();
// draw with g2 here using data from file
// emulating this:
g2.setColor(Color.RED);
float strokeWidth = (float) (2 + 6 * Math.random());
g2.setStroke(new BasicStroke(strokeWidth));
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
int x2 = (int) (MyPanel.MY_WIDTH * (1 + Math.random()) / 2);
int y2 = (int) (MyPanel.MY_HEIGHT * (1 + Math.random()) / 2);
g2.drawLine(5, 5, x2, y2);
g2.dispose();
myPanel.setBuffImg(bImg);
if (mainPanel != null) {
mainPanel.appendTextAreaText("adding image \n");
}
}
});
fm.add(loadItem);
add(fm);
}
public void setMyPanel(MyPanel myPanel) {
this.myPanel = myPanel;
}
public void setMainPanel(MainPanel mainPanel) {
this.mainPanel = mainPanel;
}
}
class MyPanel extends JPanel {
private static final int GAP = 5;
public static final int MY_WIDTH = 450;
public static final int MY_HEIGHT = 200;
private BufferedImage bImg = null;
// Graphics2D g2D; //!! -- no, never do this!!
MyPanel() {
// this.setPreferredSize(new Dimension(450, 200));
setBackground(Color.WHITE);
}
// better to override getPreferredSize
#Override
public Dimension getPreferredSize() {
return new Dimension(MY_WIDTH + 2 * GAP, MY_HEIGHT + 2 * GAP);
}
public void setBuffImg(BufferedImage bImg) {
this.bImg = bImg;
repaint();
}
#Override
protected void paintComponent(Graphics g) {
// first call the super's method:
super.paintComponent(g);
if (bImg != null) {
g.drawImage(bImg, GAP, GAP, null);
}
Graphics2D g2D = (Graphics2D) g.create();
g2D.setStroke(new BasicStroke(1));
g2D.setPaint(Color.blue);
Rectangle rect = new Rectangle(GAP, GAP, getWidth() - 2 * GAP, getHeight() - 2 * GAP);
g2D.draw(rect);
}
// Don't override paint but rather paintComponent
// public void paint (Graphics g) {
}

JComponents with Graphics.draw

I am trying to make a simple game using JPanel.
I am using Graphics Draw to display all information, including text, but I need to add user input.
I was thinking about using JTextField with absolute positioning to make it work with what is being drawn, but I have heard that absolute positioning is not a good way set up a JPanel.
Is there a better way to use both graphics paint and JComponents in the same panel?
Solution: use layout managers
Why not simply have the drawing JPanel held by another JPanel, one that uses BorderLayout and held in the BorderLayout.CENTER position. You can then place JTextFields or other control components in the outer JPanel in other positions.
You can also add a layout manager to the drawing JPanel and then add components onto this using the layout. Just remember that if you add any JPanels on top of the drawing JPanel, the added JPanels should be transparent, that is myPanel.setOpaque(false) should be called on them so that the drawing underneath shows through.
For example -- run this program to see what I mean:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class GradientPaintEg extends JPanel {
private DrawingPanel drawingPanel = new DrawingPanel();
private JSlider hue1Slider = new JSlider(0, 100, 0);
private JSlider hue2Slider = new JSlider(0, 100, 0);
public GradientPaintEg() {
Color color = drawingPanel.getColor1();
float[] hsb = Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null);
int value = (int) (hsb[0] * 100);
hue1Slider.setValue(value);
color = drawingPanel.getColor2();
hsb = Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null);
value = (int) (hsb[0] * 100);
hue2Slider.setValue(value);
hue1Slider.setMajorTickSpacing(20);
hue1Slider.setMinorTickSpacing(5);
hue1Slider.setPaintTicks(true);
hue1Slider.setPaintLabels(true);
hue1Slider.setPaintTrack(true);
hue1Slider.addChangeListener(new SliderListener(hue1Slider, drawingPanel, true));
hue1Slider.setOpaque(false);
hue2Slider.setMajorTickSpacing(20);
hue2Slider.setMinorTickSpacing(5);
hue2Slider.setPaintTicks(true);
hue2Slider.setPaintLabels(true);
hue2Slider.setPaintTrack(true);
hue2Slider.addChangeListener(new SliderListener(hue2Slider, drawingPanel, false));
hue2Slider.setOpaque(false);
JPanel sliderPanel = new JPanel(new GridLayout(0, 1, 4, 4));
sliderPanel.add(hue1Slider);
sliderPanel.add(hue2Slider);
sliderPanel.setOpaque(false);
setLayout(new BorderLayout());
// if you want to add the slider panel to the main JPanel:
// add(sliderPanel, BorderLayout.PAGE_START);
add(drawingPanel);
// if you want to add the sliderPanel to the drawing JPanel
drawingPanel.setLayout(new BorderLayout());
drawingPanel.add(sliderPanel, BorderLayout.PAGE_START);
}
private class SliderListener implements ChangeListener {
private JSlider slider;
private DrawingPanel drawingPanel;
private boolean color1Listener;
public SliderListener(JSlider slider, DrawingPanel drawingPanel, boolean color1Listener) {
this.slider = slider;
this.drawingPanel = drawingPanel;
this.color1Listener = color1Listener;
}
#Override
public void stateChanged(ChangeEvent e) {
int value = slider.getValue();
float hue = value / 100f;
Color c = Color.getHSBColor(hue, 1f, 1f);
if (color1Listener) {
drawingPanel.setColor1(c);
} else {
drawingPanel.setColor2(c);
}
}
}
private static void createAndShowGui() {
GradientPaintEg mainPanel = new GradientPaintEg();
JFrame frame = new JFrame("GradientPaintEg");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
class DrawingPanel extends JPanel {
private static final int PREF_W = 600;
private static final int PREF_H = PREF_W;
private static final float X2 = 20;
private static final float Y2 = X2;
private Color color1 = Color.RED;
private Color color2 = Color.BLUE;
public Color getColor1() {
return color1;
}
public void setColor1(Color color1) {
this.color1 = color1;
repaint();
}
public Color getColor2() {
return color2;
}
public void setColor2(Color color2) {
this.color2 = color2;
repaint();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setPaint(new GradientPaint(0, 0, color1, X2, Y2, color2, true));
g2.fillRect(0, 0, getWidth(), getHeight());
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
}
In this code example, I have a JPanel that draws, called class DrawingPanel and use within another main JPanel, the GradientPaintEg class:
public class GradientPaintEg extends JPanel {
private DrawingPanel drawingPanel = new DrawingPanel();
If I want to add components to the DrawingPanel, I first give it a layout, and then add the component(s). For instance, there is a JPanel that holds JSliders called sliderPanel that I add to the DrawingPanel instance using BorderLayout:
drawingPanel.setLayout(new BorderLayout());
drawingPanel.add(sliderPanel, BorderLayout.PAGE_START);
This adds the sliderPanel to the top of the drawingPanel.
But also note that I have to make sliderPanel non-opaque first:
JPanel sliderPanel = new JPanel(new GridLayout(0, 1, 4, 4));
sliderPanel.add(hue1Slider);
sliderPanel.add(hue2Slider);
sliderPanel.setOpaque(false);
I've also made the JSliders themselves non-opaque so that the underlying drawing shows through:
// ......
hue1Slider.setOpaque(false);
// ......
hue2Slider.setOpaque(false);
Here is a basic mcve of combining user input in JTextfield and painting, on a panel using a layout manager:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
class DrawingPanel extends JPanel {
private final JButton update;
private final JTextField input;
private final static int W = 300, H = 350, RADIUS = 100, GAP = 50;
private String text;
public DrawingPanel() {
setPreferredSize(new Dimension(W, H));
setOpaque(false);
setLayout(new BorderLayout());
update = new JButton("Update");
update.addActionListener(e->update());
add(update, BorderLayout.PAGE_START);
input = new JTextField();
add(input, BorderLayout.PAGE_END);
text = "Enter text and press button";
}
private void update() {
text = input.getText();
input.setText("");
repaint();
}
#Override
public void paintComponent(final Graphics g) {
super.paintComponents(g);
final int width = getWidth();
final int height = getHeight();
g.setColor(Color.RED);
g.fillOval(width/2 - RADIUS, height/2 - RADIUS, RADIUS*2, RADIUS*2);
g.setColor(Color.BLUE);
g.drawString(text, height/2 - RADIUS - GAP, GAP);
}
public static void main(final String[] args) {
SwingUtilities.invokeLater(()->makeGui());
}
private static void makeGui() {
JFrame frame = new JFrame();
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.add(new DrawingPanel());
frame.pack();
frame.setVisible(true);
}
}

How to put JPanel in JScrollpane?

i want to put JPanel in JScrollpane i wrote this code but it didn't work
so i need your help
centerPanel_scroll = new JScrollPane();
centerPanel_scroll.setBounds(261, 71, 750, 698);
center_panel = new JPanel();
center_panel.setLayout(null);
center_panel.setBackground(Color.cyan);
centerPanel_scroll.setViewportView(center_panel);
main_panel.add(centerPanel_scroll);
Your center_panel is in the JScrollPane. The viewport doesn't respect size/bounds but rather preferred size, so if you must, set the preferred size via setPreferredSize(...), or even better, have the component extend getPreferredSize().
I have no idea what you're trying to achieve via setBounds position at 261, 71. That's position within the viewport so it makes no sense whatsoever.
Also you're potentially messing yourself up with your use of the null layout which JScrollPanes can have a great deal of trouble with . Just don't set bounds/sizes or use null layouts and you'll find life a lot easier.
e.g.,
centerPanel_scroll = new JScrollPane();
// centerPanel_scroll.setBounds(261, 71, 750, 698);
center_panel.setPreferredSize(new Dimension(750, 698));
center_panel = new JPanel();
// center_panel.setLayout(null);
center_panel.setPreferredSize(new Dimension(900, 800));
center_panel.setBackground(Color.cyan);
centerPanel_scroll.setViewportView(center_panel);
main_panel.add(centerPanel_scroll);
e.g.,
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import javax.swing.*;
public class ScrollEg extends JPanel {
public ScrollEg() {
JScrollPane scrollPane = new JScrollPane();
scrollPane.setViewportView(new InnerPanel());
setLayout(new BorderLayout());
add(scrollPane, BorderLayout.CENTER);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(650, 500);
}
private static class InnerPanel extends JPanel {
private static final int PREF_W = 1000;
private static final Color COLOR_1 = Color.RED;
private static final Color COLOR_2 = Color.BLUE;
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_W);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setPaint(new GradientPaint(0, 0, COLOR_1, 100, 100, COLOR_2, true));
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.fillOval(0, 0, PREF_W, PREF_W);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
ScrollEg mainPanel = new ScrollEg();
JFrame frame = new JFrame("ScrollEg");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}

Java window doesn't repaint properly until I resize the window manually

I am using a quite basic setup with a class extending JPanel, which I add to a JFrame.
import java.awt.*;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.event.*;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.*;
import java.io.*;
import javax.imageio.ImageIO;
public class PinTestMCVE extends JPanel implements ActionListener{
BufferedImage loadedImage;
JButton calcButton;
public static void main(String[] args) {
new PinTestMCVE();
}
public PinTestMCVE() {
loadedImage = getTestImage();
JPanel toolbarPanel = new JPanel();
calcButton = new JButton("calcButton...");
toolbarPanel.add(calcButton);
calcButton.addActionListener(this);
JFrame jf = new JFrame();
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.getContentPane().setLayout(new BorderLayout());
jf.getContentPane().add(toolbarPanel, BorderLayout.NORTH);
jf.getContentPane().add(this, BorderLayout.CENTER);
jf.setSize(1250, 950);
jf.setVisible(true);
}
public void paintComponent(Graphics g) {
g.drawImage(loadedImage, 0, 0, this);
}
public void actionPerformed(ActionEvent e) {
System.out.println("ActionEvent " + e.getActionCommand());
if(e.getSource().equals(calcButton)){
this.repaint();
}
}
//Please ignore the inner workings of this
public static BufferedImage getTestImage(){
BufferedImage image = new BufferedImage(500, 500, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = image.createGraphics();
g2d.setPaint(Color.GRAY);
g2d.fillRect ( 0, 0, image.getWidth(), image.getHeight() );
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setPaint(Color.gray);
int x = 5;
int y = 7;
GradientPaint redtowhite = new GradientPaint(x, y, Color.red, 200, y, Color.blue);
g2d.setPaint(redtowhite);
g2d.fill(new RoundRectangle2D.Double(x, y, 200, 200, 10, 10));
return image;
}
}
What happens is that INITIALLY the window is painted properly, but once paintComponent is called, a strip of the old image (with the same height as the toolbar panel) is visible below the newly painted images - similar to playing card sticking out from a deck. But then, if I manually resize the window by for instance dragging the border, the background is grayed out as it should.
What is going on and how do I fix this?
As outlined here, you need to pack() the frame before calling setVisible(). You can override getPreferredSize() to specify a suitable initial Dimension. Also consider using a Border. See also Initial Threads.
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.*;
public class PinTestMCVE extends JPanel implements ActionListener{
private static final int SIZE = 200;
BufferedImage loadedImage;
JButton calcButton;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new PinTestMCVE();
}
});
}
public PinTestMCVE() {
loadedImage = getTestImage();
JPanel toolbarPanel = new JPanel();
calcButton = new JButton("calcButton...");
toolbarPanel.add(calcButton);
calcButton.addActionListener(this);
JFrame jf = new JFrame();
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.add(toolbarPanel, BorderLayout.NORTH);
jf.add(this, BorderLayout.CENTER);
jf.pack();
jf.setLocationRelativeTo(null);
jf.setVisible(true);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(SIZE, SIZE);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(loadedImage, 0, 0, this);
}
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("ActionEvent " + e.getActionCommand());
if(e.getSource().equals(calcButton)){
this.repaint();
}
}
//Please ignore the inner workings of this
public static BufferedImage getTestImage(){
BufferedImage image = new BufferedImage(SIZE, SIZE, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = image.createGraphics();
g2d.setPaint(Color.GRAY);
g2d.fillRect ( 0, 0, image.getWidth(), image.getHeight() );
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setPaint(Color.gray);
GradientPaint redtowhite = new GradientPaint(5, 5, Color.red, SIZE, 5, Color.blue);
g2d.setPaint(redtowhite);
g2d.fill(new RoundRectangle2D.Double(5, 5, SIZE - 10, SIZE - 10, 10, 10));
return image;
}
}

How do I get consistent rendering when scaling a JTextPane? [duplicate]

I submitted another version of this question and a sample program before: How do I get consistent rendering when scaling a JTextPane?
Recapitulating the problem: I would like to allow users to zoom into or out of a non-editable JTextPane. Running the example program submitted in the earlier question, which simply scaled the Graphics object, resulted in inconsistent spacing between runs of bold text and non-bold text.
The sample program below attempts to solve the problem by drawing the text pane to a BufferedImage at 100% and then scaling the image. This solves the problem of inconsistent spacing but the resulting text lacks crispness. Is there some combination of rendering hints (or some other change) that will result in nice crisp text?
Thanks in advance for any suggestions or comments on the feasibility of this approach.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import javax.swing.Box;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.text.BadLocationException;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyleContext;
import javax.swing.text.StyledDocument;
public class ScaledJTextPane extends JTextPane
{
double scale_;
BufferedImage raster_;
public ScaledJTextPane()
{
scale_ = 1.0;
raster_ = null;
}
public void draw(Graphics g)
{
if (raster_ == null)
{
// Draw this text pane to a BufferedImage at 100%
raster_ = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = raster_.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_OFF);
paint(g2);
}
Graphics2D g2 = (Graphics2D) g;
// Experiment with different rendering hints
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g2.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
// Scale the BufferedImage
g2.scale(scale_, scale_);
g2.drawImage(raster_, 0, 0, null);
}
public void setScale(double scale)
{
scale_ = scale;
raster_ = null;
}
private static void createAndShowGUI()
{
// Create and set up the window.
JFrame frame = new JFrame("ScaledJTextPane using BufferedImage");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final ScaledJTextPane scaledTextPane = new ScaledJTextPane();
StyledDocument doc = scaledTextPane.getStyledDocument();
Style defaultStyle = StyleContext.getDefaultStyleContext().getStyle(StyleContext.DEFAULT_STYLE);
Style boldStyle = doc.addStyle("bold", defaultStyle);
StyleConstants.setBold(boldStyle, true);
scaledTextPane.setFont(new Font("Dialog", Font.PLAIN, 14));
String boldText = "Four score and seven years ago ";
String plainText = "our fathers brought forth on this continent, a new nation, conceived in Liberty, and dedicated to the proposition that all men are created equal.";
try
{
doc.insertString(doc.getLength(), boldText, boldStyle);
doc.insertString(doc.getLength(), plainText, defaultStyle);
}
catch (BadLocationException ble)
{
System.err.println("Couldn't insert text into text pane.");
}
final JComboBox zoomCombo=new JComboBox(new String[] {"75%",
"100%", "150%", "175%", "200%"});
final JPanel panel = new JPanel()
{
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
scaledTextPane.draw(g);
}
};
zoomCombo.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
String s = (String) zoomCombo.getSelectedItem();
s = s.substring(0, s.length() - 1);
double scale = new Double(s).doubleValue() / 100;
scaledTextPane.setScale(scale);
panel.invalidate();
panel.repaint();
}
});
zoomCombo.setSelectedItem("100%");
JPanel optionsPanel = new JPanel(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
c.gridx = 0;
c.gridy = 0;
c.anchor = GridBagConstraints.WEST;
optionsPanel.add(zoomCombo, c);
c.gridx++;
c.weightx = 1;
c.fill = GridBagConstraints.HORIZONTAL;
optionsPanel.add(Box.createHorizontalGlue(), c);
// Add content to the window.
scaledTextPane.setBounds(0, 0, 450, 300);
panel.setOpaque(true);
panel.setBackground(Color.WHITE);
frame.getContentPane().add(panel, BorderLayout.CENTER);
frame.getContentPane().add(optionsPanel, BorderLayout.NORTH);
frame.setSize(900, 300);
//Display the window.
frame.setVisible(true);
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
createAndShowGUI();
}
});
}
}
may be this http://java-sl.com/Scale_In_JEditorPane.html could help.
Sadly, scaling to a larger size from a fixed resolution will always result in some aliasing artifact. Here's an alternative approach that scales the font used by JTextPane.
For low-level control, consider TextLayout, which includes a FontRenderContext that can manage the anti-aliasing and fractional metrics settings, as seen in this example.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.text.*;
/** #see https://stackoverflow.com/questions/4566211 */
public class ScaledJTextPane {
private static final int SIZE = 14;
private static final String FONT = "Dialog";
private static void createAndShowGUI() {
JFrame frame = new JFrame("ScaledJTextPane using BufferedImage");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JTextPane tp = new JTextPane();
tp.setFont(new Font(FONT, Font.PLAIN, SIZE));
tp.setPreferredSize(new Dimension(400, 300));
StyledDocument doc = tp.getStyledDocument();
Style defaultStyle = StyleContext.getDefaultStyleContext()
.getStyle(StyleContext.DEFAULT_STYLE);
Style boldStyle = doc.addStyle("bold", defaultStyle);
StyleConstants.setBold(boldStyle, true);
String boldText = "Four score and seven years ago ";
String plainText = "our fathers brought forth on this continent, "
+ "a new nation, conceived in Liberty, and dedicated to the "
+ "proposition that all men are created equal.";
try {
doc.insertString(doc.getLength(), boldText, boldStyle);
doc.insertString(doc.getLength(), plainText, defaultStyle);
} catch (BadLocationException ble) {
ble.printStackTrace(System.err);
}
final JPanel panel = new JPanel();
panel.add(tp);
final JComboBox zoomCombo = new JComboBox(new String[]{
"75%", "100%", "150%", "175%", "200%"});
zoomCombo.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
String s = (String) zoomCombo.getSelectedItem();
s = s.substring(0, s.length() - 1);
double scale = new Double(s).doubleValue() / 100;
int size = (int) (SIZE * scale);
tp.setFont(new Font(FONT, Font.PLAIN, size));
}
});
zoomCombo.setSelectedItem("100%");
JPanel optionsPanel = new JPanel();
optionsPanel.add(zoomCombo);
panel.setBackground(Color.WHITE);
frame.add(panel, BorderLayout.CENTER);
frame.add(optionsPanel, BorderLayout.NORTH);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
createAndShowGUI();
}
});
}
}
I would like to allow users to zoom into or out of a non-editable JTextPane.
Since the text pane is non-editable, maybe you can create an image of the text pane by using the Screen Image class. Then you can draw the image on a panel using the approriate scaling factor.

Categories