Mouse input problems in java - java

Edit:
I have an application that uses a swing Timer to control when an action listener interface fires. The mouse logic works but occasionally will not detect a click. Below is my commented code.
public class Board extends JPanel implements MouseListener, MouseMotionListener, ActionListener
{
private MainMenu mainMenu = new MainMenu();
private static String State; /*Makes the control flow simpler, just checking
strings that describe the state. All the states are contained in GameState class.*/
public Board()
{
this.addMouseListener(this);
this.addMouseMotionListener(this);
setVisible(true);
mainMenu.initLogIn(); /*This just loads the button images*/
Timer timer = new Timer(12, this); /*(millisecond delay, tells this class
to update any actionlistener (mouselistener etc)*/
timer.start();
}
public void paint(Graphics G)
{
super.paint(G);
Graphics G2d = (Graphics2D)G;
/*Main menu paint logic*/
// This paints buttons from mainMenu class on screen
G.drawImage(mainMenu.getTopic1().getspriteImage(),
mainMenu.getTopic1().getxCoord(),
mainMenu.getTopic1().getyCoord(),this);
G.drawImage(mainMenu.getTopic2().getspriteImage(),
mainMenu.getTopic2().getxCoord(),
mainMenu.getTopic2().getyCoord(), this);
G.drawImage(mainMenu.getTopic3().getspriteImage(),
mainMenu.getTopic3().getxCoord(),
mainMenu.getTopic3().getyCoord(),this);
/*Shows mouse input worked by changing the background color*/
if (State == GameState.MAINMENU_TOPIC1)
{
setBackground(Color.BLACK);
}
if (State == GameState.MAINMENU_TOPIC2)
{
setBackground(Color.BLUE);
}
if (State == GameState.MAINMENU_TOPIC3)
{
setBackground(Color.GRAY);
}
repaint(); //tells paint to repaint, which allows gui to update
}
#Override
public void mouseClicked(MouseEvent e)
{
Point point = e.getPoint();
/*This contains the logic to change State based on mouse clicks*/
if(mainMenu.getTopic1().getRectangle().contains(point))
{
State = GameState.MAINMENU_TOPIC1;
}
if(mainMenu.getTopic2().getRectangle().contains(point))
{
State = GameState.MAINMENU_TOPIC2;
}
if(mainMenu.getTopic3().getRectangle().contains(point))
{
State = GameState.MAINMENU_TOPIC3;
}
}
So, I am unsure why mouse clicks would not always be detected. I know there is a chance that the time allocated to update the action listeners could be too short. However, there is not very much code for the machine to loop through, so I figure this is not the problem. Any thoughts on what might cause the mouse to behave this way?
Also, I will definitely implement this later using JButtons. I am sure that would help clean up my code on the larger project
Thanks for the comments, and I hope this clears up the majority of the questions.

A mouse "click" may essentially be a double or triple click. You can get that by using evt.clickCount. It will coalite as one event.
If you want to get every "press", use mousePressed() instead.

Related

JTextField can't be focused with mouse on Mac OS

We are tasked with making a simple game. Now, we have had the game done for a long while now, but I have only just gotten around to implementing the join/host menu (using a JPanel in our JFrame, which only contains a canvas which is used to render our sprites/shapes/etc.). We have 4 JTextFields and two JButtons. Our canvas is set to disabled when this display is shown so as to not interfere with input (i.e., with its mouse listener and such). On Windows machines, we can click on all of the boxes, we get the nice I-beam cursor, etc., and we can type in there normally and then click the buttons. However, when the same is attempted on MacOS, you cannot click on the boxes or the buttons. You don't get the I-beam on the boxes. It's like they don't exist. However, we can use the tab key to switch focus through all elements, and can use that to type in the boxes, press the buttons, etc., just as you should be able to with the mouse. I've tried requesting focus like 20 different ways, but that didn't seem to work. I've made many other apps the same way (JFrame > JPanel > JButton/TextField), and they all have worked just fine on MacOS. I have never seen anything like this before.
Rather than post a whole bunch of entire files, I'll trim them down. The first one is our main entry point, the Game class. It looks something like this:
public class Game extends Canvas implements Runnable
private JFrame frame;
private ConnectScreen connectScreen;
public getFrame() { /* get reference to frame */ }
public setFrame() { /* set the frame */ }
public void run() { /* game loop, calls render() */ }
public Game() { /* create window, add canvas to it, get reference to frame, instantiate stuff, etc. */ }
public void tick() { /* every frame this happens, just tells spawners to spawn stuff, etc. */ }
public void render() {
BufferStrategy bs = this.getBufferStrategy();
if (bs == null) {
this.createBufferStrategy(3);
return;
}
Graphics g = bs.getDrawGraphics();
///////// Draw things below this/////////////
g.setColor(Color.black);
g.fillRect(0, 0, WIDTH, HEIGHT);
// SCREEN
if (!isPaused()) {
// THIS is where we tell the canvas to enable if we're not on the connect screen
if (gameState != STATE.Join && gameState != STATE.Host) {
this.setEnabled(true);
}
/* i omitted rendering from all of these, since it's not useful here. most say somethingScreen.render(g) or something like that. */
if (gameState == STATE.Wave || gameState == STATE.Multiplayer || gameState == STATE.Bosses || gameState == STATE.Survival) { // render gameplay items
} else if (gameState == STATE.Menu || gameState == STATE.Help || gameState == STATE.Credits) { // render menu
} else if (gameState == STATE.Upgrade) { // render upgrade screen
} else if (gameState == STATE.GameOver) { // render game over screen
} else if (gameState == STATE.Leaderboard) { // render leaderboard
} else if (gameState == STATE.Color) { // render color picker screen
} else if (gameState == STATE.Join || gameState == STATE.Host) {
// if we are on the connect screen, disable this canvas
this.setEnabled(false);
connectScreen.render(g);
} else if (gameState == STATE.LeaderboardDisplay) { // render the leaderboard
}
} else {
pauseMenu.render(g);
}
if(!isPaused()) {} // renders the handler's things
///////// Draw things above this//////////////
g.dispose();
bs.show();
}
}
Other than that, there's not much of interest in that class. Here's the (trimmed down) ConnectScreen class:
public class ConnectScreen extends JPanel {
JFrame frame; // this is the frame from the Game class
JTextField _____; // there are 4 of these
JButton _____; // there are 2 of these
public ConnectScreen(Game game) { // the game is passed in to get the frame from it
super();
this.setBackground(Color.black);
this.setPreferredSize(new java.awt.Dimension(Game.WIDTH, Game.HEIGHT));
this.setSize(new java.awt.Dimension(Game.WIDTH, Game.HEIGHT));
this.game = game;
this.mpSpawn = mp;
game.getFrame().add(this);
this.setFocusable(true);
/* set up each component */
/* add each component (click handlers, etc.) */
/* add panel to frame */
}
public void render(Graphics g) {
super.paintComponent(g);
this.paintComponents(g);
}
}
We know the setup of elements and such all worked, since we can see and use them all on Windows, but for some reason when we try it on MacOS, it doesn't work. We can tab-select them and type/press buttons, but for some reason you cannot click on them to focus. Any help would be appreciated. Thanks!
Fixed it, thanks to MadProgrammer. Apparently, a BufferStrategy cannot be used to render Swing components. I just added an if statement in Game::render() where it only does any rendering if the ConnectScreen isn't shown. If it is, it just disables and turns the canvas invisible, while the ConnectScreen::render() (renamed to override JPanel::paintComponent()) just uses super.paintComponent() to passively draw Swing components.

