I have a question regarding the callback speed of the mouseDragged message of the MouseMotionListener in Java Swing. This post is sort of related but it's not entirely the same so I started a question of my own.
I'm making a small in-house application with no eye on commercial distribution that is basically a digitalized TCG (Trading Card Game) emulator. For any of you familiar with MtG (Magic the Gathering), you might've heard from such a similar program. I'm trying to create something that looks sort of like this, but less fancy.
My GUI consists of a JFrame with menu and then some panels containing various buttons and labels, but I'll only go over the relevent parts to explain my problem.
In essence, I'm using a vertical split JSplitPane with a JPanel on the left, with in that a JScrollPane with a JList in it, which represents at any time the cards in your hand that you can play. On the right side of the split, I have a JLayeredPane with a background image in the DEFAULT_LAYER (subclass of JPanel that overrides the draw function to add an image) and, on various layers above the PALETTE_LAYER, I display the cards that are in play (gathered in an ArrayList) by means of custom CardPanels (another subclass of JPanel that illustrates a card). The entire JLayeredPane is thus a representation of the table in front of you with all the cards you've already played.
I first started by adding a MouseListener and a MouseMotionListener to the JLayeredPane to pick up events, allowing me to register a mouse press, check if this was above a card, then use the mouse dragged event to move the card around and finally mouse release to place it back . This all works perfectly fine and if I add logging information I can see the mouseDragged callback function is called often, allowing for a visually fast dragging motion without lag.
Today I decided to add functionality to allow the user to drag a card from his hand to the "table" (instead of double clicking on the card in the JList), so I added the appropriate listeners to the JList along with filling in some functions like MousePressed and MouseReleased. On a mouse press, I check what card from the list was clicked, I lock the list, create a custom CardPanel (but don't add it anywhere yet, I just allocate and initiate it!) and set a flag. In mouse dragged, I check if this flag is set. If it is, I check where the cursor is. If it is anywhere above the JLayeredPane, I add the CardPanel to the DRAG_LAYER and set another flag. If this second flag is set in successive calls to mouse dragged, I don't add the panel again but I just change the location. This functionality is practically the same as the one in my previous mouse dragged callback. On mouse release, I unlock the list and add the CardPanel on the correct layer in the JLayeredPane.
Everything is working as intended so I'm pretty sure my code is okay, but there is just one slight issue:
When dragging a card from the list to the layered pane (instead of from the layered pane to the layered pane), I notice the mouseDragged callback is called at a pretty low frequency by the JList (approx 10 times per second), introducing some visually disturbing lag (compared to approx 30 times per second in the first case of dragging).
I'm going to add some code snippets as to clarify my problem but I'm afraid adding all the code to allow you to run it yourself would be serious overkill.
The main question in this post is: does anybody know why the mouseDragged is called faster by one MouseMotionListener than by another MouseMotionListener? The listener to the JLayeredPane component makes fast successive calls, the listener to the JList calls significantly slower.
Note: I'm developing in Netbeans and I'm using the built-in graphical Swing Interface Builder. I'm using a JFrame form as my main class.
public class MyFrame extends JFrame{
...
protected JLayeredPane layeredPane;
protected JList cardsInHandList;
...
...
protected ArrayList<String> cardsInHand;
...
private void attachListeners(){
layeredPane.addMouseListener(new MouseAdapter(){
public void MousePressed(MouseEvent e){
// set a flag, start a drag
}
public void MouseReleased(MouseEvent e){
// unset a flag, stop a drag
}
});
layeredPane.addMouseMotionListener(new MouseMotionAdapter(){
public void MouseDragged(MouseEvent e){
// drag the card around
// gets called a lot!
// actual code:
if (e.getButton() == MouseEvent.BUTTON1) {
if (!dragging) return; // the flag
int x = e.getX() - 10;
int y = e.getY() - 10;
// snap to grid
x /= GRIDX;
x *= GRIDX;
y /= GRIDY;
y *= GRIDY;
// redraw the card at its new location
draggedCard.setLocation(x, y);
}
}
});
cardsInHandList.addMouseListener(new MouseAdapter(){
public void MousePressed(MouseEvent e){
// set a flag, start a drag
}
public void MouseReleased(MouseEvent e){
// unset a flag, stop a drag
}
});
cardsInHandList.addMouseMotionListener(new MouseMotionAdapter(){
public void MouseDragged(MouseEvent evt){
// check cursor location, drag if within bounds of layeredPane
// gets called a whole lot less!! _Why?_
// actual code:
if (!draggingFromHand) return; // the flag
// check location of cursor with own method (contains() didn't work for me)
if (isCursorAtPointAboveLayeredPane(evt.getLocationOnScreen())) {
// calculate where and snap to grid
int x = (int) (evt.getLocationOnScreen().getX() - layeredPane.getLocationOnScreen().getX())-10;
int y = (int) (evt.getLocationOnScreen().getY() - layeredPane.getLocationOnScreen().getY())-10;
// snap to grid
x /= GRIDX;
x *= GRIDX;
y /= GRIDY;
y *= GRIDY;
if(!draggingFromHandCardPanelAdded){
layeredPane.add(draggingFromHandCardPanel, JLayeredPane.DRAG_LAYER);
draggingFromHandCardPanelAdded = true;
} else {
draggingFromHandCardPanel.setLocation(x,y);
}
}
}
});
}
I'll try to build a short runnable example reproducing the problem and then attach the code somewhere but right now I got to skoot.
Thanks in advance
PS: I am aware that there is another way to drag in Java, involving TransferHandlers and all that but it just seems like too much hassle and it isn't an actual answer to my question of how come the one callback seems to be called more than the other, so please don't tell me to use that instead.
Once you drag outside the list, Java start generating synthetic mouse events for the list, which might be the cause. See the javadoc for JComponent#setAutoscrolls(boolean).
You might get better results using a global event listener, see
http://tips4java.wordpress.com/2009/08/30/global-event-listeners/
Related
GitHub
I’m making a gear simulator and I’ve set it up where you can place the gears, and I have a plan on how you can update each gears rotation speed. I’m going to make a Initialize (method?) where I make the gear that will spin check every point next to it(it’s beta size hasn’t been implemented yet) and then I will add the original gear to a dontSpin list, then make every gear the first one detected spin everything around it, except for gears in dontSpin.
My issue I’m having is how do I rotate the gear Image a bit every tick? I’ve done a bunch of research and tried implementing some things I found but they all rotate it once, and I can’t find a way/I’m not smart enough to know how to make each gear object in the gearList rotate at the rotateSpeed every tick.
If you need more information please message me as I’ve been working on this for like a week and this has been a roadblock for at least 3 days making me lose motivation.
I tried researching multiple different sites and different methods of rotating images and it seemed none were what I needed, they seemed to all be a single rotation. I tried just staring at my code for 15 minutes waiting for it to just pop in my head to no avail. I tried asking on a discord help server, where I was told “just make a method to rotate it, then use it” and I’m not even kidding lmao. I even tried asking a fellow java coder about it, but they had no idea.
Help me stack overflow, you’re my only hope.
Edit: taken down for focusing on 2 problems , so I’ll elaborate on the one problem.
Gear is a class with a Point(x,y), I have a Board class with the bulk of my code, where I have a 10 by 10 ish size grid of squares, the gear and player automatically moves around on these squares.
You can hit a button ‘E’ to add a gear, and ‘Q’ to remove a gear. Every time you add a gear, a new gear is added to the gearList ArrayList. My issue is how to update the gear Images every single tick in the board class.
Here is where the gears are drawn
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
// when calling g.drawImage() we can use "this" for the ImageObserver
// because Component implements the ImageObserver interface, and JPanel
// extends from Component. So "this" Board instance, as a Component, can
// react to imageUpdate() events triggered by g.drawImage()
// draw our graphics.
drawBackground(g);
drawScore(g);
for (Gear gear : gearList) {
gear.draw(g, this);
}
player.draw(g, this);
// this smooths out animations on some systems
Toolkit.getDefaultToolkit().sync();
}
This is what that calls
public class Gear {
// image that represents the gear's position on the board
private BufferedImage image;
private BufferedImage newSizeImage;
// current position of the gear on the board grid
private Point pos;
private int rot = 45;
private int rotSpeed = 5;
public Gear(Point gpos) {
// load the assets
loadImage();
// initialize the state
pos = gpos;
}
private void loadImage() {
try {
// you can use just the filename if the image file is in your
// project folder, otherwise you need to provide the file path.
image = ImageIO.read(new File("src/images/gear.png"));
finalImage = rotate(image.getScaledInstance(Board.TILE_SIZE, Board.TILE_SIZE, Image.SCALE_DEFAULT));
} catch (IOException exc) {
System.out.println("Error opening image file: " + exc.getMessage());
}
}
public void draw(Graphics g, ImageObserver observer) {
// with the Point class, note that pos.getX() returns a double, but
// pos.x reliably returns an int. https://stackoverflow.com/a/30220114/4655368
// this is also where we translate board grid position into a canvas pixel
// position by multiplying by the tile size.
g.drawImage(
finalImage,
pos.x * Board.TILE_SIZE,
pos.y * Board.TILE_SIZE,
observer);
}
I'm coding a game and I'm trying to get the value of a dice roll from a button click in my UI.
I'm not too familiar with the MVC principles but I heard it's the way to go. Any tips on how to get there from this code ?
int move = -1;
while (move == -1) {
this.btnLancerLeD.addMouseListener(new MouseAdapter() {
public int diceRoll;
#Override
public void mouseClicked(MouseEvent arg0) {
diceRoll = jeu.getDe().lancer();
move = diceRoll;
}
});
}
I'm trying to get diceRoll value to move my players on the board.
First, you should only call addMouseListener() once rather than doing it repeatedly in a while loop. Second, you can do the logic of the "move" all inside mouseClicked(). You probably need an if statement.
Note that Swing already contains the logic to wait for the user to do something. This is the whole point of event listeners. They are only fired when that event occurs. In the case of a button, you should use an ActionListener instead of a MouseListener. For more details, I suggest that you check out the official Oracle tutorials for Swing. You can find these with a quick Google search.
I have done a program that numerically solves a set of differential equations which describes how an "arbitrary" illness move in an isolated and constant population, it was a programming assignment from a class I took a while ago. What I've done to extend it is to add some graphical components that can pause, reset and "play" the simulation, as well as some components that allows me to change some constants in the equations.
All this was an exercise in programming as I find it to be fun and exciting and want to become better.
However, at the moment I'm stuck, what I want to do now is to make a very simple form of animation of it. I want to visualize the data I get for the number of infected, susceptibles and resistants in a grid as points. I managed to create the grid and have an idea of how to place the dots.
The problem I have is how to draw the dots as the program is working, I can draw one dot in the grid but only as the grid is created, that's it. I need to be able to create a dot at a specific place in the grid, this goes on until the number of dots reaches a finite number, say 30. At that points I want to have the first dot, the one the left, removed, all the dots shifted to the left and place the new dot at the furthest right of the grid, the whole thing is then repeated.
I think I will be able to figure it out with some help/hints about the paintComponent() method and whether I need to use repaint() method at all, I can't get my head around these for some reason. I've read through my course literature on Java, but despite the extensive sections where he explains most of the different graphical components he does not say that much about those methods, only that you don't call for the paintComponent() method, it is done automatically.
If there is something unclear let me know and I'll try to clarify it.
Thanks in advance.
//
Fox Mulder
I think I will be able to figure it out with some help/hints about the paintComponent() method and whether I need to use repaint() method at all, I can't get my head around these for some reason.
Basically, say you create a custom component by extending JPanel. When you #Override the paintComponent() method, it get implicitly called for you, so you never have to call it. So what ever you paint inside the method, gets drawn on your surface. For example
public class DrawingPanel extends JPanel {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.fillOval(x, y, 10, 10);
}
}
When you call repaint() you are basically causing the paintComponent method to be call implicitly. So to answer your question, Yes you will need to call it if you want to animate, as you will need to update some kind of variable (like the x and y) in the paintComponent() method, to see any change in the drawing.
You can see more at Performing Custom Painting
Not to handle the actual animation, you'll want to use a javax.swing.Timer. You can see more at How to use Swing Timers. Here's the basic construct
Timer ( int delayInMillis, ActionListener listener )
where delayInMillis is the time to delay between ticks(in this case animations) and the ActionListener listens for "ticks". Each tick, the actionPerformed of the ActionListener is called. There, you can put the code to update any variables you use for animation.
So for example you update the x and y, in the actionPerformed, then call repaint()
public class DrawingPanel extends JPanel {
int x = 50;
int y = 50;
public DrawingPanel() {
Timer timer = new Timer(40, new ActionListener(){
#Override
public void actionPerformed(ActionEvent e) {
x += 5;
y += 5;
repaint();
}
});
timer.start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.fillOval(x, y, 10, 10);
}
}
Now this was just a simple example. But in your case you want to animate a scatter plot. So what you can do is have a list of Points and in the actionPerformed you can add pull points from that list and push them into another list that is to be drawn. So say you have this
List<Point> originalPoints;
List<Point> pointsToDraw;
...
#Override
protected void paintComponent(Grapchics g) {
super.paintComponent(g);
for (Point point : pointsToDraw) {
g.fillOval(point.x - 5, point.y - 5, 10, 10);
}
}
Basically all the points in pointsToDraw list will be drawn. Initially it will be empty. And in the timer, you can add to the list, until the originalPoints list is exhausted. For example.
List<Point> originalPoints;
List<point> pointsToDraw;
private int currentIndex = 0;
public DrawingPanel(List<Point> originalPoints) {
this.originalPoints = originalPoints;
pointsToDraw = new ArrayList<>();
Timer timer = new Timer(40, new ActionListener(){
#Override
public void actionPerformed(ActionEvent e) {
if (currentIndex == originalPoints.size() - 1) {
((Timer)e.getSource()).stop();
} else {
pointsToDraw.add(originalPoints.get(currentIndex));
currentIndex++;
}
repaint();
}
});
timer.start();
}
So basicall you just keep a current index. When the index reaches the size of the original list, you stop the timer. Otherwise you just pop from the originalPoints and push to the pointsToDraw. For every point you add the pointsToDraw, a repaint() is called, and there will be another point for the paintComponent to draw a circle with.
The END
UDPATE
I just reread your question, and I think I have have misunderstood it. If you want all the points drawn, then basically just have one list. And draw all the points initially. with each tick, just remove the first index, advance all the rest up an index, and add a new one to the end. Though this is the implementation of a LinkedList so you may just want to use that
I'm stuck with a problem of bringing a field to the front as it's drawing, has anyone countered a problem like this before?
In plain words, the problem is that I'm using NegativeMarginVerticalFieldManager from the Advanced UI samples, I use it to display a highlighted text under each panel button, the text should go in front of the content under the panel, but now it's going in the back of it.
So, does any one know how to make it appear in the front?
The order of paint is different. Could you override paint in your fields and check order of painting?
Unfortunately there z-order is connected to order how fields were added to manager and I don't know how to change it.
What you could do - override paint in manager and call paint for children in your needed order:
public class MyManager extends NegativeMarginVerticalFieldManager {
protected void paint(Graphics g) {
for (int i = 1; i < getFieldCount(); i++) {
paintChild(getField(i), g);
}
if (getFieldCount() > 0)
paintChild(getField(0), g);
}
}
This is hack - I'm painting first field after all other.
I am working on a first person game in Java, and I am trying to get the 3D movement working.
My problem is I would like to capture mouse movement, yet keep the mouse inside the window. After I capture the mouse movement, I figure the best way to keep the mouse in my window is to center the mouse in the window after moving, using Robot.moveMouse(x,y). This works fine, however the movement from the Robot triggers an event in my window which then gets interpreted as a normal event, and thus moves my character in the world.
I've tried various schemes of keeping state and ignoring movements until I am in the center, but they all seem finicky and don't quite detect which events are user vs Robot controlled.
Is there an easy way to detect that a mouse movement came from the Robot?
Is there perhaps a simpler way to solve my problem that I am overlooking?
I solved this by switching to NEWT with JOGL 2.0 RC4. In particular, I use GLWindow and warpPointer instead of an AWT Frame with the Robot.mouseMove. With the switch, I instantly got smooth movements. Some sample code similar to what I'm doing (mileage may vary):
public class MyClass implements MouseListener {
private GLWindow window;
private int centeredX = -1;
private int centeredY = -1;
// ...
public void mouseMoved(MouseEvent e) {
if (centeredX == -1 || centeredY == -1) {
center();
return;
}
int deltaX = e.getX() - centeredX;
int deltaY = e.getY() - centeredY;
// ... Do something with the deltas
centeredX = window.getWidth() / 2;
centeredY = window.getHeight() / 2;
window.warpPointer(centeredX, centeredY);
}
}
Well, I'm not 100% about this, but have you used the getsource() or getComponent() functions on your mouse event? They may return the robot as the source of it. Barring that, I would have a class variable like boolean robotControlling and anytime it takes control of the mouse, set that to true. Then, in you mouseListener, do a if(!robotControlling){...}. Hope this helps.
EDIT: if you have unused mouse buttons in your application (Java has Button 1, Button 2 and Button 3), you could make the robot press that, and in your mouse listener ignore any events with that code pressed. (use evt.getButton() for this) Of course, thats not exactly the cleanest solution :P