repaint() method not calling paintComponent - java

I'm trying to write a program that visualizes simple sorting algorithms using Java Swing.
I have a menu with buttons that let the user choose which sorting algorithm they would like to see.
My problem is that repaint() is not calling paintComponent after every index swap, so we don't get to see the array being sorted. Instead, the program just displays the array once the panel is made visible, showing the already sorted array.
I've tried adding frame.revalidate() but it doesn't do anything since I'm not adjusting any frames or panels, just the array.
What am I missing?
Thank you!
Here is my main class and a sorting class (they're all similar).
import java.awt.Color;
import java.awt.Dimension;
import java.awt.CardLayout;
import java.util.*;
import javax.swing.*;
import java.awt.event.*;
public class AlgVisualiser implements ActionListener {
static final int N = 100;
static Integer[] arr;
static final int CONTENT_WIDTH = 800;
static final int CONTENT_HEIGHT = 800;
static JFrame frame = new JFrame("Sorting Algorithms");
static JPanel buttonPanel = new JPanel();
static JPanel arrPanel = new JPanel();
static JButton bubbleButton;
static JButton insertionButton;
static JButton selectionButton;
static Bubble bubbleSort;
static Insertion insertSort;
static Selection selectionSort;
public static void main(String[] args) {
initializeVars();
setFrame();
}
public static void setFrame() {
frame.setLayout(new CardLayout());
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setPreferredSize(new Dimension(CONTENT_WIDTH, CONTENT_HEIGHT));
frame.setLocationRelativeTo(null);
buttonPanel.setVisible(true);
frame.add(buttonPanel);
frame.add(arrPanel);
frame.pack();
}
public static void initializeVars() {
arr = new Integer[N];
arr = fillArr(arr);
arr = shuffleArr(arr);
bubbleSort = new Bubble(arr);
bubbleSort.setPreferredSize(new Dimension(CONTENT_WIDTH, CONTENT_HEIGHT));
insertSort = new Insertion(arr);
insertSort.setPreferredSize(new Dimension(CONTENT_WIDTH, CONTENT_HEIGHT));
selectionSort = new Selection(arr);
selectionSort.setPreferredSize(new Dimension(CONTENT_WIDTH, CONTENT_HEIGHT));
AlgVisualiser alg = new AlgVisualiser();
bubbleButton = new JButton("Bubble Sort");
bubbleButton.setPreferredSize(new Dimension(200, 200));
bubbleButton.addActionListener(alg);
selectionButton = new JButton("Selection Sort");
selectionButton.setPreferredSize(new Dimension(200, 200));
selectionButton.addActionListener(alg);
insertionButton = new JButton("Insertion Sort");
insertionButton.setPreferredSize(new Dimension(200, 200));
insertionButton.addActionListener(alg);
bubbleButton.setBackground(Color.WHITE);
selectionButton.setBackground(Color.WHITE);
insertionButton.setBackground(Color.WHITE);
buttonPanel.setBackground(Color.DARK_GRAY);
buttonPanel.setPreferredSize(new Dimension(CONTENT_WIDTH, CONTENT_HEIGHT));
buttonPanel.add(bubbleButton);
buttonPanel.add(selectionButton);
buttonPanel.add(insertionButton);
arrPanel.setPreferredSize(new Dimension(CONTENT_WIDTH, CONTENT_HEIGHT));
arrPanel.add(bubbleSort);
}
public void actionPerformed(ActionEvent event) {
if (event.getSource() == bubbleButton) {
buttonPanel.setVisible(false);
arrPanel.setVisible(true);
bubbleSort.sort();
} else if (event.getSource() == selectionButton) {
buttonPanel.setVisible(false);
arrPanel.setVisible(true);
insertSort.sort();
} else if (event.getSource() == insertionButton) {
buttonPanel.setVisible(false);
arrPanel.setVisible(true);
selectionSort.sort();
}
}
public static Integer[] shuffleArr(Integer[] arr) {
arr = fillArr(arr);
List<Integer> list = Arrays.asList(arr);
Collections.shuffle(list);
arr = list.toArray(arr);
return arr;
}
public static Integer[] fillArr(Integer[] arr) {
for (int i = 0; i < N; i++) {
arr[i] = i + 1;
}
return arr;
}
}
import java.awt.*;
import javax.swing.*;
public class Bubble extends JComponent {
private static int checkedIndex1;
private static int checkedIndex2;
private static final long serialVersionUID = 1L;
private Integer[] arr;
public Bubble(Integer[] arr) {
this.arr = arr;
}
public void sort() {
for (int i = 0; i < AlgVisualiser.N - 1; i++) {
for (int j = 0; j < AlgVisualiser.N - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
checkedIndex1 = i;
checkedIndex2 = j;
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
AlgVisualiser.frame.revalidate();
AlgVisualiser.frame.repaint();
}
}
}
checkedIndex1 = -1;
checkedIndex2 = -1;
AlgVisualiser.frame.revalidate();
AlgVisualiser.frame.repaint();
}
#Override
public void paintComponent(Graphics g) {
Graphics2D graphics2d = (Graphics2D) g;
graphics2d.setColor(Color.DARK_GRAY);
graphics2d.fillRect(0, 0, AlgVisualiser.CONTENT_WIDTH, AlgVisualiser.CONTENT_HEIGHT);
for (int i = 0; i < arr.length; i++) {
int width = (int) (AlgVisualiser.CONTENT_WIDTH / (double) AlgVisualiser.N);
int height = arr[i] * (AlgVisualiser.CONTENT_HEIGHT / AlgVisualiser.N);
int x = i * width;
int y = AlgVisualiser.CONTENT_HEIGHT - height;
if (i == checkedIndex1 || i == checkedIndex2) {
graphics2d.setColor(Color.RED);
} else if (checkedIndex1 == -1) {
graphics2d.setColor(Color.GREEN);
} else {
graphics2d.setColor(Color.WHITE);
}
graphics2d.fillRect(x, y, width, height);
}
}
}

