I am gonna re-post this questione again trying to be more precise and hoping I will get some help because this is driving me crazy. I am developing a board game with up to 6 player, each one with a different colored pawn. I have the following image that is loaded in BufferedImage arrays treating it as a sprite:
and this is the relative code, putting each face of each colored die in a position in the BufferedImage[]:
private BufferedImage[] initAnimationBuffer() {
BufferedImage[] result = new BufferedImage[36];
for (int i = 0; i < 6; i++) {
for (int j = i; j < 6 + i; j++)
result[i + j] = DieSprite.getSprite(j, i, 0);
}
return result;
}
Then each player, according to his color, wil have also the following matrix containing the faces of his color according to the obtained die value/position. In other words this matrix contains "a line" of the image and it is indexed by value:
private BufferedImage[][] initExactDieFaces() {
BufferedImage[][] result = new BufferedImage[6][1];
int row = -1;
String myColor = this.coreGame.getMyPartecipant().getColor();
if (myColor.equals(Constants.COLOR[0])) {
row = 0;
} else if (myColor.equals(Constants.COLOR[1])) {
row = 2;
} else if (myColor.equals(Constants.COLOR[2])) {
row = 4;
} else if (myColor.equals(Constants.COLOR[3])) {
row = 1;
} else if (myColor.equals(Constants.COLOR[4])) {
row = 5;
} else if (myColor.equals(Constants.COLOR[5])) {
row = 3;
}
int offset = 0;
for (int i = 0; i < 6; i++) {
result[i][0] = DieSprite.getSprite(row, i, offset);
offset += 2;
}
return result;
}
What I want is the following:
-when the "flip die" button is pressed, I want that (for example) 20 random die faces are shown (they should be taken from the first array, AnimationBuffer) in a specific JLabel inside a JPanel
-as soon as the previous animation has finished, I want that the obtained result of the launch of the die is shown (according to the color pawn, taken from ExcatDieFaces).
To get this I know that I need Swing Timer but I am not able to put it all together; here's some code of the startAnimationDie method which is called when the "flip die" button is pressed:
private void startAnimationDie(final JPanel dieContainer) {
final BufferedImage[] animationBuffer = initAnimationBuffer();
final BufferedImage[][] exactDieFaces = initExactDieFaces();
final AnimationSprite animation = new AnimationSprite(
animationBuffer, Constants.DIE_ANIMATION_SPEED);
/* getting launch value fromt the core Game */
int launchResult = coreGame.launchDie();
coreGame.getMyPartecipant().setLastLaunch(launchResult);
final Timer timer = new Timer(250, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
dieContainer.removeAll();
dieContainer.updateUI();
animation.start();
JLabel resultDie = new JLabel();
resultDie.setBounds(60, 265, Constants.DIE_SIZE,Constants.DIE_SIZE);
resultDie.setIcon(new ImageIcon(animationBuffer[new Random().nextInt(36)]));
dieContainer.add(resultDie);
dieContainer.updateUI();
updateUI();
repaint();
}
});
/* animation begins, rolling faces are shown each time the Timer ends*/
for(int i = 0; i<20; i++)
timer.start()
/* showing the final face according to the pawn color and the obtained result from the launch */
dieContainer.removeAll();
dieContainer.updateUI();
AnimationSprite resultAnimation = new AnimationSprite(exactDieFaces[launchResult - 1], 6);
resultAnimation.start();
resultAnimation.update();
resultDie.setIcon(new ImageIcon(exactDieFaces[launchResult - 1][0]));
resultDie.setBounds(60, 265, Constants.DIE_SIZE, Constants.DIE_SIZE);
dieContainer.add(resultDie);
dieContainer.updateUI();
dieContainer.repaint();
}
How can I make it work? I think I am supposed to use Swing.invokeAndWait but I cannot put together all the pieces...Can you help please?
Don't call updateUI, unless you're dealing with installing a look and feel, it's not doing what you think it is (and it's very inefficient)
Don't rebuild the UI each time, this is time consuming work, which is going to make the animation look stilled and staggered and probably flash a lot. Instead, simply update the icon property of the label
Use a single Timer, allow it to increment a counter, so you know how many times it's been called and update the die roll and counter on each tick.
Think of the Timer as a kind of loop, where on each iteration (tick), you need to do something (like increment the counter)
(Note- When it looks like the die has "stalled", it's because the image is been displayed more then once in sequence. You could over come this by placing all the images into a List and using Collections.shuffle. Do this three times, adding the result to another List should give you 24, no-repeating sequence (ok, it "might" repeat on the boundaries, but it's better then using Math.random ;))
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private BufferedImage[] dice = new BufferedImage[6];
private JLabel die;
public TestPane() {
try {
BufferedImage img = ImageIO.read(new File("/Users/swhitehead/Documents/Die.png"));
int width = 377 / 6;
for (int index = 0; index < 6; index++) {
dice[index] = img.getSubimage(width * index, 0, width, width);
}
} catch (IOException ex) {
ex.printStackTrace();
}
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
die = new JLabel(new ImageIcon(dice[0]));
add(die, gbc);
JButton roll = new JButton("Roll");
add(roll, gbc);
roll.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
roll.setEnabled(false);
Timer timer = new Timer(250, new ActionListener() {
private int counter;
private int lastRoll;
#Override
public void actionPerformed(ActionEvent e) {
if (counter < 20) {
counter++;
lastRoll = (int)(Math.random() * 6);
System.out.println(counter + "/" + lastRoll);
die.setIcon(new ImageIcon(dice[lastRoll]));
} else {
lastDieRollWas(lastRoll);
((Timer)e.getSource()).stop();
roll.setEnabled(true);
}
}
});
timer.start();
}
});
}
protected void lastDieRollWas(int roll) {
System.out.println("You rolled " + (roll + 1));
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
}
Related
My project uses Java Swing as a GUI. I am making a Towers of Hanoi game. I've just about got the GUI all working, but my solve command wont work properly.
Without threading calls, it immediately solves the towers as expected. I added a couple Thread.waits expected it to solve it step by step so the user can see how it does but instead, it waits some time, then solves the entire puzzle at once. I'm thinking it might not be repainting, but I'm not sure why. Does anyone know what is going on?
Heres the code for the solve:
public class Solver {
public Solver() {
// nothing
}
public void solve(
int numberBlocks,
int startPin,
int auxiliaryPin,
int endPin) {
if (numberBlocks == 1) {
movePin(startPin, endPin);
try {
Thread.sleep(200);
}
catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
else {
solve(numberBlocks - 1, startPin, endPin, auxiliaryPin);
movePin(startPin, endPin);
try {
Thread.sleep(200);
}
catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
solve(numberBlocks - 1, auxiliaryPin, startPin, endPin);
}
}
private void movePin(int startPin, int endPin) {
TowersOfHanoiGame.moveTopBlock(startPin, endPin);
}
Here is the code from the GUI that does the work:
I know its terribly written, this is my first time writing with Java Swing, Im learning it as I go. If anyone has any pointers on how to better structure this, I'd love to hear about that also.
I'm pasting the whole class, but the important methods are initListeners, and moveTopBlock, and the methods they call.
import java.awt.BorderLayout;
import java.awt.Button;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
public class TowersOfHanoiGame {
private static JFrame mainWindow;
private static JPanel mainContentPanel;
private static JPanel content;
private static ArrayList<Block> pegOneBlocks = new ArrayList<Block>();
private static ArrayList<Block> pegTwoBlocks = new ArrayList<Block>();
private static ArrayList<Block> pegThreeBlocks = new ArrayList<Block>();
private Color[] randomColors = new Color[8];
private Dimension menuSize = new Dimension(100, 100);
private static final int DISCSTEXTSIZE = 20;
private static final int MOVESTEXTSIZE = 30;
private ActionListener downButtonListener;
private ActionListener upButtonListener;
private ActionListener solveButtonListener;
private JLabel discs;
private JLabel moves;
private int discsNumber = 3;
private int movesNumber = 0;
private Solver solver = new Solver();
public TowersOfHanoiGame() {
// do nothing
initRandomColors();
initBlocks(3);
}
/**
* Initialize and display the game
*/
public void display() {
initListeners();
initWindow();
mainWindow.setVisible(true);
}
private void initListeners() {
downButtonListener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
if (discsNumber > 3) {
discsNumber--;
updateLabels();
// clearContentFrame();
clearBlockArrays();
initBlocks(discsNumber);
reDrawContentFrame();
}
}
};
upButtonListener = new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
if (discsNumber < 8) {
discsNumber++;
updateLabels();
// clearContentFrame();
clearBlockArrays();
initBlocks(discsNumber);
reDrawContentFrame();
}
}
};
solveButtonListener = new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
solver.solve(discsNumber, 0, 1, 2);
}
};
}
private void updateLabels() {
discs.setText("DISCS: " + discsNumber);
moves.setText("MOVES: " + movesNumber);
}
/**
* Init the main window
*/
private void initWindow() {
mainWindow = new JFrame("Towers Of Hanoi");
initContentPanel();
mainWindow.setContentPane(mainContentPanel);
mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainWindow.setSize(1000, 1000);
mainWindow.setResizable(false);
mainWindow.getContentPane().setBackground(Color.WHITE);
}
/**
* Init the main content panel
*/
private void initContentPanel() {
mainContentPanel = new JPanel(new BorderLayout(50, 50));
JPanel menu = initMenuFrame();
content = initContentFrame();
mainContentPanel.add(menu, BorderLayout.PAGE_START);
mainContentPanel.add(content, BorderLayout.CENTER);
}
private static JPanel initContentFrame() {
JPanel ret = new JPanel(new BorderLayout());
JPanel pegs = new JPanel(new BorderLayout());
pegs.setBackground(Color.WHITE);
ret.setBackground(Color.WHITE);
Peg peg1 = new Peg(25, 500, 1.2);
Peg peg2 = new Peg(50, 500, 1.2);
Peg peg3 = new Peg(0, 500, 1.2);
peg1.addBlocks(pegOneBlocks);
peg2.addBlocks(pegTwoBlocks);
peg3.addBlocks(pegThreeBlocks);
pegs.add(peg1, BorderLayout.LINE_START);
pegs.add(peg2, BorderLayout.CENTER);
pegs.add(peg3, BorderLayout.LINE_END);
ret.add(pegs, BorderLayout.CENTER);
return ret;
}
private Color randomColor() {
int R = (int)(Math.random() * 256);
int G = (int)(Math.random() * 256);
int B = (int)(Math.random() * 256);
Color color = new Color(R, G, B); // random color, but can be bright or
// dull
// to get rainbow, pastel colors
Random random = new Random();
final float hue = random.nextFloat();
final float saturation = 0.9f;// 1.0 for brilliant, 0.0 for dull
final float luminance = 1.0f; // 1.0 for brighter, 0.0 for black
color = Color.getHSBColor(hue, saturation, luminance);
return color;
}
private void initRandomColors() {
for (int i = 0; i < 8; i++) {
randomColors[i] = randomColor();
}
}
private void initBlocks(int numBlocks) {
int startWidth = Block.LONGESTWIDTH;
for (int i = 0; i < numBlocks; i++) {
Block b = new Block((startWidth - (i * 15)), randomColors[i]);
pegOneBlocks.add(b);
}
}
private static void clearContentFrame() {
mainContentPanel.remove(content);
mainContentPanel.repaint();
}
private void clearBlockArrays() {
pegOneBlocks.clear();
pegTwoBlocks.clear();
pegThreeBlocks.clear();
}
public static void reDrawContentFrame() {
content = initContentFrame();
mainContentPanel.add(content, BorderLayout.CENTER);
mainContentPanel.repaint();
}
public static void moveTopBlock(int startPin, int destinationPin) {
Block b = null;
if (startPin == 0) {
b = pegOneBlocks.get(pegOneBlocks.size() - 1);
pegOneBlocks.remove(pegOneBlocks.size() - 1);
}
else if (startPin == 1) {
b = pegTwoBlocks.get(pegTwoBlocks.size() - 1);
pegTwoBlocks.remove(pegTwoBlocks.size() - 1);
}
else if (startPin == 2) {
b = pegThreeBlocks.get(pegThreeBlocks.size() - 1);
pegThreeBlocks.remove(pegThreeBlocks.size() - 1);
}
if (destinationPin == 0) {
pegOneBlocks.add(b);
}
else if (destinationPin == 1) {
pegTwoBlocks.add(b);
}
else if (destinationPin == 2) {
pegThreeBlocks.add(b);
}
reDrawContentFrame();
content.validate();
mainContentPanel.validate();
mainWindow.validate();
}
/**
* Build the menu panel
*
* #return menu panel
*/
private JPanel initMenuFrame() {
JPanel ret = new JPanel(new BorderLayout());
ret.setPreferredSize(menuSize);
// left
JPanel left = new JPanel(new FlowLayout());
left.setPreferredSize(menuSize);
JLabel label = new JLabel("DISCS: 3");
discs = label;
label.setFont(new Font("Serif", Font.BOLD, DISCSTEXTSIZE));
Button down = new Button("Decrease");
down.addActionListener(downButtonListener);
Button up = new Button("Increase");
up.addActionListener(upButtonListener);
left.add(label);
left.add(up);
left.add(down);
// mid
moves = new JLabel("MOVES: 0");
moves.setHorizontalAlignment(JLabel.CENTER);
moves.setFont(new Font("Serif", Font.BOLD, MOVESTEXTSIZE));
// right
JPanel right = new JPanel(new FlowLayout());
Button solve = new Button("Solve");
solve.addActionListener(solveButtonListener);
Button reset = new Button("Reset");
right.add(solve);
right.add(reset);
// sync
JPanel menu = new JPanel(new BorderLayout());
menu.add(left, BorderLayout.LINE_START);
menu.add(moves, BorderLayout.CENTER);
menu.add(right, BorderLayout.LINE_END);
ret.add(menu, BorderLayout.CENTER);
return ret;
}
}
solveButtonListener = new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
solver.solve(discsNumber, 0, 1, 2);
}
};
The problem is that code invoked for any listener is executed on the Event Dispatch Thread (EDT). The EDT is responsible for responding to event and repaint the GUI. The Thread.sleep() method causes the EDT to sleep and as a result the GUI can't repaint itself until all the code has finished executing.
What you need to do is start a separate Thread when you invoke the solver.solve(...) method.
Read the section from the Swing tutorial on Concurrency for more information.
Note, the above suggestion to use a separate Thread is still not a proper solution. Swing was designed to be single Thread, which means that all updates to the state of your GUI and the repainting of the GUI should be done on the EDT. So this would mean you should also be using SwingUtilities.invokeLater() to add code to the EDT for processing. I have never tried doing this when using recursion, so I'm not sure the best way to do this.
First Time three random images shown on Jframe from three diffrent arrays.
even MouseClicked Method triggered but images does not refresh in Frame.
I want to refresh three random images each time i click on Frame.
Please help
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.Random;
import javax.swing.*;
public class Cards extends JFrame implements MouseListener {
public static void main(String[] args) {
JFrame frame = new Cards();
frame.setTitle("Cards");
frame.setSize(500, 500);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
new Cards();
}
public Cards() {
this.getContentPane().addMouseListener(this);
cards1();
cards2();
cards3();
}
public void cards1() {
ImageIcon[] images = new ImageIcon[10];
for (int i = 1; i < images.length; i++) {
images[i] = new ImageIcon("Drawables//Images//" + i + ".png");
}
int[] threeRandoms = new int[1];
Random ran = new Random();
for (int i = 0; i < threeRandoms.length; i++) {
threeRandoms[i] = ran.nextInt(10);
}
setLayout(new GridLayout(1, 4, 5, 5));
add(new JLabel(images[threeRandoms[0]]));
}
public void cards2() {
ImageIcon[] images = new ImageIcon[10];
for (int i = 1; i < images.length; i++) {
images[i] = new ImageIcon("Drawables//Images1//" + i + ".png");
}
int[] threeRandoms = new int[1];
Random ran = new Random();
for (int i = 0; i < threeRandoms.length; i++) {
threeRandoms[i] = ran.nextInt(10);
}
setLayout(new GridLayout(1, 4, 5, 5));
add(new JLabel(images[threeRandoms[0]]));
}
public void cards3() {
// this.getContentPane().addMouseListener(this);
ImageIcon[] images = new ImageIcon[10];
for (int i = 1; i < images.length; i++) {
images[i] = new ImageIcon("Drawables//Images2//" + i + ".png");
}
int[] threeRandoms = new int[1];
Random ran = new Random();
for (int i = 0; i < threeRandoms.length; i++) {
threeRandoms[i] = ran.nextInt(10);
}
// Labels with gridLayout
setLayout(new GridLayout(1, 4, 5, 5));
add(new JLabel(images[threeRandoms[0]]));
}
public void mouseClicked(MouseEvent e) {
System.out.println("The frame was clicked.");
new Cards();
}
public void mouseEntered(MouseEvent e) {
System.out.println("The mouse entered the frame.");
}
public void mouseExited(MouseEvent e) {
System.out.println("The mouse exited the frame.");
}
public void mousePressed(MouseEvent e) {
System.out.println("The left mouse button was pressed.");
}
public void mouseReleased(MouseEvent e) {
System.out.println("The left mouse button was released.");
}
}
I'm sorry, but I'm confused by your code. For one thing your cards1(), cards2() and cards3() methods look to be all the very same, and if so, why 3 different methods? Why not just one method? In those methods you appear to be trying to add JLabels repeatedly. Are you trying to add many many JLabels to the GUI? Or are you simply trying to display 3 images that change randomly on mouse action?
I would recommend structuring things a bit differently:
If possible, read all necessary images in once in your class's constructor, put the images into ImageIcons and then add them to an ArrayList or several ArrayLists if need be.
Don't add new JLabels each time a mouseClick occurs.
Create a JPanel give it a GridLayout and in your class constructor add to it three JLabels that are either instance fields or in an array or ArrayList.
Add this JPanel to your JFrame.
Add a MouseListener to each JLabel
in that MouseListener's mousePressed(MouseEvent e) method (not mouseClicked) randomize your number and use that number to call setIcon(...) on the JLabel source, obtained by calling getSource() on your MouseEvent parameter.
For example:
import java.awt.GridLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.*;
#SuppressWarnings("serial")
public class RandomImages extends JPanel {
private static final int LABEL_COUNT = 3;
private Random random = new Random();
public RandomImages() {
setLayout(new GridLayout(1, 3));
for (int i = 0; i < LABEL_COUNT; i++) {
final List<Icon> iconList = new ArrayList<>();
// TODO: get images for the ith list
// and fill iconList with ImageIcons from the first grouping
// create JLabel and give it the first Icon from the List above
final JLabel label = new JLabel(iconList.get(0));
label.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
// get random number from random object using iconList.size()
// get random Icon from list
// set label's icon via setIcon(...)
}
});
// add to GUI
add(label);
}
}
private static void createAndShowGui() {
JFrame frame = new JFrame("RandomImages");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new RandomImages());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
Concrete example 2:
import java.awt.GridLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.imageio.ImageIO;
import javax.swing.*;
#SuppressWarnings("serial")
public class RandomChessMen extends JPanel {
// for this example I get a sprite sheet that holds several sprite images in it
// the images can be found here: http://stackoverflow.com/questions/19209650
private static final String IMAGE_PATH = "http://i.stack.imgur.com/memI0.png";
private static final int LABEL_COUNT = 2;
private static final int ICON_COLUMNS = 6;
private Random random = new Random();
public RandomChessMen() throws IOException {
URL url = new URL(IMAGE_PATH);
BufferedImage largeImg = ImageIO.read(url);
setLayout(new GridLayout(1, 0));
// break down large image into its constituent sprites and place into ArrayList<Icon>
int w = largeImg.getWidth() / ICON_COLUMNS;
int h = largeImg.getHeight() / LABEL_COUNT;
for (int i = 0; i < LABEL_COUNT; i++) {
final List<Icon> iconList = new ArrayList<>();
int y = (i * largeImg.getHeight()) / LABEL_COUNT;
// get 6 icons out of large image
for (int j = 0; j < ICON_COLUMNS; j++) {
int x = (j * largeImg.getWidth()) / ICON_COLUMNS;
// get subImage
BufferedImage subImg = largeImg.getSubimage(x, y, w, h);
// create ImageIcon and add to list
iconList.add(new ImageIcon(subImg));
}
// create JLabel
final JLabel label = new JLabel("", SwingConstants.CENTER);
int eb = 40;
label.setBorder(BorderFactory.createEmptyBorder(eb, eb, eb, eb));
// get random index for iconList
int randomIndex = random.nextInt(iconList.size());
Icon icon = iconList.get(randomIndex); // use index to get random Icon
label.setIcon(icon); // set label's icon
label.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
Icon secondIcon = label.getIcon();
// so we don't repeat icons
while (label.getIcon() == secondIcon) {
int randomIndex = random.nextInt(iconList.size());
secondIcon = iconList.get(randomIndex);
}
label.setIcon(secondIcon);
}
});
// add to GUI
add(label);
}
}
private static void createAndShowGui() {
JFrame frame = new JFrame("RandomImages");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
try {
frame.getContentPane().add(new RandomChessMen());
} catch (IOException e) {
e.printStackTrace();
System.exit(-1);
}
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
I have made these changes to your code:
Instead of having three methods cards1() cards2() cards3(), i have just made one cards() method.
Everytime you click on the frame, three random images get loaded.
I have set every image inside a JLabel in order to make it easy to update it.
The code below works perfectly according to your needs.
package example;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.Random;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
public class Cards extends JFrame implements MouseListener {
JPanel subPanel1;
JLabel label1, label2, label3;
static ImageIcon[] images ;
static Random ran ;
static int[] threeRandoms;
public Cards() {
super("Cards");
subPanel1 = new JPanel();
// Set up first subpanel
subPanel1.setPreferredSize (new Dimension(400, 400));
//subPanel1.setBackground (Color.cyan);
label1 = new JLabel ("image 1",SwingConstants.CENTER);
label2 = new JLabel ("image 2", SwingConstants.LEFT);
label3 = new JLabel ("image 3", SwingConstants.CENTER);
subPanel1.add (label1);
subPanel1.add (label2);
subPanel1.add (label3);
add(subPanel1);
addMouseListener(this);
setSize(500, 500);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack();
setLocationRelativeTo(null);
setVisible(true);
System.out.println("Success.....");
}
public void cards() {
for (int i = 0; i < threeRandoms.length; i++)
threeRandoms[i] = ran.nextInt(3);
label1.setIcon(images[threeRandoms[0]]);
label2.setIcon(images[threeRandoms[1]]);
label3.setIcon(images[threeRandoms[2]]);
}
#Override
public void mouseClicked(MouseEvent e) {
System.out.println("mouseClicked");
cards();
}
#Override
public void mouseEntered(MouseEvent e) {
System.out.println("mouseEntered");
}
#Override
public void mouseExited(MouseEvent e) {
System.out.println("mouseExited");
}
#Override
public void mousePressed(MouseEvent e) {
System.out.println("mousePressed");
}
#Override
public void mouseReleased(MouseEvent e) {
System.out.println("mouseReleased");
}
public static void loadImages(){
images = new ImageIcon[4];
ran = new Random();
threeRandoms = new int[3];
for (int i = 1; i <= images.length; i++) {
images[i-1] = new ImageIcon("Drawables//Images//" + i + ".png");
}
}
public static void main(String[] args) {
loadImages();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new Cards();
}
});
}
}
Just want the color of the letters to change with a little pauses (pause may vary as per the time given for a word and length of the word).
The following code works fine for me.But I think I have created a mess with my logic.I can understand, but it should be easy for my team mates to understand.
Code:
import java.awt.Color;
import java.lang.reflect.InvocationTargetException;
import java.awt.Toolkit;
import javax.swing.JPanel;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.text.DefaultStyledDocument;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.Timer;
public class Reminder
{
static JFrame frame;
Toolkit toolkit;
Timer timer;
int point=0,temp=0,hil=0,point2=0;long time1=0,time2=0;
static StyledDocument doc;
static JTextPane textpane;
String[] arr={"Tes"," hiiii"," what"," happpn"};
int i=0;
int[] a=new int[5];
public Reminder()
{
a[0]=1000;
a[1]=900;
a[2]=300;
a[3]=1500;
a[4]=1700;
ActionListener actionListener = new ActionListener()
{
public void actionPerformed(ActionEvent actionEvent)
{
point =arr[i].length();
temp=point+1;
time1=System.currentTimeMillis();
new Thread(new t1()).start();
}
};
timer = new Timer(a[i], actionListener);
timer.setInitialDelay(0);
timer.start();
}
public class t1 implements Runnable
{ /* true idea to use current time is beacuse i want to check and make
sure that the time taken since the timer started, and the present time should
not exceed the time given in the array in any case*/
public void run()
{
try
{
time2=System.currentTimeMillis();
while(time2-time1<=a[i]-200){Thread.sleep((long) (a[i] / (arr[i].length() * 4)));
if(hil<=temp-1)
{
doc.setCharacterAttributes(point2,hil, textpane.getStyle("Red"), true);}
hil++;
time2=System.currentTimeMillis();
}
doc.setCharacterAttributes(point2,point+1, textpane.getStyle("Red"), true);
point2+=point;hil=0;i++;
timer.setDelay(a[i]);
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
public static void newcompo()
{
JPanel panel = new JPanel();
doc = (StyledDocument) new DefaultStyledDocument();
textpane = new JTextPane(doc);
textpane.setText("Test hiiii what happpn");
javax.swing.text.Style style = textpane.addStyle("Red", null);
StyleConstants.setForeground(style, Color.RED);
panel.add(textpane);
frame.add(panel);
frame.pack();
}
public static void main(String args[]) throws InterruptedException
, InvocationTargetException
{
SwingUtilities.invokeAndWait(new Runnable()
{
#Override
public void run()
{
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
newcompo();
Reminder aa= new Reminder();
}
});
}
}
Any suggestions?How can I simplify?
UPDATE FOR ERROR
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
public class KaraokeTest {
private int[] timingsArray = {1000, 900, 300, 1500};//word/letters timings
private String[] individualWordsToHighlight = {"Tes", " hiiii", " what", " happpn"};//each individual word/letters to highlight
private int count = 0;
private final JTextPane jtp = new JTextPane();
private final JButton startButton = new JButton("Start");
private final JFrame frame = new JFrame();
public KaraokeTest() {
initComponents();
}
private void initComponents() {
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
for (String s : individualWordsToHighlight) {
String tmp = jtp.getText();
jtp.setText(tmp + s);
}
jtp.setEditable(false);
startButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
startButton.setEnabled(false);
count = 0;
//create Arrays of individual letters and their timings
final ArrayList<String> chars = new ArrayList<>();
final ArrayList<Integer> charsTiming = new ArrayList<>();
for (String s : individualWordsToHighlight) {
for (int i = 0; i < s.length(); i++) {
chars.add(String.valueOf(s.charAt(i)));
System.out.println(String.valueOf(s.charAt(i)));
}
}
for (int x = 0; x < timingsArray.length; x++) {
for (int i = 0; i < individualWordsToHighlight[x].length(); i++) {
charsTiming.add(timingsArray[x] / individualWordsToHighlight[x].length());
System.out.println(timingsArray[x] / individualWordsToHighlight[x].length());
}
}
new Timer(1, new AbstractAction() {
#Override
public void actionPerformed(ActionEvent ae) {
if (count < charsTiming.size()) {
highlightNextWord();
//restart timer with new timings
((Timer) ae.getSource()).setInitialDelay(charsTiming.get(count));
((Timer) ae.getSource()).restart();
} else {//we are at the end of the array
reset();
((Timer) ae.getSource()).stop();//stop the timer
}
count++;//increment counter
}
}).start();
}
});
frame.add(jtp, BorderLayout.CENTER);
frame.add(startButton, BorderLayout.SOUTH);
frame.pack();
frame.setVisible(true);
}
private void reset() {
startButton.setEnabled(true);
jtp.setText("");
for (String s : individualWordsToHighlight) {
String tmp = jtp.getText();
jtp.setText(tmp + s);
}
JOptionPane.showMessageDialog(frame, "Done");
}
private void highlightNextWord() {
//we still have words to highlight
int sp = 0;
for (int i = 0; i < count + 1; i++) {//get count for number of letters in words (we add 1 because counter is only incrementd after this method is called)
sp += 1;
}
//highlight words
Style style = jtp.addStyle("RED", null);
StyleConstants.setForeground(style, Color.RED);
((StyledDocument) jtp.getDocument()).setCharacterAttributes(0, sp, style, true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new KaraokeTest();
}
});
}
}
Gives me Exception:
Exception in thread "AWT-EventQueue-0" java.lang.RuntimeException: Uncompilable source code - illegal start of type
at KaraokeTest$1.actionPerformed(KaraokeTest.java:47)
at javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:1995)
at javax.swing.AbstractButton$Handler.actionPerformed(AbstractButton.java:2318)
at javax.swing.DefaultButtonModel.fireActionPerformed(DefaultButtonModel.java:387)
at javax.swing.DefaultButtonModel.setPressed(DefaultButtonModel.java:242)
at javax.swing.plaf.basic.BasicButtonListener.mouseReleased(BasicButtonListener.java:236)
at java.awt.Component.processMouseEvent(Component.java:6263)
at javax.swing.JComponent.processMouseEvent(JComponent.java:3267)
at java.awt.Component.processEvent(Component.java:6028)
at java.awt.Container.processEvent(Container.java:2041)
at java.awt.Component.dispatchEventImpl(Component.java:4630)
at java.awt.Container.dispatchEventImpl(Container.java:2099)
at java.awt.Component.dispatchEvent(Component.java:4460)
at java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4574)
at java.awt.LightweightDispatcher.processMouseEvent(Container.java:4238)
at java.awt.LightweightDispatcher.dispatchEvent(Container.java:4168)
at java.awt.Container.dispatchEventImpl(Container.java:2085)
at java.awt.Window.dispatchEventImpl(Window.java:2475)
at java.awt.Component.dispatchEvent(Component.java:4460)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:599)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:269)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:184)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:174)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:169)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:161)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:122)
OK, here is a cleaned up version of your code which should approximatively perform the same thing:
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultStyledDocument;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
public class Reminder {
private static final String TEXT = "Test hiiii what happpn";
private static final String[] WORDS = TEXT.split(" ");
private JFrame frame;
private Timer timer;
private StyledDocument doc;
private JTextPane textpane;
private List<Integer> times = Arrays.asList(1000, 900, 300, 1500);
private int stringIndex = 0;
private int index = 0;
public void startColoring() {
ActionListener actionListener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent actionEvent) {
doc.setCharacterAttributes(stringIndex, 1, textpane.getStyle("Red"), true);
stringIndex++;
try {
if (stringIndex >= doc.getLength() || doc.getText(stringIndex, 1).equals(" ")) {
index++;
}
if (index < times.size()) {
double delay = times.get(index).doubleValue();
timer.setDelay((int) (delay / WORDS[index].length()));
} else {
timer.stop();
System.err.println("Timer stopped");
}
} catch (BadLocationException e) {
e.printStackTrace();
}
}
};
timer = new Timer(times.get(index), actionListener);
timer.setInitialDelay(0);
timer.start();
}
public void initUI() {
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel();
doc = new DefaultStyledDocument();
textpane = new JTextPane(doc);
textpane.setText(TEXT);
javax.swing.text.Style style = textpane.addStyle("Red", null);
StyleConstants.setForeground(style, Color.RED);
panel.add(textpane);
frame.add(panel);
frame.pack();
frame.setVisible(true);
}
public static void main(String args[]) throws InterruptedException, InvocationTargetException {
SwingUtilities.invokeAndWait(new Runnable() {
#Override
public void run() {
Reminder reminder = new Reminder();
reminder.initUI();
reminder.startColoring();
}
});
}
}
A few tricks to help others read and understand your code:
Use coherent and appropriate indentation (I personnally try to stick to default Sun Java conventions)
Follow the Java coding conventions (constants are in upper-case, class name starts with an upper-case, variables and methods start with a lower case, use camel case)
Use meaningful variable and method names
class member should be declared one by one (don't use int i, j, k; )
Use a single instruction per line (avoid stuffs like if(something) doSomething(); else {doSomethingElse1(); doSomethingElse2();} on a single line)
Avoid unnecessary usage of the static keyword (to the exception of constants)
Try to avoid coupling your code so much (try to make the minimum assumptions on how the rest of the code performs)
Add javadoc and comments in your code, this is always a good practice and it is of great help for you and others.
Your main cause for concern is that you not doing the updates related to JTextPane on the Event Dispatch Thread.
For a situation like this, when you really wanted to update a certain thingy from another Thread, always use EventQueue.invokeLater(...) or EvenQueue.invokeAndWait(), which can asynchronously (former)/synchronously (latter) update your request on the EDT, though care must be taken, as invokeAndWait() might can lead to deadlocks/run conditions if not used in the right sense.
Here is your updated code, that might work for your expectations. Hope you can modify the code, as per your liking.
import javax.swing.*;
import javax.swing.text.DefaultStyledDocument;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
import java.awt.*;
import java.lang.reflect.InvocationTargetException;
/**
* Created with IntelliJ IDEA.
* User: Gagandeep Bali
* Date: 1/12/13
* Time: 5:55 PM
* To change this template use File | Settings | File Templates.
*/
public class ColouringText
{
private StyledDocument document;
private JTextPane textPane;
private String message;
private String[] parts;
private Timer timer;
private int counter;
private int start, end;
private Thread thread = new Thread()
{
#Override
public void run()
{
while (counter < parts.length)
{
final int len = parts[counter++].length();
try
{
EventQueue.invokeAndWait(new Runnable()
{
#Override
public void run()
{
document.setCharacterAttributes(
start, len, textPane.getStyle("RED"), true);
}
});
Thread.sleep(len * 1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
catch (InvocationTargetException e)
{
e.printStackTrace();
}
start += (len + 1);
}
}
};
public ColouringText()
{
document = (StyledDocument) new DefaultStyledDocument();
message = "Hello there... Joey Rohan. Had you ever thought about putting indentations before pasting your code.";
parts = message.split(" ");
counter = 0;
start = 0;
end = 6;
System.out.println("Message Length : " + message.length());
}
private void displayGUI()
{
JFrame frame = new JFrame("Colouring Text Example");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
JPanel contentPane = new JPanel();
textPane = new JTextPane(document);
textPane.setText(message);
Style style = textPane.addStyle("RED", null);
StyleConstants.setForeground(style, Color.RED);
contentPane.add(textPane);
frame.setContentPane(contentPane);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
thread.start();
}
public static void main(String... args)
{
EventQueue.invokeLater(new Runnable()
{
#Override
public void run()
{
new ColouringText().displayGUI();
}
});
}
}
Having been the one who answered your 2 previous questions on a similar task:
highlighting words in java
highlighting the word in java
and others not agreeing on my close vote, I decided to work on this a bit.
I have taken my latest example and edited to simplify where possible etc to make things more readable for you to understand, which in turn should help you understand how to go about simplifying your own.
I dont understand why you start a new thread to make words highlight slowly. Simply use my other examples and supply single letters rather than multiple letters and their timings to highlight, thus they will be highlighted individually. Or just make a method to do this work for you and iterate through those arrays as the letters and timings.
The below example uses an array of integers which hold the timings for words to be highlighted. It also contains an array for each individual word/letters we would like to highlight:
private int[] timingsArray = {1000, 900, 300, 1500};//word/letters timings
private String[] individualWordsToHighlight = {"Tes", " hiiii", " what", " happpn"};//each individual word/letters to highlight
In our start button we have a method to convert the above to single timings/letters for each individual letter for a letter by letter highlight:
//create Arrays of individual letters and their timings
final ArrayList<String> chars = new ArrayList<>();
final ArrayList<Integer> charsTiming = new ArrayList<>();
for (String s : individualWordsToHighlight) {
for (int i = 0; i < s.length(); i++) {
chars.add(String.valueOf(s.charAt(i)));
System.out.println(String.valueOf(s.charAt(i)));
}
}
for (int x = 0; x < timingsArray.length; x++) {
for (int i = 0; i < individualWordsToHighlight[x].length(); i++) {
charsTiming.add(timingsArray[x] / individualWordsToHighlight[x].length());
System.out.println(timingsArray[x] / individualWordsToHighlight[x].length());
}
}
Next there is a single Timer which will be started when start button is pressed which will highlight words/letters and restart itself with a new delay each time until all words/letters have been highlighted:
new Timer(1, new AbstractAction() {
#Override
public void actionPerformed(ActionEvent ae) {
if (count < charsTiming.size()) {
highlightNextWord();
//restart timer with new timings
((Timer) ae.getSource()).setInitialDelay(charsTiming.get(count));
((Timer) ae.getSource()).restart();
} else {//we are at the end of the array
reset();
((Timer) ae.getSource()).stop();//stop the timer
}
count++;//increment counter
}
}).start();
Hope this helps.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
public class KaraokeTest {
private int[] timingsArray = {1000, 900, 300, 1500};//word/letters timings
private String[] individualWordsToHighlight = {"Tes", " hiiii", " what", " happpn"};//each individual word/letters to highlight
private int count = 0;
private final JTextPane jtp = new JTextPane();
private final JButton startButton = new JButton("Start");
private final JFrame frame = new JFrame();
public KaraokeTest() {
initComponents();
}
private void initComponents() {
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
for (String s : individualWordsToHighlight) {
String tmp = jtp.getText();
jtp.setText(tmp + s);
}
jtp.setEditable(false);
startButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
startButton.setEnabled(false);
count = 0;
//create Arrays of individual letters and their timings
final ArrayList<String> chars = new ArrayList<>();
final ArrayList<Integer> charsTiming = new ArrayList<>();
for (String s : individualWordsToHighlight) {
for (int i = 0; i < s.length(); i++) {
chars.add(String.valueOf(s.charAt(i)));
System.out.println(String.valueOf(s.charAt(i)));
}
}
for (int x = 0; x < timingsArray.length; x++) {
for (int i = 0; i < individualWordsToHighlight[x].length(); i++) {
charsTiming.add(timingsArray[x] / individualWordsToHighlight[x].length());
System.out.println(timingsArray[x] / individualWordsToHighlight[x].length());
}
}
new Timer(1, new AbstractAction() {
#Override
public void actionPerformed(ActionEvent ae) {
if (count < charsTiming.size()) {
highlightNextWord();
//restart timer with new timings
((Timer) ae.getSource()).setInitialDelay(charsTiming.get(count));
((Timer) ae.getSource()).restart();
} else {//we are at the end of the array
reset();
((Timer) ae.getSource()).stop();//stop the timer
}
count++;//increment counter
}
}).start();
}
});
frame.add(jtp, BorderLayout.CENTER);
frame.add(startButton, BorderLayout.SOUTH);
frame.pack();
frame.setVisible(true);
}
private void reset() {
startButton.setEnabled(true);
jtp.setText("");
for (String s : individualWordsToHighlight) {
String tmp = jtp.getText();
jtp.setText(tmp + s);
}
JOptionPane.showMessageDialog(frame, "Done");
}
private void highlightNextWord() {
//we still have words to highlight
int sp = 0;
for (int i = 0; i < count + 1; i++) {//get count for number of letters in words (we add 1 because counter is only incrementd after this method is called)
sp += 1;
}
//highlight words
Style style = jtp.addStyle("RED", null);
StyleConstants.setForeground(style, Color.RED);
((StyledDocument) jtp.getDocument()).setCharacterAttributes(0, sp, style, true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new KaraokeTest();
}
});
}
}
UPDATE
In response to your edited question.
Again the code complies fine for me, which brings me to the conclusion our java Runtimes differ and might be causing a problem. Mine is:
java version "1.7.0_10" Java(TM) SE Runtime Environment (build
1.7.0_10-b18) Java HotSpot(TM) 64-Bit Server VM (build 23.6-b04, mixed mode)
UPDATE 2:
As per your comment on your java version:
JDK 6, NetBeans 6.5.1
Have you tried:
final ArrayList<String> chars = new ArrayList<String>();
final ArrayList<Integer> charsTiming = new ArrayList<Integer>();
note i dont use <> anymore as Java 6 does not support the Diamond operator it would have to have the data type included i.e <Integer>.
I'm making a piano application in Java. This is one of the functions,
public void playOnce(int time) {
play();
doClick(time);
stop();
}
public void play() {
channel[0].noteOn(note, 60);
}
public void stop() {
channel[0].noteOff(note);
}
I'll provide a minimal working example if necessary, but I wanted to make sure it's not an obvious issue. The problem is that playOnce is called in a while loop. playOnce is in a Key class, and each Key has a different note. In each iteration of the while loop, playOnce is called on a different key. Once all the keys have been played, it stops.
The doClick method correctly pressed the key, but it's not released until all the keys have been played. In fact, while the keys are being played, you can't do anything, even press the pause button. For this problem, I guess I could put the entire loop in a different thread, but I don't think that type of solution will allow the key to be released.
EDIT: Yea, I figured out I need a new thread to get other actions to work, but I still need a fix for doClick(). This might be more complicated than I thought so here's a working example,
Main.java
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.text.DecimalFormat;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.JTextArea;
import javax.swing.SpinnerNumberModel;
public class Main implements ActionListener {
final int WHITE_KEY_WIDTH, WHITE_KEY_HEIGHT, BLACK_KEY_WIDTH,
BLACK_KEY_HEIGHT;
final int WIDTH;
final JFileChooser fc;
{
WHITE_KEY_WIDTH = Key.WHITE_KEY_WIDTH;
BLACK_KEY_WIDTH = Key.BLACK_KEY_WIDTH;
WHITE_KEY_HEIGHT = Key.WHITE_KEY_HEIGHT;
BLACK_KEY_HEIGHT = Key.BLACK_KEY_HEIGHT;
WIDTH = 3 * (WHITE_KEY_WIDTH * 7) + WHITE_KEY_WIDTH;
fc = new JFileChooser();
}
public static Key keys[] = new Key[48];
private static int index = 0;
private String prevText = "";
JTextArea shabadEditor = null;
JSpinner tempoControl;
JFrame frame;
File curFile;
public static void main(String[] args) {
new Main();
}
public Main() {
frame = new JFrame();
JPanel mainPanel = new JPanel();
JPanel controlPanel = new JPanel();
JLayeredPane pianoPanel = new JLayeredPane();
mainPanel.setLayout(new GridBagLayout());
JButton playButton = new JButton("Play");
JButton pauseButton = new JButton("Pause");
playButton.addActionListener(this);
playButton.setActionCommand("play");
pauseButton.addActionListener(this);
pauseButton.setActionCommand("pause");
SpinnerNumberModel model = new SpinnerNumberModel(1, 0, 2, .1);
tempoControl = new JSpinner(model);
JSpinner.NumberEditor editor = (JSpinner.NumberEditor) tempoControl
.getEditor();
DecimalFormat format = editor.getFormat();
format.setMinimumFractionDigits(1);
Dimension d = tempoControl.getPreferredSize();
d.width = 40;
tempoControl.setPreferredSize(d);
GridBagConstraints c = new GridBagConstraints();
// Construct each top level component
controlPanel.add(playButton);
controlPanel.add(pauseButton);
controlPanel.add(tempoControl);
shabadEditor = new JTextArea(20, 78);
constructKeyboard(pianoPanel);
// Add the piano panel and shabad editor to the window
c.gridx = 0;
c.gridy = 0;
c.weightx = 1.0;
c.anchor = GridBagConstraints.NORTHWEST;
mainPanel.add(controlPanel, c);
c.gridx = 0;
c.gridy = 1;
c.weightx = 1.0;
// c.weighty = 1.0;
c.anchor = GridBagConstraints.NORTHWEST;
pianoPanel
.setPreferredSize(new Dimension(WIDTH - 18, WHITE_KEY_HEIGHT));
mainPanel.add(pianoPanel, c);
c.gridx = 0;
c.gridy = 2;
c.weightx = 1.0;
c.weighty = 1.0;
c.anchor = GridBagConstraints.NORTHWEST;
mainPanel.add(shabadEditor, c);
frame.add(mainPanel);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(WIDTH, WHITE_KEY_HEIGHT * 3 + 30);
frame.setLocation(250, 60);
frame.setVisible(true);
}
void constructKeyboard(Container panel) {
int i = 0;
int j = 0;
for (int k = 0; k < 3; k++) {
addWhiteKey(panel, i++);
addBlackKey(panel, j++);
addWhiteKey(panel, i++);
addBlackKey(panel, j++);
addWhiteKey(panel, i++);
addWhiteKey(panel, i++);
j++;
addBlackKey(panel, j++);
addWhiteKey(panel, i++);
addBlackKey(panel, j++);
addWhiteKey(panel, i++);
addBlackKey(panel, j++);
j++;
addWhiteKey(panel, i++);
}
}
void addWhiteKey(Container panel, int i) {
WhiteKey b = new WhiteKey();
b.setLocation(i++ * WHITE_KEY_WIDTH, 0);
panel.add(b, 0, -1);
keys[index++] = b;
}
void addBlackKey(Container panel, int factor) {
BlackKey b = new BlackKey();
b.setLocation(WHITE_KEY_WIDTH - BLACK_KEY_WIDTH / 2 + factor
* WHITE_KEY_WIDTH, 0);
panel.add(b, 1, -1);
keys[index++] = b;
}
#Override
public void actionPerformed(ActionEvent arg0) {
String action = arg0.getActionCommand();
if (action.equals("play")) {
System.out.println("working");
for (int i = 0; i < 10; i++) {
keys[i].playOnce(500);
}
}
}
}
Key.java
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.sound.midi.MidiChannel;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Synthesizer;
import javax.swing.JButton;
public class Key extends JButton implements MouseListener {
private static final long serialVersionUID = 1L;
public static final int WHITE_KEY_HEIGHT = 200;
public static final int WHITE_KEY_WIDTH = 40;
public static final int BLACK_KEY_WIDTH = 20;
public static final int BLACK_KEY_HEIGHT = 120;
private static int noteCount = 40;
public int note;
private static Synthesizer synth = null;
static {
try {
synth = MidiSystem.getSynthesizer();
synth.open();
} catch (MidiUnavailableException e) {
e.printStackTrace();
}
}
MidiChannel channel[];
public Key() {
note = noteCount++;
// Instrument[] instruments = synth.getAvailableInstruments();
// for (Instrument instrument : instruments) {
// System.out.println(instrument.getName());
// System.out.println(instrument.getPatch().getBank());
// System.out.println(instrument.getPatch().getProgram());
// }
channel = synth.getChannels();
channel[0].programChange(20);
addMouseListener(this);
}
public void playOnce(int time) {
play();
doClick(time);
stop();
}
public void play() {
channel[0].noteOn(note, 60);
}
public void stop() {
channel[0].noteOff(note);
}
#Override
public void mouseClicked(MouseEvent e) {
System.out.println(this.note);
}
#Override
public void mouseEntered(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mouseExited(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mousePressed(MouseEvent e) {
play();
}
#Override
public void mouseReleased(MouseEvent e) {
stop();
}
}
BlackKey.java
import java.awt.Color;
public class BlackKey extends Key {
private static final long serialVersionUID = 1L;
public BlackKey() {
super();
setBackground(Color.BLACK);
setSize(BLACK_KEY_WIDTH, BLACK_KEY_HEIGHT);
}
}
WhiteKey.java
import java.awt.Color;
public class WhiteKey extends Key {
private static final long serialVersionUID = 1L;
public WhiteKey() {
super();
setBackground(Color.WHITE);
setSize(WHITE_KEY_WIDTH, WHITE_KEY_HEIGHT);
}
}
EDIT: After doing a bit of work with threading, this is what I have
By putting the for loop in another thread, the keys are released at the right time:
#Override
public void actionPerformed(ActionEvent arg0) {
String action = arg0.getActionCommand();
if (action.equals("play")) {
System.out.println("working");
new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 20; i++) {
keys[i].playOnce(100);
}
}
}).start();
}
}
}
The issue now is that the keyboard glitches. The keyboard is created using a layered pane, and for some reason when the keys are released the layers that are supposed to be on the bottom come to the top. When I hover my mouse over them, the glitch goes away. Any ideas?
EDIT2: I fixed the glitches. I simply had to add
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
after doClick();
The problem with your approach is that you are blocking the event thread. This thread is responsible for user input, painting and window updates. My guess is now, that doClick's timeout gets checked inside the event thread (seems logical), so it won't get released until your actionPerformed method exits (and so the event thread can continue its event processing).
A solution to this problem would be (as you already mentioned) moving your for loop to another thread and call doClick using SwingUtilities.invokeLater.
I have a simple puzzle game. There is an image consisting of 16 tiles (randomly placed). Images are stored in an array and when game is launched they're added to main JPanel.
Game works in this way : Each image has atributes 'place' and 'number'. 'Place' is the current place on grid (either correct or not) and 'number' is the desired place for the image. When a user clicks image their 'place' and 'number' attributes are checked. If they match nothing happens. If not game checks if any image is currently in memory. If there is none, then this image's 'place' and 'number' are stored. If there is some image in memory, then the currently clicked image's 'plac'e is checked with stored image's 'number'. When they match - their places are exchanged. This part works properly. But now, I'm calling addComponent method on my JPanel with updated images and simply nothing happens. Shouldn't the new images be added to JPanel replacing the old ones ?
package Bonus;
import javax.swing.*;
import java.util.Random;
import java.awt.event.*;
import java.awt.*;
class Puzzle extends JPanel implements ActionListener {
private int selected_nr=-1;
private int selected_pl=-1;
private boolean memory=false;
private static Img[] images;
public Puzzle(){
JFrame f = new JFrame("Smile");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
f.setSize(252,252);
f.setVisible(true);
setLayout(new GridLayout(4, 4));
images = new Img[16];
int[] buttons = new int[16];
for(int i=0; i<16; i++){
buttons[i] = i;
}
int rand;
int temp;
Random random;
random = new Random(System.currentTimeMillis());
for (int i = 0; i < buttons.length; i++) {
rand = (random.nextInt() & 0x7FFFFFFF) % buttons.length;
temp = buttons[i];
buttons[i] = buttons[rand];
buttons[rand] = temp;
}
for (int i = 0; i < 16; i++) {
images[i] = new Img(i, buttons[i]);
}
addComponents(images);
}
public void addComponents(Img[] im){
this.removeAll();
for(int i=0; i<16; i++){
im[i].addActionListener(this);
im[i].setPreferredSize(new Dimension(53,53));
add(im[i]);
}
this.validate();
}
public void actionPerformed(ActionEvent e) {
Img b = (Img)(e.getSource());
int num = b.getNumber();
int pl = b.getPlace();
if(!(b.rightPlace())){
if(memory){
if(pl == selected_nr){
images[pl].setPlace(selected_pl);
images[selected_pl].setPlace(selected_nr);
selected_nr = -1;
selected_pl = -1;
memory = false;
addComponents(images);
}
else{
System.out.println("Try other image");
}
}
else{
memory = true;
selected_nr = num;
selected_pl = pl;
}
}
else{
System.out.println("OK !");
}
}
public static void main(String args[]) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
new Puzzle();
}
});
}
}
class Img extends JButton {
int number;
int place;
ImageIcon img;
public Img(int p, int n){
number = n;
place = p;
img = new ImageIcon("u"+number+".jpg", BorderLayout.CENTER);
setIcon(img);
}
public boolean rightPlace(){
boolean correct=false;
if(number == place){
correct = true;
}
return correct;
}
public void setPlace(int i){
place = i;
}
public int getNumber(){
return number;
}
public int getPlace(){
return place;
}
}
EDIT: Changed the code to use the answers, but still no luck. addComponents() gets updated images[] but doesn't revalidate them.
Rather than relying on precut image files, here's an example of slicing an existing image and shuffling the resulting pieces. It combines the helpful (+1) suggestions of both #Frederick and #akf.
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
public class ImageLabelPanel extends JPanel implements ActionListener {
private static final int N = 4;
private final List<JLabel> list = new ArrayList<JLabel>();
private final Timer timer = new Timer(1000, this);
ImageLabelPanel() {
this.setLayout(new GridLayout(N, N));
BufferedImage bi = null;
try {
bi = ImageIO.read(new File("image.jpg"));
} catch (IOException e) {
e.printStackTrace();
}
for (int r = 0; r < N; r++) {
for (int c = 0; c < N; c++) {
int w = bi.getWidth() / N;
int h = bi.getHeight() / N;
BufferedImage b = bi.getSubimage(c * w, r * h, w, h);
list.add(new JLabel(new ImageIcon(b)));
}
}
createPane();
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
f.pack();
f.setVisible(true);
timer.start();
}
private void createPane() {
this.removeAll();
for (JLabel label : list) add(label);
this.validate();
}
#Override
public void actionPerformed(ActionEvent e) {
Collections.shuffle(list);
createPane();
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new ImageLabelPanel();
}
});
}
}
You are adding all of your components again to your JPanel without actually removing any of them. In your addComponents() method, I would first call removeAll(). You might want to rename that method to highlight the side-effects, as it no longer would only be adding components. Perhaps, resetComponents() would be better.
After changing the components, you need to 'refresh' the Swing component by calling invalidate() or revalidate().