How to create new instances of a timer Task instead of canceling and restarting it all over?

Well the thing is that I have a project where I have to make a game on java. In my game there's a spaceship that shoots lasers. I have the mechanics for shooting the laser more or less figured out but I am currently using a timer task to make the laser fly through the JFrame and give the impression a laser was shot.
Problem is that TimerTask seems to bug out as soon as I start shooting many times.
The main goal is to move an object across the screen at a given speed.
Is there something else I could do to achieve this? Is there a better way to implement this?
I appreciate all the help I could get, Thanks.
Here is some of the code:
public Space() {
this.setBackground(Color.BLACK);
this.setCursor(Cursor.getDefaultCursor());
this.addMouseMotionListener(new MouseAdapter() {
public void mouseMoved(MouseEvent e) {
repaint();
x = e.getX()-spaceFighterIcon.getIconHeight()/2;
y = e.getY()-spaceFighterIcon.getIconWidth()/2;
}
public void mouseDragged(MouseEvent e) {
repaint();
x = e.getX()-spaceFighterIcon.getIconHeight()/2; //Positions the cursor on the middle of the spaceShip and viceVersa
y = e.getY()-spaceFighterIcon.getIconWidth()/2;
}
}
);
this.addMouseListener(new MouseAdapter(){
public void mousePressed(MouseEvent e) {
if(timerRunning = true){
laserTimer.cancel();
laserTimer.purge();
laserFired = false;
}
if(SwingUtilities.isLeftMouseButton(e)){ // Gets where the laser is going to be shot from
repaint();
laserX = e.getX()-spaceFighterIcon.getIconWidth()/6;
laserY = e.getY();
laserFired = true;
}
if(SwingUtilities.isRightMouseButton(e)){
}
if(SwingUtilities.isMiddleMouseButton(e)){
}
}
});
}
public void paintComponent(Graphics g) {
this.graphics = g;
super.paintComponent(g);
spaceFighterIcon.paintIcon(this, g, x, y);
if(laserFired == true){
shootLaser();
}
}
public void shootLaser(){
laserIcon.paintIcon(this, graphics, laserX, laserY-50); // paints the laser
laserTimer = new Timer();
laserTimer.schedule(new AnimateLasers(), 0, 200); // Timer to move the laser across the frame
timerRunning = true;
repaint();
}
public void lasers(){
laserY = laserY-1; // function to move the laser
if(laserY <= 0){
laserTimer.cancel();
laserTimer.purge();
}
}
public class AnimateLasers extends TimerTask {
public void run() {
lasers();
repaint();
}
}
Start by taking a look at Concurrency in Swing and How to use Swing Timers instead of java.util.Timer.
Swing Timer is safer to use with Swing, as it executes it ticks within the context of the Event Dispatching Thread
Also take a look at Painting in AWT and Swing and Performing Custom Painting for more details about how painting works
Don't maintain a reference to the Graphics context outside of the paint method. Your component will be told when it should repaint it self by the system (via the call to the paintComponent method), essentially, you use the time to update the location of the "laser" and call repaint, then paint the laser within the paintComponent when it's called by the paint system