There are several problems with the posted code:
frame.getContentPane().setPreferredSize(new Dimension(CONTENT_WIDTH, CONTENT_HEIGHT));
...
frame.add(buttonPanel);
frame.add(arrPanel);
and
bubbleSort.setPreferredSize(new Dimension(CONTENT_WIDTH, CONTENT_HEIGHT));
...
arrPanel.setPreferredSize(new Dimension(CONTENT_WIDTH, CONTENT_HEIGHT));
arrPanel.add(bubbleSort);
You set 3 different components to the same size. They can't all be the same size, since the content pane of the frame contains multiple components.
Don't keep setting the preferred size of components. It is the responsibility of each component to determine its own preferred size.
With components like buttons, if you want the button bigger, you can use the setMargins(...) method.
When doing custom painting, you would override the getPreferredSize() of the class, since your custom painting code knows best what the size should be.
static JPanel buttonPanel = new JPanel();
static JPanel arrPanel = new JPanel();
static JButton bubbleButton;
static JButton insertionButton;
static JButton selectionButton;
static Bubble bubbleSort;
static Insertion insertSort;
static Selection selectionSort;
Don't use static variable everywhere. This indicates improper design.
public static void initializeVars() {
Again, don't use static methods. Also improper design.
You need to create a class with instance variables and method that can access these instance variables.
graphics2d.fillRect(0, 0, AlgVisualiser.CONTENT_WIDTH, AlgVisualiser.CONTENT_HEIGHT);
Don't access variable from another class when doing painting. Instead you would use the getWidth() and getHeight() methods to determine the current size of the component so you can fil in the background of the panel.
AlgVisualiser.frame.revalidate();
AlgVisualiser.frame.repaint();
Don't repaint the entire frame. You are only changing your custom component you you only need to repaint the component, not the entire frame. There is also no need for the reavalidate(). The revalidate() method is used to invoke the layout manager. Your are not adding or removing components from the panel.
the program just displays the array once the panel is made visible, showing the already sorted array.
Now the hard part.
The repaint() method just adds a paint request to the RepaintManager. The RepaintManager will then consolidate requests and add a paint request to the Event Dispatch Thread (EDT) which will repaint the frame.
The problem is your looping code executes so fast that you don't see the individual steps. So you need to use a Thread.sleep(...) so slow down processing to give the GUI a chance to paint each step.
Now you have another problem. If you use Thread.sleep() on the EDT, the GUI still can't repaint() itself until the loop is finished executing.
So you need to execute the soring code on a separate Thread. Then you can tell the sorting Thread to sleep and tell the GUI to repaint itself. One way to do this is to use a SwingWorker.
Read the section from the Swing tutorial on Concurrency for more information on the EDT and the SwingWorker.

Related

How to assign or initiate an integer into JButton?

package javaapplication3;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
class BattleShip2 extends JFrame implements ActionListener {
JButton[][] array2dtop = new JButton[10][10];
BattleShip2(String title) { //Board Display
super(title);//show Title
setLayout(new FlowLayout()); // button posistion
for(int x = 0; x < array2dtop.length; x++) {
for(int y = 0; y < array2dtop.length; y++) {
array2dtop[x][y] = new JButton(String.valueOf((x * 10) + y)); //display
array2dtop[x][y].setBounds(20,40,140,20);
array2dtop[x][y].setActionCommand("x"); //itself value
array2dtop[x][y].addActionListener(this);
add(array2dtop[x][y]);
}
}
}
public void actionPerformed(ActionEvent evt) { //operation
String cmd = evt.getActionCommand();
if(cmd == "x") {
System.out.println("x");
}
}
}
public class pratice3 {
public static void main(String[] args) {
BattleShip2 board = new BattleShip2("BattleShip");
board.setSize(500, 400);
board.setLocation(250, 250);
board.setDefaultCloseOperation(board.EXIT_ON_CLOSE); //Press x to EXIT
board.setVisible(true);
}
}
Hi, i am creating a battleship game, and my idea is when the user or AI hit the button, it could show ship size
For example, there have 3 ships: ship1(size = 3), ship2(size = 2), ship3(size = 1), and when the user/AI hit the ship1, and they see 3, they will know they hit ship1
but I am having troubles on the setActioncommand
on the code,
array2dtop[x][y].setActionCommand("x")
I want to assign or declare a integer(the size of the ship) into array2dtop[x][y]
but I have no idea but assign a string and how can I use it in the actionPerformed
I want to assign or declare a integer(the size of the ship) into array2dtop[x][y]
You can just parse the string to an integer using Integer.parseInt:
class BattleShip2 extends JFrame implements ActionListener {
JButton[][] array2dtop = new JButton[10][10];
BattleShip2(String title) {
super(title);
setLayout(new GridLayout(10, 10, 5, 5));
for (int x = 0; x < array2dtop.length; x++) {
for (int y = 0; y < array2dtop.length; y++) {
array2dtop[x][y] = new JButton(String.valueOf((x * 10) + y));
array2dtop[x][y].setActionCommand(String.valueOf(3));
array2dtop[x][y].addActionListener(this);
// Force buttons to be square.
Dimension dim = array2dtop[x][y].getPreferredSize();
int buttonSize = Math.max(dim.height, dim.width);
array2dtop[x][y].setPreferredSize(new Dimension(buttonSize, buttonSize));
add(array2dtop[x][y]);
}
}
}
public void actionPerformed(ActionEvent evt) {
int cmd = Integer.parseInt(evt.getActionCommand());
if (cmd == 3) {
System.out.println(3);
}
}
}
public class pratice3 {
public static void main(String[] args) {
BattleShip2 board = new BattleShip2("BattleShip");
board.pack();
board.setLocationRelativeTo(null);
board.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
board.setVisible(true);
}
}
Notes:
I changed the layout to GridLayout with the same vertical and horizontal gaps as FlowLayout's defaults. It will assure that all button have the same size.
Don't use setBounds, let the layout manager take care of that.
I added 3 lines that make the buttons square, you can remove that.
Instead of using setSize for a JFrame, use pack.
Setting the location using board.setLocationRelativeTo(null) (After the call to pack) is usually a better idea since you don't know the size of the screen.
board.EXIT_ON_CLOSE should be accessed in a static way: JFrame.EXIT_ON_CLOSE (for example).
When calling Integer.parseInt, you might want to catch NumberFormatException.
With that being said, there are other ways to achieve this, such as using setClientProperty on the buttons, or just passing the number to the ActionListener's constructor.

Why my GUI won't update even tho repaint() is being called?

I am having some difficulties using swing workers, timers, and I am actually a little confused.
As far as my understanding goes, I have to put on a timer to set-up recurring tasks that have to be called by the EDT.
I'm trying to make a program that shows graphically a sorting alghoritm (like this : https://www.youtube.com/watch?v=kPRA0W1kECg )
I just don't understand why the GUI won't refresh. I am quite sure the repaint method is being called since I put a sysout showing me the ordered values and it seems to work , but the GUI just... doesn't change.
Here's my code:
public class MainWindow {
private JFrame frame;
JPanel panel;
public final static int JFRAME_WIDTH = 800;
public final static int JFRAME_HEIGHT = 600;
public final static int NELEM = 40;
ArrayList<Double> numbers;
ArrayList<myRectangle> drawables = new ArrayList<myRectangle>();
Lock lock = new ReentrantLock();
Condition waitme = lock.newCondition();
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
MainWindow window = new MainWindow();
window.frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public MainWindow() {
initialize();
}
private void initialize() {
frame = new JFrame();
frame.setBounds(100, 100, JFRAME_WIDTH + 20, JFRAME_HEIGHT + 40);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
panel = new myPanel();
frame.getContentPane().add(panel, BorderLayout.CENTER);
Timer timer = new Timer(500, new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
lock.lock();
try{
//Updating the gui
panel.repaint();
panel.revalidate();
//Giving the OK to the sorting alghoritm to proceed.
waitme.signal();
}catch(Exception e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
});
timer.start();
SwingWorker<Integer, String> sw = new SwingWorker<Integer, String>(){
#Override
protected Integer doInBackground() throws Exception {
mapAndCreate();
bubbleSort();
return null;
}
};
sw.execute();
}
private void bubbleSort() throws InterruptedException{
for(int i=0; i < NELEM; i++){
for(int j=1; j < (NELEM-i); j++){
if(drawables.get(j-1).wid > drawables.get(j).wid){
//swap the elements!
myRectangle temp = drawables.get(j-1);
drawables.set(j-1, drawables.get(j));
drawables.set(j, temp);
lock.lock();
try{
//Wait for the GUI to update.
waitme.await();
}catch(Exception e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
}
}
}
/***
* Function that maps values from 0 to 1 into the rectangle width.
*/
private void mapAndCreate() {
double max = 0;
numbers = new ArrayList<Double>(NELEM);
//Finding maximum.
for(int i = 0; i < NELEM; i++){
Double currElem = Math.random();
if(currElem > max) max = currElem;
numbers.add(currElem);
}
//Mapping process
int offset = 0;
for(int j = 0; j < NELEM; j++){
Integer mapped = (int) (( JFRAME_WIDTH * numbers.get(j) ) / max);
myRectangle rect = new myRectangle(offset , mapped);
drawables.add(rect);
offset += JFRAME_HEIGHT / NELEM;
}
}
private class myRectangle{
int myy , wid , colorR,colorG,colorB;
public myRectangle(int y , int wid){
this.myy = y;
this.wid = wid;
Random r = new Random();
colorR = r.nextInt(255);
colorG = r.nextInt(255);
colorB = r.nextInt(255);
}
}
private class myPanel extends JPanel{
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for(myRectangle rectan : drawables){
Graphics2D graphics2D = (Graphics2D)g;
System.out.println(rectan.wid);
Rectangle2D.Double rect = new Rectangle2D.Double(0,rectan.myy,rectan.wid,JFRAME_HEIGHT / NELEM);
graphics2D.setColor(new Color(rectan.colorR,rectan.colorG,rectan.colorB));
graphics2D.fill(rect);
}
System.out.println("====================================================================================================");
}
}
}
Most OSs (or rather the UI frameworks which they use) don't support concurrent access. Simply put, you can't render two strings of text at the same time.
That's why Swing runs all rendering operations in the UI thread. Calling rendering functions (like paint()) outside of the UI thread can cause all kinds of problems. So when you do it, Swing will just remember "I should repaint" and return (instead of doing any actual work). That way, Swing protects you but most people would prefer to get an error with a useful message.
A timer always also means that there is a thread somewhere which executes when the timer runs out. This is not the UI thread of Swing. So any paing operations there must be wrapped with EventQueue.invokeLater() or similar.
Another common bug is to hog the UI thread (so no rendering happens because you do complex calculations there). That's what the SwingWorker is for. Again, in most methods of the SwingWorker, calling methods which would render something is forbidden (-> use invokeLater()).
So my guess is that the UI thread waits for the lock and the lock simply isn't unlocked early or often enough. See this demo how to do a simple animation in Swing.
public class TimerBasedAnimation extends JPanel implements ActionListener {
public void paint(Graphics g) {
// setup
// do some first-run init stuff
// calculate the next frame
// render frame
}
public void actionPerformed(ActionEvent e) {
repaint();
}
public static void main(String[] args) {
JFrame frame = new JFrame("TimerBasedAnimation");
frame.add(new TimerBasedAnimation());
...
}
}
As you can see in the code doesn't lock. Instead, you just send "render now" events from actionPerformed to Swing. Some time later, Swing will call paint(). There is no telling (and no way to make sure or force Swing) when this will happen.
So good animation code will take the current time, calculate the animation state at that time and then render it. So it doesn't blindly step through N phases in M seconds. Instead, it adjusts for every frame to create the illusion that the animation is smooth when it really isn't.
Related:
Java: Safe Animations with Swing
How to Use Swing Timers

How to change position of a JLabel?

I'm trying to use a timer to change the position of a JLabel, from one spot on my JPanel to another. I'm not sure if I could use say .getLocation(), then change only the horizontal x value, and finally use .setLocation() to effectively modify the JLabel. I've also used .getBounds and .setBounds, but am still unsure how I can obtain the old horizontal x value to change and reapply to the new x value.
The code I tried looks something like this, but neither is a valid way to change the position of the JLabel.
// mPos is an arraylist of JLabels to be moved.
for(int m = 0; m < mPos.size(); m++){
mPos.get(m).setLocation(getLocation()-100);
}
or
for(int m = 0; m < mPos.size(); m++){
mPos.get(m).setBounds(mPos.get(m).getBounds()-100);
}
If I could just get the position of the horizontal x value I can change the position of the label.
Try with Swing Timer if you are looking for some animation.
Please have a look at How to Use Swing Timers
Here is the sample code:
int delay = 1000; //milliseconds
ActionListener taskPerformer = new ActionListener() {
public void actionPerformed(ActionEvent evt) {
//...Perform a task...
}
};
new Timer(delay, taskPerformer).start();
Find a Sample code here
Sample code: (Move Hello World message 10px horizontally left to right at interval of 200 ms)
private int x = 10;
...
final JPanel panel = new JPanel() {
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawString("Hello World", x, 10);
}
};
int delay = 200; // milliseconds
ActionListener taskPerformer = new ActionListener() {
public void actionPerformed(ActionEvent evt) {
x += 10;
if (x > 100) {
x = 10;
}
panel.repaint();
}
};
new Timer(delay, taskPerformer).start();
I made a similar example just so you can get the basic jest of it, try copy pasting this in a new class called "LabelPlay" and it should work fine.
import java.awt.EventQueue;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JButton;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
public class LabelPlay {
private JFrame frame;
private JLabel label;
private Random rand;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
LabelPlay window = new LabelPlay();
window.frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public LabelPlay() {
initialize();
}
private void initialize() {
frame = new JFrame();
frame.setBounds(100, 100, 659, 518);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(null);
label = new JLabel("YEEEHAH!");
label.setBounds(101, 62, 54, 21);
frame.getContentPane().add(label);
JButton btnAction = new JButton("Action!");
rand = new Random();
btnAction.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
int a = rand.nextInt(90)+10;
int b = rand.nextInt(90)+10;
int c = rand.nextInt(640)+10;
int d = rand.nextInt(500)+10;
label.setBounds(a, b, c, d);
}
});
btnAction.setBounds(524, 427, 89, 23);
frame.getContentPane().add(btnAction);
}
}
If you want this to happen in a loop at certain times, you can just put it in a loop and use Thread.sleep(amount of miliseconds) in the loop before you run the code.
Why don't you create a JLabel at position a, set it to visible, and another one at position b, setting it to not visible? After the timer is up, hide the first and show the second.
Are you planning on creating some moving imageIcon for some type of game? Or some label that moves pretty much everywhere ?
I would use an Absolute Layout and set Location manually everytime.
myPanel.setLayout(null);
// an initial point
int x = 100;
int y =100;
while (
//some moving pattern
x++; // 1 pixel per loop
y+=2; // 2 pixels per loop
myLabel.setLocation(x,y);
}

