Drawing with a JPanel's getGraphics()? - java

I've run into a bit of a wall here. I read elsewhere while trying to fix this issue that you are never supposed to getGraphics(). The problem is, I can't use the provided Graphics context from the paint() / paintComponent() methods. I require it to only call my generate(Graphics g) function once, and I can not provide Graphics outside of the override functions.
Any tips? Trimmed for your convenience.
public class Main extends JPanel {
...
static JFrame displayFrame, inputFrame;
...
...
// Generator node list
ArrayList<Node> nodes = new ArrayList<Node>();
public static void main(String[] args) {
// Set up the frame
screenSize = Toolkit.getDefaultToolkit().getScreenSize();
displayFrame = new JFrame("City generator");
displayFrame.setSize(screenSize.width / 3, screenSize.width / 3);
displayFrame.setLocation(screenSize.width / 2 - displayFrame.getWidth()
/ 2, screenSize.height / 2 - displayFrame.getHeight() / 2);
displayFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
displayFrame.add(new Main());
// displayFrame.setUndecorated(true);
displayFrame.setBackground(Color.lightGray);
displayFrame.setVisible(true);
displayFrame.addMouseMotionListener(new MouseAdapter() {
public void mouseMoved(MouseEvent e) {
// Mouse movement events here
}
});
}
// Override function
public void paintComponent(Graphics g) {
super.paintComponent(g);
// Calls multiple times
generate(g);
}
private void generate(Graphics g) {
............................

Instead of painting directly to the Graphics context, you could generate a BufferedImage image of what you want painted and paint to that instead...
private BufferedImage buffer;
public BufferedImage generate() {
if (buffer == null) {
buffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_IMAGE_ARGB);
Graphics2D g2d = buffer.createGraphics();
// Paint away...
g2d.dispose();
}
return buffer;
}
Then you would paint the result within the paintComponent method...
public void paintComponent(Graphics g) {
super.paintComponent(g);
// Calls multiple times
BufferedImage img = generate();
g.drawImage(img, 0, 0, this);
}
As an example...

Why not just keep a boolean value to track if generate() has been called.
public boolean calledGenerate = false;
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (!calledGenerate) generate(g);
}
void generate(Graphics g) {
calledGenerate = true;
....
}
also you are calling AWT/Swing code outside of the EDT which is a bad idea.
In your main function you normally should call:
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
//build your awt/swing objects here
}
});
What does generate() do though? There is probably a better way to do it.

Related

how do you draw a line that resize when window size change? [duplicate]

