can anyone think of a good way to ignore the single click that comes with a double-click in Java?
I'm looking to have different behaviors for each such that:
... can anyone think of a way to do this? Some sort of timer set-up maybe? An ideas appreciated:-)
<disclaimer>...and yes, I know I'm committing a most heinous usability / UI faux pas. </disclaimer>
EDIT #2:
Even though this works the delay due to the timer is maddening - I'm abandoning this solution, and using middle-click for selection instead of double-click...
EDIT:
Thanks cgull - this is what I was able to come up with given your confirmation that there's no easy way to do this (note that if I set the timer < 200 odd racing is seen between the click & the timer, but as long as I set this to a value > 200 things work just peachy):
public void mouseClicked(MouseEvent e) {
System.out.println( "Click at (" + e.getX() + ":" + e.getY() + ")" );
if (e.getClickCount() == 2) {
System.out.println( " and it's a double click!");
wasDoubleClick = true;
}else{
Integer timerinterval = (Integer)
Toolkit.getDefaultToolkit().getDesktopProperty(
"awt.multiClickInterval");
timer = new Timer(timerinterval.intValue(), new ActionListener() {
public void actionPerformed(ActionEvent evt) {
if (wasDoubleClick) {
wasDoubleClick = false; // reset flag
} else {
System.out.println( " and it's a simple click!");
}
}
});
timer.setRepeats(false);
timer.start();
}
}
Indeed you'll need to set up a Timer in your overridden mouseClicked() method of your MouseAdapter to detect the time interval between the two clicks. The default interval in ms can be found by querying Toolkit.getDefaultToolkit().getDesktopProperty("awt.multiClickInterval")
. If another mouse click is detected before the timer expires, then you have a double-click, else once the timer expires, you can process the single-click.
Actually I think there is a simpler solution (use InputEvent's getWhen() method):
class DCListener extends MouseAdapter{
private long maxTimeBetweenClicks; // in millis
private long firstClickTime=0;
private Runnable eventHandler;
public DCListener(long maxTimeBetweenClicks,Runnable eventHandler){
this.maxTimeBetweenClicks=maxTimeBetweenClicks;
this.eventHandler=eventHandler;
}
public void mouseClicked(MouseEvent e){
if((e.getWhen()-firstClickTime)<=maxTimeBetweenClicks){
firstClickTime=0; // 3 clicks are not 2 double clicks
eventHandler.run();
}else{
firstClickTime=e.getWhen();
}
}
}
An alternative solution:
I figured out this before I found the solution in this question. The idea is the same, use a timer, although more complicated :).
Use SwingWorker
:
class BackgroundClickHandler extends SwingWorker<Integer, Integer> {
@Override
protected Integer doInBackground() throws Exception {
Thread.sleep(200);
// Do what you want with single click
return 0;
}
}
In mouseClicked()
method you can do something like this:
if (event.getClickCount() == 1) {
// We create a new instance everytime since
// the execute() method is designed to be executed once
clickHandler = new BackgroundClickHandler();
try {
clickHandler.execute();
} catch(Exception e) {
writer.println("Here!");
}
}
if (event.getClickCount() == 2) {
clickHandler.cancel(true);
//Do what you want with double click
}
I hope it be useful.
Have you tried implementing the MouseListener interface already?
I think MouseEvent has a click count method ( or property ) to know that.
I bet you have gone through that already, so what is the problem you're facing there?
Probably what you can do is to code the time interval elapsed between a single and a double click with a thread or something.
So a single click will only be valid if another click is not issued in let's say 300 ms. ( something configurable )
The idea is:
public void listen for the single click()
if ( in x time there has not been another click )
then
we have a single click
proceed with your single click handling
else
we have a double click
proceed with your double click handling
Or something like that.
Thanks to this thread I made this simillar solution, that is the way I would imagine it. It keeps a Timer
thread (where single click events are scheduled with a 200ms delay) alive all the time - this could upset someone, but afterall starting a thread is the expensive stuff, isn't it?
Track of the last TimerTask
is kept and it's cancel()
method is called if a double click arrives. A double click cancels its single click if it arrives within 200ms, or has no effect if late.
I use JavaFX, but that should not matter. The core stuff is the Timer
and TimerTask
.
import java.util.Timer;
import java.util.TimerTask;
import javafx.fxml.FXML;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.MouseButton;
class SomeController {
private static final long MAX_MULTICLICK_INTERVAL = 200; //the milliseconds delay
private final Timer singleLeftClickTimer = new Timer(true); //isDeamon = true, will be terminated with the rest of the app
private TimerTask lastSingleClickTask;
private void onMouseClickedFiltered(MouseEvent event) {
//TODO process clicks
//For the primary button you get only single clicks that were not followed by double clicks within 200ms
//Double clicks and all clicks of other buttons go through unchanged
}
@FXML
private void onMouseClicked(MouseEvent event) {
if (event.getButton() != MouseButton.PRIMARY) {
onMouseClickedFiltered(event);
} else if (event.getClickCount() == 2) {
lastSingleClickTask.cancel(); //if 200ms passed from associated single click, this will have no effect
onMouseClickedFiltered(event);
} else if (event.getClickCount() == 1) {
lastSingleClickTask = new TimerTask() {
@Override
public void run() {
onMouseClickedFiltered(event);
}
};
singleLeftClickTimer.schedule(lastSingleClickTask, MAX_MULTICLICK_INTERVAL);
} // else: might swallow primary button clicks with clickCount outside 1 and 2 which is ok for me
}
}
Looking up the answer to this question I was thinking about a different solution. Couldn't find it so I share my thoughts:
The delay the other solutions introduce in the single click processing are a little painful because you get this delayed response. However those solutions are the simplest to implement and don't bother the rest of the code.
The other option is:
All events that a single click can produce are Undoable and are undone once an asociated double click event arrives .
Implementing Undo is an interesting topic, but for simplicity I will give a very basic example instead:
import javafx.fxml.FXML;
import javafx.scene.input.MouseEvent;
class SomeController {
private static final long MAX_MULTICLICK_INTERVAL = 200; //the milliseconds delay
private long lastSingleClickTime = 0;
private int singleClickCounter = 0, doubleClickCounter = 0;
private void singleClick(MouseEvent event) {
singleClickCounter++;
}
private void undoSingleClick(MouseEvent event) {
singleClickCounter--;
}
private void doubleClick(MouseEvent event) {
doubleClickCounter++;
}
@FXML
private void onMouseClicked(MouseEvent event) {
if (event.getClickCount() == 2) {
if (System.currentTimeMillis() <= lastSingleClickTime + MAX_MULTICLICK_INTERVAL) {
undoSingleClick(event);
lastSingleClickTime = 0; //if another double-click arrived we don't want to undo anything
}
doubleClick(event);
} else if (event.getClickCount() == 1) {
lastSingleClickTime = System.currentTimeMillis(); //in AWT use the event.getWhen() method instead
singleClick(event);
} // else: might swallow button clicks with clickCount outside 1 and 2 which is ok for me
System.out.printf("Single clicks: %d, double clicks: %d\n", singleClickCounter, doubleClickCounter);
}
}
Note that primary, secondary and middle button events are mixed for simplicity here. You should track a lastSingleClickTime
for each of the buttons or limit the processing to the primary button only, since that is usually the only button using double-clicks anyway.
With a data model implementing Undo one would remember the Action
the single click fired (let that be a class having a run()
and undo()
method doing opposite things) and call .undo()
on that Action
instance when the double click event arrived. There are pitfalls of course. But that would be a long chapter.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.