how to refresh the colors of button contained in a JFrame?

I am coding a little game in which i have taken a grid of JButtons in a JFrame and i want to refresh the colors of the buttons contained in a JFrame,which is already visible.As explained below
void foo(){
mainFrame.setVisible(true);//mainFrame is defined at class level.
//color update code for the buttons.
mainFrame.setVisible(true);
}
Result i am getting is not as expected and my screen gets freeze .Isn't it the right way to achieve what i wanted?
EDIT
ok i am explaining it in detail what i want to achieve.i have a class,as:-
import javax.swing.*;
import java.awt.*;
import java.util.*;
class Brick extends JButton{
public void setRandomColors(){
int random = (int) (Math.random()*50);
if(random%13==0){
this.setBackground(Color.MAGENTA);
}
else if(random%10==0){
this.setBackground(Color.red);
}
else if(random%9==0){
this.setBackground(Color.yellow);
}
else if(random%7==0){
this.setBackground(Color.orange);
}
else if(random%2==0){
this.setBackground(Color.cyan);
}
else{
this.setBackground(Color.PINK);
}
}
public void setBlackColor(){
this.setBackground(Color.black);
}
}
class Grid {
JFrame mainGrid = new JFrame();
ArrayList<Brick> bunchOfBricks = new ArrayList<>();
int gridLength = 8;//gridlenth is equals to gridweight as i have considered a Square grid.
int totalBricks = gridLength*gridLength;
public void formBunchOfBricks(){
for(int i=0;i<totalBricks;i++){
bunchOfBricks.add(new Brick());
}
}
public void formColoredGrid(){
Brick aBrick;
mainGrid.setLayout(new GridLayout(8,8));
for(int i=0;i<totalBricks;++i){
aBrick = (bunchOfBricks.get(i));
aBrick.setRandomColors();
mainGrid.add(aBrick);
}
mainGrid.setVisible(true);//its ok upto here iam getting randomly colored Frame of Bricks or so called JButtons.
delay(15);//Sorry for this one,i warn you not to laugh after looking its defination.
}
/*
I want following function to do following things:-
1.it should firstly display the Grid whose all buttons are black Colored.
2.After some time the original colored,first Row of grid formed by formColoredGrid should be displayed and all the rest Rows should be black.
3.Then second row turns colored and all other rows should be black......and so on upto last row of Grid.
*/
public void movingRows(){
setGridBlack();
delay(1);//see in upper method,for this horrible thing.
for(int i=0;i<gridLength;++i){
setGridBlack();
for (int j=0;j<gridLength;++j){
Brick aBrick = bunchOfBricks.get((gridLength*i)+j);
aBrick.setRandomColors();//Bricks are colored Row by Row.
}
delay(5);//already commented this nonsense.
mainGrid.setVisible(true);//using setVisible again,although this frame is already visible,when i called formColoredGrid.
setGridBlack();
}
//oh! disappointing,i have almost broken my arm slamming it on table that why the function result in a screen full of black buttons.
}
public void setGridBlack(){
for(int i=0;i<totalBricks;i++){
bunchOfBricks.get(i).setBlackColor();
}
}
public void delay(int a){
for ( int i=0;i<90000000;++i){
for(int j=0;j<a;++j){
}
}
}
public static void main(String args[]){
Grid g1 = new Grid();
g1.formBunchOfBricks();
g1.formColoredGrid();
g1.movingRows();
}
}
Please Help me what is the way out?
Your problem is in code not shown here:
//color update code for the buttons.
You're likely running a loop that never ends on the Swing event thread, possibly a never-ending while loop that polls the state of something(a guess), freezing your GUI. Solution: don't do this; don't use a continuous polling loop. Instead, change the colors based on responses to events as Swing is event-driven.
For better more specific help, please show the offending code and tell us more about your program.
Edit
If you're trying to show colored rows, one by one marching down the board, then my guess is right, you'll want to use a Swing Timer, one that uses an int index to indicate which row is being displayed in color. You'd increment the index inside of the Timer's ActionPerformed class, and then when all rows have been displayed stop the Timer. For example something like so:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import javax.swing.*;
#SuppressWarnings("serial")
public class MyGrid extends JPanel {
private static final int GRID_LENGTH = 8;
private static final Color BTN_BACKGROUND = Color.BLACK;
private static final Color[] COLORS = { Color.MAGENTA, Color.CYAN,
Color.RED, Color.YELLOW, Color.ORANGE, Color.PINK, Color.BLUE,
Color.GREEN };
private static final int PREF_W = 400;
private static final int PREF_H = PREF_W;
private static final int TIMER_DELAY = 800;
private JButton[][] buttonGrid = new JButton[GRID_LENGTH][GRID_LENGTH];
private Map<JButton, Color> btnColorMap = new HashMap<>();
private Random random = new Random();
public MyGrid() {
setLayout(new GridLayout(GRID_LENGTH, GRID_LENGTH));
for (int row = 0; row < buttonGrid.length; row++) {
for (int col = 0; col < buttonGrid[row].length; col++) {
JButton btn = new JButton();
btn.setBackground(BTN_BACKGROUND);
// !! add action listener here?
add(btn);
buttonGrid[row][col] = btn;
}
}
new Timer(TIMER_DELAY, new TimerListener()).start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
public void resetAllBtns() {
for (JButton[] row : buttonGrid) {
for (JButton btn : row) {
btn.setBackground(BTN_BACKGROUND);
}
}
}
private class TimerListener implements ActionListener {
private int row = 0;
#Override
public void actionPerformed(ActionEvent e) {
resetAllBtns(); // make all buttons black
if (row != buttonGrid.length) {
for (int c = 0; c < buttonGrid[row].length; c++) {
int colorIndex = random.nextInt(COLORS.length);
Color randomColor = COLORS[colorIndex];
buttonGrid[row][c].setBackground(randomColor);
// !! not sure if you need this
btnColorMap.put(buttonGrid[row][c], randomColor);
}
row++;
} else {
// else we've run out of rows -- stop the timer
((Timer) e.getSource()).stop();
}
}
}
private static void createAndShowGui() {
MyGrid mainPanel = new MyGrid();
JFrame frame = new JFrame("MyGrid");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
Please have a look at the Swing Timer Tutorial as well.
Edit 2
You ask:
but what is the reason of failure of this program,is it the useless delay function?
Your delay method does nothing but busy calculations on the Swing event thread:
public void delay(int a) {
for (int i = 0; i < 90000000; ++i) {
for (int j = 0; j < a; ++j) {
}
}
}
It's little different from a crude attempt at calling Thread.sleep(...), and is crude because you can't explicitly control how long it will run as you can with thread sleep. Again, the problem is that you're making these calls on the Swing event dispatch thread or EDT, the single thread that is responsible for all Swing drawing and user interactions. Blocking this thread will block your program making it non-running or frozen.

Dynamically initializing JButtons with unique ImageIcons

This is my first attempt at using a GUI layout in Java. I am trying to create a simple memory card game, where the user flips over two cards, and tries to get a match, and if there's no match they flip back over, if there's a match they stay flipped until you get all the matches. I might have made it hard on myself though in that I made the whole game dynamic to the constructor variables that define the number of columns and rows of cards. I thought this was better than hard-coding a certain value in, but now I'm having trouble putting the images in my img folder onto my cards.
I understand that variables of variables are not permitted in Java & this is really hard for me to adapt to as a ColdFusion developer. Can you help me think of ways to accomplish this this the proper way in Java?
Here is a simplified version of my code.
import javax.swing.JFrame;
public class MemoryApp
{
public static void main(String args[])
{
//Creates a new game with 3 columns and 4 rows
final CardGame myGame = new CardGame(3, 4);
javax.swing.SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
createAndShowGUI(myGame.getCols(), myGame.getRows());
}
});
}
private static void createAndShowGUI(int c, int r) {
//Create and set up the window.
Window frame = new Window("GridLayoutDemo", c, r);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Set up the content pane.
frame.addComponentsToPane(frame.getContentPane());
//Display the window.
frame.pack();
frame.setVisible(true);
}
}
Card game class:
public class CardGame
{
private int cols, rows;
public CardGame(int c, int r)
{
cols = c;
rows = r;
}
public int getCols(){
return cols;
}
public int getRows(){
return rows;
}
}
The window with the grid layout:
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.GridLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSeparator;
public class Window extends JFrame
{
private int cols, rows;
GridLayout windowGrid = new GridLayout(1,1);
public Window(String t, int c, int r)
{
super(t);
cols = c;
rows = r;
windowGrid.setColumns(c);
windowGrid.setRows(r);
}
public void addComponentsToPane(final Container pane)
{
final JPanel compsToExperiment = new JPanel();
compsToExperiment.setLayout(windowGrid);
JPanel controls = new JPanel();
controls.setLayout(new GridLayout(cols,rows));
int countCard = (cols * rows) / 2;
/**
* Add buttons to experiment with Grid Layout.
* This is where I'd like to be able to loop through
* countCard and create the required number of buttons
* as well as put images on the buttons like so:
*
* ImageIcon image1 = new ImageIcon(getClass().getResource("card1.png"));
* through
* ImageIcon image1 = new ImageIcon(getClass().getResource("card" & cardCount & ".png"));
*
* Below is how I would attempt this in ColdFusion- I know
* the variable of variables syntax is invalid, it is just
* to show you what I mean.
*/
// for(int i = 0; i < countCard; i++;)
// {
// compsToExperiment.add(new JButton("../img/card" & i & ".png"));
// ImageIcon variables["image" & i] = new ImageIcon(getClass().getResource("card" & i & ".png"));
// imageButton.setIcon(variables["image" & i]);
// etc. with ButtonClickEventHandler, haven't gotten that far yet
// }
pane.add(compsToExperiment, BorderLayout.NORTH);
pane.add(new JSeparator(), BorderLayout.CENTER);
}
}
Based on the code commented-out code that you posted so far, one approach could be like this:
public class Window extends JFrame
{
...
// A java.util.List that stores all the buttons, so
// that their icons may be changed later
private List<JButton> buttons = new ArrayList<JButton>();
// A java.util.List that stores all the ImageIcons that
// may be placed on the buttons
private List<ImageIcon> imageIcons = new ArrayList<ImageIcon>();
public void addComponentsToPane(final Container pane)
{
...
for(int i = 0; i < countCard; i++;)
{
// String concatenation is done with "+" in Java, not with "&"
String fileName = "card" + i + ".png";
// Create the icon and the button containing the icon
ImageIcon imageIcon = new ImageIcon(getClass().getResource(fileName));
JButton button = new JButton(imageIcon);
// Add the button to the main panel
compsToExperiment.add(button);
// Store the button and the icon in the lists
// for later retrieval
imageIcons.add(imageIcon);
buttons.add(button);
// Attach an ActionListener to the button that will
// be informed when the button was clicked.
button.addActionListener(createActionListener(i));
}
}
private ActionListener createActionListener(final int cardIndex)
{
return new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
clickedCardButton(cardIndex);
}
};
}
private void clickedCardButton(int cardIndex)
{
System.out.println("Pressed button for card "+cardIndex);
// Here you can change the icons of the buttons or so...
JButton button = buttons.get(cardIndex);
ImageIcon imageIcon = imageIcons.get(cardIndex);
....
}
You mentioned that this is your first attempt to build a GUI with Java. So I assume that this is only intended for "practicing". If your intention was to build a "real application", you should rather consider some Model-View-Controller (MVC) approach for this.

Categories

Resources