Java Jpanel Repaint/update probleme - java

i have a problem with the method Repaint of my jpanel.
I'm trying to make a "Racing game" with the Java Swing and by following the MVC architecture :
i have 5 classes :
Main : runs the MVC
public class Main {
private static Model model;
private static View view;
private static Controller controller;
public static void main(String[] args) {
model = new Model();
view = new View();
controller = new Controller();
model.addObserver(view);
controller.addModule(model);
controller.addView(view);
view.addContoller(controller);
}
}
Model :
import java.util.ArrayList;
import java.util.Observable;
public class Model extends Observable{
private ArrayList<Car> cars;// List of cars
public Model() {
cars = new ArrayList<Car>();
cars.add(new Car(this,0, 50));
cars.add(new Car(this,0, 50));
cars.add(new Car(this,0, 50));
}
public void startCar(int i){
//i is the index of the selected element in the checkbox
//if i==0 then the selected element is "All cars" else is a specific car
if(i>0)
cars.get(i-1).start();
else{
for(int j=0;j<cars.size();j++)
cars.get(j).start();
}
}
public void speedUpCar(int i) {
if(i>0)
cars.get(i-1).incVitess();
else{
for(int j=0;j<cars.size();j++)
cars.get(j).incVitess();
}
}
public void notifyView(){
setChanged();
notifyObservers(cars);
}
public void speedDownCar(int i) {
if(i>0)
cars.get(i-1).decVitess();
else{
for(int j=0;j<cars.size();j++)
cars.get(j).decVitess();
}
}
public void stopCar(int i) {
if(i>0)
cars.get(i-1).stop();
else{
for(int j=0;j<cars.size();j++)
cars.get(j).stop();
}
}
}
the View :
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.GridLayout;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.Observable;
import java.util.Observer;
import java.util.Vector;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class View implements Observer {
private JFrame fen;
private JPanel btnPanel,panel;
private JButton play,speedUp,speedDown,stop;
private JComboBox<String> listeCar;
private boolean test = false;
public View() {
fen = new JFrame();
fen.setTitle("Car Racing");
fen.setSize(900, 400);
fen.setLocationRelativeTo(null);
fen.setResizable(false);
Vector<String> v = new Vector<String>();
v.add("All cars");v.add("car 1");v.add("car 2");v.add("car 3");
listeCar = new JComboBox<String>(v);
play = new JButton("Play");
speedUp = new JButton("+");
speedDown = new JButton("-");
stop = new JButton("stop");
panel = new JPanel(new GridLayout(3,1));
btnPanel = new JPanel();
btnPanel.add(listeCar);
btnPanel.add(play);
btnPanel.add(speedUp);
btnPanel.add(speedDown);
btnPanel.add(stop);
Container c = fen.getContentPane();
c.setLayout(new BorderLayout());
c.add(btnPanel, BorderLayout.SOUTH);
c.add(panel, BorderLayout.CENTER);
fen.setVisible(true);
fen.addWindowListener(new WindowAdapter() {
#Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
public void addContoller(Controller controller){
play.addActionListener(controller);
speedUp.addActionListener(controller);
speedDown.addActionListener(controller);
stop.addActionListener(controller);
}
#Override
public void update(Observable arg0, Object c) {
ArrayList<Car> cars = (ArrayList<Car>)c;
for(int i=0;i<cars.size();i++){
Car car = cars.get(i);
if(!test){ // if its the first tima, add the cars to the panel
panel.add(car);
}else{
car.repaint(); // << the Problem is HERE
}
}
test = true;
}
public JButton getPlay() {
return play;
}
public JButton getSpeedUp() {
return speedUp;
}
public JButton getSpeedDown() {
return speedDown;
}
public JButton getStop() {
return stop;
}
public JComboBox<String> getListeCar() {
return listeCar;
}
}
The controller:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class Controller implements ActionListener{
private Model model;
private View view;
public Controller() {
}
public void addModule(Model m) {
model = m;
model.notifyView();
}
public void addView(View v){
view = v;
}
#Override
public void actionPerformed(ActionEvent e) {
if(e.getSource() == view.getPlay()){
model.startCar(view.getListeCar().getSelectedIndex());
}else if(e.getSource() == view.getSpeedUp()){
model.speedUpCar(view.getListeCar().getSelectedIndex());
}else if(e.getSource() == view.getSpeedDown()){
model.speedDownCar(view.getListeCar().getSelectedIndex());
}else if(e.getSource() == view.getStop()){
model.stopCar(view.getListeCar().getSelectedIndex());
}
}
}
The car class :
import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JPanel;
public class Car extends JPanel{
private int id,x,y,vitess;
private Thread thread;
private Model model;
private boolean start = true;
private boolean forward = true;
private Color color;
private boolean threadStarted = false;
private BufferedImage bg; // background image
public Car(Model model,int x,int y) {
this.x =x;
this.y = y;
vitess = 7;
this.model = model;
try {
bg = ImageIO.read(new File("road.png"));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
color = changeColor(); // Random color
thread = new Thread(new CarThread(this));
start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(bg, 0, 0, null);
g.setColor(color);
g.fillRect(x, y, 100, 50); // the car is a simple rectangle
}
public void start() {
start = true;
if(!threadStarted){
threadStarted = true;
thread.start();
}else{
thread.resume();
}
}
public void move(){
System.out.println("X:"+x);
if(forward){
if(x<this.getWidth()){
x+=2;
}else{
color = changeColor();
forward = false;
}
}else{
if(x>0){
x-=2;
}else{
color = changeColor();
forward = true;
}
}
model.notifyView();
}
private Color changeColor() {
int r = (int)(Math.random()*255);
int g = (int)(Math.random()*255);
int b = (int)(Math.random()*255);
return new Color(r,g,b);
}
public void stop(){
start = false;
thread.suspend();
}
public void incVitess(){
if(vitess>1)
vitess--;
}
public void decVitess(){
if(vitess<6)
vitess++;
}
public int getId() {
return id;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getVitess() {
return vitess;
}
public boolean getStart(){
return start;
}
public void setStart(boolean m){
this.start = m;
}
public Color getColor(){
return color;
}
}
CarThraed class:
public class CarThread implements Runnable{
private Car car;
public CarThread(Car car) {
this.car = car;
}
#Override
public void run() {
while(car.getStart()){
car.move();
try {
Thread.sleep(car.getVitess());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
if you run the project you will note that the cars does not reach the end of the frame, even if used:
if(x<this.getWidth()) x++;
but when i replace
car.repaint(); // << the Problem is HERE
with
car.update(car.getGraphics()); // << the Problem is HERE
the cars can now reach the end of the Frame, but the buttons in the btnJpanel disappear
image with repaint here
image with update here
thank you in advance

I can't say that I've read all your code, but your model never notifies the view. I mean nowhere inside of the model class's code is the notifyView() method called. It should be the model's responsibility to call this whenever its state is changed.
Note that this:
car.update(car.getGraphics()); // << the Problem is HERE
Should not be used as the Graphics obtained is not stable.
Also, your model holds view components, an ArrayList of Car, a class that extends JPanel, another thing that should never occur as the model should be completely ignorant of the view, with the exception being that it knows that some things may be listening to it, and it needs to notify those things, and that's it. Instead your Model should hold an ArrayList of non-view, non-JPanel logical Car objects.
Edit
You state:
In the model i have the method: public void notifyView() which is called by the car's method public void Move() , That means whenever the thread calls the move method of the car, it calls the notifyView of the model
No, the Car should not be calling this method -- only the model itself should call this when its state is changed.
Also, I see that you've got very dangerous code using Thread#suspend() and Thread#resume() method calls. These methods have been deprecated as they've been found to be dangerous. To find out why, please check the API for Thread as well as this useful article. You will most definitely want to avoid their use.
Suggestions
Make Car a logical non-GUI class that knows its position and can have its position changed.
Override your drawing JPanels protected void paintComonent(Graphics g) method and use the Car information from the model to draw the Cars. In other words, use the state of the model to affect the state of the view.
Use a Swing Timer instead of a background thread, to change your Car positions.
Have the model and only the model call the notifyView method when its state has been changed.
Edit
Your main bug is here:
class Car extends JPanel {
private int id, x, y, vitess;
//....
public int getX() {
return x;
}
public int getY() {
return y;
}
You're inadvertently overriding the Car JPanel's getX and getY methods, messing up the location of these components. This is another reason to avoid overriding Swing components unless necessary -- to avoid these hidden side effects.
Get rid of or rename these methods.

Related

How can I fade out or fade in by command JPanel, its components and its color

I wanted to make a Glass Panel that contain a JPanel with white background, border and the msg "please wait".
Here is the code example:
JLabel glassLabel = new JLabel("Please wait");
FadingPanel msg = new FadingPanel();
glassLabel.setFont(new Font("Dialog", Font.BOLD, 26));
msg.setLayout(new BorderLayout());
msg.add(glassLabel,BorderLayout.NORTH);
msg.setBackground(Color.white);
msg.setFont(UIManager.getFont("Table.font").deriveFont(24f));
msg.setBorder(new CompoundBorder(new TitledBorder(""),
new EmptyBorder(20,20,20,20)));
It will fade in and out while waiting for the query.
the problem is that I am getting a bad result.
need help
the other is that none of them show it with glass panel
Animating the opacity state of a glassPane is no different from animating the state of any Swing component, after all, the glassPane is just another component.
one is that the Timer system doesn't know if the start function started and it keeps the panel hanging on because it closing it before fading the panel and then before it shows it and then it dont try to close it again
This is more about your own internal state management. The panel shouldn't care, it should just be responding to the request to change opacity level, forward or backwards
What you should have, is some kind of "engine" which can provide events when certain states are achieved, at which time, you make decisions about what should be done, removing the functionality from the "panel" itself.
Theory TL;DR
Okay, first, some theory.
Animation...
Animation is the illusion of change over time. In your case, you're moving from 0 to 1 and back again over a specified period of time. This is commonly known as "linear progression/animation". Most naive animation implementations will simple add a constant delta to a value and keep doing so until a desired state is reached. This is naive because not all systems are equal. Some will be able to achieve the desired state faster than others, making the animation uneven and providing a poor user experience.
Instead, you should be focused on perform a operation over a fixed period of time, calculating the required value as fast as the system will allow. This allows the animation to "drop" frames as required based on the system's capabilities. This is commonly known as "duration based animation".
This approach is much more powerful, as it allows you to play around with the speed of the animation in a very simply way. It also allows you do some very advanced operations, like easement, which wouldn't be easily achievable through a linear progression.
Swing and animation...
Swing is SINGLE threaded. This means you can't perform blocking or long running operations within the context of the Event Dispatching Thread.
Swing is also NOT thread safe. This means you shouldn't update the UI (or any state the UI depends on) from outside the context of the EDT.
For animation, what you need is some way to post, fast, repetitive, events onto the EDT, which will allow you to make changes to the UI safely. For this, the most common tool is a Swing Timer...
The Framework
So based on that, what we need is some kind of "engine", which given a "range" and a "duration" can notify us of "ticks" on a regular bases from which we can calculate the progression that the animation has played, and calculate the value we should use based on our inputs ... simple ...
I, personally, prefer to use an animation library, but the simple framework presented in the examples basically abstracts all these concepts into a re-usable framework.
Make it so...
nb: I ran out of room, so the underlying framework is included in the main example
Okay, that's all nice and fluffy, but how does this actually help us. Essentially, the idea of the above is to abstract common functionality out and make it re-usable (and yes, I actually do use it, a lot)
What we now need, is a component which can actually use it, something like...
public interface FaderListener {
public void fadeDidComplete(FadePane pane);
}
public class FadePane extends JPanel {
private double alpha = 1;
private boolean fadingIn = true;
private DoubleAnimatable animatable;
private Duration duration = Duration.ofSeconds(5);
private List<FaderListener> listeners = new ArrayList<>(5);
public FadePane() {
setOpaque(false);
}
public void addFadeListener(FaderListener listener) {
listeners.add(listener);
}
public void removeFadeListener(FaderListener listener) {
listeners.remove(listener);
}
public boolean isFadingIn() {
return fadingIn;
}
public double getAlpha() {
return alpha;
}
#Override
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setComposite(AlphaComposite.SrcOver.derive((float)getAlpha()));
g2d.setColor(getBackground());
g2d.fillRect(0, 0, getWidth(), getHeight());
super.paint(g2d);
g2d.dispose();
}
protected void fadeTo(double to) {
double currentAlpha = getAlpha();
if (animatable != null) {
animatable.stop();
animatable = null;
}
if (currentAlpha == to) {
fadeDidComplete();
return;
}
DoubleRange animationRange = new DoubleRange(currentAlpha, to);
double maxFrom = to == 1 ? 1 : 0;
double maxTo = to == 1 ? 0 : 1;
DoubleRange maxRange = new DoubleRange(maxFrom, maxTo);
animatable = new DoubleAnimatable(animationRange, maxRange, duration, new AnimatableListener<Double>() {
#Override
public void animationChanged(Animatable<Double> animatable) {
alpha = animatable.getValue();
repaint();
}
}, new AnimatableLifeCycleListenerAdapter<Double>() {
#Override
public void animationCompleted(Animatable<Double> animatable) {
fadeDidComplete();
}
});
Animator.INSTANCE.add(animatable);
}
public void fadeIn() {
fadingIn = true;
fadeTo(1);
}
public void fadeOut() {
fadingIn = false;
fadeTo(0);
}
protected void fadeDidComplete() {
for (FaderListener listener : listeners) {
listener.fadeDidComplete(this);
}
}
}
Okay, this is a pretty simple concept. It's a JPanel which has a alpha property which changes the opacity level of the component - basically, this is all faked, as Swing only support opaque and transparent components, not translucent components. So we set the component to be transparent and manually paint the background ourselves.
The component exposes two methods, fadeIn and fadeOut and supports a FaderListener which can be used to notify interested parties that the fade operation has been completed
Runnable example...
import java.awt.AlphaComposite;
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.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setBackground(Color.RED);
setLayout(new BorderLayout());
FadePane pane = new FadePane();
pane.setLayout(new GridBagLayout());
pane.add(new JLabel("Look ma, no hands"));
add(pane);
JButton btn = new JButton("Switch");
btn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
btn.setEnabled(false);
if (pane.isFadingIn()) {
pane.fadeOut();
} else {
pane.fadeIn();
}
}
});
add(btn, BorderLayout.SOUTH);
pane.addFadeListener(new FaderListener() {
#Override
public void fadeDidComplete(FadePane pane) {
btn.setEnabled(true);
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
public interface FaderListener {
public void fadeDidComplete(FadePane pane);
}
public class FadePane extends JPanel {
private double alpha = 1;
private boolean fadingIn = true;
private DoubleAnimatable animatable;
private Duration duration = Duration.ofSeconds(5);
private List<FaderListener> listeners = new ArrayList<>(5);
public FadePane() {
setOpaque(false);
}
public void addFadeListener(FaderListener listener) {
listeners.add(listener);
}
public void removeFadeListener(FaderListener listener) {
listeners.remove(listener);
}
public boolean isFadingIn() {
return fadingIn;
}
public double getAlpha() {
return alpha;
}
public void setFaddedOut() {
alpha = 0;
fadingIn = false;
}
public void setFaddedIn() {
alpha = 1;
fadingIn = true;
}
#Override
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setComposite(AlphaComposite.SrcOver.derive((float)getAlpha()));
g2d.setColor(getBackground());
g2d.fillRect(0, 0, getWidth(), getHeight());
super.paint(g2d);
g2d.dispose();
}
protected void fadeTo(double to) {
double currentAlpha = getAlpha();
if (animatable != null) {
animatable.stop();
animatable = null;
}
if (currentAlpha == to) {
fadeDidComplete();
return;
}
DoubleRange animationRange = new DoubleRange(currentAlpha, to);
double maxFrom = to == 1 ? 1 : 0;
double maxTo = to == 1 ? 0 : 1;
DoubleRange maxRange = new DoubleRange(maxFrom, maxTo);
animatable = new DoubleAnimatable(animationRange, maxRange, duration, new AnimatableListener<Double>() {
#Override
public void animationChanged(Animatable<Double> animatable) {
alpha = animatable.getValue();
repaint();
}
}, new AnimatableLifeCycleListenerAdapter<Double>() {
#Override
public void animationCompleted(Animatable<Double> animatable) {
fadeDidComplete();
}
});
Animator.INSTANCE.add(animatable);
}
public void fadeIn() {
fadingIn = true;
fadeTo(1);
}
public void fadeOut() {
fadingIn = false;
fadeTo(0);
}
protected void fadeDidComplete() {
for (FaderListener listener : listeners) {
listener.fadeDidComplete(this);
}
}
}
public class DoubleAnimatable extends AbstractAnimatable<Double> {
public DoubleAnimatable(DoubleRange animationRange, DoubleRange maxRange, Duration duration, AnimatableListener<Double> listener, AnimatableLifeCycleListener<Double> lifeCycleListener) {
super(animationRange, duration, listener, lifeCycleListener);
double maxDistance = maxRange.getDistance();
double aniDistance = animationRange.getDistance();
double progress = Math.min(100, Math.max(0, Math.abs(aniDistance / maxDistance)));
Duration remainingDuration = Duration.ofMillis((long) (duration.toMillis() * progress));
setDuration(remainingDuration);
}
}
public interface AnimatableListener<T> {
public void animationChanged(Animatable<T> animatable);
}
public interface AnimatableLifeCycleListener<T> {
public void animationStopped(Animatable<T> animatable);
public void animationCompleted(Animatable<T> animatable);
public void animationStarted(Animatable<T> animatable);
public void animationPaused(Animatable<T> animatable);
}
public class AnimatableLifeCycleListenerAdapter<T> implements AnimatableLifeCycleListener<T> {
#Override
public void animationStopped(Animatable<T> animatable) {
}
#Override
public void animationCompleted(Animatable<T> animatable) {
}
#Override
public void animationStarted(Animatable<T> animatable) {
}
#Override
public void animationPaused(Animatable<T> animatable) {
}
}
public abstract class AbstractAnimatable<T> implements Animatable<T> {
private Range<T> range;
private LocalDateTime startTime;
private Duration duration = Duration.ofSeconds(5);
private T value;
private AnimatableListener<T> animatableListener;
private AnimatableLifeCycleListener<T> lifeCycleListener;
// private Easement easement;
private double rawOffset;
public AbstractAnimatable(Range<T> range, Duration duration, AnimatableListener<T> listener) {
this.range = range;
this.value = range.getFrom();
this.animatableListener = listener;
}
public AbstractAnimatable(Range<T> range, Duration duration, AnimatableListener<T> listener, AnimatableLifeCycleListener<T> lifeCycleListener) {
this(range, duration, listener);
this.lifeCycleListener = lifeCycleListener;
}
// public AbstractAnimatable(Range<T> range, Duration duration, Easement easement, AnimatableListener<T> listener) {
// this(range, duration, listener);
// this.easement = easement;
// }
//
// public AbstractAnimatable(Range<T> range, Duration duration, Easement easement, AnimatableListener<T> listener, AnimatableLifeCycleListener<T> lifeCycleListener) {
// this(range, duration, easement, listener);
// this.lifeCycleListener = lifeCycleListener;
// }
//
// public void setEasement(Easement easement) {
// this.easement = easement;
// }
//
// #Override
// public Easement getEasement() {
// return easement;
// }
public Duration getDuration() {
return duration;
}
public Range<T> getRange() {
return range;
}
public void setRange(Range<T> range) {
this.range = range;
}
#Override
public T getValue() {
return value;
}
protected void setDuration(Duration duration) {
this.duration = duration;
}
public double getCurrentProgress(double rawProgress) {
double progress = Math.min(1.0, Math.max(0.0, getRawProgress()));
// Easement easement = getEasement();
// if (easement != null) {
// progress = easement.interpolate(progress);
// }
return Math.min(1.0, Math.max(0.0, progress));
}
public double getRawProgress() {
if (startTime == null) {
return 0.0;
}
Duration duration = getDuration();
Duration runningTime = Duration.between(startTime, LocalDateTime.now());
double progress = rawOffset + (runningTime.toMillis() / (double) duration.toMillis());
return Math.min(1.0, Math.max(0.0, progress));
}
#Override
public void tick() {
if (startTime == null) {
startTime = LocalDateTime.now();
fireAnimationStarted();
}
double rawProgress = getRawProgress();
double progress = getCurrentProgress(rawProgress);
if (rawProgress >= 1.0) {
progress = 1.0;
}
value = getRange().valueAt(progress);
fireAnimationChanged();
if (rawProgress >= 1.0) {
fireAnimationCompleted();
}
}
#Override
public void start() {
if (startTime != null) {
// Restart?
return;
}
Animator.INSTANCE.add(this);
}
#Override
public void stop() {
stopWithNotification(true);
}
#Override
public void pause() {
rawOffset += getRawProgress();
stopWithNotification(false);
double remainingProgress = 1.0 - rawOffset;
Duration remainingTime = getDuration().minusMillis((long) remainingProgress);
setDuration(remainingTime);
lifeCycleListener.animationStopped(this);
}
protected void fireAnimationChanged() {
if (animatableListener == null) {
return;
}
animatableListener.animationChanged(this);
}
protected void fireAnimationCompleted() {
stopWithNotification(false);
if (lifeCycleListener == null) {
return;
}
lifeCycleListener.animationCompleted(this);
}
protected void fireAnimationStarted() {
if (lifeCycleListener == null) {
return;
}
lifeCycleListener.animationStarted(this);
}
protected void fireAnimationPaused() {
if (lifeCycleListener == null) {
return;
}
lifeCycleListener.animationPaused(this);
}
protected void stopWithNotification(boolean notify) {
Animator.INSTANCE.remove(this);
startTime = null;
if (notify) {
if (lifeCycleListener == null) {
return;
}
lifeCycleListener.animationStopped(this);
}
}
}
public interface Animatable<T> {
public Range<T> getRange();
public T getValue();
public void tick();
public Duration getDuration();
//public Easement getEasement();
// Wondering if these should be part of a secondary interface
// Provide a "self managed" unit of work
public void start();
public void stop();
public void pause();
}
public abstract class Range<T> {
private T from;
private T to;
public Range(T from, T to) {
this.from = from;
this.to = to;
}
public T getFrom() {
return from;
}
public T getTo() {
return to;
}
#Override
public String toString() {
return "From " + getFrom() + " to " + getTo();
}
public abstract T valueAt(double progress);
}
public class DoubleRange extends Range<Double> {
public DoubleRange(Double from, Double to) {
super(from, to);
}
public Double getDistance() {
return getTo() - getFrom();
}
#Override
public Double valueAt(double progress) {
double distance = getDistance();
double value = distance * progress;
value += getFrom();
return value;
}
}
public enum Animator {
INSTANCE;
private Timer timer;
private List<Animatable> properies;
private Animator() {
properies = new ArrayList<>(5);
timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
List<Animatable> copy = new ArrayList<>(properies);
Iterator<Animatable> it = copy.iterator();
while (it.hasNext()) {
Animatable ap = it.next();
ap.tick();
}
if (properies.isEmpty()) {
timer.stop();
}
}
});
}
public void add(Animatable ap) {
properies.add(ap);
timer.start();
}
protected void removeAll(List<Animatable> completed) {
properies.removeAll(completed);
}
public void remove(Animatable ap) {
properies.remove(ap);
if (properies.isEmpty()) {
timer.stop();
}
}
}
}
But it's not a glassPane
... ok, as I said, a glassPane is just another component
This is a simple example which makes use of the frame's glassPane and will, when the panel is faded out, reset the glassPane to a default component
import java.awt.AlphaComposite;
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.GridBagLayout;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new GridBagLayout());
JButton btn = new JButton("Switch");
btn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
Window window = SwingUtilities.getWindowAncestor(TestPane.this);
if (!(window instanceof JFrame)) {
System.out.println("Not out frame");
return;
}
JFrame frame = (JFrame) window;
FadePane pane = new FadePane();
pane.setLayout(new BorderLayout());
pane.add(new JLabel("All your base are belong to us"));
pane.setFaddedOut();
pane.addFadeListener(new FaderListener() {
#Override
public void fadeDidComplete(FadePane pane) {
System.out.println("Completed");
if (pane.getAlpha() == 1) {
System.out.println("Fade out");
pane.fadeOut();
} else {
System.out.println("Remove glasspane");
frame.setGlassPane(new JPanel());
}
}
});
frame.setGlassPane(pane);
System.out.println("Fade in");
pane.setVisible(true);
pane.fadeIn();
}
});
add(btn);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
}
nb: The required classes are in the previous example
Consider using JDialog container. When it is undecorated, you can change its opacity:
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Point;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.WindowConstants;
public class FadeDialog extends JDialog {
private float alfa = 1;
private JLabel label;
private boolean isFadeIn = true;
private JButton fadeIn, fadeOut;
FadeDialog() {
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
setLocation(new Point(300, 300));
getContentPane().setLayout(new BorderLayout(5,0));
setUndecorated(true); //opacity supported for undecorated JDialogs
JButton close = new JButton("Close");
close.addActionListener(e -> dispose());
getContentPane().add(close, BorderLayout.PAGE_END);
getContentPane().add(new ContentPane(), BorderLayout.CENTER);
pack();
setVisible(true);
Timer timer = new Timer(2000, e -> fade());//endless fade-in-out loop
timer.setInitialDelay(100);
timer.start();
}
void fade() {
alfa = isFadeIn ? alfa + 0.1f : alfa -0.1f;
if(alfa <=0 ) {
alfa = 0; isFadeIn = true;
}else if(alfa >= 1) {
alfa = 1; isFadeIn = false;
}
fadeIn.setEnabled(! isFadeIn); fadeOut.setEnabled(isFadeIn);
label.setText("Alfa is " + alfa);
setOpacity(alfa); //set JDialog opacity
}
class ContentPane extends JPanel {
ContentPane() {
setPreferredSize(new Dimension(200, 100));
setLayout(new BorderLayout());
fadeIn = new JButton("Fade In");
fadeIn.addActionListener(e -> isFadeIn = true);
add(fadeIn, BorderLayout.PAGE_START);
label = new JLabel("Alfa is " + alfa);
add(label, BorderLayout.CENTER);
fadeOut = new JButton("Fade Out");
fadeOut.addActionListener(e -> isFadeIn = false);
add(fadeOut, BorderLayout.PAGE_END);
}
}
public static void main(String[] args) {
new FadeDialog();
}
}

