I am trying to find the most efficient way to create a dynamic java graphical application. I want to build a large screen, with many different parts, all of which re-drawn or updated using a distinct thread, such that the screen looks "alive". However, my initial attempt at doing that was horrible, the screen got very slow, buggy etc - so I figured I need to create different modules (JPanels), each of which contains other graphical parts (lines, circles, etc), and each distinct JPanel being redrawn separately (when needed), instead of the whole main panel (or frame).
So I have written a small demo program - my program contains a single window, with multiple panels, wrapped in my object called "MyPanel" - each such MyPanel contains several drawn lines (I have a Line object), all lines starting from the top-left corner and have different lengths and angles). Each distinct MyPanel has a different line color (see this image).
I instantiate several worker threads, each designated for one MyPanel - the workers wait for 5 seconds, then try to re-draw all lines in the following manner:
- Remove all existing lines from the JPanel (MyPanel).
- Create new lines with different angles and lengths.
- redraw the JPanel (MyPanel) by invoking super.repaint() this is the entire purpose, to update only this panel, have it redraw itself with all of its sub-parts, and not the entire program
However, something weird happens: when the panels are re-drawn, each one is redrawn in a way that probably contains all other MyPanels too, or mirrors the main screen somehow - its very unclear what exactly happens here. Also, all "background opacity" of the panels is gone (see this image).
Before I attach my code, let me say that it uses a null LayoutManager. I know this is a big "no no" in terms of efficiency, modularity and whatnot. However I don't have a choice since I need to create a very graphically complicated and exact demo quickly, which only serves as a proof-of-concept, so for now, all of these flaws are negligible. I know it's horrible design-wise, it hurts me too, but that's the only way I can make it on time.
Here is the code - what happens? and how can I efficiently re-draw different parts of the program if not using this way? note I cannot "repaint over existing lines with the background color", since there is a background image in my main program.
Any help would be appreciated!
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
/**
* Displays the main windows (this is the "JFrame" object).
*/
public class GUI extends JFrame
{
/**
* A customized panel which contains several lines with different coordinates, all starting from
* the top left corner of the panel with coordinates (1,1). The object contains a method which
* removes all drawn lines from the panel, then redraws lines with different vectors.
*/
public static class MyPanel extends JPanel
{
private List<Line> _lines;
private Color _color;
private int _facet;
private int _numLines;
public MyPanel(int facet, int numLines, Color color)
{
_facet = facet;
_color = color;
_numLines = numLines;
_lines = new ArrayList<>();
super.setLayout(null);
createLines();
}
public void createLines()
{
for(Line line : _lines)
{
remove(line);
}
_lines.clear();
Random r = new Random();
for(int i = 0; i < _numLines; i++)
{
int lengthX = r.nextInt(_facet) + 1;
int lengthY = r.nextInt(_facet) + 1;
Line line = new Line(1, 1, 1 + lengthX, 1 + lengthY, 1, _color);
line.setBounds(1, 1, 1 + lengthX, 1 + lengthY);
super.add(line);
_lines.add(line);
}
super.repaint();
}
}
/**
* Represents a line, drawn with antialiasing at a given start and end coordinates
* and a given thickness.
*/
public static class Line extends JPanel
{
private int _startX;
private int _startY;
private int _endX;
private int _endY;
private float _thickness;
private Color _color;
public Line(int startX, int startY, int endX, int endY, float thickness, Color color)
{
_startX = startX;
_startY = startY;
_endX = endX;
_endY = endY;
_thickness = thickness;
_color = color;
}
public void paint(Graphics g)
{
Graphics2D g2d = (Graphics2D)g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(_color);
g2d.setStroke(new BasicStroke(_thickness));
g2d.drawLine(_startX, _startY, _endX, _endY);
}
}
/**
* Stores all "MyPanel" panels of the GUI.
* The "MyPanels" are rectangular panels containing lines of the same color
* (different color across different panels).
*/
public List<MyPanel> panels;
public GUI()
{
setSize(800, 800);
setLayout(null);
setTitle("Y U no work??");
panels = new ArrayList<>();
// The starting positions (x,y) of the "MyPanel"s. All panels are squares of
// height = 300 and width = 300.
int[][] coords = {{1, 1}, {100, 100}, {200, 100}, {50, 300}, {300, 300},
{0, 400}, {300, 400}, {350, 250}, {370, 390}};
// The colors of the lines, drawn in the panels.
Color[] colors = {Color.RED, Color.GREEN, Color.BLUE, Color.ORANGE, Color.CYAN,
Color.MAGENTA, Color.YELLOW, Color.PINK, Color.darkGray};
for(int i = 0; i < colors.length; i++)
{
MyPanel panel = new MyPanel(300, 50, colors[i]);
panel.setBackground(new Color(0, 0, 0, 0));
// Set the *exact* start coordinates and width/height (null layout manager).
panel.setBounds(coords[i][0], coords[i][1], 300, 300);
add(panel);
panels.add(panel);
}
}
/**
* A runnable used to instantiate a thread which waits for 5 seconds then redraws
* the lines of a given "MyPanel".
*/
public static class Actioner implements Runnable
{
private MyPanel _panel;
public Actioner(MyPanel panel)
{
_panel = panel;
}
public void run()
{
while(true)
{
try
{
Thread.sleep(5000);
}
catch(Exception e) {}
_panel.createLines();
}
}
}
public static void main(String[] args)
{
GUI GUI = new GUI();
EventQueue.invokeLater(() ->
{
GUI.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
GUI.setVisible(true);
});
// Create all operating threads (one per "MyPanel").
for(MyPanel panel : GUI.panels)
{
new Thread(new Actioner(panel)).start();
}
}
}