What's the value of "Graphics g" in "paint()" for a Java applet?

I am a beginner when it comes to making Java applets, and for my first applet, I drew a smiley face using paint(). Now, I want to make the smiley face blink. I have managed to get my timers and everything set up, but I need to use the start() method to get the timers going, and it seems that by including other methods, the paint method does not invoke itself. Because of this, I am assuming that I need to invoke paint() from start(), but the problem is I do not know what I am supposed to initialize the Graphics variable to in order to get paint() to actually work.
SSCCE
import java.awt.*;
import javax.swing.*;
import java.applet.Applet;
import java.awt.event.*;
public class Project2_15 extends Applet
{
public void paint(Graphics g)
{
setBackground(Color.lightGray);
}
// This handles the starting of timer execution.
public void start()
{
Graphics g; // What do I initialize this to?
paint(g);
}
// Timer Stuff
ActionListener blinkShut;
public Project2_15(final Graphics g) {
this.blinkShut = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
g.setColor(Color.black);
}
};
}
}
Here is the code with some corrections:
import java.awt.*;
import javax.swing.*;
import java.applet.Applet;
import java.awt.event.*;
public class Project2_15 extends Applet
{
public boolean wink = false;
Timer timer;
public void paint(Graphics g)
{
super.paint(g);
// Graphics g; // What do I initialize this to? ALREADY INITIALIZED
//paint(g);
if (wink) {
g.drawLine(1,1,100,100);
} else {
g.drawOval(1,1,100,100);
}
}
// This handles the starting of timer execution. NO IT DOES NOT!
// public void start()
#Override
public void init()
{
setBackground(Color.lightGray);
timer = new Timer(250,blinkShut);
}
#Override
public void start() {
timer.start();
}
#Override
public void stop() {
timer.stop();
}
// Timer Stuff
ActionListener blinkShut = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
wink = !wink;
repaint();
}
};
}
See Performing Custom Painting. It would be the same basic process with applet or frame.
Add a container (Panel/JPanel) to the top-level container.
Override the paint(..) AWT or paintComponent(..) Swing method.
Call super.. as the first statement.
Do the custom painting to the supplied Graphics instance.
Animation can be achieve using a Swing based Timer.
Of course I would tend to replace steps 1) to 4) with painting to a BufferedImage displayed in a JLabel/ImageIcon.
You need your timer to change the state of the applet, suggest that it be repainted, and then have your applet's paint method to react to the state. Some suggestions:
Use a Swing Timer for your timer
Give your applet a non-static boolean field called blink.
In the Timer's actionPerformed method, change the boolean field of the applet, blink, to its opposite state: blink = !blink;
Then call repaint(). This will tell the JVM to possibly repaint the applet.
In your paint(...) method use the state of the blink variable in an if block, and if true paint an eye, if false paint a closed eye.
You're better off using a Swing applet or JApplet.
If you're using a JApplet, then you'll do your painting in a JPanel's paintComponent(...) method, not in the paint method.
Either way, be sure to call the super method as the first method call in your painting method, either super.paint(g) if in the Applet's paint method or super.paintComponent(g) if in a JPanel's paintComponent method. This allows your GUI to erase previous painting.
Edit
Regarding your code:
public void start()
{
Graphics g; // What do I initialize this to?
paint(g);
}
and:
public Project2_15(final Graphics g) {
this.blinkShut = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
g.setColor(Color.black);
}
};
}
Please throw this code out as you almost never paint this way or call paint directly. Please read or re-read my recommendations above.
Edit 2
Regarding your comments:
So I can't just create a separate timer and put the code in their?
I never said this. Feel free to use a separate timer, and put in decent code inside of it. You of course will have to discard your current code since you do not want to manipulate the Graphics object directly as you're trying to do.
In addition to his eyes blinking, I was also hoping to have his tongue go in and out using a separate timer.
Then go for it!