How to remove a JLabel when I click a certain key on the keyboard?

So I have a GUI, a Tile class and a method class. I created four tiles in my Game class which consists of Tiles that has contains a letter and a color in each of them. I now want to create a method where when I click a key on my keyboard to that specific letter on the tile, it will remove the Tile . How would I go about that? Do I create that method in my model class and call it in my Game(GUI) class?
Your game is the "controller", it's responsible for managing the functionality and communication between the model and view.
Your view should be a representation of your model
Your model (and possibly your view) should be providing event notification support, to which you controller will need to monitor, in order to manage the requirements and logic.
To start with, you code is in mess. You are making to much use of static and it's not going to help you.
For example, I re-worked your Tile class to look more like this.
public class Tile extends JLabel {
public static Font font = new Font("Serif", Font.BOLD, 39);
private char _c;
public Tile(char c, Color background) {
setBackground(background);
setOpaque(true);
_c = c;
setText(convert());
}
public static char randomLetter() {
Random r = new Random();
char randomChar = (char) (97 + r.nextInt(25));
return randomChar;
}
public char getChar() {
return _c;
}
public String convert() {
return String.valueOf(getChar());
}
}
Rather then calling randomLetter each time you called getChar or convert, you should only be using it when you actually need a new character, otherwise you'll never know what the Tile's character actually is/was to begin with
Next, we need some kind of observer contract for the mode, so it can tell us when things have changed, for example.
public interface ModelListener {
public void tileWasRemoved(Tile tile);
}
It's nothing special, but this provides a means for the Model to provide notification when a Tile is removed and which Tile was actually removed.
Next, I updated the Model so that it actual "models" something. The Model now maintains a list of Tiles and provides functionality for adding and removing them. It also provides support for the ModelListener and event triggering
public class Model {
private ArrayList<Tile> list = new ArrayList<Tile>();
private List<ModelListener> listeners = new ArrayList<>(25);
public Model() {
}
public void addModelListener(ModelListener listener) {
listeners.add(listener);
}
public void removeModelListener(ModelListener listener) {
listeners.remove(listener);
}
protected void fireTileRemoved(Tile tile) {
for (ModelListener listener : listeners) {
listener.tileWasRemoved(tile);
}
}
public void removeByChar(char value) {
Iterator<Tile> iterator = list.iterator();
while (iterator.hasNext()) {
Tile tile = iterator.next();
if (value == tile.getChar()) {
fireTileRemoved(tile);
iterator.remove();
}
}
}
private void add(Tile tile) {
list.add(tile);
}
private Iterable<Tile> getTiles() {
return Collections.unmodifiableList(list);
}
}
Next, I went to the Game and updated it so it adds Tiles to the Model and uses the Model data to setup the UI. It then registers the KeyListener and ModelListener
public Game() {
model = new Model();
model.add(new Tile(Tile.randomLetter(), Color.WHITE));
model.add(new Tile(Tile.randomLetter(), Color.RED));
model.add(new Tile(Tile.randomLetter(), Color.GREEN));
model.add(new Tile(Tile.randomLetter(), Color.YELLOW));
JFrame frame = new JFrame();
frame.getContentPane().setLayout(new GridLayout(4, 1));
frame.setSize(500, 800);
frame.setVisible(true);
for (Tile tile : model.getTiles()) {
frame.add(tile);
}
model.addModelListener(new ModelListener() {
#Override
public void tileWasRemoved(Tile tile) {
frame.remove(tile);
frame.revalidate();
frame.repaint();
}
});
frame.getContentPane().addKeyListener(this);
frame.getContentPane().setFocusable(true);
frame.getContentPane().requestFocusInWindow();
}
And finally, the keyTyped event now asks the Model to remove a Tile of the given key...
#Override
public void keyTyped(KeyEvent e) {
model.removeByChar(e.getKeyChar());
}
As a proof of concept...
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Game implements KeyListener {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
new Game();
}
});
}
private Model model;
public Game() {
model = new Model();
model.add(new Tile(Tile.randomLetter(), Color.WHITE));
model.add(new Tile(Tile.randomLetter(), Color.RED));
model.add(new Tile(Tile.randomLetter(), Color.GREEN));
model.add(new Tile(Tile.randomLetter(), Color.YELLOW));
JFrame frame = new JFrame();
frame.getContentPane().setLayout(new GridLayout(4, 1));
frame.setSize(500, 800);
frame.setVisible(true);
for (Tile tile : model.getTiles()) {
frame.add(tile);
}
model.addModelListener(new ModelListener() {
#Override
public void tileWasRemoved(Tile tile) {
frame.remove(tile);
frame.revalidate();
frame.repaint();
}
});
frame.getContentPane().addKeyListener(this);
frame.getContentPane().setFocusable(true);
frame.getContentPane().requestFocusInWindow();
}
#Override
public void keyPressed(KeyEvent e) {
}
#Override
public void keyReleased(KeyEvent e) {
}
#Override
public void keyTyped(KeyEvent e) {
model.removeByChar(e.getKeyChar());
}
public interface ModelListener {
public void tileWasRemoved(Tile tile);
}
public class Model {
private ArrayList<Tile> list = new ArrayList<Tile>();
private List<ModelListener> listeners = new ArrayList<>(25);
public Model() {
}
public void addModelListener(ModelListener listener) {
listeners.add(listener);
}
public void removeModelListener(ModelListener listener) {
listeners.remove(listener);
}
protected void fireTileRemoved(Tile tile) {
for (ModelListener listener : listeners) {
listener.tileWasRemoved(tile);
}
}
public void removeByChar(char value) {
Iterator<Tile> iterator = list.iterator();
while (iterator.hasNext()) {
Tile tile = iterator.next();
if (value == tile.getChar()) {
fireTileRemoved(tile);
iterator.remove();
}
}
}
private void add(Tile tile) {
list.add(tile);
}
private Iterable<Tile> getTiles() {
return Collections.unmodifiableList(list);
}
}
public static class Tile extends JLabel {
public static Font font = new Font("Serif", Font.BOLD, 39);
private char _c;
public Tile(char c, Color background) {
setBackground(background);
setOpaque(true);
_c = c;
setText(convert());
}
public static char randomLetter() {
Random r = new Random();
char randomChar = (char) (97 + r.nextInt(25));
return randomChar;
}
public char getChar() {
return _c;
}
public String convert() {
return String.valueOf(getChar());
}
}
}
How ever...
As a general rule of thumb, KeyListener is a pain to work with and you should be making use of the key bindings API instead, for example
import java.awt.AWTKeyStroke;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.KeyStroke;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Game {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
new Game();
}
});
}
private Model model;
public Game() {
model = new Model();
model.add(new Tile(Tile.randomLetter(), Color.WHITE));
model.add(new Tile(Tile.randomLetter(), Color.RED));
model.add(new Tile(Tile.randomLetter(), Color.GREEN));
model.add(new Tile(Tile.randomLetter(), Color.YELLOW));
JFrame frame = new JFrame();
frame.getContentPane().setLayout(new GridLayout(4, 1));
frame.setSize(500, 800);
frame.setVisible(true);
for (Tile tile : model.getTiles()) {
frame.add(tile);
KeyStroke ks = KeyStroke.getKeyStroke(tile.getChar());
String name = "typed." + tile.getChar();
Action action = new TileAction(model, tile.getChar());
registerKeyBinding((JComponent)frame.getContentPane(), name, ks, action);
}
model.addModelListener(new ModelListener() {
#Override
public void tileWasRemoved(Tile tile) {
frame.remove(tile);
frame.revalidate();
frame.repaint();
}
});
}
public void registerKeyBinding(JComponent parent, String name, KeyStroke ks, Action action) {
InputMap im = parent.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
ActionMap am = parent.getActionMap();
im.put(ks, name);
am.put(name, action);
}
public class TileAction extends AbstractAction {
private Model model;
private char value;
public TileAction(Model model, char value) {
this.model = model;
this.value = value;
}
#Override
public void actionPerformed(ActionEvent e) {
model.removeByChar(value);
}
}
public interface ModelListener {
public void tileWasRemoved(Tile tile);
}
public class Model {
private ArrayList<Tile> list = new ArrayList<Tile>();
private List<ModelListener> listeners = new ArrayList<>(25);
public Model() {
}
public void addModelListener(ModelListener listener) {
listeners.add(listener);
}
public void removeModelListener(ModelListener listener) {
listeners.remove(listener);
}
protected void fireTileRemoved(Tile tile) {
for (ModelListener listener : listeners) {
listener.tileWasRemoved(tile);
}
}
public void removeByChar(char value) {
Iterator<Tile> iterator = list.iterator();
while (iterator.hasNext()) {
Tile tile = iterator.next();
if (value == tile.getChar()) {
fireTileRemoved(tile);
iterator.remove();
}
}
}
private void add(Tile tile) {
list.add(tile);
}
private Iterable<Tile> getTiles() {
return Collections.unmodifiableList(list);
}
}
public static class Tile extends JLabel {
public static Font font = new Font("Serif", Font.BOLD, 39);
private char _c;
public Tile(char c, Color background) {
setBackground(background);
setOpaque(true);
_c = c;
setText(convert());
}
public static char randomLetter() {
Random r = new Random();
char randomChar = (char) (97 + r.nextInt(25));
return randomChar;
}
public char getChar() {
return _c;
}
public String convert() {
return String.valueOf(getChar());
}
}
}
See How to Use Key Bindings for more details.