I've written an app that custom draws everything inside paint() based on fixed pixel positions. Then I disabled resize of the frame so its always visible.
However, now I would like to be able to resize it but I dont want to change my drawling code. I was hoping I could grab the 300x300 square of the Graphics g object and resize it to the JFrame current size after all of my drawling code, but I have no idea what I'm doing.
Here sample code. In this I want the 100x100 square to remain in the middle, proportionate to the resized JFrame:
package DrawAndScale;
import java.awt.Color;
import java.awt.Graphics;
public class DASFrame extends javax.swing.JFrame {
public DASFrame() {
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
this.setSize(300, 300);
}
public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new DASFrame().setVisible(true);
}
});
}
#Override
public void paint(Graphics g) {
g.setColor(Color.BLACK);
g.fill3DRect(100, 100, 100, 100, true);
}
}
Thanks.
Assuming you rename your method that paints for 300x300 as paint300, define a buffered image:
#Override public void paint(Graphics g) {
Image bufferImage = createImage(300, 300); // empty image
paint300(bufferImage.getGraphics()); // fill the image
g.drawImage(bufferImage, 0, 0, null); // send the image to graphics device
}
Above is when you want to draw at full size (300x300).
If your window is resized:
#Override public void paint(Graphics g) {
Image bufferImage = createImage(300, 300);
paint300(bufferImage.getGraphics());
int width = getWidth();
int height = getHeight();
CropImageFilter crop =
new CropImageFilter((300 - width)/2, (300 - height)/2 , width, height);
FilteredImageSource fis = new FilteredImageSource(bufferImage, crop);
Image croppedImage = createImage(fis);
g.drawImage(croppedImage, 0, 0, null);
}
The new classes are from from java.awt.image.*.
I didn't test this code. It's just to send you in the right direction.
if you want to painting Custom paint then look for JLabel or JPanel and including Icon/ImageIcon inside, simple example about that
import java.awt.*;
import javax.swing.*;
public class MainComponentPaint extends JFrame {
private static final long serialVersionUID = 1L;
public MainComponentPaint() {
setTitle("Customize Preffered Size Test");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public void display() {
add(new CustomComponent());
pack();
setMinimumSize(getSize());
setPreferredSize(getSize());
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
setVisible(true);
}
});
}
public static void main(String[] args) {
MainComponentPaint main = new MainComponentPaint();
main.display();
}
}
class CustomComponent extends JComponent {
private static final long serialVersionUID = 1L;
#Override
public Dimension getPreferredSize() {
return new Dimension(50, 50);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
int w = getWidth();
int h = getHeight();
for (int i = 0; i < Math.max(w, h); i += 20) {
g.drawLine(i, 0, i, h);
g.drawLine(0, i, w, i);
}
}
}
Not an expert, but you could just scale the Graphics2D object (the passed Graphics is in fact a Graphics2D instance), where the x and y ratios are the ratios of the fixed size you chose to draw and the actual size of the frame.
See http://download.oracle.com/javase/6/docs/api/java/awt/Graphics2D.html#scale%28double,%20double%29
You could do this with some math.
public void paint(Graphics g){
int height = 100;
int width = 100;
int x = (this.getWidth() / 2) - (width / 2);
int y = (this.getHeight() / 2) - (height / 2);
g.setColor(Color.BLACK);
g.fill3DRect(x, y, width, height, true);
}
Or if you wanted to keep the width and height of the box with the same proportion, use int width = this.getWidth() / 3; and int height = this.getHeight() / 3.
The other option is to use Graphics2D.scale(), as JB pointed out, the passed Graphics object is actually a Graphics2D object.

How to create a method in other class drawing on JPanel

My goal: create a method of class Point (code at the bottom), that allows me to draw multiple points (and lines in the future), so it looks like this:
Point pointA = new Point(0,0);
Point pointB = new Point(10,20);
pointA.draw(); // or probably: pointA.draw(someSurface);
pointB.draw();
I took code from a tutorial and modified it.
I found many similar problems while searching for answer, but either none of them had the answer for my case or I couldn't implement it.
As you can see below, I tried to create the method both inside the Point class and inside the Surface class (commented code), but without success.
What is the problem and what should I fix in my code?
Main:
public class Main {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
BasicGraphics ex = new BasicGraphics();
ex.setVisible(true);
Point pointA = new Point(0,0);
Point pointB = new Point(10,10);
pointA.draw(ex.currentSurface);
pointB.draw(ex.currentSurface);
}
});
}
}
BasicGraphics:
public class BasicGraphics extends JFrame {
public Surface currentSurface;
public BasicGraphics() {
initUI();
}
private void initUI() {
currentSurface=new Surface();
add(currentSurface);
setTitle("My points");
setSize(100, 100);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
Surface:
class Surface extends JPanel {
/*public void drawPoint(Graphics g, Point givenPoint) {
//I want to represent points as little circles
Ellipse2D circle = new Ellipse2D.Double();
Graphics2D g2d = (Graphics2D) g;
circle.setFrameFromCenter(givenPoint.x, givenPoint.y, 10,10);
g2d.draw(circle);
}*/
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
}
}
Point:
public class Point {
public double x;
public double y;
public Point(double newX, double newY){
x=newX;
y=newY;
}
public void draw(Surface givenSurface){
Graphics g = givenSurface.getGraphics();
Ellipse2D circle = new Ellipse2D.Double();
Graphics2D g2d = (Graphics2D) g;
circle.setFrameFromCenter(x, y, 10,10);
g2d.draw(circle);
givenSurface.paint(g2d);
//givenSurface.drawPoint(g2d,this);
//givenSurface.repaint();
}
}
edit: Solutions, thanks to camickr. That's not the most elegant way I can imagine, but it works:
draw method in Point:
public void draw(Surface givenSurface){
givenSurface.pointsList.add(this);
}
Surface class:
class Surface extends JPanel {
public ArrayList<Point> pointsList = new ArrayList<>();
private void drawPoints(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
double diameter = 10;
for (Point currentPoint : pointsList) {
Ellipse2D circle = new Ellipse2D.Double(currentPoint.x - diameter / 2, currentPoint.y - diameter / 2, diameter, diameter);
g2d.fill(circle);
}
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
drawPoints(g);
}
}
create a method of class Point (code at the bottom), that allows me to draw multiple points (and lines in the future).
All custom painting needs to be done in the paintComponent() method of the panel. So every time the panel is painted the points/lines are repainted.
So if you want to paint multiple object you need to:
Create an ArrayList of the Object you want to paint
Create a method to add Objects to this list
In the paintComponent() method you iterate through the list and paint each Object.
Check out the DrawOnCompont example from Custom Painting Approaches. It shows how you can dynamically add Rectangle to be painted.
Since the paintComponent method will be invoked at setVisible(true) but you draw your Points after that, You have to repaint. See code modification.
public class Main {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
BasicGraphics ex = new BasicGraphics();
ex.setVisible(true);
Point pointA = new Point(0,0);
Point pointB = new Point(10,10);
pointA.draw(ex.currentSurface);
pointB.draw(ex.currentSurface);
ex.revalidate();
ex.repaint();
}
});
}
}