How to force repaint of a glass pane?

I have a Swing app with a glass pane over a map.
It paints dots at certain positions. When I click somewhere on the map, and the glass pane receives the message CONTROLLER_NEW_POLYGON_MARK I
want do display an additional dot at the position specified in the event data (see MyGlassPane.propertyChange).
The glass pane class is called MyGlassPane. Using the debugger I validated that addPointToMark is actually called in propertyChange.
But no additional dots appear on the screen.
How can I change the code so that PointSetMarkingGlassPane.paintComponent is called whenever an event (IEventBus.CONTROLLER_NEW_POLYGON_MARK) is fired?
public class PointSetMarkingGlassPane extends JComponent implements IGlassPane {
private final ILatLongToScreenCoordinatesConverter latLongToScreenCoordinatesConverter;
private final List<Point.Double> pointsToMark = new LinkedList<Point.Double>();
public PointSetMarkingGlassPane(final ILatLongToScreenCoordinatesConverter aConverter) {
this.latLongToScreenCoordinatesConverter = aConverter;
}
protected void addPointToMark(final Point.Double aPoint)
{
if (aPoint != null)
{
pointsToMark.add(aPoint);
}
}
#Override
protected void paintComponent(final Graphics aGraphics) {
for (final Point.Double pointToMark : pointsToMark)
{
final Point positionInScreenCoords = latLongToScreenCoordinatesConverter.getScreenCoordinates(pointToMark);
drawCircle(aGraphics, positionInScreenCoords, Color.red);
}
}
private void drawCircle(Graphics g, Point point, Color color) {
g.setColor(color);
g.fillOval(point.x, point.y, 10, 10);
}
}
public class MyGlassPane extends PointSetMarkingGlassPane implements PropertyChangeListener {
public MyGlassPane(ILatLongToScreenCoordinatesConverter aConverter) {
super(aConverter);
addPointToMark(DemoGlassPane.ARTYOM);
}
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (IEventBus.CONTROLLER_NEW_POLYGON_MARK.equals(evt.getPropertyName()))
{
addPointToMark((Point.Double)evt.getNewValue());
invalidate();
}
}
}
As I think invalidate() only flags your component to check sizes and layout. You should call repaint() to repaint your pane.
Also I am wondering why you use propertyChangeListener for mouse clicks. I would prefer just simple mouse listener + MouseAdapter and MouseEvent x, y, buttons state.
invalidate() probably won't help you, as it flags a component for layout changes, not painting changes. Why not call repaint() instead?
For better performance, you could call the repaint method which takes a Rectangle (or four ints representing a rectangle), so that only the newly added point is repainted; I would suggest changing the return type of addPointToMark from void to java.awt.Point, and have it return the result of latLongToScreenCoordinatesConverter.getScreenCoordinates, so MyGlassPane can derive a rectangle from that Point which can then be passed to a repaint method.

Drawing problem in java