JMenuItems on a JPopupMenu sometimes aren't drawn

I have a JPopupMenu that is displayed when a JButton is held down. This menu contains a series of JMenuItems, each associated with an action, that change sometimes. The problem I'm having is that intermitently the JMenuItems aren't being drawn, I simply get a grey JPopupMenu, but the items appear if I move the mouse cursor over them. I thought the problem might be with not properly repainting the components after changes, but testing show the problem keeps happening even when there are no changes to the items. Here is the relevant code:
if (!listChanged) {
myPopupMenu.show(myButton, x, y);
} else {
List<String> menuList = getMenuList();
MyData data = getData();
myPopupMenu.removeAll();
for (int i = 0; i < menuList.size(); i++) {
String name = menuList.get(i);
JMenuItem item = new JMenuItem(new MyMenuAction(this, name,
data, i));
item.addActionListener(this);
myPopupMenu.add(item);
myPopupMenu.validate();
}
myPopupMenu.repaint();
myPopupMenu.show(myButton, x, y);
}
...
private static class MyMenuAction extends AbstractAction {
private MyClass parent;
private int index;
private MyData data;
public MyMenuAction (MyClass parent, String name,
MyData data, int index) {
super(name);
this.parent = parent;
this.index = index;
this.data = data;
}
#Override
public void actionPerformed(ActionEvent e) {
Object[] actionParameters;
try {
actionParameters = data.getParameters(index);
} catch (ImmediateException e1) {
log(e1.getMessage(), "Error");
return;
}
parent.myButtonAction(actionParameters);
}
}
Just to clarify, the actions are working fine and 8 times out of 10 the JPopupMenu and all the JMenuItems are drawn right, but I can't figure out why they don't appear sometimes (regardless of whether the list has changed or not). Any help would be appreciated.
EDIT:
Ok, following Andrew Thompson's recomendation, here is a short complete example. Many of the methods have been striped bare, but the basic is still there. Just click and hold the button "SHOW MENU" to display the JPopupMenu. Since the problem is intermitent, it may be necessary to do it several times until the problem occurs.
package main;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JToolBar;
import javax.swing.UIManager;
public class MyClass implements ActionListener, MouseListener {
boolean listChanged = true;
boolean mousePressed = false;
long clickStart;
JPopupMenu myPopupMenu;
JButton myButton;
JFrame myFrame;
ArrayList<String> list;
public static void main(String[] args) {
MyClass myClass = new MyClass();
myClass.start();
}
private void start() {
myFrame = new JFrame();
myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
System.out.println(e.getClass().getName() + " " + e.getMessage());
}
startList();
myButton = new JButton("SHOW MENU");
myPopupMenu = new JPopupMenu();
JToolBar toolbar = new JToolBar();
toolbar.add(new JButton("Button1"));
toolbar.add(new JButton("Button2"));
toolbar.add(new JButton("Button3"));
toolbar.add(new JButton("Button4"));
toolbar.add(new JButton("Button5"));
toolbar.add(myButton);
myButton.addMouseListener(this);
toolbar.setBorder(BorderFactory.createEtchedBorder(1));
JPanel emptyPanel = new JPanel();
myFrame.add(toolbar, BorderLayout.PAGE_START);
myFrame.add(emptyPanel, BorderLayout.CENTER);
myFrame.pack();
myFrame.setExtendedState(myFrame.getExtendedState()
| JFrame.MAXIMIZED_BOTH);
myFrame.setVisible(true);
}
public void showMenu() {
if (!listChanged) {
myPopupMenu.show(myButton, 0, myButton.getHeight());
} else {
listChanged = false;
List<String> menuList = getMenuList();
MyData data = getData();
myPopupMenu.removeAll();
for (int i = 0; i < menuList.size(); i++) {
String name = menuList.get(i);
JMenuItem item = new JMenuItem(new MyMenuAction(this, name,
data, i));
item.addActionListener(this);
myPopupMenu.add(item);
myPopupMenu.validate();
}
myPopupMenu.repaint();
myPopupMenu.show(myButton, 0, myButton.getHeight());
}
}
private void startList() {
list = new ArrayList<String>();
list.add("Item 1");
list.add("Item 2");
list.add("Item 3");
list.add("Item 4");
list.add("Item 5");
}
private List<String> getMenuList() {
return list;
}
public void myButtonAction() {
Object[] defaultParameters = getDefaultParameters();
myButtonAction(defaultParameters);
}
private Object[] getDefaultParameters() {
// Placeholder
return null;
}
public void myButtonAction(Object[] actionParameters) {
// Placeholder
}
private MyData getData() {
// Placeholder
return new MyData();
}
private void changeList(List<String> newList) {
list.clear();
list.addAll(newList);
listChanged = true;
}
#Override
public void actionPerformed(ActionEvent e) {
// Placeholder
}
#Override
public void mouseClicked(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mousePressed(MouseEvent e) {
if (e.getSource() == myButton) {
mousePressed = true;
clickStart = System.currentTimeMillis();
new Thread(new Runnable() {
#Override
public void run() {
synchronized (this) {
while (mousePressed)
try {
this.wait(10);
if (System.currentTimeMillis() - clickStart > 300) {
MyClass.this.showMenu();
return;
}
} catch (InterruptedException e1) {
break;
}
MyClass.this.myButtonAction();
}
}
}).start();
}
}
#Override
public void mouseReleased(MouseEvent e) {
mousePressed = false;
}
#Override
public void mouseEntered(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mouseExited(MouseEvent e) {
// TODO Auto-generated method stub
}
private static class MyData {
public Object[] getParameters(int index) {
// Placeholder
return null;
}
}
private static class MyMenuAction extends AbstractAction {
private MyClass parent;
private int index;
private MyData data;
public MyMenuAction(MyClass parent, String name, MyData data, int index) {
super(name);
this.parent = parent;
this.index = index;
this.data = data;
}
#Override
public void actionPerformed(ActionEvent e) {
Object[] actionParameters;
try {
actionParameters = data.getParameters(index);
} catch (Exception e1) {
System.out.println(e1.getMessage());
return;
}
parent.myButtonAction(actionParameters);
}
}
}
I have a JPopupMenu that is displayed when a JButton is held down.
First of all I have a problem with a UI like that. The standard is to show a popup when you right click (in windows). Follow known standards. Read the section from the Swing tutorial on Bringing Up a Popup Menu for more information and working examples.
Secondly, I can't reproduce the problem (no matter how long I try). Random problems are usually caused by the GUI not being updated on the EDT. So don't use a Thread.
Instead use a Swing Timer.
Set the Timer to fire after 200ms at which time you display the menu. Code executed from the Timer IS invoked on the EDT. So you would restart() the Timer in mousePressed. and stop() the Timer in mouseReleased.

Clearing a Sudoku table

I'm working on this piece of code which can be found at
http://pastebin.com/7bCFtUHL
Basically, I want to add a clear method (button) which clears the sudoku after having it solved.
I've tried making a loop that goes through every cell and puts it to null but I'm not completely sure how to connect it exactly. Nor am I sure in which class I'd have to create it so it can be connected to the GUI where I have the other button.
EDIT:
This is the clear method I currently got
public void clearCells(){
for (int y = 0; y < 9; y++) {
for (int x = 0; x < 9; x++) {
cells[y][x] = null;
cells[y][x].setText("");
}
}
}
Now I need to attach that to the JButton in another class, how would that be possible?
My clear button looks like this
JButton clear = new JButton("Clear");
clear.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e)
{
//Code
}
}
);
What code would I need to add in the actionPerformed method to connect it with my clearCells method?
Again, I would put the "meat" of the clear method in the model itself. The general form of a solution would be to do this:
clear.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
myModel.clearCells();
}
});
Where the Model class would have a public void clearCells() method that iterates through the cells and clears them.
Edit 1
Note: yeah I did look at your pastebin code link and one big problem I see is that your SwingSudokuBoard class extends the SudokuBoard class, and this is misuse of inheritance where you should be using composition instead. The SwingSudokuBoard class should hold an instance of a SudokuBoard object and call methods on it.
Edit 2
You ask:
I'm not sure that I can completely understand you. You want me to have the clear method in the same class as I got the button, but then I cant call the cells. I added x.clearCells(); while x being what? My main class like, SwingSudokuBoard.clearCells(); ? Eitherway, if I add what you say the program complaints that it want the clearCells method and cells to be static. But if I put them to static, I get a NullPointerException.
I think that you need to use the Model-View-Control (MVC) pattern or an abbreviated version of it, perhaps one where you combine the view with the control since your program is small. I suggest that you have a separate model class, here this would likely be the SudokuBoard class, and then a view class, here probably the SwingSudokuBoard class. Your view's control methods (the ActionListeners) would call the model's clearCells() method. And don't use static anything here.
Edit 3
You ask:
I assume something along with these lines. Model: SudokuBoard; View: SwingSudokuBoard; Control: SwingSudoKiller. How would that go about? I'd have the actionListener posted above in the control. How would the other classes look like? Since I assume the clear method lays in the Model which you want to be in SudokuBoard but it cant connect with the cells there.
I'm not a professional, nor have I received formal programming training, so theory is one of my weak points, but my interpretation of MVC is that the view listens to the model and updates itself when the model notifies it of changes and that the control listens to the view and responds to view changes by notifying the model. This precise pattern has variations and does not need to be followed exactly to the letter, but the key in all of this is to separate out in your code the separate concerns as much as possible so that "coupling" (the number of direct connections between classes) is low or "loose" and "cohesion" (code that deals with the same concerns) is high or "tight".
In your program, again I'd combine the view and control by using anonymous inner listeners just as you're doing. I'd have the view/control, which is the SwingSudokuBoard class, hold an instance of the SudokuBoard class as a class field, and have the view/control's anonymous listeners call methods on the SudokuBoard field. When I've done this sort of thing before, I've given the model support for being observed by giving it a SwingPropertyChangeSupport object as well as public addPropertyChangeListener(...) and removePropertyChangeListener(...) methods. Then the view could respond easily to changes in the model.
You state:
Since I assume the clear method lays in the Model which you want to be in SudokuBoard but it cant connect with the cells there.
I'm not sure what you mean by this. The model holds the cells. Perhaps you don't mean the logical cells held by the model but rather the displayed cells held by the view. The view would add a listener to the model, and when notified of changes to the model, would ask the model for its data and use that to update the visualized cells.
Edit 4
For example:
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
import javax.swing.event.SwingPropertyChangeSupport;
public class OverlySimpleModelView {
private static void createAndShowGui() {
Model model = new Model();
ViewControl viewControl = new ViewControl(model);
JFrame frame = new JFrame("OverlySimpleModelView");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(viewControl.getMainComponent());
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
class ViewControl {
private JPanel mainPanel = new JPanel();
private JTextField number1Field = new JTextField(5);
private JTextField number2Field = new JTextField(5);
private JTextField productField = new JTextField(5);
private Model model;
public ViewControl(Model model) {
this.model = model;
model.addPropertyChangeListener(new MyPropChngListener());
productField.setEditable(false);
productField.setFocusable(false);
mainPanel.add(number1Field);
mainPanel.add(new JLabel(" * "));
mainPanel.add(number2Field);
mainPanel.add(new JLabel(" = "));
mainPanel.add(productField);
CalculateAction calculateAction = new CalculateAction("Calculate", KeyEvent.VK_C);
mainPanel.add(new JButton(calculateAction));
number1Field.addActionListener(calculateAction);
number2Field.addActionListener(calculateAction);
mainPanel.add(new JButton(new ClearAction("Clear", KeyEvent.VK_L)));
}
public JComponent getMainComponent() {
return mainPanel;
}
private class MyPropChngListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
number1Field.setText(String.valueOf(model.getNumber1()));
number2Field.setText(String.valueOf(model.getNumber2()));
productField.setText(String.valueOf(model.calculateProduct()));
}
}
private class CalculateAction extends AbstractAction {
public CalculateAction(String text, int keyCode) {
super(text);
putValue(MNEMONIC_KEY, keyCode);
}
#Override
public void actionPerformed(ActionEvent evt) {
try {
double number1 = Double.parseDouble(number1Field.getText());
double number2 = Double.parseDouble(number2Field.getText());
model.setNumber1(number1);
model.setNumber2(number2);
} catch (NumberFormatException e) {
e.printStackTrace();
}
}
}
private class ClearAction extends AbstractAction {
public ClearAction(String text, int keyCode) {
super(text);
putValue(MNEMONIC_KEY, keyCode); // to allow buttons a mnemonic letter
}
#Override
public void actionPerformed(ActionEvent evt) {
model.clear();
}
}
}
class Model {
public static final String NUMBERS_CHANGED = "numbers changed";
private double number1 = 0.0;
private double number2 = 0.0;
private SwingPropertyChangeSupport propChngSupport =
new SwingPropertyChangeSupport(this);
public double getNumber1() {
return number1;
}
public double getNumber2() {
return number2;
}
public void clear() {
setNumber1(0.0);
setNumber2(0.0);
}
// make number1 field a "bound" property, one that notifies listeners if it is changed.
public void setNumber1(double number1) {
Double oldValue = this.number1;
Double newValue = number1;
this.number1 = number1;
propChngSupport.firePropertyChange(NUMBERS_CHANGED, oldValue , newValue);
}
// ditto for the number2 field
public void setNumber2(double number2) {
Double oldValue = this.number2;
Double newValue = number2;
this.number2 = number2;
propChngSupport.firePropertyChange(NUMBERS_CHANGED, oldValue , newValue);
}
public double calculateProduct() {
return number1 * number2;
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
propChngSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
propChngSupport.removePropertyChangeListener(listener);
}
}
Or maybe better since it uses an array of numbers:
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
import javax.swing.event.SwingPropertyChangeSupport;
public class OverlySimpleModelView {
private static void createAndShowGui() {
Model model = new Model(5);
ViewControl viewControl = new ViewControl(model);
JFrame frame = new JFrame("OverlySimpleModelView");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(viewControl.getMainComponent());
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
class ViewControl {
private JPanel mainPanel = new JPanel();
private JTextField[] numberFields;
private JTextField productField = new JTextField(5);
private Model model;
public ViewControl(Model model) {
this.model = model;
model.addPropertyChangeListener(new MyPropChngListener());
productField.setEditable(false);
productField.setFocusable(false);
CalculateAction calculateAction = new CalculateAction("Calculate", KeyEvent.VK_C);
numberFields = new JTextField[model.getNumberFieldsLength()];
for (int i = 0; i < numberFields.length; i++) {
numberFields[i] = new JTextField("0.0", 5);
mainPanel.add(numberFields[i]);
numberFields[i].addActionListener(calculateAction);
if (i < numberFields.length - 1) {
mainPanel.add(new JLabel(" + "));
} else {
mainPanel.add(new JLabel(" = "));
}
}
mainPanel.add(productField);
mainPanel.add(new JButton(calculateAction));
mainPanel.add(new JButton(new ClearAction("Clear", KeyEvent.VK_L)));
}
public JComponent getMainComponent() {
return mainPanel;
}
private class MyPropChngListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
for (int i = 0; i < numberFields.length; i++) {
numberFields[i].setText(String.valueOf(model.getNumber(i)));
}
productField.setText(String.valueOf(model.calculateSum()));
}
}
private class CalculateAction extends AbstractAction {
public CalculateAction(String text, int keyCode) {
super(text);
putValue(MNEMONIC_KEY, keyCode);
}
#Override
public void actionPerformed(ActionEvent evt) {
try {
double[] numbers = new double[numberFields.length];
for (int i = 0; i < numbers.length; i++) {
numbers[i] = Double.parseDouble(numberFields[i].getText());
}
model.setNumbers(numbers);
} catch (NumberFormatException e) {
e.printStackTrace();
}
}
}
private class ClearAction extends AbstractAction {
public ClearAction(String text, int keyCode) {
super(text);
putValue(MNEMONIC_KEY, keyCode); // to allow buttons a mnemonic letter
}
#Override
public void actionPerformed(ActionEvent evt) {
model.clear();
}
}
}
class Model {
public static final String NUMBERS_CHANGED = "numbers changed";
private double[] numbers;
private SwingPropertyChangeSupport propChngSupport =
new SwingPropertyChangeSupport(this);
public Model(int length) {
numbers = new double[length];
}
public void setNumbers(double[] numbers) {
double[] oldValue = this.numbers;
double[] newValue = numbers;
this.numbers = numbers;
propChngSupport.firePropertyChange(NUMBERS_CHANGED, oldValue , newValue);
}
public double calculateSum() {
double sum = 0.0;
for (double number : numbers) {
sum += number;
}
return sum;
}
public double getNumber(int i) {
return numbers[i];
}
public int getNumberFieldsLength() {
return numbers.length;
}
public void clear() {
double[] newNumbers = new double[numbers.length];
setNumbers(newNumbers);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
propChngSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
propChngSupport.removePropertyChangeListener(listener);
}
}

