Clearing a Sudoku table - java

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);
}
}

Related

problem with calling the method after pressing the key

I would like to implement KeyListiner so that when pressing Space key program would generate new object of Function class and draw new image representing this function. For now program is working but pressing space key does not allow to generate a new image. Every advice will be greatly appreciated :)
Main class:
public class Main {
public static void main(String[] args) {
JFrame window = new JFrame(SOFTWARE_TITLE);
MainView mainView = new MainView();
Key key = new Key();
Controller controller = new Controller(mainView);
key.setController(controller);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setSize(790, 580);
window.setLocationRelativeTo(null);
window.setResizable(false);
window.add(mainView);
window.setVisible(true);
}
}
MainView class:
public class MainView extends JPanel {
private Function function;
public MainView(){
super();
setBackground(Color.GRAY);
setLayout(null);
setSize(200, 200);
function = new Function();
DrawBoard drawBoard = new DrawBoard(function);
drawBoard.setSize(500, 500);
drawBoard.setBackground(Color.lightGray);
drawBoard.setLocation(250, 20);
add(drawBoard);
setVisible(true);
}
}
DrawBoard class:
public class DrawBoard extends Canvas {
short CUSTOM_WIDTH = 5 * 100;
private Function function;
public DrawBoard(Function function) {
this.function = function;
}
public void paint(Graphics g) {
double difference = function.getzMax() - function.getzMin();
for (int indexX = 0; indexX < 100; indexX++) {
for (int indexY = 0; indexY < 100; indexY++) {
double value = function.getPoints()[indexX][indexY].getZ() - function.getzMin();
double corrected = setFloatInRGBRange(difference, value);
g.setColor(intToColor((int) corrected));
g.fillRect(indexX * 5, CUSTOM_WIDTH - ((indexY+1) * 5), 5, 5);
}
}
}
public Color intToColor(int colNum){
return new Color(colNum, colNum, colNum);
}
private int setFloatInRGBRange(double difference, double value){
return (int) ((255 * value)/difference);
}
}
Key class:
public class Key implements KeyListener {
Controller controller;
public void setController(Controller controller) {
this.controller = controller;
}
#Override
public void keyTyped(KeyEvent e) {
}
#Override
public void keyPressed(KeyEvent e) {
}
#Override
public void keyReleased(KeyEvent e) {
if (e.getKeyChar() == KeyEvent.VK_SPACE){
controller.generateFormula();
}
}
}
Controller class:
public class Controller {
MainView mainView;
public Controller(MainView mainView) {
this.mainView = mainView;
}
public void generateFormula() {
this.mainView = new MainView();
}
}
There is some other things that need be reviewed in code you posted. But for your main concern i guess you donc check Key strokes properly.
I would use e.getKeyCode() instead of e.getKeyChar() to check the event.
So your condition would be : e.getKeyCode() == KeyEvent.VK_SPACE
it is out of topic, but :
Your use of Function in multiples classes is unnecessary. It could be used only in DrawBoard
Don't mix Swing and AWT components as other already advised

Observer Pattern on MVC for specific fields