JPanel flashing

In a Java application im making i want to update my view and redraw some targets with the positions they have.
My GameView render function and constructor:
//Constructor
public GameView(){
frame = new JFrame("Hunter");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
drawing = new MyDrawing();
drawing.setDoubleBuffered(true);;
drawing.setIgnoreRepaint(true);
frame.getContentPane().add(BorderLayout.CENTER,drawing);
frame.setResizable(false);
frame.setSize(800,600);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
//Render method where i want to draw all the object. (called form another class)
public void Render(ArrayList<Target> targets){
//drawing is a MyDrawing object see next code.
// i clear the jpanel with a white background
drawing.drawBackground();
// Loop al targets and paint them
for (Target target : targets) {
drawing.paintComponent(target.getX(), target.getY(), target.getSize());
}
}
The MyDrawing class.
public class MyDrawing extends JPanel {
//Paint a target
public void paintComponent( int x, int y, int size){
Graphics g = getGraphics();
g.setColor(Color.black);
g.fillOval(x, y, size, size);
}
public void drawBackground(){
Graphics g = getGraphics();
g.setColor(Color.white);
g.fillRect(0, 0, this.getWidth(),this.getHeight());
}
}
Render in my GameView is called in a loop from my GameController;
public void Run(){
while(notClosed){
Render();
}
try {
Thread.sleep(1000/100);
} catch (Exception e) {
// TODO: handle exception
}
}
}
public void Render(){
myView.Render(myGame.getTargets());
}
Eveything I want is drawn on the screen but i see some flashes. Especialy on the left side of the jpanel the flashes are very visible. Also once in every x? secons the whole screen flashes. I call the setDubbelBuffer(true)in the constructor of gameview.

I can't get double buffering to work