Repaint thread works fine when triggering business function, but freezes when triggered by reference

It's a bit difficult to explain but here is the situation:
I'm programming a Cellular Automation program and have a mainScreen class that extends the JFrame and that JFrame includes the custom Jpanel that is also a thread that continues to repaint his self. The main class (App) creates the mainScreen.
Now is the strange thing that it is working (real-time view of the generations) when i call the business function (that is envolving the cells in a while loop) from the App class, but when i call the same function from the MainScreen class (through keyboard input) then the repainting doesn't accour, but i see console output that the generations are envolving.. And the program isn't responding to the close cross of the window, and in the other scenario it closes just like normal when running the algorithm.
So, why isn't my Jpanel repainting?
Hope you guys (girls) can help.
Class relation: App<-MainScreen<-MapPanel
App
package Business;
import GUI.MainScreen;
import java.util.Random;
public class App {
public static final int _CELL_SIZE = 5;
public static final int _WIN_WIDTH = 800;
public static final int _WIN_HEIGTH = 600;
public static final int _HELP_HEIGTH = 72;
public static final double _LIFE_START = 0.1F;
private MainScreen _mainScreen;
private Cell[][] _map;
private int _generation;
private Random _random;
private boolean _running;
public App() {
_generation = 0;
_running = false;
_random = new Random(System.currentTimeMillis());
newMap();
_mainScreen = new MainScreen(this);
//envolveCells();
//cout();
}
public void envolveCells() {
_generation = 0;
_running = true;
Cell[][] newMap = new Cell[getNumOfRows()][getNumOfCells()];
while(_running) {
newMap = new Cell[getNumOfRows()][getNumOfCells()];
//envolve cells
for(int row = 0; row < getNumOfRows(); row++) {
for(int cell = 0; cell < getNumOfCells(); cell++) {
System.out.println(_map[row][cell].envolve());
newMap[row][cell] = new Cell(_map[row][cell].envolve());
newMap[row][cell].setNeighbors(_map[row][cell].getNeighbors());
}
}
_map = newMap;
_generation++;
try {
Thread.sleep(100);
} catch(Exception ex) { }
}
}
package GUI;
import Business.App;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JFrame;
public class MainScreen extends JFrame implements KeyListener {
private App _app;
private MapPanel _mapPanel;
public MainScreen(App app){
_app = app;
_mapPanel = new MapPanel(app);
setTitle("Cellular Automaton sandbox - Sven van Zoelen");
setSize(App._WIN_WIDTH, App._WIN_HEIGTH + App._HELP_HEIGTH);
setResizable(false);
setDefaultCloseOperation(EXIT_ON_CLOSE);
add(_mapPanel);
addKeyListener(this);
setVisible(true);
}
public void keyTyped(KeyEvent e) {
if(e.getKeyChar() == 'r') {
System.out.println("Run mode");
_app.envolveCells();
}
}
MapPanel
package GUI;
import Business.App;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JPanel;
public class MapPanel extends JPanel implements Runnable {
private App _app;
private Font _font = new Font("Arial", Font.PLAIN, 10);
public MapPanel(App app) {
_font = new Font("Arial", Font.PLAIN, 10);
_app = app;
new Thread(this).start();
}
....
public void run() {
while(true) {
repaint();
try {
Thread.sleep(50);
} catch(Exception ex) { }
}
}
}
Your "business logic" looks like it should run at a separate thread from the GUI. Thus, don't invoke it directly from the GUI thread (in the action listener), but put it in a new Thread there.
As long as your action listener (or key-listener, in this case) does not return, your GUI will not be painted, as painting occurs in the same thread as input-handling.
public void keyTyped(KeyEvent e) {
if(e.getKeyChar() == 'r') {
new Thread("envolver") { public void run() {
System.out.println("Run mode");
_app.envolveCells();
}).start();
}
}

Categories