My Swing application is likely not thread-safe - not sure why, though - java

Edited at the request of commenters. I hope this is compliant.
First post! Trying to understand why my Swing application will not advance from one panel to the next. Here is the general flow of the code :
public class MainWindow {
JFrame mainFrame;
ChangeablePanel currentScreen; // abstract and extends JPanel, has getters &
setters for a Timer (swing timer), a String (nextScreen), and an Image
(background image). also has a close(AWTEvent e) method that simply calls
"this.setVisible(false);"
public MainWindow() {
mainFrame = new JFrame("New Arcana");
mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setTitleFrame();
} // MainFrame constructor
public void changeFrame(String frameType, String frameName) {
switch (frameType) {
case "Title":
setTitleFrame();
break;
case "Town":
setTownFrame(frameName);
break;
case "Movie":
setMovieFrame(frameName);
break;
default:
break;
} // switch
} // changeFrame
private void setTitleFrame() {
currentScreen = new TitlePanel();
currentScreen.addComponentListener(new ScreenChangeListener());
...
mainFrame.setContentPane(currentScreen);
mainFrame.setSize(titleScreenLength, titleScreenHeight); // put constants here if you want
mainFrame.setLocationRelativeTo(null);
mainFrame.setVisible(true);
} // setTitleFrame
private void setTownFrame(String townName) {
currentScreen = new TownPanel(townName);
currentScreen.addComponentListener(new ScreenChangeListener());
...
mainFrame.setContentPane(currentScreen);
mainFrame.setSize(townScreenLength, townScreenHeight); // put constants here if you want
mainFrame.setVisible(true);
} // setTownFrame
private void setMovieFrame(String movieName) {
currentScreen = new MoviePanel(movieName);
currentScreen.addComponentListener(new ScreenChangeListener());
...
mainFrame.setContentPane(currentScreen);
mainFrame.setSize(titleScreenLength, titleScreenHeight); // put constants here if you want
mainFrame.setVisible(true);
} // setMovieFrame
private class ScreenChangeListener implements ComponentListener {
#Override
public void componentHidden(ComponentEvent e) {
gotoNextScreen(e);
}
public void componentMoved(ComponentEvent e) {}
public void componentResized(ComponentEvent e) {}
public void componentShown(ComponentEvent e) {}
} // ScreenChangeListener
public void gotoNextScreen(ComponentEvent e) {
changeFrame(currentScreen.getNextScreen(), null);
}
} // MainWindow
public class Start {
...
public static void main(String[] args) {
initialize();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new MainWindow();
}
});
} // main
...
} // Start
public class TitlePanel extends ChangeablePanel implements ActionListener {
JButton newGame, continueGame;
public TitlePanel() {
setFocusable(true);
...
newGame = new JButton("New Game");
continueGame = new JButton("Continue");
newGame.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
setNextScreen("Movie");
close(e);
}
});
add(newGame);
add(continueGame);
createTimer(10, this);
getTimer().start();
} // TitlePanel constructor
#Override
public void actionPerformed(ActionEvent e) {
repaint();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
drawTitleScreen(g2d);
} // paintComponent
private void drawTitleScreen(Graphics2D g2d) {
g2d.drawImage(getBGImage(), 0, 0, null);
newGame.setLocation(170, 550);
continueGame.setLocation(605, 550);
} // drawTitleScreen
} // TitlePanel
public class MoviePanel extends ChangeablePanel implements ActionListener {
public MoviePanel(String movieName) {
setFocusable(true);
addKeyListener(new AnyKeyActionListener());
...
createTimer(10, this);
getTimer().start();
} // TitlePanel constructor
#Override
public void actionPerformed(ActionEvent e) {
repaint();
}
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
drawMovie(g2d);
} // paintComponent
private void drawMovie(Graphics2D g2d) {
g2d.drawImage(getBGImage(), 0, 0, null);
} // drawTitleScreen
private class AnyKeyActionListener extends KeyAdapter {
public void keyTyped(KeyEvent e) {
setNextScreen("Town");
close(e);
} // keyPressed
} // listener to check for keystrokes
} // MoviePanel
The MainFrame is to be populated with more frames as the application advances based on user-input (currently, only MoviePanel and TownPanel are coded), and their code is fairly analogous to this one -- I pasted MoviePanel as well.
Execution breaks down after the KeyAdapter-based listener above. However, when I run my application in Debug mode in Eclipse with breakpoints, this indeed does what it's supposed to do and advances from the MoviePanel to the TownPanel. It is because of this that I suspect threading is the culprit here. Note that I did try many different combinations of the SwingUtilities.invokeLater() technique on the code-blocks above, but it didn't change anything. Any help would be appreciated; thanks!