On the MVC pattern, which is the best option for the Model to notify the View (if this is the right approach in the first place) where, from all the fields of data the Model is storing, only a couple of them are updated. Specifically when we only want to update specific fields of the View.
I am currently using a MVC pattern with Observer/Subscriber (JAVA Swing) as described here: https://stackoverflow.com/a/6963529 but when the Model updates, it changes everything in the View when the update() funcion is called, it's impossible to determine which field from the Model changed in order to update only the required field in the View.
I read this topic: https://softwareengineering.stackexchange.com/a/359008 and this as well: https://stackoverflow.com/a/9815189 which I think it's usefull, but for the later, I can't understand very well how can I set a propertyChangeListener on a variale (int, float, etc). Also related to this: https://stackoverflow.com/a/9815189
The Main class where the software start to run:
public class Main {
public static void main(String[] args) {
Model m = new Model();
View v = new View(m);
Controller c = new Controller(m, v);
c.initController();
}
}
So the code that I have on Model is this:
public class Model extends Observable {
//...
private float speed;
private int batteryPercentage;
public float getSpeed() {
return speed;
}
public void setSpeed(float speed) {
this.speed = speed;
setChanged();
notifyObservers();
}
public int getBatteryPercentage() {
return batteryPercentage;
}
public void setBatteryPercentage(int batteryPercentage) {
this.batteryPercentage = batteryPercentage;
setChanged();
notifyObservers();
}
}
The view knows the Model:
public class View implements Observer {
private Model model;
private JTextField txtFldSpeed;
private JTextField txtFldBattery;
private JFrame mainWindow;
public View(Model m) {
this.model = m;
initialize();
}
private void initialize() {
mainWindow = new JFrame();
mainWindow.setTitle("New Window");
mainWindow.setMinimumSize(new Dimension(1280, 720));
mainWindow.setBounds(100, 100, 1280, 720);
mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel tPanel1 = new JPanel();
tPanel1.setBorder(new LineBorder(new Color(0, 0, 0)));
tPanel1.setLayout(null);
mainWindow.getContentPane().add(tPanel1);
mainWindow.getContentPane().add(tPanel1);
txtFldSpeed = new JTextField();
txtFldSpeed.setEditable(false);
txtFldSpeed.setBounds(182, 11, 116, 22);
tPanel1.add(txtFldSpeed);
txtFldBattery = new JTextField();
txtFldBattery.setEditable(false);
txtFldBattery.setBounds(182, 43, 116, 22);
tPanel1.add(txtFldBattery);
mainWindow.setVisible(true);
}
#Override
public void update(Observable o, Object arg) {
txtFldSpeed.setText(Float.toString(model.getSpeed()) + " kn");
txtFldBattery.setText(Integer.toString(model.getBatteryPercentage()) + " %");
}
}
The Controller adds the View as a Observer of the Model:
public class Controller {
private Model model;
private View view;
public Controller(Model m, View v) {
this.model = m;
this.view = v;
}
public void initController() {
model.addObserver(view);
model.setSpeed(10);
}
}
What I am expecting is something that, when the Model is updated, let's say, function setSpeed() is called, the View is told that she needs to update itself on that specific field and not every "changable" field (like the txtFldBattery.
I want to do this because on the View, there are fields being updated a couple of times per second, and because I need to update everything on the view, a JComboBox which doesn't need to update that often, keeps closing when trying to select a option.
I would use SwingPropertyChangeSupport, make each of the model's state fields a "bound property" so that each state field can be listened to separately.
For instance, say you have a model that looked like this:
public class MvcModel {
public static final String SPEED = "speed";
public static final String BATTERY = "battery";
public static final int MAX_SPEED = 40;
private float speed;
private int batteryPercentage;
private SwingPropertyChangeSupport pcSupport = new SwingPropertyChangeSupport(this);
public float getSpeed() {
return speed;
}
public void setSpeed(float speed) {
float oldValue = this.speed;
float newValue = speed;
this.speed = speed;
pcSupport.firePropertyChange(SPEED, oldValue, newValue);
}
public int getBatteryPercentage() {
return batteryPercentage;
}
public void setBatteryPercentage(int batteryPercentage) {
int oldValue = this.batteryPercentage;
int newValue = batteryPercentage;
this.batteryPercentage = batteryPercentage;
pcSupport.firePropertyChange(BATTERY, oldValue, newValue);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
pcSupport.removePropertyChangeListener(listener);
}
public void addPropertyChangeListener(String name, PropertyChangeListener listener) {
pcSupport.addPropertyChangeListener(name, listener);
}
public void removePropertyChangeListener(String name, PropertyChangeListener listener) {
pcSupport.removePropertyChangeListener(name, listener);
}
}
Both the speed and the batteryPercent fields are "bound fields" in that any changes to these fields will trigger the property change support object to fire a notification message to any listeners that have registered with the support object, as reflected in the public void setXxxx(...) methods.
This way the controller could register listeners on the model for whatever properties it wants to listen to, and then notify the view of any changes. For example:
class SpeedListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
float speed = model.getSpeed();
view.setSpeed(speed);
}
}
The set up could look something like:
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.GridLayout;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
import javax.swing.event.SwingPropertyChangeSupport;
public class MVC2 {
private static void createAndShowGui() {
MvcModel model = new MvcModel();
MvcView view = new MvcView();
MvcController controller = new MvcController(model, view);
controller.init();
JFrame frame = new JFrame("MVC2");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(view.getMainDisplay());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
class MvcView {
private JPanel mainPanel = new JPanel();
private JSlider speedSlider = new JSlider(0, MvcModel.MAX_SPEED);
private JSlider batterySlider = new JSlider(0, 100);
private JProgressBar speedBar = new JProgressBar(0, MvcModel.MAX_SPEED);
private JProgressBar batteryPercentBar = new JProgressBar(0, 100);
public MvcView() {
speedSlider.setMajorTickSpacing(5);
speedSlider.setMinorTickSpacing(1);
speedSlider.setPaintTicks(true);
speedSlider.setPaintLabels(true);
speedSlider.setPaintTrack(true);
batterySlider.setMajorTickSpacing(20);
batterySlider.setMinorTickSpacing(5);
batterySlider.setPaintTicks(true);
batterySlider.setPaintLabels(true);
batterySlider.setPaintTrack(true);
speedBar.setStringPainted(true);
batteryPercentBar.setStringPainted(true);
JPanel inputPanel = new JPanel(new GridLayout(0, 1));
inputPanel.add(createTitledPanel("Speed", speedSlider));
inputPanel.add(createTitledPanel("Battery %", batterySlider));
JPanel displayPanel = new JPanel(new GridLayout(0, 1));
displayPanel.add(createTitledPanel("Speed", speedBar));
displayPanel.add(createTitledPanel("Battery %", batteryPercentBar));
mainPanel.setLayout(new GridLayout(1, 0));
mainPanel.add(createTitledPanel("Input", inputPanel));
mainPanel.add(createTitledPanel("Display", displayPanel));
}
private JComponent createTitledPanel(String title, JComponent component) {
JPanel titledPanel = new JPanel(new BorderLayout());
titledPanel.setBorder(BorderFactory.createTitledBorder(title));
titledPanel.add(component);
return titledPanel;
}
public JComponent getMainDisplay() {
return mainPanel;
}
public void setSpeed(float speed) {
speedBar.setValue((int) speed);
}
public void setBatteryPercent(int batteryPercent) {
batteryPercentBar.setValue(batteryPercent);
}
public JSlider getSpeedSlider() {
return speedSlider;
}
public JSlider getBatterySlider() {
return batterySlider;
}
}
class MvcController {
private MvcModel model;
private MvcView view;
public MvcController(MvcModel model, MvcView view) {
this.model = model;
this.view = view;
model.addPropertyChangeListener(MvcModel.BATTERY, new BatteryListener());
model.addPropertyChangeListener(MvcModel.SPEED, new SpeedListener());
view.getSpeedSlider().addChangeListener(chngEvent -> {
int value = view.getSpeedSlider().getValue();
model.setSpeed(value);
});
view.getBatterySlider().addChangeListener(chngEvent -> {
int value = view.getBatterySlider().getValue();
model.setBatteryPercentage(value);
});
}
public void init() {
view.getSpeedSlider().setValue(10);
view.getBatterySlider().setValue(100);
model.setSpeed(10);
model.setBatteryPercentage(100);
}
class SpeedListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
float speed = model.getSpeed();
view.setSpeed(speed);
}
}
class BatteryListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
int batteryPercent = model.getBatteryPercentage();
view.setBatteryPercent(batteryPercent);
}
}
}
Side note: Observer and Observable have been deprecated in the most recent version of Java and so should their use should probably be avoided.
In your update method implementation you can determine with first argument o which Observable has changed and with second argument arg which value changed when you call: notifyObservers(this.speed);
Note that notifyObservers's signature accepts Object, and float primitive is not a subclass of Object.

