Java Swing鼠标拖动回调速度
我对 Java Swing 中 MouseMotionListener
的 mouseDragged
消息的回调速度有疑问。 这篇文章有点相关,但并不完全相同所以我开始问自己的问题。
我正在制作一个小型内部应用程序,不考虑商业发行,它基本上是一个数字化 TCG(集换式卡牌游戏)模拟器。对于熟悉 MtG(万智牌)的人来说,您可能听说过这样一个类似的程序。我正在尝试创建一些看起来有点像 this,但不太花哨。
我的 GUI 由一个带有菜单的 JFrame 和一些包含各种按钮和标签的面板组成,但我只会浏览相关部分来解释我的问题。
本质上,我使用的是垂直分割的 JSplitPane
,左侧有一个 JPanel
,其中有一个 JScrollPane
和一个 JList
,代表任何时候你手上可以打的牌。在分割的右侧,我有一个 JLayeredPane
,其背景图像位于 DEFAULT_LAYER
(JPanel
的子类,它覆盖了 绘制
函数来添加图像),并且在PALETTE_LAYER
上方的各个层上,我显示正在玩的卡片(聚集在一个ArrayList
)通过自定义 CardPanel
(说明卡片的 JPanel
的另一个子类)。因此,整个 JLayeredPane 代表了您面前的桌子以及您已经打过的所有牌。
我首先向 JLayeredPane
添加一个 MouseListener
和一个 MouseMotionListener
来拾取事件,允许我注册鼠标按下,检查是否这样位于卡片上方,然后使用鼠标拖动事件来移动卡片,最后释放鼠标将其放回原位。这一切都工作得很好,如果我添加日志信息,我可以看到 mouseDragged 回调函数经常被调用,从而实现视觉上快速的拖动运动而不会出现延迟。
今天,我决定添加功能以允许用户将卡片从手中拖到“桌子”(而不是双击 JList 中的卡片),因此我向JList
以及填充一些函数,例如 MousePressed
和 MouseReleased
。按下鼠标时,我检查单击了列表中的哪张卡,锁定列表,创建自定义 CardPanel
(但不要将其添加到任何地方,我只是分配并启动它!)设置一个标志。在鼠标拖动中,我检查是否设置了该标志。如果是,我检查光标在哪里。如果它位于 JLayeredPane
上方的任何位置,我会将 CardPanel
添加到 DRAG_LAYER
并设置另一个标志。如果在连续调用鼠标拖动时设置了第二个标志,我不会再次添加面板,而只是更改位置。这个功能实际上和我之前的鼠标拖动回调中的功能相同。释放鼠标后,我解锁列表并将 CardPanel
添加到 JLayeredPane
中的正确图层上。
一切都按预期工作,所以我很确定我的代码没问题,但只有一个小问题:
将卡片从列表拖动到分层窗格(而不是从分层窗格到分层窗格)时,我注意到mouseDragged
回调由 JList
调用的频率相当低(大约每秒 10 次),引入了一些视觉上令人不安的延迟(相比之下,JList 每秒大约 30 次)第一种情况是拖动)。
我将添加一些代码片段来澄清我的问题,但我担心添加所有代码以允许您自己运行它会严重矫枉过正。
这篇文章的主要问题是:有人知道为什么一个 MouseMotionListener
调用 mouseDragged
比另一个 MouseMotionListener
调用得更快吗? JLayeredPane
组件的侦听器进行快速连续调用,JList
调用的侦听器速度明显较慢。
注意:我正在 Netbeans 中进行开发,并使用内置的图形 Swing Interface Builder。我使用 JFrame 表单作为我的主类。
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);
}
}
}
});
}
我将尝试构建一个简短的可运行示例来重现问题,然后将代码附加到某处,但现在我必须开始了。
预先感谢
PS:我知道还有另一种方式可以在 Java 中拖动,涉及 TransferHandlers 等,但它看起来太麻烦了,而且它并不是我的问题的实际答案为什么一个回调似乎比另一个回调被调用更多,所以请不要告诉我使用它。
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 CardPanel
s (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.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
一旦将鼠标拖动到列表之外,Java 就会开始为列表生成合成鼠标事件,这可能就是原因。请参阅 JComponent#setAutoscrolls(boolean) 的 javadoc。
使用全局事件侦听器可能会获得更好的结果,请参阅
http://tips4java.wordpress.com/2009/08/30/全局事件监听器/
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/