I have tried quite a few tutorials and I can't seem to get double buffering to work. Here is my main (the update() method is the code i tried, but I still see flickering):
public class Main extends JApplet implements Runnable {
private static final long serialVersionUID = 1L;
private static int width = 900;
private static int height = 600;
public static int fps = 60;
public Thread thread = new Thread(this);
private Image dbImage;
private Graphics dbg;
public static Ailoid ailoid = new Ailoid();
// Initialize
public void init() {
setSize(width, height);
setBackground(Color.white);
ailoid.setLocation(new Location(100, 100));
AlienManager.registerAlien(ailoid);
}
// Paint graphics
public void paint(Graphics g) {
super.paint(g);
g.setColor(Color.green);
for (Alien alien : AlienManager.getAliens()) {
Location loc = alien.getLocation();
int x = loc.getX();
int y = loc.getY();
g.fillRect(x, y, 10, 20);
}
}
// Update graphics for double buffering
public void update(Graphics g) {
if (dbImage == null) {
dbImage = createImage (width, height);
dbg = dbImage.getGraphics();
}
dbg.setColor (getBackground ());
dbg.fillRect (0, 0, width, height);
dbg.setColor (getForeground());
paint (dbg);
g.drawImage (dbImage, 0, 0, this);
}
// Thread start
#Override
public void start() {
thread.start();
}
// Thread stop
#Override
public void destroy() {
thread = null;
}
// Thread run
#Override
public void run() {
while (thread != null) {
Updater.run();
repaint();
try {
// 1000 divided by fps to get frames per millisecond
Thread.sleep(1000 / fps);
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
If someone could help it would be greatly appreciated!
Instead of drawing your objects one by one, draw them on an image and then tell the renderer to draw that entire image. This eliminates the flickering.
Here's an example of how you might accomplish it.
class DoubleBufferedCanvas extends Canvas {
public void update(Graphics g) {
Graphics offgc;
Image offscreen = null;
Dimension d = size();
// create the offscreen buffer and associated Graphics
offscreen = createImage(d.width, d.height);
offgc = offscreen.getGraphics();
// clear the exposed area
offgc.setColor(getBackground());
offgc.fillRect(0, 0, d.width, d.height);
offgc.setColor(getForeground());
// do normal redraw
paint(offgc);
// transfer offscreen to window
g.drawImage(offscreen, 0, 0, this);
}
}

PhoneScreenVerticalManager weirdness on incoming call

Im making an app which has a listener on incoming calls and then adds fields til the phonescreen..
However i cannot seem to really control the width of either labelfields or the PhoneScreenVerticalManager i use.
If i try to set a border or background on the PhoneScreenVerticalManager nothing happens at all.
It also seems like USE_ALL_WIDTH in the labelfield constructor doesn't change anything.
And getting the labelfields to left align i also couldn't get to work (tried DrawStyle.Left in the labelfield constructor).
here is my code:
public Incoming(int callId) {
this.callId = callId;
PhoneCall call = Phone.getCall(callId);
String number = call.getPhoneNumber();
Vector contact = ContactUtil.getContactByPhone(number);
screenModel = new ScreenModel(callId);
phoneScreenPortrait = screenModel.getPhoneScreen(PhoneScreen.PORTRAIT, PhoneScreen.INCOMING);
final XYRect rect = screenModel.getDimensions(PhoneScreen.PORTRAIT, PhoneScreen.INCOMING);
PhoneScreenVerticalManager manager = new PhoneScreenVerticalManager()
{
public void paint(Graphics g) {
g.setColor(Color.BLACK);
g.setBackgroundColor(Color.WHITE);
g.clear();
super.paint(g);
}
protected void sublayout(int width, int height)
{
super.sublayout(rect.width, height);
super.setExtent(rect.width, height);
}
};
manager.setBackground(BackgroundFactory.createSolidBackground(Color.RED));
manager.setBorder(BorderFactory.createSimpleBorder(new XYEdges(BORDER_PADDING, BORDER_PADDING, BORDER_PADDING, BORDER_PADDING), Border.STYLE_SOLID));
String s = res.getString(FOUND_IN_CONTACTS);
LabelField header = new LabelField(s, LabelField.USE_ALL_WIDTH)
{
protected void layout(int width, int height)
{
super.layout(rect.width, height);
setExtent(rect.width, height);
}
public void paint(Graphics g) {
g.setColor(Color.BLACK);
g.setBackgroundColor(Color.WHITE);
g.clear();
super.paint(g);
}
};
header.setBackground(BackgroundFactory.createSolidBackground(Color.WHITE));
manager.add(header);
LabelField label = new LabelField(contact.firstElement().toString(), LabelField.USE_ALL_WIDTH)
{
protected void layout(int width, int height)
{
super.layout(rect.width, height);
setExtent(rect.width, height);
}
public void paint(Graphics g) {
g.setColor(Color.BLACK);
g.setBackgroundColor(Color.WHITE);
g.clear();
super.paint(g);
}
};
label.setBackground(BackgroundFactory.createSolidBackground(Color.WHITE));
manager.add(label);
phoneScreenPortrait.add(manager);
screenModel.sendAllDataToScreen();
}
Any ideas would be greatly appreciated!ยจ
Thanks
though the phonescreen managers are derived from the field manager it doesn't support all the field manager properties.
so the solution is to set up normal field manager and then add those to the phonescreen managers

Categories