passing data between JPanels

I'm making Tetris in Java and would like to have the game play on the left and scoring, buttons, and nextPiece on the right, like so:
You'll notice that the score on the Game Panel is updating, but the score on the Score Panel (on the right) is not.
On the Game Panel, I have global variables for score and level: private int level, totalScore; which are initialized to 0.
and this in my paint component():
g.setColor(Color.RED);
g.drawString("Level: " + level, this.getWidth()/2+110, this.getHeight()/2-200);
g.drawString("Score: " + totalScore, this.getWidth()/2+110, this.getHeight()/2-170);
Then I have this code within the Game Panel which calculates level and scoring:
public void changeLevel () {
int max = (level+1)*100;
if (totalScore >= max) {
System.out.println(max + "reached... next level");
level++;
totalScore = 0;
timer();
}
}
public int tallyScore(int totalLines) {
int score = 0;
switch (totalLines) {
case 1: score = 40 * (level + 1);
break;
case 2: score = 100 * (level + 1);
break;
case 3: score = 300 * (level + 1);
break;
case 4: score = 1200 * (level + 1);
break;
default: break;
}
return score;
}
//loop through all rows starting at bottom (12 rows)
public void checkBottomFull() {
int lines = 0;
for(int row = totalRows-1; row > 0; row--) {
while (isFull(row)) {
lines++;
clearRow(row);
}
}
totalScore += tallyScore(lines);
//check if level needs to be changed based on current score...
changeLevel();
//reset lines after score has been incremented
lines=0;
}
And since I want the Score Panel to display the score, I have these two methods in Game Panel which return the global variables:
public int getScore() {
return totalScore;
}
public int getLevel() {
return level;
}
In my Score Panel paintComponent() I have board.getLevel() and board.getScore() (board class is the Game Panel) so I can feed the Game Panel scores to the Score Panel.
g.setColor(Color.BLACK);
g.drawString("Level: " + board.getLevel(), this.getWidth()/2, this.getHeight()/2-130);
g.drawString("Score: " + board.getScore(), this.getWidth()/2, this.getHeight()/2-100);
Yet as you can see from the picture, these scores are not updating.
Any thoughts?
Thanks!
You will want to separate concerns so that they can be shared. Consider making a class for the logic and data that underlies your GUI, and say you call this class you Model class. Then you can give it a level and a score field and make them "bound properties", meaning that other classes can listen for changes to these fields. I usually do this by giving my Model a SwingPropertyChangeSupport object and give it an addPropertyChangeListener(PropertyChangeListener listener) and a removePropertyChangeListener(PropertyChangeListener listener), and then I notify all registered PropertyChangeListener's of changes by calling PropertyChangeSupport's fire. e.g.,
import java.beans.PropertyChangeListener;
import javax.swing.event.SwingPropertyChangeSupport;
public class Model {
public static final String SCORE = "score";
public static final String LEVEL = "level";
private SwingPropertyChangeSupport pcSupport =
new SwingPropertyChangeSupport(this);
private int score;
private int level;
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
pcSupport.removePropertyChangeListener(listener);
}
public int getScore() {
return score;
}
public void setScore(int score) {
int oldValue = this.score;
int newValue = score;
this.score = score;
pcSupport.firePropertyChange(SCORE, oldValue, newValue);
}
public int getLevel() {
return level;
}
public void setLevel(int level) {
int oldValue = this.level;
int newValue = level;
this.level = level;
pcSupport.firePropertyChange(LEVEL, oldValue, newValue);
}
}
Then any GUI or view component that wishes to listen to changes in the values may do so. The below class combines "View" with "Control" if you're studying MVC structure:
import java.awt.FlowLayout;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class UseModelGui {
private static void createAndShowGui() {
Panel1 panel1 = new Panel1();
Panel2 panel2 = new Panel2();
Model model = new Model();
panel1.setModel(model);
panel2.setModel(model);
JFrame frame = new JFrame("UseModelGui");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(new FlowLayout());
frame.getContentPane().add(panel1);
frame.getContentPane().add(panel2);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
class Panel1 extends JPanel {
private JTextField scoreField = new JTextField(2);
private JTextField levelField = new JTextField(2);
public Panel1() {
scoreField.setFocusable(false);
scoreField.setEditable(false);
levelField.setFocusable(false);
levelField.setEditable(false);
add(new JLabel("score:"));
add(scoreField);
add(new JLabel("Level:"));
add(levelField);
setBorder(BorderFactory.createTitledBorder("Check Values"));
}
public void setModel(Model model) {
model.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent pcEvt) {
if (Model.LEVEL.equals(pcEvt.getPropertyName())) {
String level = pcEvt.getNewValue().toString();
levelField.setText(level);
} else if (Model.SCORE.equals(pcEvt.getPropertyName())) {
String score = pcEvt.getNewValue().toString();
scoreField.setText(score);
}
}
});
}
}
class Panel2 extends JPanel {
private JSpinner scoreSpinner = new JSpinner(new SpinnerNumberModel(0, 0,
20, 1));
private JSpinner levelSpinner = new JSpinner(new SpinnerNumberModel(0, 0,
10, 1));
private Model model;
public Panel2() {
add(new JLabel("score:"));
add(scoreSpinner);
add(new JLabel("Level:"));
add(levelSpinner);
setBorder(BorderFactory.createTitledBorder("Set Values"));
scoreSpinner.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent evt) {
int score = ((Integer) scoreSpinner.getValue()).intValue();
if (model != null) {
model.setScore(score);
}
}
});
levelSpinner.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent evt) {
int level = ((Integer) levelSpinner.getValue()).intValue();
if (model != null) {
model.setLevel(level);
}
}
});
}
public void setModel(Model model) {
this.model = model;
}
}
The beauty of this is that Panel1 has no knowledge of Panel2, and Model has no knowledge of either. All Panel1 knows of is if Model changes. All Panel2 knows is that it is changing the Model's state. All the Model knows is that its state can change, and its values can be listened to.
You're right that in this simple example this is over-kill, but as soon as you start having complex data and state, this makes sense and becomes quite helpful.
You should separate your business logic from your presentation. Please read about: Model View Controller pattern.

