I'm trying to make a Paint program using java , I have three events in the jPanel to draw my line.
my problem is that when I am drawing the new line , the first one removed (I think the problem in the dragged event!) .. and so on.
Note that while the mouse is dragged the line will be stucked to the mouse
here is my events code:
private void jPanel1MousePressed(java.awt.event.MouseEvent evt) {
g1=(Graphics2D) jPanel1.getGraphics();
p1=jPanel1.getMousePosition();
}
JLayer lpane;
private void jPanel1MouseDragged(java.awt.event.MouseEvent evt) {
if(p1!=null){
lpane = new JLayer();
jPanel1.add(lpane, BorderLayout.CENTER);
lpane.setBounds(0, 0, 328, 257);
g2=(Graphics2D) lpane.getGraphics();
l=new Line(p1.x,p1.y,jPanel1.getMousePosition().x,jPanel1.getMousePosition().y);
l.draw(g2);
//lpane.repaint();
lpane.setVisible(false);
lpane.removeAll();
lpane.disable(); jPanel1.remove(lpane);
}
}
private void jPanel1MouseReleased(java.awt.event.MouseEvent evt) {
if(p1!=null)
{
g1=(Graphics2D) jPanel1.getGraphics();
p2=jPanel1.getMousePosition();
l=new Line(p1.x,p1.y,p2.x,p2.y);
g1.setColor(Color.red);
l.draw(g1);
p1=null;
}
}
Graphics2D g1,g2; Point p1=null,p2=null; Line l;
getGraphics is not how painting should be done in Swing, instead override the panels paintComponent and paint your components state there.
The paintComponent method needs to know what to paint whenever it is called, as it may be called any number of times, many times without your interaction or knowledge.
One approach is to build a List of shapes or Points, which can then be looped through and painted each time paintComponent is called. The benefit of this is you can remove these shapes/points should you wish.
See Pinting in AWT and Swing and Performing Custom Painting for more detals
Also take a look at this example for an idea
The usual way of doing this is to create a (Buffered)Image the size of your Component, fill the background color, and then draw each new line on the Image as well. In your paintComponent method, all you call is g.drawImage(...);
In your panel:
public void paintComponent(Graphics g) {
if (mSizeChanged) {
handleResize();
}
g.drawImage(mImg, 0, 0, null);
}
In your MouseMotionListener:
public void mouseDragged(MouseEvent me) {
Graphics g = mImg.getGraphics();
Point p = me.getPoint();
g.drawLine(mLastPoint.x, mLastPoint.y, p.x, p.y); }
Related
I am trying to create a 3D game engine. I am using the triangle rasterizing technique so I have a list of lines (that make up triangles) in my CanvasPanel class and each time that paintComponent() is called it goes through all the lines and displays them on the screen. The problem is in my main loop, since I want to remove all lines in order to update the scene but when I call it it's being called while the paintComponent() method is still running so I get a ConcurrentModificationException. I tried having a loop going that checks if it has finished drawing and only then clear the lines list but for some reason it works only when adding a print statement, otherwise it stops working after a few seconds. What should I do to make sure the timing is right?
Relevant code in Main:
import javax.swing.*;
import java.awt.*;
public class Main
{
public static int width = 1280, height = 720;
public static void main(String[] args)
{
JFrame window = new JFrame("3D Game");
CanvasPanel canvas = new CanvasPanel(width, height);
window.setLayout(new FlowLayout());
window.getContentPane().add(canvas);
window.pack();
window.setVisible(true);
window.setResizable(false);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Some irrelevant calculations
while (true) // Main loop
{
// Some irrelevant calculations
for (float[][] triangle: cubeMesh)
{
// Here I'm adding lines to the lines list.
}
canvas.repaint(); // update canvas
// My attempt at making sure that clearing the lines list happens only after drawing
while (true)
{
if (!canvas.isDrawing()}
{
// Only works if I print something
break;
}
}
canvas.clearLines(); // here is the problematic line
}
}
}
Relevant code in CanvasPanel:
public class CanvasPanel extends JPanel
{
BufferedImage canvas;
JLabel label;
private ArrayList<Line> lines;
private boolean drawing;
public CanvasPanel(int width, int height)
{
canvas = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
label = new JLabel(new ImageIcon(canvas));
lines = new ArrayList<>();
add(label);
drawing = false;
}
public boolean isDrawing()
{
return drawing;
}
public void clearLines()
{
lines.clear();
}
#Override
protected void paintComponent(Graphics g)
{
drawing = true;
super.paintComponent(g);
g.setColor(Color.black);
g.fillRect(0, 0, getWidth(), getHeight());
for (Line line: lines)
{
g.setColor(line.color);
g.drawLine(line.x1, line.y1, line.x2, line.y2);
}
drawing = false;
}
public void addLine(int x1, int y1, int x2, int y2, Color color)
{
lines.add(new Line(x1, y1, x2, y2, color));
}
First, go read Performing Custom Painting and Painting in AWT and Swing to get a better understanding of how painting works in Swing.
Swing is lazy and makes use of a passive rendering workflow. This means that painting is done when the system decides it needs to be done. You can "request" a paint pass via the RepaintManager (normal via calling repaint on the component)
What this does is, if the RepaintManager agrees, places a "paint" event on the Event Dispatching Queue, which is, at some point in the future picked up by the Event Dispatching Thread and a paint pass is executed (this may be consolidated so that multiple components are painted in one pass).
There are two problems you're facing:
Painting can occur for any number of reasons, many of which you don't control and you're notified by
There's no reliable way to determine if or when a paint pass will/has occurred.
Now, add in the issue that Swing is not thread safe and you run into a mountain of other issues.
See Concurrency in Swing for more details.
So, what's the answer?
Well, you could use Worker Threads and SwingWorker or a Swing Timer to synchronise the updates depending on your needs, but honestly, I don't think making use of Swing's paint process is what you really need.
Another consideration would be use a backing buffer of your own. That is, use two or more BufferedImages onto which you draw the "state" and then pass these to the canvas to be drawn at some point in the future.
However, I would suggest taking control of the painting process directly yourself. Instead, have a look at BufferStrategy and BufferCapabilities (and the JavaDocs because it has a nice example).
This will provide you with direct control over the painting process so you can decide when and how it should be painted.
I am trying to create a window frame to display a game window. I extended JFrame in my GameWindow class and created two methods: drawBackground, which fills the screen with a solid rectangle, and drawGrid, which draws successive lines using a for-loop to make a grid. Here is my code.
public class GameWindow extends JFrame {
// instance variables, etc.
public GameWindow(int width, Color bgColor) {
super();
// ...
this.setVisible(true);
}
public void drawBackground() {
Graphics g = this.getGraphics();
g.setColor(bgColor);
g.fillRect(0, 0, this.getWidth(), this.getWidth());
// I suspect that the problem is here...
this.update(g);
this.revalidate();
this.repaint();
g.dispose();
}
public void drawGrid() {
Graphics g = this.getGraphics();
g.setColor(Color.BLACK);
for (int i = tileWidth; i < TILE_COUNT * tileWidth; i += tileWidth) {
g.drawLine(0, i * tileWidth, this.getWidth(), i * tileWidth);
g.drawLine(i * tileWidth, 0, i * tileWidth, this.getHeight());
}
// ... and here.
this.update(g);
this.revalidate();
this.repaint();
g.dispose();
}
}
However, when I try to test this class in a program like this:
public class Main {
public static void main(String[] args) {
GameWindow game = new GameWindow(700);
game.drawBackground();
game.drawGrid();
}
}
the frame appears on screen but remains blank; neither the background nor the grid is being drawn. I tried Graphics g = this.getGraphics() to this.getContentPane().getGraphics(). I also tried using many different combinations and orders in drawBackground and drawGrid of revalidate, update, etc. None of these attempts seemed to work. How can I fix this issue?
Well, Graphics g = this.getGraphics(); would be a good place to start. Since repaint just schedules a paint pass to occur with the RepaintManager, all the code using getGraphics will simply be ignored.
This is not how custom painting works. getGraphics can return null and is, at best, a snapshot of the last paint cycle, anything you paint to it will be wiped clean on the next paint cycle.
Also, don't dispose of a Graphics context you did not create, on some systems this will stop other components from been able to use it
Start by taking a look at Performing Custom Painting and Painting in AWT and Swing for a better understanding into how painting works and how you should work with it.
You're also, likely, going to want to have a read through Concurrency in Swing and How to Use Swing Timers for ideas on creating a "main loop" to update the UI at constant rate, as Swing is single threaded AND not thread safe
JPanel Initiation
p = new JPanel() {
private static final long serialVersionUID = 1L;
public void paintComponent(Graphics g) {
if(errors == 1)
g.drawOval(215, 50, 75, 75);
else if(errors == 2)
g.drawOval(200,200,200,200);
}
};
Method that calls repaint
static void drawHead() {
System.out.println("Head");
errors = 1;
p.removeAll();
p.revalidate();
p.repaint();
}
Before repaint my frame looks like this, http://i.imgur.com/XQlQeul.png
And afterwards it looks like this, http://i.imgur.com/RnVuUzt.png
I'm thinking there is an error in my drawHead() method but I cannot seem to resolve the issue. Does anyone know what is going on? My desired outcome would be the first image, but with a head drawn in.
You've broken the paint chain by failing to call super.paintComponent first before you performed any custom painting
Graphics is shared resource, every component painted in a during a paint cycle will share the same Graphics context, this means that whatever was previously painted to the Graphics context will remain unless you clear it.
One of the jobs of paintComponent is to prepare the Graphics context for painting by filling it with the background color of the component
I have an assignment to create a paint program in Java. I have managed to create something but not exactly what I wanted.
My problem is that I cannot create a JFrame in my IDE(NetBeans 7.0.1) from the options that the IDE gives me, and call the paint classes correctly.
To be more specific I want to press a button from one panel(ex. Panel1) and paint in Panel2,in the same frame.
That's the calling of the class:
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
PaintFlower102 f = new PaintFlower102();
}
Part of Class:
super("Drag to Paint");
getContentPane().add(new Label ("Click and Drag"),BorderLayout.SOUTH);
// add(new JButton("Brush 20"),BorderLayout.NORTH);
addMouseMotionListener( new MouseMotionAdapter() {
#Override
public void mouseDragged(MouseEvent event) {
xval=event.getX();
yval=event.getY();
repaint();
}
});
setSize(500, 500);
setVisible(true);
setDefaultCloseOperation(PaintFlower102.DISPOSE_ON_CLOSE);
}
public void paint(Graphics g) {
g.fillOval(xval, yval, 10, 10);
}
The problem is that if I do not put the extend JFrame in the class this doesn't work. And if I do, it creates a new frame in which I can draw.
Suggestions:
Don't ever paint directly in a JFrame except under rare circumstances of absolute need (this isn't one of them).
Instead paint in a JPanel or JComponent or other derivative of JComponent.
Paint in the class's paintComponent(Graphics g) method, not in paint(Graphics g).
Read the Java tutorials on this as it's all explained well there. Check out Trail: 2D Graphics and Performing Custom Painting.
I might be wrong, but I think that you need to include super.paintComponent(g), and override the paintComponent method like Hovercraft Full Of Eels said.
public void paintComponent(Graphics g) {
super.paintComponent(g);
// Draw Oval
g.fillOval(xval, yval, 10, 10);
}
I'm working on a project and I've read up as much as I can on double buffering in java. What I want to do is add a component or panel or something to my JFrame that contains the double buffered surface to draw to. I want to use hardware acceleration if possible, otherwise use regular software renderer. My code looks like this so far:
public class JFrameGame extends Game {
protected final JFrame frame;
protected final GamePanel panel;
protected Graphics2D g2;
public class GamePanel extends JPanel {
public GamePanel() {
super(true);
}
#Override
public void paintComponent(Graphics g) {
g2 = (Graphics2D)g;
g2.clearRect(0, 0, getWidth(), getHeight());
}
}
public JFrameGame() {
super();
gameLoop = new FixedGameLoop();
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
panel = new GamePanel();
panel.setIgnoreRepaint(true);
frame.add(panel);
panel.setVisible(true);
frame.setVisible(true);
}
#Override
protected void Draw() {
panel.repaint(); // aquire the graphics - can I acquire the graphics another way?
super.Draw(); // draw components
// draw stuff here
// is the buffer automatically swapped?
}
#Override
public void run() {
super.run();
}
}
I created an abstract game class and a game loop that calls Update and Draw. Now, if you see my comments, that's my main area of concern. Is there a way to get the graphics once instead of going through repaint and paintComponent and then assigning a variable every redraw? Also, is this hardware accelerated by default? If not what should I do to make it hardware accelerated?
If you want more control over when the window is updated and to take advantage of hardware page flipping (if available), you can use the BufferStrategy class.
Your Draw method would then look something like this:
#Override
protected void Draw() {
BufferStrategy bs = getBufferStrategy();
Graphics g = bs.getDrawGraphics(); // acquire the graphics
// draw stuff here
bs.show(); // swap buffers
}
The downside is that this approach does not mix well with event-driven rendering. You generally have to choose one or the other. Also getBufferStrategy is implemented only in Canvas and Window making it incompatible with Swing components.
Tutorials can be found here, here and here.
Don't extend JPanel. Extend JComponent. It's virtually the same and has less interfering code. Also, you'd do the drawing code in paintComponent only. If you need to manually refresh the component, you'd use component.redraw().