Do the following:
invokeLater for creation ont the GUI Event Dispatch Thread
No repaint() during construction
setVisible last
Especially on event listeners again use invokeLater, to let buttons and such be responsive, and have then actions being taken with response too.
public static void main(String[] args) {
...
SwingUtilities.invokeLater() {
#Override()
new Runnable() {
new MainFrame().setVisible(true);
}
};
}
Code review
In TitlePanel.TitlePanel better use an absolute layout (that means null), instead of using setLocation in the painting code.
setLayout(null);
newGame = new JButton("New Game");
continueGame = new JButton("Continue");
newGame.setBounds(170, 550, 120, 24);
continueGame.setBounds(605, 550, 120, 24);
In ChangeablePanel.close ensure also timer.stop().
In MainWindow use invokeLater:
public void gotoNextScreen(ComponentEvent e) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
changeFrame(currentScreen.getNextScreen(), null);
}
});
}
In MoviePanel I cannot see that addKeyListener could function; maybe the left-out code? Or is this maybe the error you saw?
Furthermore I find a simple repaint() dubious; would have expected something like:
public void actionPerformed(ActionEvent e) {
invalidate();
repaint(10L);
}

Related

Use of KeyListener with Thread

I want to move two objects at the same time.
I want to use Thread for this. But it gives an error in the run method.
How can I do this?
How can I do this using keyboard events
Like 2-player games?
Here's the code:
public class First extends JPanel implements Runnable,KeyListener{
int y1=303/2;
private int vy=0;
public void paintComponent(Graphics g){
g.setColor(Color.BLUE);
g.fillRect(10,y1, 15, 20);
}
public void setVelocity(int v){
vy=v;
}
#Override
public void run() {
int keyCode=e.getKeyCode();
if(keyCode==KeyEvent.VK_UP ){
}
if(keyCode==KeyEvent.VK_S){
}
else if(keyCode==KeyEvent.VK_DOWN){
}
else if(keyCode==KeyEvent.VK_W){
}
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
public static void main(String[] args){
JFrame jf=new JFrame();
Panel p=new Panel();
jf.add(p);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.setSize(300,300);
jf.setVisible(true);
}
#Override
public void keyTyped(KeyEvent e) {
}
#Override
public void keyPressed(KeyEvent e) {
}
#Override
public void keyReleased(KeyEvent e) {
}
}
class Second extends JPanel{
int y2=303/2;
private int vy=0;
public void setVelocity(int v){
vy=v;
}
#Override
public void paintComponent(Graphics g){
g.setColor(Color.YELLOW);
g.fillRect(150,y2, 15, 20);
}
}
class Panel extends JPanel{
First f=new First ();
Second s=new Second();
public void paintComponent(Graphics g){
s.paintComponent(g);
f.paintComponent(g);
}
}
How can I do this using keyboard events Like 2-player games?
An event is only generated for the last key pressed, so basically you need to track all the keys pressed (and when they are released).
I've done this in the past using Key Bindings with a Swing Timer.
Check out the KeyboardAnimation example found in Motion Using the Keyboard for a working example of this approach.
The link will also explain what Key Bindings are and why they should be preferred over a KeyListener.

How can i wait for a frame to perform an action before the code execute the next line?

I want to initialize a Graphical User Interface (GUI) for the user to input a form. After this is accomplished i want to open a new GUI, but as soon as the first GUI pops-up the next one is initialized to.
Is there any way to solve this without using waits and notifies?
here is an example of my code:
public static void main(String[] args) {
new GUIForm();
// wait until the user inputs the complete form
new GUIWelcome();
}
It is really simple I woild like to keep it that way.
Create an Interface OnActionListener
public interface OnActionListener {
public void onAction();
}
Add these code in GUIForm class
private OnActionListener listener;
private JButton action;
public GUIForm(OnActionListener listener) {
this.listener = listener;
action = new JButton("Action");
action.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
GUIForm.this.listener.onAction();
}
});
}
Now you can achieve that
new GUIForm(new OnActionListener() {
#Override
public void onAction() {
new GUIWelcome();
}
});
You need to use some sort pub/sub mechanism. This in a nutshell is what you need:
public class PubSub {
public static void main(String[] args) {
JFrame frame1 = new JFrame("GUIForm");
frame1.setSize(640, 480);
JButton button = new JButton("User Input");
JFrame frame2 = new JFrame("Welcome");
frame2.setSize(320, 240);
button.addMouseListener(new MouseAdapter() {
#Override
public void mouseEntered(MouseEvent e) {
button.setCursor(new Cursor(Cursor.HAND_CURSOR));
}
#Override
public void mouseExited(MouseEvent e) {
button.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
}
#Override
public void mouseClicked(MouseEvent e) {
frame2.setVisible(true);
}
});
frame1.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame1.add(button);
frame1.setVisible(true);
}
}
This version uses JFrame's listeners, but you could implement your on callback mechanism to accomplish the same