Is MVC in Swing Thread Safe

I'm trying to touch limits of MVC architecture in Swing, but as I tried everything all (from SwingWorker or Runnable#Thread) are done on EDT
my questions:
is there some limits or strictly depends by order of the implementations
(wrapped into SwingWorker or Runnable#Thread) ?
limited is if is JComponent#method Thread Safe or not ?
essential characteristic of an MVC architecture in Swing, ?
inc. Container Re-Layout ?
note: for my SSCCE I take one of great examples by HFOE, and maybe by holding this principes strictly isn't possible to create any EDT lack or GUI freeze
import java.awt.BorderLayout;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.LinkedList;
import java.util.Queue;
import javax.swing.*;
public class MVC_ProgressBarThread {
private MVC_ProgressBarThread() {
MVC_View view = new MVC_View();
MVC_Model model = new MVC_Model();
MVC_Control control = new MVC_Control(view, model);
view.setControl(control);
JFrame frame = new JFrame("MVC_ProgressBarThread");
frame.getContentPane().add(view);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
MVC_ProgressBarThread mVC_ProgressBarThread = new MVC_ProgressBarThread();
}
});
}
}
class MVC_View extends JPanel {
private static final long serialVersionUID = 1L;
private MVC_Control control;
private JProgressBar progressBar = new JProgressBar();
private JButton startActionButton = new JButton("Press Me and Run this Madness");
private JLabel myLabel = new JLabel("Nothing Special");
public MVC_View() {
startActionButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
buttonActionPerformed();
}
});
JPanel buttonPanel = new JPanel();
startActionButton.setFocusPainted(false);
buttonPanel.add(startActionButton);
setLayout(new BorderLayout(10, 10));
add(buttonPanel, BorderLayout.NORTH);
progressBar.setStringPainted(true);
add(progressBar, BorderLayout.CENTER);
myLabel.setIcon(UIManager.getIcon("OptionPane.questionIcon"));
myLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
add(myLabel, BorderLayout.SOUTH);
}
public void setControl(MVC_Control control) {
this.control = control;
}
private void buttonActionPerformed() {
if (control != null) {
control.doButtonAction();
}
}
public void setProgress(int progress) {
progressBar.setValue(progress);
}
public void setProgressLabel(String label) {
progressBar.setString(label);
}
public void setIconLabel(Icon icon) {
myLabel.setIcon(icon);
}
public void start() {
startActionButton.setEnabled(false);
}
public void done() {
startActionButton.setEnabled(true);
setProgress(100);
setProgressLabel(" Done !!! ");
setIconLabel(null);
}
}
class MVC_Control {
private MVC_View view;
private MVC_Model model;
public MVC_Control(final MVC_View view, final MVC_Model model) {
this.view = view;
this.model = model;
model.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent pce) {
if (MVC_Model.PROGRESS.equals(pce.getPropertyName())) {
view.setProgress((Integer) pce.getNewValue());
}
if (MVC_Model.PROGRESS1.equals(pce.getPropertyName())) {
view.setProgressLabel((String) pce.getNewValue());
}
if (MVC_Model.PROGRESS2.equals(pce.getPropertyName())) {
view.setIconLabel((Icon) pce.getNewValue());
}
}
});
}
public void doButtonAction() {
view.start();
SwingWorker<Void, Void> swingworker = new SwingWorker<Void, Void>() {
#Override
protected Void doInBackground() throws Exception {
model.reset();
model.startSearch();
return null;
}
#Override
protected void done() {
view.done();
}
};
swingworker.execute();
}
}
class MVC_Model {
public static final String PROGRESS = "progress";
public static final String PROGRESS1 = "progress1";
public static final String PROGRESS2 = "progress2";
private static final int MAX = 11;
private static final long SLEEP_DELAY = 1000;
private int progress = 0;
private String label = "Start";
private PropertyChangeSupport pcs = new PropertyChangeSupport(this);
private PropertyChangeSupport pcs1 = new PropertyChangeSupport(this);
private PropertyChangeSupport pcs2 = new PropertyChangeSupport(this);
private final String[] petStrings = {"Bird", "Cat", "Dog",
"Rabbit", "Pig", "Fish", "Horse", "Cow", "Bee", "Skunk"};
private int index = 1;
private Queue<Icon> iconQueue = new LinkedList<Icon>();
private Icon icon = (UIManager.getIcon("OptionPane.questionIcon"));
public void setProgress(int progress) {
int oldProgress = this.progress;
this.progress = progress;
PropertyChangeEvent evt = new PropertyChangeEvent(this, PROGRESS,
oldProgress, progress);
pcs.firePropertyChange(evt);
}
public void setProgressLabel(String label) {
String oldString = this.label;
this.label = label;
PropertyChangeEvent evt = new PropertyChangeEvent(this, PROGRESS1,
oldString, label);
pcs1.firePropertyChange(evt);
}
public void setIconLabel(Icon icon) {
Icon oldIcon = this.icon;
this.icon = icon;
PropertyChangeEvent evt = new PropertyChangeEvent(this, PROGRESS2,
oldIcon, icon);
pcs2.firePropertyChange(evt);
}
public void reset() {
setProgress(0);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcs.addPropertyChangeListener(listener);
pcs1.addPropertyChangeListener(listener);
pcs2.addPropertyChangeListener(listener);
}
public void startSearch() {
iconQueue.add(UIManager.getIcon("OptionPane.errorIcon"));
iconQueue.add(UIManager.getIcon("OptionPane.informationIcon"));
iconQueue.add(UIManager.getIcon("OptionPane.warningIcon"));
iconQueue.add(UIManager.getIcon("OptionPane.questionIcon"));
for (int i = 0; i < MAX; i++) {
int newValue = (100 * i) / MAX;
setProgress(newValue);
setProgressLabel(petStrings[index]);
index = (index + 1) % petStrings.length;
setIconLabel(nextIcon());
try {
Thread.sleep(SLEEP_DELAY);
} catch (InterruptedException e) {
}
}
}
private Icon nextIcon() {
Icon icon1 = iconQueue.peek();
iconQueue.add(iconQueue.remove());
return icon1;
}
}
This is too long for a comment...
First and this is unrelated to the rest of this answer: there are many different MVCs out there and the one you used in that piece of code you posted here is not the same as the one used in the article you linked to: http://www.oracle.com/technetwork/articles/javase/mvc-136693.html
The article correctly points out that it's just "A common MVC implementation" (one where the view registers a listener listening to model changes). Your implementation is a different type of MVC, where the controller registers a listener listening to model changes and then updates the view.
Not that there's anything wrong with that: there are a lot of different types of MVCs out there (*).
(Another little caveat... Your view is aware of your controller in your example, which is a bit weird: there are other ways to do what you're doing without needing to "feed" the controller to the view like you do with your setControl(...) inside your MVCView.)
But anyway... You're basically nearly always modifying the GUI from outside the EDT (which you shouldn't be doing):
public void setIconLabel(final Icon icon) {
myLabel.setIcon(icon);
}
You can check it by adding this:
System.out.println("Are we on the EDT? " + SwingUtilities.isEventDispatchThread());
This is because you're eventually doing these updates from your SwingWorker thread (the SwingWorker thread is run outside the EDT: it's basically the point of a Swing worker).
I'd rather update the GUI from the EDT, doing something like this:
public void setIconLabel(final Icon icon) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
myLabel.setIcon(icon);
}
});
}

