I want to plot many sinusoids with different frequencies on a panel, but because painting is destructive, I cannot achieve this. I can only see the last sinusoid plotted.
In the code, the generateSinus() method is called only three times, but it might be called many times and I don't want to call g.fillOval() method for each sinusoid in the paintComponent() method.
Is there a way to achieve non-destructive painting and see all sinusioids with different frequencies?
Please see the code below:
package testdrawsinus;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class TestDrawSinus extends JPanel
{
private static double[] x;
private static double[] y;
private static boolean buttonClicked = false;
private static JPanel panel = new TestDrawSinus();
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
g.setColor(Color.GREEN);
if (buttonClicked)
{
for (int i=0; i<x.length; i++)
{
g.fillOval((int)x[i] + panel.getWidth()/2, -1*((int)y[i]) + panel.getHeight()/2, 10, 10);
}
buttonClicked = false;
}
}
private static void generateSinus(int freq)
{
x = new double[200];
y = new double[200];
for (int i=0; i<=199; i++)
{
x[i]= (double)i;
y[i] = 100*Math.sin(2*Math.PI*freq*i/200);
}
}
public static void main(String[] args)
{
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(null);
frame.setSize(800, 600);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
panel.setLayout(null);
panel.setBounds(20,20, 700,400);
panel.setBackground(Color.BLACK);
frame.add(panel);
JButton button1 = new JButton();
button1.setText("plot");
button1.setBounds(300, 500, 150, 50);
frame.add(button1);
button1.addActionListener(new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
buttonClicked = true;
generateSinus(1);
panel.repaint();
generateSinus(2);
panel.repaint();
generateSinus(3);
panel.repaint();
}
});
}
}
Thanks for your help.
There are a number of ways you might do this, one way would be to seperate each series into its own model, then have the panel paint each model, for example...
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Main {
public static void main(String[] args) {
new Main();
}
private Color[] masterColors = new Color[]{
Color.BLACK, Color.BLUE, Color.CYAN, Color.DARK_GRAY,
Color.GREEN, Color.MAGENTA, Color.ORANGE, Color.PINK,
Color.RED, Color.WHITE, Color.YELLOW
};
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
TestDrawSinus sinusPane = new TestDrawSinus();
JFrame frame = new JFrame();
frame.add(sinusPane);
JButton button1 = new JButton();
button1.setText("plot");
frame.add(button1, BorderLayout.SOUTH);
button1.addActionListener(new ActionListener() {
private List<Color> colors = new ArrayList<>();
private int freq = 0;
#Override
public void actionPerformed(ActionEvent e) {
sinusPane.addSinusPlot(generateSinus(++freq, nextColor()));
sinusPane.addSinusPlot(generateSinus(++freq, nextColor()));
sinusPane.addSinusPlot(generateSinus(++freq, nextColor()));
}
protected Color nextColor() {
if (colors.isEmpty()) {
colors.addAll(Arrays.asList(masterColors));
Collections.shuffle(colors);
}
return colors.remove(0);
}
});
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
private Random rnd = new Random();
private SinusPlot generateSinus(int freq, Color color) {
double[] x = new double[200];
double[] y = new double[200];
for (int i = 0; i < 200; i++) {
x[i] = (double) i;
y[i] = 100 * Math.sin(2d * Math.PI * freq * i / 200d);
}
return new SinusPlot(x, y, color);
}
public class SinusPlot {
private double[] x;
private double[] y;
private Color color;
public SinusPlot(double[] x, double[] y, Color color) {
this.x = x;
this.y = y;
this.color = color;
}
public int getSize() {
return x.length;
}
public double getXAt(int index) {
return x[index];
}
public double getYAt(int index) {
return y[index];
}
public Color getColor() {
return color;
}
}
public class TestDrawSinus extends JPanel {
private List<SinusPlot> plots = new ArrayList<>(8);
public void addSinusPlot(SinusPlot plot) {
plots.add(plot);
repaint();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(800, 600);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
for (SinusPlot plot : plots) {
System.out.println(plot.getColor());
g2d.setColor(plot.getColor());
for (int i = 0; i < plot.getSize(); i++) {
Ellipse2D dot = new Ellipse2D.Double(((getWidth() - plot.getSize()) / 2) + plot.getXAt(i), plot.getYAt(i) + getHeight() / 2, 10, 10);
g2d.fill(dot);
}
}
g2d.dispose();
}
}
}
null layouts aren't going to help you, take the time to learn understand how the layout management system works, it will save you lot of time and effort. See Laying Out Components Within a Container for more details.
static is not your friend (especially in this context), make the effort to understand how to live without it (and when to use it)
Related
Howcome this code below wont work? I want to add new Ovals to the ArrayList every 200 ms and display them and run them one by one. It works fine when Im running one particle s.runner(); but it doesnt seem to run all my particles.
MAIN:
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import javax.swing.JFrame;
import javax.swing.Timer;
public class ExempelGraphics extends JFrame implements ActionListener {
Timer t;
private int inc = 0;
ArrayList<Surface> particle = new ArrayList<>();
Surface s;
public ExempelGraphics() {
t = new Timer(10, this);
t.start();
s = new Surface(10, 10);
initUI();
}
private void initUI() {
add(s);
setSize(350, 250);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public void actionPerformed(ActionEvent e) {
// s.runner();
// add
if (inc++ % 20 == 0) {
particle.add(new Surface(10, 10));
}
// display
for (int i = 0; i < particle.size(); i++) {
Surface p = particle.get(i);
p.runner();
}
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
ExempelGraphics ex = new ExempelGraphics();
ex.setVisible(true);
}
});
}
}
GRAPHICS:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JPanel;
public class Surface extends JPanel {
private int locX = 0;
private int locY = 0;
public Surface(int locX, int locY) {
this.locX = locX;
this.locY = locY;
}
public void runner() {
locX = locX + 1;
repaint();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.RED);
g2d.fillOval(locX, locY, 10, 10);
}
}
I think that you're program structure is broken. You should have only one JPanel here that does the drawing, that has its paintComponent overridden, and your Surface class should be a logical class and not a component class -- in other words, don't have it extend JPanel, and give it a public void draw(Graphics g) method where you draw the oval. Then have the drawing JPanel hold an ArrayList of these surfaces, and in the main JPanel's paintComponent method, iterate through the surfaces, calling each one's draw method.
Also your Timer's delay is not realistic and is too small. 15 would be much more realistic.
Also, don't call repaint() from within surface, since that will generate too many repaint calls unnecessarily. Instead call it from within the Timer's ActionListener after calling the runner methods on all the Surface objects.
Also note that every time you add a component to a JFrame's contentPane in a default fashion, you cover up the previously added components. If you go by my recommendations above, this isn't an issue since you'd only be adding that single JPanel to it.
For example:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
#SuppressWarnings("serial")
public class ExampleGraphics2 extends JPanel {
private static final int PREF_W = 650;
private static final int PREF_H = 500;
private static final int TIMER_DELAY = 20;
private List<Surface> surfaces = new ArrayList<>();
public ExampleGraphics2() {
new Timer(TIMER_DELAY, new TimerListener()).start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
for (Surface surface : surfaces) {
surface.draw(g);
}
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
private class TimerListener implements ActionListener {
private int index = 0;
#Override
public void actionPerformed(ActionEvent e) {
index++;
index %= 20;
if (index == 0) {
surfaces.add(new Surface(10, 10));
}
for (Surface surface : surfaces) {
surface.runner();
}
repaint();
}
}
private static void createAndShowGui() {
JFrame frame = new JFrame("Example Graphics 2");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new ExampleGraphics2());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
package foo1;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
public class Surface {
private int locX = 0;
private int locY = 0;
public Surface(int locX, int locY) {
this.locX = locX;
this.locY = locY;
}
public void runner() {
locX = locX + 1;
}
public void draw(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.RED);
g2d.fillOval(locX, locY, 10, 10);
}
}
I tried changing the alpha of a drawn string by doing this:
g2d.drawString("HelloWorld", 50, 50, alphaValue);
This resulted in a compilation error.
All I'm trying to do is make a drawn string slowly turn transparent.
You need to set the color
Color curr = g2d.getColor();
// Color curr = Color.GREEN;
g2d.setColor(new Color(curr.getRed(), curr.getGreen(), curr.getBlue(), alphaValue));
g2d.drawString("HelloWorld", 50, 50);
As Sergiy said, you need to change the alpha of the Color used by the graphics.
Here's an example of one way can fade out the text.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class ExampleFrame extends JFrame {
private JPanel drawPanel = new DrawPanel();
private Timer timer;
private int alpha = 255;
private final int TIMER_TICK = 50;
private final int ALPHA_TICK_VALUE = 3;
private class DrawPanel extends JPanel {
final int PANEL_HEIGHT = 80;
final int PANEL_WIDTH = 100;
final int TEXT_MARGIN = 20;
DrawPanel() {
setPreferredSize(new Dimension(PANEL_WIDTH, PANEL_HEIGHT));
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Color color = new Color(0, 0, 0, alpha);
g.setColor(color);
g.drawString("Hello World", TEXT_MARGIN,
PANEL_HEIGHT / 2 + g.getFontMetrics().getHeight() / 2);
}
}
public void createAndShow() {
getContentPane().add(drawPanel);
timer = new Timer(TIMER_TICK, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
alpha -= ALPHA_TICK_VALUE;
if (alpha >= 0) {
drawPanel.repaint();
} else {
alpha = 0;
timer.stop();
}
}
});
pack();
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
timer.start();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
ExampleFrame ef = new ExampleFrame();
ef.createAndShow();
}
});
}
}
I've just moved over from Pygame so Java 2D in an applet is a little new to me, especially when it comes to repainting the screen. In pygame you can simply do display.fill([1,1,1]) but how do I do this in an applet in Java? I understand the use of repaint() but that doesn't clear the screen - any moving object is not 'removed' from the screen so you just get a long line of painted circles.
Here's my code that I've been testing with:
package circles;
import java.applet.Applet;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.util.Random;
public class circles extends Applet implements Runnable {
private static final long serialVersionUID = -6945236773451552299L;
static Random r = new Random();
String msg = "Click to play!";
static int w = 800, h = 800;
int[] txtPos = { (w/2)-50,(h/2)-50 };
int[] radiusRange = { 5,25 };
int[] circles;
static int[] posRange;
int x = 0, y = 0;
int radius = 0;
int cursorRadius = 10;
boolean game = false;
public static int[] pos() {
int side = r.nextInt(5-1)+1;
switch(side) {
case 1:
posRange = new int[]{ 1,r.nextInt(w),r.nextInt((h+40)-h)+h,r.nextInt(270-90)+90 };
break;
case 2:
posRange = new int[]{ 2,r.nextInt((w+40)-w)+w,r.nextInt(h),r.nextInt(270-90)+90 };
break;
case 3:
posRange = new int[]{ 3,r.nextInt(w),r.nextInt(40)-40,r.nextInt(180) };
break;
case 4:
posRange = new int[]{ 4,r.nextInt(40)-40,r.nextInt(h),r.nextInt(180) };
break;
}
System.out.println(side);
return posRange;
}
public void start() {
setSize(500,500);
setBackground(Color.BLACK);
new Thread(this).start();
}
public void run() {
}
public void update(Graphics g) {
paint(g);
}
public void paint(Graphics e) {
Graphics2D g = (Graphics2D) e;
if(System.currentTimeMillis()%113==0) {
x+=1;
y+=1;
}
g.setColor(Color.BLUE);
g.fillOval(x,y,20,20);
repaint();
}
}
You need to call super.paint(g); in your paint method, as to not leave paint artifacts.
Never call repaint() from inside the paint method
Don't explicitly call paint, as you do in update(), when you mean to call reapaint()
just update the x and y values from inside the update() method, then call repaint()
You don't need to take a Graphics argument in update()
You need to call update() somewhere repeatedly in a loop, as it updates the x and y and reapint()s
If your class is going to be a Runnable, then you should put some code in the run() method. That's probably where you should have your loop
import java.applet.Applet;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
public class circles extends Applet implements Runnable {
int x = 0, y = 0;
public void start() {
setSize(500, 500);
setBackground(Color.BLACK);
new Thread(this).start();
}
public void run() {
while (true) {
try {
update();
Thread.sleep(50);
} catch (InterruptedException ex) {
}
}
}
public void update() {
x += 5;
y += 6;
repaint();
}
public void paint(Graphics e) {
super.paint(e);
Graphics2D g = (Graphics2D) e;
g.setColor(Color.BLUE);
g.fillOval(x, y, 20, 20);
}
}
Side Notes
Why use Applets in the first place. If you must, why use AWT Applet and not Swing JApplet? Time for an upgrade.
Here's how I'd redo the whole thing in Swing, using a Swing Timer instead of a loop and Thread.sleep, as you should be doing.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class Circle extends JPanel{
private static final int D_W = 500;
private static final int D_H = 500;
int x = 0;
int y = 0;
public Circle() {
setBackground(Color.BLACK);
Timer timer = new Timer(50, new ActionListener(){
public void actionPerformed(ActionEvent e) {
x += 5;
y += 5;
repaint();
}
});
timer.start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLUE);
g.fillOval(x, y, 20, 20);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(D_W, D_H);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame();
frame.add(new Circle());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}
See How to use Swing Timers
See Create GUIs with Swing
Here's more advanced example for you to look at and ponder.
UPDATE
"Problem is, that's a JPANEL application. I specifically want to make an applet easily usable on a web page. "
You can still use it. Just use the JPanel. Take out the main method, and instead of Applet, use a JApplet and just add the JPanel to your applet. Easy as that.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JApplet;
import javax.swing.JPanel;
import javax.swing.Timer;
public class CircleApplet extends JApplet {
#Override
public void init() {
add(new Circle());
}
public class Circle extends JPanel {
private static final int D_W = 500;
private static final int D_H = 500;
int x = 0;
int y = 0;
public Circle() {
setBackground(Color.BLACK);
Timer timer = new Timer(50, new ActionListener() {
public void actionPerformed(ActionEvent e) {
x += 5;
y += 5;
repaint();
}
});
timer.start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLUE);
g.fillOval(x, y, 20, 20);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(D_W, D_H);
}
}
}
I am trying to make a program that work like this:
In Window class every time I click on the button, the method panel2 of Panel is called: first it is drawing a first circle, then a second one (after the time defined in the timer). Then, I click again on the button, and it is drawing a fist circle, then a second one then a third one. etc.
The problem is that it when I click to obtain 3 circles appearing one after the other, the two first circles drawn at the previous step (before I pressed a second time the button) stay on the screen and only the third circle is drawn when i press the button (instead of having : first circle drawn, second circle drawn, third circle drawn). I hope I am clear.
Here is a simple code:
Window
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Window extends JFrame implements ActionListener{
int h = 2;
Panel b = new Panel();
JPanel container = new JPanel();
JButton btn = new JButton("Start");
JButton bouton = new JButton();
Panel boutonPane = new Panel();
public Window(){
this.setTitle("Animation");
this.setSize(300, 300);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLocationRelativeTo(null);
container.setBackground(Color.white);
container.setLayout(new BorderLayout());
JPanel top = new JPanel();
btn.addActionListener(this);
top.add(btn);
container.add(top);
this.setContentPane(container);
this.setVisible(true);
}
public void window2(){
this.setTitle("ADHD");
this.setSize(1000,700);
this.setLocationRelativeTo(null);
if (h < 11){
boutonPane.panel2(h);
bouton.addActionListener(this);
boutonPane.add(bouton);
this.add(boutonPane);
this.setContentPane(boutonPane);
updateWindow2();
}
this.setVisible(true);
}
public void updateWindow2(){
boutonPane.panel2(h);
this.revalidate();
this.repaint();
}
public void actionPerformed(ActionEvent e){
if ((JButton) e.getSource() == btn){
System.out.println("pressed0");
window2();
}
if ((JButton) e.getSource() == bouton){
h++;
System.out.println("pressed" + h);
updateWindow2();
}
}
public static void main(String[] args){
Window w = new Window();
}
}
Panel
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Panel extends JPanel implements ActionListener{
int m;
int u=0;
int lgi, lrgi;
int [] ta;
Timer timer1 = new Timer(300, this);
Panel(){
}
public void panel2(int n){
m=n;
ta = new int [n];
for(int it=0; it<m;it++){
ta[it]=100*it;
}
timer1.start();
}
public void paintComponent(Graphics gr){
super.paintComponent(gr);
gr.setColor(Color.red);
for(int i=0;i<m;i++){
gr.fillOval(ta[i],ta[i], 150, 150);
}
}
#Override
public void actionPerformed(ActionEvent arg0) {
if(u<m){
u++;
revalidate();
repaint();
}
}
}
Your code needs use two int values to decide how many circles to draw and when:
The first int should be the count of current circles to draw, say called, currentCirclesToDraw.
The second int will be the number of circles to draw total.
If you use a List<Ellipse2D> like I suggest, then this number will be the size of the list. So if the List is called ellipseList, then the 2nd number will be ellipseList.size().
The first variable will be incremented in the timer up to the size of the list, but no larger, and will be used by paintComponent method to decide how many circles to draw.
Key point here: the first number, the currentCirclesToDraw, must be re-set to 0 when the button is pressed. This way your paintComponent method will start out drawing 0 circles, then 1, then 2, ...
For example, the paintComponent method could look like so:
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(CIRCLE_COLOR);
for (int i = 0; i < currentCirclesToDraw && i < ellipseList.size(); i++) {
g2.fill(ellipseList.get(i));
}
}
I use the second term in the for loop conditional statement, i < currentCirclesToDraw && i < ellipseList.size() as an additional fail-safe to be sure that we don't try to draw more circles then we have in our list.
My Timer's ActionListener would increment the currentCirclesToDraw variable and call repaint. It would stop the Timer once currentCirclesToDraw reaches the size of the ellipseList:
private class TimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
if (currentCirclesToDraw < ellipseList.size()) {
currentCirclesToDraw++;
repaint();
} else {
// stop the Timer
((Timer)e.getSource()).stop();
}
}
}
And my button's actionPerformed method would reset currentCirclesToDraw to 0, would add a new Ellipse2D to my ellipseList (if we've not yet reached the MAX_CIRCLE_INDEX), would call repaint() to clear the JPanel, and would construct and start the Timer:
public void actionPerformed(java.awt.event.ActionEvent arg0) {
currentCirclesToDraw = 0; // this is key -- reset the index used to control how many circles to draw
if (ellipseList.size() < MAX_CIRCLE_INDEX) {
double x = (ellipseList.size()) * CIRCLE_WIDTH / Math.pow(2, 0.5);
double y = x;
double w = CIRCLE_WIDTH;
double h = CIRCLE_WIDTH;
ellipseList.add(new Ellipse2D.Double(x, y, w, h));
}
repaint(); // clear image
new Timer(TIMER_DELAY, new TimerListener()).start();
};
Edit 3/30/14
Note it all can be put together like this:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.geom.Ellipse2D;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
/**
* http://stackoverflow.com/a/22714405/522444
* http://stackoverflow.com/questions/22712655/repaint-in-panel-method-not-updated
* #author Pete
*
*/
#SuppressWarnings("serial")
public class TimerCircles extends JPanel {
private static final int PREF_W = 1000;
private static final int PREF_H = 700;
private static final Color CIRCLE_COLOR = Color.RED;
public static final int MAX_CIRCLE_INDEX = 11;
public static final int TIMER_DELAY = 300;
public static final int CIRCLE_WIDTH = 100;
private final List<Ellipse2D> ellipseList = new ArrayList<>();
private int currentCirclesToDraw = 0;
public TimerCircles() {
add(new JButton(new ButtonAction("New Circle", KeyEvent.VK_C)));
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(CIRCLE_COLOR);
for (int i = 0; i < currentCirclesToDraw && i < ellipseList.size(); i++) {
g2.fill(ellipseList.get(i));
}
}
private class ButtonAction extends AbstractAction {
public ButtonAction(String name, int mnemonic) {
super(name);
putValue(MNEMONIC_KEY, mnemonic);
}
public void actionPerformed(java.awt.event.ActionEvent arg0) {
currentCirclesToDraw = 0; // this is key -- reset the index used to control how many circles to draw
if (ellipseList.size() < MAX_CIRCLE_INDEX) {
double x = (ellipseList.size()) * CIRCLE_WIDTH / Math.pow(2, 0.5);
double y = x;
double w = CIRCLE_WIDTH;
double h = CIRCLE_WIDTH;
ellipseList.add(new Ellipse2D.Double(x, y, w, h));
}
repaint(); // clear image
new Timer(TIMER_DELAY, new TimerListener()).start();
};
}
private class TimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
if (currentCirclesToDraw < ellipseList.size()) {
currentCirclesToDraw++;
repaint();
} else {
// stop the Timer
((Timer)e.getSource()).stop();
}
}
}
private static void createAndShowGui() {
TimerCircles mainPanel = new TimerCircles();
JFrame frame = new JFrame("TimerCircles");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
I have a JFrame with dimension 500 X 500: it contains a JComponent with size 1000 X 1000 and this JComponent is contained into a JScrollBar with scrollbar always present.
The screenshot
The code
/**component */
public class LinesComponent extends JPanel
....
/***other class: CommandPanel contains the LinesComponents*/
public class CommandPanel extends JPanel{
....
private LinesComponent panel;
/*JScrollPanel*/
private void buildScrollPanel(Container container) {
JScrollPane scroll = new JScrollPane(panel);
scroll.setSize(1000,1000);
scroll.setBorder(new LineBorder(Color.BLACK));
scroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
scroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
container.add(scroll);
}
/**JFrame*/
private void buildFrame(String title) {
this.testFrame = new JFrame(title);
this.testFrame.setLayout(new FlowLayout());
this.testFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.testFrame.setLocationRelativeTo(null);
this.testFrame.setSize(500,500);
this.testFrame.setBackground(EnumColor.BACKGROUND_PANEL.getValue());
}
//end CommandPanel
As showed into screenshot, the window has the scrollbar but it isn't working.
I tried to change the dimensions of JPanel and JFrame but the situation doesn't change.
I know that the scroll appears when the dimension of JComponent are greater of dimensione of JPanel container, but in this moment I see that I lose some information but I don't understand what is.
Do you have any suggest, please?
PS into screenshot I show that the scroll doesn't work, but in my desiderata the scroll has to work in vertical and horizontal senses
PS PS all my code in only one class
package testDebug;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.LayoutManager;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ScrollPaneConstants;
import javax.swing.border.LineBorder;
public class AllClasses {
public static void main(String[] a){
CommandPanel commandPanel=new CommandPanel("test", new ArrayList<MyPoint>());
}
}
class LinesComponent extends JPanel{
private static final long serialVersionUID = 1L;
private final LinkedList<Line> lines = new LinkedList<Line>();
private static class Line {
final int x1;//x del primo punto
final int y1;//y del primo punto
final int x2;//x del secondo punto
final int y2;//y del secondo punto
final Color color;
final int pressure;
public Line(int x1, int y1, int x2, int y2, Color color) {
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
this.color = color;
this.pressure=3;
}
public Line(int x1, int y1, int x2, int y2, int newPressure, Color color) {
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
this.pressure=newPressure;
this.color = color;
}
}//Line
public LinesComponent(){
setBorder(new LineBorder(Color.BLACK));
setBackground(Color.WHITE);
setPreferredSize(new Dimension(400, 400));
}
public void clearLines() {
this.lines.clear();
repaint();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Line line : this.lines) {
g.setColor(line.color);
g.fillOval(line.x1,line.y1, line.pressure, line.pressure);
}
}
public void addPoint(MyPoint p, Color randomColor) {
//call addLine(double x, double y, double xtilt, double ytilt, Color randomColor);
}
private void addLine(double x, double y, double xtilt, double ytilt, Color randomColor) {
this.lines.add(new Line((int) x, (int) y, (int) xtilt, (int) ytilt, randomColor));
repaint();
}
}
enum EnumButton {
DECREASE_X("-"),
INCREASE_X("+"),
DECREASE_Y("-"),
INCREASE_Y("+"),
DECREASE_ZOOM("-"),
INCREASE_ZOOM("+");
private JButton button;
public JButton button(){
return this.button;
}
private EnumButton(String s){
this.button=new JButton(s);
}
public void addActionListener(ActionListener a){
this.button().addActionListener(a);
}
}
class CommandPanel extends JPanel {
private static final long serialVersionUID = 1L;
private static final String INITIAL_POSITION="0";
private JFrame testFrame = null;
private LinesComponent panelSignature;
private JTextField positionX=new JTextField(5);
private JTextField positionY=new JTextField(5);
public CommandPanel(String title, List<MyPoint> newPoints) {
super();
positionX.setText(INITIAL_POSITION);
positionY.setText(INITIAL_POSITION);
buildFrame(title);
Container container = testFrame.getContentPane();
this.panelSignature=initPanel();
buildScrollPanel(container);
paintLine(newPoints);
allignButtons();
defineFrame();
}
private void buildScrollPanel(Container container) {
JScrollPane scroll = new JScrollPane(panelSignature);
scroll.setSize(300,300);
scroll.setBorder(new LineBorder(Color.BLACK));
scroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
scroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
container.add(scroll);
}
private void defineFrame() {
this.testFrame.pack();
this.testFrame.setVisible(true);
}
private void paintLine(List<MyPoint> points) {
Iterator<MyPoint> iterator = points.iterator();
while (iterator.hasNext()) {
MyPoint point = iterator.next();
this.panelSignature.addPoint(point,Color.BLUE);
}
}//disegnaLinea
private void allignButtons() {
JPanel buttonPanel = new JPanel(new FlowLayout());
sectionHorizontalMovement(buttonPanel);
sectionVerticalMovement(buttonPanel);
sectionZoom(buttonPanel);
this.testFrame.add(buttonPanel);
}
private void sectionZoom(JPanel buttonPanel) {
buttonPanel.add(EnumLabel.ZOOM.label());
buttonPanel.add(EnumButton.INCREASE_ZOOM.button());
buttonPanel.add(EnumButton.DECREASE_ZOOM.button());
buttonPanel.add(EnumLabel.ZOOM_DIRECTION.label());
buttonPanel.add(EnumLabel.EMPTY.label());
}//sezioneZoom
private void sectionVerticalMovement(JPanel pannelloPulsanti) {
pannelloPulsanti.add(EnumLabel.MOVE_UP_DOWN.label());
pannelloPulsanti.add(EnumButton.INCREASE_Y.button());
pannelloPulsanti.add(EnumButton.DECREASE_Y.button());
pannelloPulsanti.add(EnumLabel.Y_DIRECTION.label());
pannelloPulsanti.add(EnumLabel.MAX_Y_ALLOWED.label());
}//sezioneSpostamentoVerticale
private void sectionHorizontalMovement(JPanel pannelloPulsanti) {
pannelloPulsanti.add(EnumLabel.MOVE_RIGHT_LEFT.label());
pannelloPulsanti.add(EnumButton.INCREASE_X.button());
pannelloPulsanti.add(EnumButton.DECREASE_X.button());
pannelloPulsanti.add(EnumLabel.X_DIRECTION.label());
pannelloPulsanti.add(EnumLabel.MAX_X_ALLOWED.label());
}//sezioneSpostamentoOrizzontale
public LinesComponent initPanel() {
LinesComponent linesComponent= new LinesComponent();
linesComponent.setBorder(new LineBorder(Color.GRAY));
return linesComponent;
}
private void buildFrame(String titolo) {
this.testFrame = new JFrame(titolo);
this.testFrame.setLayout(new FlowLayout());
this.testFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.testFrame.setLocationRelativeTo(null);
this.testFrame.setSize(700,700);
this.testFrame.setBackground(Color.WHITE);
}
}
enum EnumLayout {
FRAME(new FlowLayout()), BUTTON_PANEL(new GridLayout(3, 5));
private LayoutManager value;
private EnumLayout(LayoutManager manager){
this.value=manager;
}
public LayoutManager layout() {
return value;
}
}
enum EnumLabel {
MOVE_RIGHT_LEFT("Sposta in orizzontale"),
MOVE_UP_DOWN("Sposta in verticale"),
ZOOM("Zoom"), EMPTY(""),
MAX_X_ALLOWED("LARGHEZZA: 600"), MAX_Y_ALLOWED("ALTEZZA: 600"),
X_DIRECTION("0"), Y_DIRECTION("0"), ZOOM_DIRECTION("1")
;
private JLabel label;
private EnumLabel(String lab){
this.label=new JLabel(lab);
}
public JLabel label (){
return this.label;
}
}
class MyPoint {
// class with the data: useless for position of panel
}
Scrollpane and its scrollbars entirely rely on the size of the component in the viewport, which, by default, depends of the preferred size of that component.
Check this code which shows you a basic example of how this all works:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class TestScrollPane {
public static class CustomComponent extends JPanel {
private static final int RADIUS = 20;
private int x = 0;
private int y = 0;
private double speed = 18;
private double dx;
private double dy;
public CustomComponent() {
dx = speed;
dy = speed;
Timer t = new Timer(20, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
x += dx;
y += dy;
if (x + RADIUS > getWidth()) {
x = getWidth() - RADIUS;
dx = -speed;
} else if (x < 0) {
x = 0;
dx = speed;
}
if (y + RADIUS > getHeight()) {
y = getHeight() - RADIUS;
dy = -speed;
} else if (y < 0) {
y = 0;
dy = speed;
}
repaint();
}
});
t.start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.RED);
g.fillOval(x, y, RADIUS, RADIUS);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(600, 600);
}
}
protected void initUI() {
JFrame window = new JFrame(TestScrollPane.class.getSimpleName());
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JScrollPane scroll = new JScrollPane(new CustomComponent(), JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
window.add(scroll);
window.setSize(600, 500);
window.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new TestScrollPane().initUI();
}
});
}
}
Actually your JScrollPane is 1000x1000px, so if there are scroll bars, you won't see them, because your frame is 500x500. Note that the JScrollPane acts on its content, that means that it shows scrollbars if the panel you are adding to it is bigger than the JScrollPane itself, but doesn't add scrollbars if the container is smaller.
The viewable area/space is defined (in most cases) by the view's preferred size. This suggests that your problem lies with your scroll panes view and not the scroll pane or anything else you've provided us
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.GridBagLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class TestScrollPane02 {
public static void main(String[] args) {
new TestScrollPane02();
}
public TestScrollPane02() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new JScrollPane(new LargePane()));
frame.setSize(200, 200);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class LargePane extends JPanel {
public LargePane() {
setLayout(new GridBagLayout());
add(new JLabel("I'm a large panel"));
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.RED);
g.drawLine(0, 0, getWidth(), getHeight());
g.drawLine(getWidth(), 0, 0, getHeight());
}
}
}
You can also have a look the Scrollable interface which provides additional hints back to the scroll pane...