How to use the repaint method in Java Swing

I am very new to the Graphics portion of Java. I have created a frame and on it I have added a panel whose color has been set to Green. Now on clicking that panel I want to draw a circle using a test class's object called Mypanel. But it does not happen. Please guide !
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JPanel;
class Mypanel extends JPanel
{
#Override
public void paintComponent(Graphics g)
{
g.drawOval(15, 15, 5, 5);
}
}
public class algo extends javax.swing.JFrame {
public algo() {
initComponents();
jPanel1.setBackground(Color.GREEN);
}
Mypanel p = new Mypanel() ;
private void jPanel1MouseClicked(java.awt.event.MouseEvent evt) {
p.repaint();
}
public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new algo().setVisible(true);
}
});
}
}
If I were to guess I would say that I am not supposed to use the repaint method, but I was told that this was to be used.
That code as supplied would not compile. For better help sooner, post a Minimal, Complete, and Verifiable example or Short, Self Contained, Correct Example.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
class Mypanel extends JPanel {
boolean clicked = false;
Mypanel() {
setBackground(Color.GREEN);
MouseListener mouseListener = new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
clicked = true;
repaint();
}
};
this.addMouseListener(mouseListener);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 100);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (clicked) {
g.drawOval(15, 15, 50, 50);
}
}
}
public class algo extends JFrame {
public algo() {
initComponents();
pack();
//jPanel1.setBackground(Color.GREEN); ?!?
}
protected final void initComponents() {
add(new Mypanel());
}
public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new algo().setVisible(true);
}
});
}
}
There are a few things to correct in your example...
When you create the frame (i.e. in the constructor) you'll want to call super(). This is the first thing the constructor has to do. Then, you'll probably want to set an initial width/height, and set the background color of the frame green.
You need to add a mouse listener so that the mouseClicked method is actually called. Then have it add the 'MyPanel' object to the frame, and call repaint.
I think that's roughly what you're going for.

changing the value of a text field as another is changed and the same but reversed causing an error