I am a new in java, and I need to implement a paint application, and I'm kinda stuck at the beggining, I managed to draw lines to a JPanel which I added to a JFrame, but each line drawn resets the entire drawing, and in the draw area remains only the last line drawn. I hope I made myself understood, here his the code:
class Shapes extends JFrame {
public JFrame mf = new JFrame("Paint");
DrawArea da = new DrawArea();
JToggleButton lineButton = new JToggleButton(new ImageIcon("line.gif"));
JToggleButton brushButton = new JToggleButton();
JToggleButton pencilButton = new JToggleButton();
JToggleButton eraserButton = new JToggleButton(new ImageIcon("eraser_icon.png"));
JToggleButton rectangleButton = new JToggleButton();
JToggleButton ovalButton = new JToggleButton();
Shapes() {
da.setBounds(120, 50, 500, 350);
da.setBackground(Color.YELLOW);
mf.setSize(700, 500);
mf.setLayout(null);
lineButton.setBounds(0, 50, 40, 40);
brushButton.setBounds(40, 50, 40, 40);
eraserButton.setBounds(0, 90, 40, 40);
pencilButton.setBounds(40, 90, 40, 40);
rectangleButton.setBounds(0, 130, 40, 40);
ovalButton.setBounds(40, 130, 40, 40);
mf.setBackground(Color.red);
mf.add(lineButton);
mf.add(brushButton);
mf.add(pencilButton);
mf.add(eraserButton);
mf.add(rectangleButton);
mf.add(ovalButton);
mf.add(da);
mf.show();
mf.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
mf.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
System.out.println("x:" + e.getX() + "y:" + e.getY() + "\n" + "x2:" + e.getXOnScreen() + "y2:" + e.getYOnScreen());
}
});
eraserButton.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e)
{
eraserButton.setSelectedIcon(new ImageIcon("eraser_icon_selected.png"));
}
});
lineButton.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e)
{
lineButton.setSelectedIcon(new ImageIcon("line_selected.png"));
}
});
da.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
da.setXvalue(e.getX());
da.setYvalue(e.getY());
}
public void mouseReleased(MouseEvent e) {
da.setX2value(e.getX());
da.setY2value(e.getY());
da.repaint();
}
});
da.addMouseMotionListener(new MouseAdapter() {
public void mouseDragged(MouseEvent e) {
da.repaint();
da.setX2value(e.getX());
da.setY2value(e.getY());
}
});
}
}
public class DrawArea extends JPanel {
int x1value,y1value,x2value,y2value;
public int getX2value() {
return x2value;
}
public void setX2value(int x2value) {
this.x2value = x2value;
}
public int getY2value() {
return y2value;
}
public void setY2value(int y2value) {
this.y2value = y2value;
}
public JPanel dra=new JPanel();
public int getXvalue() {
return x1value;
}
public void setXvalue(int xvalue) {
this.x1value = xvalue;
}
public int getYvalue() {
return y1value;
}
public void setYvalue(int yvalue) {
this.y1value = yvalue;
}
public void paint(Graphics g)
{
super.paint(g);
g.setColor(Color.red);
g.drawLine(getXvalue(),getYvalue(),getX2value(),getY2value());
}
}
class Paint extends JPanel
{
public static void main(String args[])
{
Shapes s=new Shapes();
}
}
See Custom Painting Approaches for two solutions. The examples draw rectangles, but the concept is the same for lines.
Override paintComponent(), not paint(). Read this tutorial. When a panel needs to be redrawn, you call that panels repaint() method.
Paint is called by the window manager any time it considers that area 'unfresh'. If you do it the way you're doing it right now, you will draw the last line drawn every time.
The proper way to do this would be to make a BufferedImage in memory and draw on that. Then, in the paint method, blit the BufferedImage onto the surface. This also makes scrolling and zooming quite easy to do.
Whenever you perform such an action, invalidate the surface so that the window manager will call the paint method for you.
You are only storing one line, and overwriting it each time, so when the component is repainted, the old one is erased and the new one is redrawn.
The expectation of paintComponent and the like is that your implementation will draw EVERY graphical element that you want to appear, each time it is called.
Instead of storing x1, y1, x2, y2, you should make a LineSegment class or similar that stores those values. Then, when you paint, you call g.drawLine() for each LineSegment object that you've stored (presumably in an ArrayList or similar). Then, when the component is redrawn, all of your line segments should appear on the screen.
A little bit off topic, but I had a few uncomfortable minutes cause I used update() instead of repaint(). I advice to everyone working with SWING to spend some time checking which methods should handled as thread safe and which ones has to be on EDT (Event Dispatcher Thread) to make sure you won't get some unexpected errors.
This is a good article about this.
Also, at the beginning think through if you want to have an undo/redo system in your app...
If so, than how many steps you want to allow being withdrawn. If you want to allow this feature than you cannot just draw and forget about what you draw last time.
Also it would be not memory efficient to store all the images you draw so far. I'm not an expert and I'm not saying this is the best practice but I would go this way:
I would make two lists.
One of them would store the applied drawing actions,
the other would contain the withdrawn drawing actions.
Drawing action would be an interface and some class would implement it for each specific kind of drawing action (LineDrawAction, CirceDrawAction...).
When you draw a new line or whatever you would empty the withdrawn actions list and add it to the applied action list. When someone undo the last action, than I would just remove the last drawing actions from the applied list and would add to the withdrawn list (etc...). Depending on if you want to allow only the last x action to be undone when a list reaches this x limit I would remove the first drawing action from the list or queue and would finally draw to the picture - this means permanent drawing and this cannot be undone.
I hope it's clear and useful even if not a direct answer to your question.

Categories