I am trying to update an ImageIcon on a JLabel which sits on a JLayeredPane, but there is a lot of latency between when the setting thread sends the proper state to the JLabel object and when the GUI displays the ImageIcon of the proper state. The following code is an example of the issue, look for the difference in time between the print of the button being on/off and when the displayed icon gets lighter/darker.
The setting thread:
new Thread(new Runnable() { // setting thread
#Override
public void run() {
// TODO Auto-generated method stub
try {
while(true) {
System.out.println("testButton on"); // print that the button is on
testButton.updateState(1); // set button state to on
Thread.sleep(70 + random.nextInt(500)); //sleep between 70 and 570 milliseconds
System.out.println("testButton off");// print that the button is off
testButton.updateState(0); // set button state to off
Thread.sleep(70 + random.nextInt(500)); // sleep between 70 and 570 milliseconds
}
} catch(Exception e) {
e.printStackTrace();
}
}
}).start();
The button object:
class Button extends JLabel {
ImageIcon released;
ImageIcon pressed;
String text;
public Button(int x, int y, String text) {
released = new ImageIcon("src/components/images/button.png");
pressed = new ImageIcon("src/components/images/buttonDown.png");
setBounds(x,y, 100, 100);
this.text = text;
setIcon(released);
}
public void updateState(int data) {
if (data == 1) {
setIcon(pressed);
}
else {
setIcon(released);
}
}
}
The ImageIcons are only 325 bytes, so what might be causing the latency? I looked up about the Event Dispatcher Thread and many people say it should be instantaneous for an image to get painted.
End goal: Have many button objects on screen with the setting thread calling them to update based on randomly occurring actions. The displayed icon for a specific button object should change immediately as it is set in the function. The setting thread will not be constantly looping, instead loop once for every action sent (it is twice here just to show the issue).
Any suggestions or things to try I will test as soon as I can.
Edit: In the end the thread that gets the information will call to a device driver in Linux where it will wait for a response and only when it gets a response will it need to update the window. From what I know timer is used to update something at regular intervals, but I am likely wrong.
As explained in the comments running long processes on the The Event Dispatch Thread blocks it, so it does not respond to changes.
Also you are not suppose to update Swing components from other (not EDT) threads.
You need to use Swing tools like SwingWorker or Timer.
The following mcve demonstrates a simple slide-show using Timer:
import java.awt.BorderLayout;
import java.io.IOException;
import java.net.URL;
import java.util.Random;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import javax.swing.Timer;
public class ChangeButtonIcon extends JPanel{
private final URL[] urls = {
new URL("https://findicons.com/files/icons/345/summer/128/cake.png"),
new URL("http://icons.iconarchive.com/icons/atyourservice/service-categories/128/Sweets-icon.png"),
new URL("https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS_FkBgG3_ux0kCbfG8mcRHvdk1dYbZYsm2SFMS01YvA6B_zfH_kg"),
};
private int iconNumber = 0;
private final JButton button;
private boolean stop = true;
private final Random random;
private static final int MIN_DELAY = 70, DELAY = 500;
private Timer timer;
public ChangeButtonIcon() throws IOException {
random = new Random();
button = new JButton();
button.setIcon(new ImageIcon(urls[iconNumber]));
button.setHorizontalTextPosition(SwingConstants.CENTER);
button.addActionListener(e -> startStopSlideShow());
add(button);
}
private void startStopSlideShow(){
stop = ! stop;
if(stop){
timer.stop();
return;
}
timer = new Timer( MIN_DELAY+ random.nextInt(DELAY), (e)->swapIcon());
timer.start();
}
private void swapIcon() {
iconNumber = iconNumber >= urls.length -1 ? 0 : iconNumber+1;
button.setIcon(new ImageIcon(urls[iconNumber]));
}
public static void main(String[] args) throws IOException{
JFrame window = new JFrame();
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.add(new ChangeButtonIcon());
window.add(new JLabel("Click image to start / stop"), BorderLayout.PAGE_END);
window.pack();
window.setVisible(true);
}
}
Related
all
What I'm trying to do is to create a bouncing balls java program. Which I did. Each time the user presses start balls will populate the screen. the only problem I'm having is that I don't know how to pause it. Any help would be appreciated. I tried adding something similar to how I did the addball function but don't know how to apply that to pause the ball. I have tried to do the puase function by adding the button pause but don't know how to get it working
BounceFrame:
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class BounceFrame extends JFrame {
private static final long serialVersionUID = 1L;
private BallComponent ballComponent;
public BounceFrame() {
setTitle("Bounce");
ballComponent = new BallComponent();
add(ballComponent, BorderLayout.CENTER);
JPanel buttonPanel = new JPanel();
// Adds more balls.
addButton(buttonPanel, "Start", new ActionListener() {
public void actionPerformed(ActionEvent ae) {
addBall();
}
});
addButton(buttonPanel, "Pause", new ActionListener() {
public void actionPerformed(ActionEvent ae) {
}
});
// Closes the panel.
addButton(buttonPanel, "Close", new ActionListener() {
public void actionPerformed(ActionEvent ae) {
System.exit(0);
}
});
add(buttonPanel, BorderLayout.SOUTH);
pack();
}
public void addButton(Container c, String title, ActionListener listener) {
JButton b = new JButton(title);
c.add(b);
b.addActionListener(listener);
}
public void addBall() {
Ball b = new Ball(ballComponent.getBounds());
RunnableBall rB = new RunnableBall(b, ballComponent);
Thread t = new Thread(rB);
t.start();
}
}
RunnableBall:
import java.util.logging.Level;
import java.util.logging.Logger;
public class RunnableBall implements Runnable {
private Ball b;
private BallComponent comp;
private static final int DELAY = 3; //Controls speed of the balls.
public RunnableBall(Ball b, BallComponent comp)
{
this.b = b;
this.comp = comp;
}
#Override
public void run() {
comp.add(b);
while (true)
{
b.move(comp.getBounds());
comp.repaint();
try
{
Thread.sleep(DELAY);
}
catch (InterruptedException ex) {
Logger.getLogger(RunnableBall.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
}
When you add the ball you need to store the RunnableBall object you are creating in some collection variable, such as a variable of type ArrayList<RunnableBall>, for example. Then in your Pause button's ActionListener you can loop through the ArrayList and call a pause method on each of your RunnableBalls.
So you'll need to then define a pause method inside RunnableBall, which sets a boolean variable "isPaused" to true. So then you'll need to create that variable, isPaused, inside the RunnableBall class, and make it change the behaviour of the run method. You should be able to figure that bit out.
One thing you'll need to take care with is the fact that because you're using multiple threads, you'll need the communication between those threads (i.e. the process of setting the isPaused variable to true or false) to be thread-safe. I think you could achieve that by declaring the isPaused variable to be volatile, but there are other ways to do it.
I have been having a problem with my JFrame. I have added five JButtons for a user interface for an rpg program that I'm working on. When the "Status" button is pressed, the JFrame freezes, and nothing, not even EXIT_ON_CLOSE works. I want to know how to get the status button to work, and how to avoid this problem with any of the other buttons.
Here's the ButtonListener class:
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
public class ButtonListeners {
public static final int WIDTH=360;
public static final int HEIGHT=360;
final static Monsters sk = new Monsters("Skeleton",120,20,30,5,50);
final static Monsters dm = new Monsters("Dark Mage",130,10,40,10,100);
final static Monsters kn = new Monsters("Knight",160,30,40,2,120);
final static Monsters sm = new Monsters("Slime",200,1,50,5,150);
final static Monsters go = new Monsters("golem",500,50,55,15,400);
final static Monsters dg = new Monsters("dragon",1000,35,100,25,600);
final static Monsters bk = new Monsters("Black Knight",2000,35,90,12,1000);
final static Monsters zm = new Monsters("Zombie",100,30,35,5,50);
public static void UI(){
final JFrame frame=new JFrame("Guantlet");
frame.setSize(800,600);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final Player p = new Player();
frame.setSize(WIDTH,HEIGHT);
final JButton item = new JButton("Items");
final JButton status=new JButton("Status");
final JButton attack=new JButton("Attack");
final JButton defend = new JButton("Defend");
final JButton mStat = new JButton("Monster Status");
frame.setVisible(true);
frame.add(attack,BorderLayout.EAST);
frame.add(defend,BorderLayout.WEST);
frame.add(item, BorderLayout.NORTH);
frame.add(status, BorderLayout.SOUTH);
frame.add(mStat, BorderLayout.CENTER);
final ArrayList<Monsters> monOrder = new ArrayList<Monsters>();
monOrder.add(0,sk);
monOrder.add(1,zm);
monOrder.add(2,kn);
monOrder.add(3,sm);
monOrder.add(4,dm);
monOrder.add(5,go);
monOrder.add(6,dg);
monOrder.add(7,bk);
frame.setVisible(true);
JOptionPane.showMessageDialog(frame, "Welcome to the arena! Many opponents await.");
JOptionPane.showMessageDialog(frame,"A Skeleton draws near!");
class Button1Listener implements ActionListener{
#Override
public void actionPerformed(ActionEvent e) {
boolean battle1 = true;
while(battle1){
if(e.getSource() == attack){// &&monOrder.contains(sk) && monOrder.contains(zm) && monOrder.contains(kn) && monOrder.contains(dm) && monOrder.contains(go) && monOrder.contains(dg)&& monOrder.contains(bk)) {
if(monOrder.contains(sk)){
sk.mHP=sk.mHP-sk.attacked(p);
sk.status();
sk.isAlive();
if(sk.isAlive()){
p.hp=p.hp-sk.attacking(p);
System.out.println("The Skeleton has "+sk.mHP+" health left");
System.out.println("You have "+"You have "+p.hp+ " health left");
p.status();
}else if(!sk.isAlive()){
monOrder.remove(0);
p.exp=p.exp+sk.exp;
p.levelUp();
JOptionPane.showMessageDialog(frame,"A Zombie emerges!");
}
//}
System.out.println(zm.mHP);
}
if(monOrder.contains(zm) && !monOrder.contains(sk)){
zm.mHP=zm.mHP-zm.attacked(p);
zm.status();
zm.isAlive();
if(zm.isAlive()){
p.hp=p.hp-zm.attacking(p);
System.out.println("The Skeleton has "+zm.mHP+" health left");
System.out.println("You have "+"You have "+p.hp+ " health left");
p.status();
}else if(!zm.isAlive()){
monOrder.remove(0);
p.exp=p.exp+zm.exp;
p.levelUp();
JOptionPane.showMessageDialog(frame,"A Dark Mage appears before you!");
}
}
break;
}
}
}
}
ActionListener b1L=new Button1Listener();
attack.addActionListener(b1L);
status.addActionListener(b1L);
status.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if(e.getSource() == status ){
JOptionPane.showMessageDialog(frame, "Maximum Health = " + p.maxHP+" \n Strength = " + p.str + "\n Speed = "+p.spd+"\n Experience to next level- "+(p.reqExp-p.exp));
Thread t = new Thread(new Runnable() {
public void run() {
// execute query here
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// update the ui here
final JFrame frame=new JFrame("Guantlet");
frame.setSize(800,600);
frame.add(attack,BorderLayout.EAST);
frame.add(defend,BorderLayout.WEST);
frame.add(item, BorderLayout.NORTH);
frame.add(status, BorderLayout.SOUTH);
frame.add(mStat, BorderLayout.CENTER);
}
});
}
});
t.start();
}
}
});
}
}
You've got a long-running bit of code here:
while(battle1){
//....
}
and since this is running in the Swing event thread, it is tying up the event thread, freezing your application. The solution is to avoid doing this, avoid tying up the event thread. Possible solutions depend on your needs including use of a Swing Timer for a game loop, use of a background Thread for long-running processes, or re-constructing your code so that this while loop isn't needed.
A quick review of your code suggests that perhaps you would want to go the Swing Timer route. If you Google Java Swing Timer tutorial you'll get decent info on using this.
As an aside, you are over-using the static modifier and should fix this. You should use this only sparingly and only for specific needs.
Aside number 2: look into the M-V-C or Model-View-Control design pattern as a way to separate your game logic out of your GUI. You've got them mixed together in a way that will make extending, improving and debugging your program difficult.
Why are you using while(true) an infinite loop whereas you have called break statement in a condition. what happened if condition is not matched? Please look it again as shown below.
while(battle1){
if(e.getSource() == attack){
...
break;
}
}
When status button is clicked then if(e.getSource() == attack) will never be matched and your program will go in infinite loop.
Currently i'm trying to have java hold down a key like follows:
Robot rob;
rob.keyPress(KeyEvent.VK_ENTER);
Thread.sleep(3000);
rob.keyRelease(KeyEvent.VK_ENTER);
This should hold enter down for 3 seconds, causing the repeating effect after a second or so. In other words, if you were to manually hold the "r" key, it would first type r, and then after about a second it would go like rrrrrrrr. I want this effect from the robot. I also tried:
curTime = System.currentTimeMillis();
while(System.currentTimeMillis() - curTime < duration)
{
rob.keyPress(whatever);
}
rob.keyRelease(whatever);
This, however, is extremely sensitive and a duration of 1 second outputs... well, as many whatever's as your computer can in 1 second. Thousands of lines worth. This is not my intention. Any ideas? Thanks!
P.S. The reason I want this behavior is because im writing a little scripting language to automate games with. If I want to hold the up arrow key like a normal person, I think that I need the behavior i'm talking about.
Edit:
Since there seems to be some confusion, I appologize. Let me elaborate. In my first code peice, if I choose "r" to be the character, it will just print ONE r regardless of the duration. If you, on your keyboard, press "r" for 5 seconds, it will go -> r...rrrrrrrrrrrrrrr where ... means like a second of time. That is the behavior I want, but I wont get it. The second code is where I try to spam click "press", but this literally types "r" EVERY time it executes. So if I am in a timed loop for a duration, every time that loop iterates it will send the "r" button. That's not what I want. What I want, again , is the same result that would happen as if you pushed r down on your keyboard for 3 seconds. First its just one r, and then rrrrrrrrrrrrrrrrrrrrr. I'm not even sure what the release() method does... I figured if you left it on press without release, it would just SPAM the screen in a loop! Why wouldnt it, the key is PRESSED? This is what is confusing me. Apparently when a key is pressed it doesnt STAY pressed.
If I understand your problem, you can't get key repeats to occur when using Robot and keyPress.
In this case, you may need to produce a "psudo" "long" key press.
Basically, I tried something like this:
Robot bot = new Robot();
bot.setAutoDelay(1);
int duration = 3000;
long start = System.currentTimeMillis();
while (System.currentTimeMillis() - start < duration) {
bot.keyPress(KeyEvent.VK_R);
bot.keyRelease(KeyEvent.VK_R);
}
Which, rapidly pressed and releases the key over a period of time...
And I used this to test it...
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.Robot;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class TestRobot {
public static void main(String[] args) {
new TestRobot();
}
public TestRobot() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private JTextArea ta;
public TestPane() {
setLayout(new BorderLayout());
ta = new JTextArea(20, 20);
ta.setWrapStyleWord(true);
ta.setLineWrap(true);
add(new JScrollPane(ta));
JButton btn = new JButton("Start");
btn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
ta.requestFocusInWindow();
ta.append("Start\n");
SwingWorker worker = new SwingWorker<Object, Object>() {
#Override
protected Object doInBackground() throws Exception {
Robot bot = new Robot();
bot.setAutoDelay(1);
int duration = 3000;
long start = System.currentTimeMillis();
while (System.currentTimeMillis() - start < duration) {
bot.keyPress(KeyEvent.VK_R);
bot.keyRelease(KeyEvent.VK_R);
}
return null;
}
#Override
protected void done() {
ta.append("\nDone");
}
};
worker.execute();
}
});
add(btn, BorderLayout.SOUTH);
}
}
}
Updated
With a little testing, I was able to get this to work...
Robot bot = new Robot();
bot.setAutoDelay(50);
int duration = 3000;
long start = System.currentTimeMillis();
while (System.currentTimeMillis() - start < duration) {
bot.keyPress(KeyEvent.VK_R);
}
bot.keyRelease(KeyEvent.VK_R);
Now, if you play around with the autoDelay property, you can adjust the time (in milliseconds) between each event, which may produce a more desirable effect...
Why not use a for loop?
for (int i = 0; i < 10; i++)
rob.keyPress(whatever);
Or, to emulate the hold down effect you want:
rob.keyPress(whatever);
Thread.sleep(500);
for (int i = 0; i < 10; i++) {
rob.keyPress(whatever);
Thread.sleep(10);
}
If you want more "organic" behaviour, just randomize the number of iterations.
So I'm making a game where you can put bombs on the location of your character. Each bomb is associated with a GIF image when the bomb is displayed and eventually go BOOM (think about Bomberman).
The problem was, when i tried to paint more than one bomb on the screen, it was painted from the last frame of the GIF. Investigating, I found the method image.flush() to reset the GIF cicle but now the problem is that every time I paint a second bomb on the screen, the GIF cycle is reset for all previously bombs on screen.
Here is my constructor for each bomb:
public Tnt(int x, int y){
this.x = x;
this.y = y;
ImageIcon ii = new ImageIcon("src/main/resources/modelObjects/tnt.gif");
image = ii.getImage();
image.flush();
}
Every bomb i create enters an ArrayList (listTnt) and is removed after 6 secs, so i only paint the bombs already active.
Here is my method for drawing:
public void draw(Graphics2D g2d, JPanel board){
for(Tnt tnt: listTnt){
g2d.drawImage(tnt.getImage(), tnt.getX(), tnt.getY(), board);
}
}
EDIT: Seems that the problem was ImageIcon, since it reuses the image using Toolkit.getImage. Instead, Toolkit.createImage create a not reusable image.
Here is my new constructor for Tnt that worked perfectly:
public Tnt(int x, int y){
this.x = x;
this.y = y;
Toolkit t = Toolkit.getDefaultToolkit ();
image = t.createImage("src/main/resources/modelObjects/tnt.gif");
}
I dont even need image.flush() now. Thank you all.
The underlying Image is being reused amongst each ImageIcon.
Judging by the OpenJDK source code, it appears to be due to the fact that each simply requests the Image via Toolkit.getImage.
This method has a nifty caveat, however, which explains the issue at hand:
The underlying toolkit attempts to resolve multiple requests with the same filename to the same returned Image.
Instead, you should skip the ImageIcon step completely (since it's inappropriate to be using a Swing class unnecessarily in the first place), and instead call Toolkit.createImage, which states in the documentation:
The returned Image is a new object which will not be shared with any other caller of this method or its getImage variant.
Good luck.
As I did not know how to solve this, I tried #super_ solution and it works quite nicely. I share the code for anyone who wants an example. +1 to him
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.MalformedURLException;
import java.net.URL;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class TestAnimatedGif {
private static final int IMAGE_COUNT = 9;
protected void initUI() {
JFrame frame = new JFrame(TestAnimatedGif.class.getSimpleName());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JPanel panel = new JPanel();
frame.add(panel);
frame.setSize(600, 400);
frame.setVisible(true);
final Timer t = new Timer(1000, null);
t.addActionListener(new ActionListener() {
int count = 0;
#Override
public void actionPerformed(ActionEvent e) {
if (count < IMAGE_COUNT) {
try {
JLabel image = new JLabel(new ImageIcon(Toolkit.getDefaultToolkit().createImage(
new URL("http://www.sitevip.net/gifs/bomba/BOMB-B_animado.gif"))));
panel.add(image);
count++;
panel.revalidate();
panel.repaint();
System.err.println("image added");
} catch (MalformedURLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
} else {
t.stop();
}
}
});
t.start();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new TestAnimatedGif().initUI();
}
});
}
}
I have a window with a MenuItem "maddbound3" with the following ActionListener:
maddbound3.addActionListener
(
new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
menu_addbound3();
}
}
);
When the menu is clicked this listener calls menu_addbound3() below:
void menu_addbound3()
{
while(getEditMode() != EditMode.NONE)
{
System.out.println("!... " + getEditMode());
synchronized(this)
{
try
{
wait();
}
catch(InterruptedException e)
{
e.printStackTrace();
}
}
}
}
A MouseClicked event alters the value of the edit mode and issues a notifyAll() so that the while loop should exit. However, tests have shown that when the system is running through the while loop, the MouseClicked event never occurs on clicking the mouse.
Does the ActionListener block the MouseClicked event? How can I resolve this issue?
Thanks
Don't have a while(true) on the Swing event thread, and likewise don't call wait() on the Swing event thread -- you'll freeze the whole GUI making it completely unresponsive. You need to understand that the main Swing event thread or "event dispatch thread" is responsible for all Swing drawing and user interaction, and so if you tie it up with long-running or freezing code, you lock your entire GUI.
Instead, change the state of your program -- perhaps by setting a variable or two, and have the behavior of your program depend on this state. If you need more specific advice, please tell us what behavior you're trying to achieve, and we can perhaps give you a better way of doing it.
For more on the Swing event thread, please read: Lesson: Concurrency in Swing
Edit
You state:
When the user clicks the menu item I want to obtain information via a series of "discrete" mouse clicks from the window. Hence, on clicking the menu, the user would be prompted to "select a point in the window". So, what I need is for my ActionListener function (menu_addbound3) to then wait for a mouse click. Hence the wait/notify setup. A mouse click changes the edit_mode and notifyAll() causes the wait in the while loop to exit which then causes the while loop to exit and I can then prompt for my next bit of information within the menu_addbound3 function, repeating this as as I need to.
Thanks for the clarification, and now I can definitely tell you that you are doing it wrong, that you most definitely do not want to use the while loop or wait or notify. There are many ways to solve this issue, one could be to use some boolean or enum variables to give the program a state and then alter its behavior depending on the state. Your EditMode enum can be used in the MouseListener to let it know that its active, and then you could also give the MouseListener class a boolean variable windowPointSelected, set to false, and then only set it true after the first click has been made.
Edit 2
For example:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.*;
#SuppressWarnings("serial")
public class ProgState extends JPanel {
private static final int PREF_W = 400;
private static final int PREF_H = PREF_W;
private static final Color EDIT_COLOR = Color.red;
private EditMode editMode = EditMode.NONE;
private boolean firstPointSelected = false;
private JMenuBar jMenuBar = new JMenuBar();
private JTextField firstPointField = new JTextField(15);
private JTextField secondPointField = new JTextField(15);
public ProgState() {
add(firstPointField);
add(secondPointField);
JMenu menu = new JMenu("Menu");
menu.add(new JMenuItem(new AbstractAction("Edit") {
#Override
public void actionPerformed(ActionEvent arg0) {
setEditMode(EditMode.EDITING);
setFirstPointSelected(false);
}
}));
jMenuBar.add(menu);
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent mEvt) {
if (getEditMode() == EditMode.EDITING) {
Point p = mEvt.getPoint();
String pStr = String.format("[%d, %d]", p.x, p.y);
if (!isFirstPointSelected()) {
firstPointField.setText(pStr);
setFirstPointSelected(true);
} else {
secondPointField.setText(pStr);
setEditMode(EditMode.NONE);
}
}
}
});
}
public void setEditMode(EditMode editMode) {
this.editMode = editMode;
Color c = editMode == EditMode.NONE ? null : EDIT_COLOR;
setBackground(c);
}
public EditMode getEditMode() {
return editMode;
}
public void setFirstPointSelected(boolean firstPointSelected) {
this.firstPointSelected = firstPointSelected;
}
public boolean isFirstPointSelected() {
return firstPointSelected;
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
public JMenuBar getJMenuBar() {
return jMenuBar;
}
private static void createAndShowGui() {
ProgState progState = new ProgState();
JFrame frame = new JFrame("EditMode");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(progState);
frame.setJMenuBar(progState.getJMenuBar());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
enum EditMode {
NONE, EDITING
}
From the discussion it seems that having your class assume a number of states is the best way to proceed. We can achieve this by one or more enum variables. The reason I found this so hard to grasp initially is that I couldn't see the benefit of having all of ones code in the MouseClicked function. This is ugly and unmanageable at best.
However, using multiple enums and splitting processing into a number of external functions, we do indeed achieve a nice system for what we want.