I am making a simple Miles-Kilometers converter that updates automatically as you type. The problem is that is that an error is thrown somewhere. I believe that this is because as i change one of the fields it handles the event and changes the other field but since that also has an event handler for when it is changed it tries to change the other field itself and they keep firing events back and forth until something somewhere explodes. Any idea how I can fix this or is there a different problem completely ?
Here's my code:
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
public class Book extends JFrame{
private JTextField jtfKilometers = new JTextField(10);
private JTextField jtfMiles = new JTextField(10);
public Book(){
setLayout(new BorderLayout(10, 0));
JPanel jlblPanel = new JPanel(new GridLayout(2, 0, 50, 5));
jlblPanel.add(new JLabel("Kilometers"));
jlblPanel.add(new JLabel("Miles"));
add(jlblPanel, "West");
JPanel jtfPanel = new JPanel(new GridLayout(2, 0, 5, 5));
jtfPanel.add(jtfKilometers);
jtfPanel.add(jtfMiles);
add(jtfPanel, "Center");
jtfKilometers.getDocument().addDocumentListener(new DocumentListener(){
#Override
public void insertUpdate(DocumentEvent e) {
if(jtfKilometers.getText().equals("")){
jtfMiles.setText("");
}else{
jtfMiles.setText(Double.parseDouble(jtfKilometers.getText()) * 0.621371 + "");
}
}
#Override
public void removeUpdate(DocumentEvent e) {
insertUpdate(e);
}
#Override
public void changedUpdate(DocumentEvent e) {
insertUpdate(e);
}
});
jtfMiles.getDocument().addDocumentListener(new DocumentListener(){
#Override
public void insertUpdate(DocumentEvent e) {
if(jtfMiles.getText().equals("")){
jtfKilometers.setText("");
}else{
jtfKilometers.setText(Double.parseDouble(jtfMiles.getText()) * 1.60934 + "");
}
}
#Override
public void removeUpdate(DocumentEvent e) {
insertUpdate(e);
}
#Override
public void changedUpdate(DocumentEvent e) {
insertUpdate(e);
}
});
}
public static void main(String[] args){
Book f = new Book();
f.setDefaultCloseOperation(MyFrame.EXIT_ON_CLOSE);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
You need to add additional guard on the focus of the text fields, so that you will be modifying only the other text field, not recursively both of them.
jtfKilometers.getDocument().addDocumentListener(new DocumentListener() {
#Override
public void insertUpdate(DocumentEvent e) {
if (jtfKilometers.hasFocus()) { // ADD THIS LINE
if (jtfKilometers.getText().equals("")) {
jtfMiles.setText("");
} else {
jtfMiles.setText(Double.parseDouble(jtfKilometers.getText()) * 0.621371 + "");
}
}
}
and similarly
jtfMiles.getDocument().addDocumentListener(new DocumentListener() {
#Override
public void insertUpdate(DocumentEvent e) {
if (jtfMiles.hasFocus()) { // ADD THIS LINE
if (jtfMiles.getText().equals("")) {
jtfKilometers.setText("");
} else {
jtfKilometers.setText(Double.parseDouble(jtfMiles.getText()) * 1.60934 + "");
}
}
}
An easy fix for this is checking if the frame has focus when the event is triggered. This will prevent the event from triggering back and forth like is happening to you now.
See the adjusted code snippet from your sample below...
public void insertUpdate(DocumentEvent e) {
if(jtfMiles.hasFocus()){//Check for focus here....repeat same check on your other "insertUpdate" method for your other frame.
if(jtfMiles.getText().equals("")){
jtfKilometers.setText("");
}else{
jtfKilometers.setText(Double.parseDouble(jtfMiles.getText()) * 1.60934 + "");
}
}
}
Hope this helps!

repaint() not called when running app from Eclipse?

I never get "paint" written to my command line window when I use Eclipse and Run->cmd to run the program. It works fine if I run System.out.print() from paintComponent in another program. Someone who can help?
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class GUI extends JPanel implements KeyListener, ActionListener
{
private static final long serialVersionUID = 1L;
JFrame frmMain = new JFrame("Kodning");
JTextField text = new JTextField();
JPanel pan = new JPanel();
static char bokstav;
static int x=10, y=80;
boolean convert = false;
String s;
Timer t = new Timer(10, this);
public static void main(String[] args)
{
#SuppressWarnings("unused")
GUI g = new GUI();
}
public GUI()
{
frmMain.setSize(600, 120);
frmMain.setLayout(new GridLayout(2, 1));
frmMain.addWindowListener(hornStang());
frmMain.add(text);
frmMain.add(pan);
frmMain.setFocusable(true);
frmMain.setVisible(true);
frmMain.addKeyListener(this);
text.addKeyListener(this);
pan.addKeyListener(this);
t.start();
}
private static WindowAdapter hornStang()
{
return new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
};
}
public void keyPressed(KeyEvent e)
{
if(e.getKeyCode()== KeyEvent.VK_ENTER)
{
System.out.println("dechifrera");
repaint();
deshiffrera(text.getText());
}
}
public void keyReleased(KeyEvent arg0){}
public void keyTyped(KeyEvent arg0){}
public void deshiffrera(String s)
{
s = this.s;
repaint();
}
#override
public void paintComponent(Graphics g)
{
System.out.println("paint");
for(int i=0;i<s.length();i++)
{
bokstav = s.charAt(i);
switch (bokstav)
{
case 'a':nere(g); hoger(g); prick(g, 0); break;
//en massa case
default:break;
}
x=x+12;
}
}
#Override
public void actionPerformed(ActionEvent e)
{
repaint();
}
}
The component must be added to a visible window/frame/component for it's paintComponent to be called.
GUI is only added as a KeyListener but is neither added to the JFrame, nor any other visible component in the code above. There is no reason for calling paintComponent since the component is not being displayed at all.
There are a number of issues with your code:
Your GUI panel is not in the frame (shouldn't it be added instead of pan?)
String s is uninitialized, which causes a NullPointerException
paint should be overridden instead of paintComponents
paint should not change the state of the component, because it can be called any time.
etc...
You probably miss the output of "System.out.println("paint");" ?
GUI-Apps under Windows cant write to the console (they dont have a console, because it would suck if every GUI-App would also open a black window).
There are two java-interpreters under windows: "javaw.exe" which is a GUI-App and silently discards any System.out-writes. And "java.exe" which is a console-app and allows writing to the console. Try to start your program with "java.exe"
I use this with AWT (not 100% sure whether it's working in Swing too...)
Graphics g = _yourcomponent_.getGraphics();
if (g != null) {
_yourcomponent_.paint(g);
// below the estimated code for Swing:
_yourcomponent_.paintComponent(g);
}

Categories