How to pass variables/objects from action listener to driver class?

I have those to classes: the main and GUI. In GUI there is actionListener which gathers information from user interface panel. How can send these variables to driver class, to execute everything there? I need to add them to infinite loop to draw a moving object, drawing methods are in other class. GUI has extended frame
here are the classes:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class GUI extends Frame implements WindowListener,ActionListener {
JLabel name1 = new JLabel("Name");
JLabel color1 = new JLabel("Color");
JLabel diam1 = new JLabel("Diameter");
JLabel dist1 = new JLabel("Distance");
JLabel speed1 = new JLabel("Speed");
JTextField name2 = new JTextField();
JTextField color2 = new JTextField();
JTextField diam2 = new JTextField();
JTextField dist2 = new JTextField();
JTextField speed2 = new JTextField();
public void windowClosing(WindowEvent e) {
dispose();
System.exit(0);
}
double distance;
int Speed;
double diameter;
public void actionPerformed(ActionEvent e) {
createVariables();
}
public void createVariables(){
try {
distance = Double.parseDouble(dist2.getText());
Speed = Integer.parseInt(speed2.getText());
diameter = Double.parseDouble(diam2.getText());
}
catch(NumberFormatException i) {
}
Planet cP = new Planet(name2.getText(),distance,diameter,color2.getText(),Speed );
Main plnt = new Main(cP);
}
public void createFrame1(){
addWindowListener(this);
setLayout(new GridLayout(6,6,5,5));
JButton mygt = new JButton("Add planet");
mygt.addActionListener(this);
name2.setText("belekoks");color2.setText("RED");diam2.setText("30");dist2.setText(" 60");speed2.setText("2");
add(name1);add(name2);add(color1);add(color2);add(diam1);
add(diam2);add(dist1);add(dist2);add(speed1);add(speed2);
add(mygt);
}
public GUI(String title){
super(title);
createFrame1();
}
public void windowClosed(WindowEvent e) {}
public void windowActivated(WindowEvent arg0) {}
public void windowDeactivated(WindowEvent arg0) {}
public void windowDeiconified(WindowEvent arg0) {}
public void windowIconified(WindowEvent arg0) {}
public void windowOpened(WindowEvent arg0) {}
}
and Main:
import java.awt.event.ActionEvent;
import java.util.Random;
public class Main{
/**
* #param args
*/
public Main(Planet name){
super(name);
}
public static void main(String[] args) {
SolarSystem system = new SolarSystem(300, 300);
GUI p = new GUI("New Planet");
p.pack();
p.setVisible(true);
Sun sun = new Sun("Sun", 0, 90, "YELLOW", 0);
Planet venus = new Planet("Venus",70,15,"ORANGE",2);
Planet earth = new Planet("Earth",100,20,"BLUE",1);
Moon moon = new Moon("Moon",earth.dist,5,"GRAY", 1, 30, 3);
Moon[] moons = new Moon[100];
moons[0] = moon;
Planet[] planets = new Planet[100];
planets[0] = earth;
planets[1] = venus;
Random rG = new Random();
Asteroid[] asteroids = new Asteroid[100];
for(int i=0; i<100;i++){
Asteroid Asteroids = new Asteroid("Asteroid", rG.nextDouble()+rG.nextInt(10)+45,rG.nextDouble()+rG.nextInt(10),"DARK_GRAY",rG.nextInt(360 ), 1);
asteroids[i]=Asteroids;
}
/*LinkedList list = new LinkedList();
for (int i=0;i<1;i++){
list.add(moons[i]);
}
for (int i=0;i<2;i++){
list.add(planets[i]);
}
for (int i=0;i<100;i++){
list.add(asteroids[i]);
}
for (int i=0;i<100;i++){
list.add(sun);
}
for(int y=0;y<2;y++){
planets[2].move();
planets[2].drawOn(system);
system.finishedDrawing();
}
for (int i=0; i>=0; i++){
sun.drawOn(system);
for(int y=0;y<3;y++){
planets[y].move();
planets[y].drawOn(system);
}
for (int y=0; y<100; y++){
asteroids[y].move();
asteroids[y].drawOn(system);
}
for (int y=0; y<1; y++){
moons[y].move();
moons[y].drawOn(system);
}
system.finishedDrawing();
}
*/
}
}
i don't expect you to look through whole code, but the main thing is that i get variables in actionListener in GUI and i want to pass them to main in order to create an object which I could draw later.
planet class creates an object:
public class Planet extends CosmicEntity {
public Planet(String name, double distance, double diameter, String col, int speed) {
super(name,distance,diameter,col,speed, 0);
}
}
and CosmicEntity class which holds all the variables and methods to draw a planet from passed variables (super(name,distance,diameter,col,speed, 0)).
It's hard to know exactly what you need based on the information provided, but I think that you don't want to use an infinite loop, not for your GUI. Most simple animations can be easily driven by a Swing Timer.
As for your other problem, a the general rule is that objects can call methods of other objects if they have a valid reference to the other object. How to do this for your project will depend on your code. Often we pass references of one object to another though as a method or constructor parameter. For instance,
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
public class FooMain {
public static void main(String[] args) {
FooNonGui nonGuiReference = new FooNonGui();
FooGui fooGui = new FooGui(nonGuiReference);
fooGui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
fooGui.pack();
fooGui.setLocationRelativeTo(null);
fooGui.setVisible(true);
}
}
class FooGui extends JFrame implements ActionListener {
private FooNonGui nonGuiVariable;
private int counter = 0;
public FooGui(FooNonGui nonGuiParameter) {
super("GUI");
this.nonGuiVariable = nonGuiParameter;
JButton button = new JButton("Button");
button.addActionListener(this); // I hate doing this, but for brevity's sake...
add(button);
}
public void actionPerformed(ActionEvent e) {
nonGuiVariable.nonGuiMethod(counter);
counter++;
}
}
class FooNonGui {
public void nonGuiMethod(int counter) {
System.out.print("In non-GUI method. ");
System.out.println("counter is " + counter);
}
}
I suggest that you give us more information so that we can be more knowledgeable about your problem and give you better help. This link may help: Smart Questions
Edit A: What are you adding your planets to? The SolarSystem object? Whichever class it is, likely will have or should have a public addPLanet(Planet planet) method. If SolarSystem, then you'll want to pass a reference to this object into your GUI class similar to what I do above.

Categories