Stop Win+R from opening run tool - java

In my javafx program is a popup which lets user press keys and then it sets label accordingly. My problem is with key combinations that are shortcuts for underlying OS for example if user presses Win+R then Run.exe starts but my program should just set the label to "Win+R". My question is how to stop keyevents from triggering OS shortcuts.
Here is the relevant code.
public void showInput() {
Set codes = new HashSet();
Stage inputWindow = new Stage();
GridPane pane = new GridPane();
Scene scene = new Scene(pane);
Label label = new Label("Here comes the pressed keys");
scene.setOnKeyPressed(e -> {
e.consume();
int code = e.getCode().ordinal();
if (label.getText().equals("Here comes the pressed keys")){
codes.add(code);
label.setText(String.valueOf(e.getCode().getName()));
} else if (!codes.contains(code)){
codes.add(code);
label.setText(label.getText() + "+" + e.getCode().getName());
}
});
scene.setOnKeyReleased(e -> {
e.consume();
inputWindow.close();
});
pane.add(label, 0, 0);
inputWindow.setScene(scene);
inputWindow.show();
}
I tried e.consume() but it did not help.

It's possible with JNA, but is a bad idea. Don't intercept well-known key combinations.
Nevertheless, below is a working example. It basically uses the SetWindowsHookEx Win32 API and then blocks the Win+R key combination in the hook callback.
import com.sun.jna.platform.win32.*;
public class Test {
public static User32.HHOOK hHook;
public static User32.LowLevelKeyboardProc lpfn;
public static void main(String[] args) throws Exception {
WinDef.HMODULE hMod = Kernel32.INSTANCE.GetModuleHandle(null);
lpfn = new User32.LowLevelKeyboardProc() {
boolean winKey = false;
public WinDef.LRESULT callback(int nCode, WinDef.WPARAM wParam, WinUser.KBDLLHOOKSTRUCT lParam) {
if (lParam.vkCode == 0x5B)
winKey = (lParam.flags & 0x80) == 0;
if (lParam.flags == 0 && lParam.vkCode == 0x52 && winKey) {
System.out.println("Win-R pressed");
return new WinDef.LRESULT(-1);
}
return User32.INSTANCE.CallNextHookEx(hHook, nCode, wParam, lParam.getPointer());
}
};
hHook = User32.INSTANCE.SetWindowsHookEx(User32.WH_KEYBOARD_LL, lpfn, hMod, 0);
if (hHook == null) {
System.out.println("Unable to set hook");
return;
}
User32.MSG msg = new User32.MSG();
while (User32.INSTANCE.GetMessage(msg, null, 0, 0) != 0) {
}
if (User32.INSTANCE.UnhookWindowsHookEx(hHook))
System.out.println("Unhooked");
}
}
(The needed JNA JAR dependency is net.java.dev.jna : platform)

Not possible, Java layer is above OS layer meaning your code is handled by the JVM and the JVM is handled by the OS. So there is no way to "skip" the OS layer and send your commands directly to Java.

Related

Bad display for JavaFX WebView under macOS

We are using JavaFX WebView as our in-app browser. It works well under Windows 10.
However, when we run in under macOS Catalina 10.15.7, all display gone hair wire. The app is run on Java 8.
Here's the code for our simple in-app browser.
SimpleSwingBrowser.java
public class SimpleSwingBrowser extends JDialog {
private final JFXPanel jfxPanel = new JFXPanel();
private WebEngine engine;
private String loadedURL = null;
private final JPanel panel = new JPanel(new BorderLayout());
public SimpleSwingBrowser() {
super(JStock.instance(), JDialog.ModalityType.APPLICATION_MODAL);
initComponents();
}
private void initComponents() {
createScene();
// http://stackoverflow.com/questions/11269632/javafx-hmtleditor-doesnt-react-on-return-key
jfxPanel.addKeyListener(new KeyAdapter() {
#Override
public void keyTyped(KeyEvent e) {
if (e.getKeyChar() == 10) {
e.setKeyChar((char) 13);
Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(e);
}
}
});
panel.add(jfxPanel, BorderLayout.CENTER);
getContentPane().add(panel);
java.awt.Dimension screenSize = java.awt.Toolkit.getDefaultToolkit().getScreenSize();
setBounds((screenSize.width-460)/2, (screenSize.height-680)/2, 460, 680);
}
private void createScene() {
Platform.runLater(new Runnable() {
#Override
public void run() {
final WebView view = new WebView();
engine = view.getEngine();
engine.titleProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, final String newValue) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
SimpleSwingBrowser.this.setTitle(newValue);
}
});
}
});
engine.getLoadWorker().stateProperty().addListener(new ChangeListener<State>() {
#Override
public void changed(ObservableValue<? extends State> observable, State oldValue, final State newValue) {
if (newValue == FAILED) {
final int result = JOptionPane.showConfirmDialog(
panel,
MessagesBundle.getString("error_message_unable_connect_to_internet"),
MessagesBundle.getString("error_title_unable_connect_to_internet"),
JOptionPane.YES_NO_OPTION);
if (result == JOptionPane.YES_OPTION) {
if (loadedURL != null) {
engine.load(loadedURL);
}
}
}
}
});
// http://stackoverflow.com/questions/11206942/how-to-hide-scrollbars-in-the-javafx-webview
// hide webview scrollbars whenever they appear.
view.getChildrenUnmodifiable().addListener(new ListChangeListener<Node>() {
#Override
public void onChanged(Change<? extends Node> change) {
Set<Node> deadSeaScrolls = view.lookupAll(".scroll-bar");
for (Node scroll : deadSeaScrolls) {
scroll.setVisible(false);
}
}
});
jfxPanel.setScene(new Scene(view));
}
});
}
public void loadURL(final String url) {
Platform.runLater(new Runnable() {
#Override
public void run() {
String tmp = toURL(url);
if (tmp == null) {
tmp = toURL("http://" + url);
}
loadedURL = tmp;
engine.load(tmp);
}
});
}
private static String toURL(String str) {
try {
return new URL(str).toExternalForm();
} catch (MalformedURLException exception) {
return null;
}
}
}
Any idea why this happens? Is there anything I can do to resolve? Thanks.
As also stated in this answer you can try to use another user agent. I used the following user agent (which is simulating Firefox on macOS) some time ago when I had a similar issue with Java 1.8.0_66:
webView.getEngine().setUserAgent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:54.0) Gecko/20100101 Firefox/54.0");
That did help for some sites but not for all (e.g. Google Maps was using correct font with above user agent but not loading completly due to other issues). Updating to Java 9 (9.0.1_11 at that time) gave better results. Later I tried the following approach:
String userAgent = this.browser.getEngine().getUserAgent();
if (userAgent != null) {
String[] parts = userAgent.split(" ");
String manipulatetUserAgent = "";
for (int i = 0; i < parts.length; i++) {
if (i > 0) {
manipulatetUserAgent += " ";
}
if (parts[i] != null && parts[i].contains("JavaFX")) {
manipulatetUserAgent += "Version/13.1.1"; // current safari version
} else {
manipulatetUserAgent += parts[i];
}
}
this.browser.getEngine().setUserAgent(manipulatetUserAgent);
}
But actually some month ago I went from trying to fix JavaFX WebView to learning how to use Java Chromium Embedded Framework (JCEF) which is
a simple framework for embedding Chromium-based browsers in other applications using the Java programming language.
It certainly has more overhead during integration but it looks promising.
I was really enthusiatic about WebView at the beginning but encountered to many bugs over time. First I waited for the bugs to be fixed, but then more bugs appeared. Last thing that was frustrating me was login not working reliably on some sites (probably because they were using Google's reCAPTCHA). So be aware that the user agent fix may only help temporary if it's working at all.

JavaFX: handle key combination and mouse event simultaneously

I need to react on a key + mouse event combination like:
Ctrl + Shift + R + left_mousebutton_clicked
But I can't figure out, how to handle the "left_mousebutton_clicked" only if the key combination of Ctrl + Shift + R occurs.
A solution like
if(MouseEvent.isControlDown())
will not work cause there may be different key combinations with any kind of the letters.
Any ideas?
You can use a container to store the currently pressed keys:
private final Set<KeyCode> pressedKeys = new HashSet<>();
You can attach listeners to the Scene of the control the you want to target with the mouse-click:
scene.setOnKeyPressed(e -> pressedKeys.add(e.getCode()));
scene.setOnKeyReleased(e -> pressedKeys.remove(e.getCode()));
While these listeners maintain the set, you can simply attach a listener on the target Node:
Label targetLabel = new Label("Target Label");
targetLabel.setOnMouseClicked(e -> {
if (e.getButton() == MouseButton.PRIMARY &&
pressedKeys.contains(KeyCode.R) &&
e.isShortcutDown() &&
e.isShiftDown())
System.out.println("handled!");
});
Example Application:
public class MouseClickExample extends Application {
private final Set<KeyCode> pressedKeys = new HashSet<>();
public static void main(String[] args) {
launch(args);
}
#Override public void start(Stage stage) {
VBox root = new VBox();
Scene scene = new Scene(root, 450, 250);
scene.setOnKeyPressed(e -> pressedKeys.add(e.getCode()));
scene.setOnKeyReleased(e -> pressedKeys.remove(e.getCode()));
Label targetLabel = new Label("Target Label");
targetLabel.setOnMouseClicked(e -> {
if (e.getButton() == MouseButton.PRIMARY && pressedKeys.contains(KeyCode.R) && e.isShortcutDown() && e.isShiftDown())
System.out.println("handled!");
});
root.getChildren().add(targetLabel);
stage.setScene(scene);
stage.show();
}
}
Note: The meta keys are also stored in the Set but they are not used by this example. The meta keys could be also checked in the set rather than using methods on the mouse-event.
Both ctrl and shift can be done the way you aproached it there. The left mouse key is the PrimaryButton
if(mouseEvent.isControlDown() && mouseEvent.isShiftDown && mouseEvent.isPrimaryKeyDown){
// Do your stuff here
}
And for the "non special" key (like r) I thnk you need to make a global boolean - and a seperate keyevent listener for it. So:
boolean rIsDown = false;
scene.setOnKeyPressed(e -> {
if(e.getCode() == KeyCode.R){
System.out.println("r was pressed");
//set your global boolean "rIsDown" to true
}
});
scene.setOnKeyReleased(e -> {
if(e.getCode() == KeyCode.R){
System.out.println("r was released");
//set it rIsDown back to false
}
});
Then use your it together with the other conditions...
if(mouseEvent.isControlDown() && mouseEvent.isShiftDown && rIsDown && mouseEvent.isPrimaryKeyDown){
// Do your stuff here
}

javafx: bug when trying to close window properly

I am designing the close window functionality for my desktop application. A high level explanation of the functionality is listed:
If I click the Exit menuItem, it prompts a ConfirmBox the user to confirm whether he wants to save or not before closing the application.
If the user click on the CloseButton on the window to force close the window (i.e. setOnCloseRequest function), the Exit menuItem event is fire off, which brings the user to case (1) again.
Within my ConfirmBoxcode, I have bind ENTER key to save things, N key to not save things and ESCAPE key to close confirmBox.
I have also set accelerator for the Exit menuItem (METAKEY + E).
Everything works fine. However, there is a minor bug if I follow this special sequence of steps. Whenever I use the accelerator for the Exit menuItem (i.e. METAKEY + E) and then I press either one of the 3 keys(ENTER, ESCAPE, N), the confirmBox closes but it pops up again.
I am wondering why is this happening only in this very special case?
public class ConfirmBox {
// answer[0] determines the need to Save
// answer[1] determines whether to close the application or not
private static boolean[] answer = new boolean[]{false,false};
private static Stage window;
public static boolean[] displayWarning(String title, String message){
window = new Stage();
window.initModality(Modality.APPLICATION_MODAL);
window.setTitle(title);
window.setMinWidth(300);
Label label = new Label();
label.setText(message);
Button yesButton = new Button("Yes");
Button noButton = new Button("No");
// needToSave = true, close Application = true and close this confirmbox
yesButton.setOnAction(ey ->{
answer[0] = true;
answer[1] = true;
window.close();
});
// needToSave = false, close Application = true and close this confirmbox
noButton.setOnAction(en -> {
answer[0] = false;
answer[1] = true;
window.close();
});
// needToSave = false, close Application = false and close this confirmbox
window.setOnCloseRequest(e -> {
answer[0] = false;
answer[1] = false;
closeConfirmBox();
});
// key binding
window.addEventHandler(KeyEvent.KEY_PRESSED, e -> {
if ( e.getCode() == KeyCode.N){
noButton.fire();
e.consume();
}
});
// bind enter key to yesButton
window.addEventHandler(KeyEvent.KEY_PRESSED, ev -> {
if (ev.getCode() == KeyCode.ENTER ){
yesButton.fire();
ev.consume();
}
});
window.addEventFilter(KeyEvent.KEY_PRESSED, ev ->{
if(ev.getCode()==KeyCode.ESCAPE){
ev.consume();
answer[0] = false;
answer[1] = false;
closeConfirmBox();
}
});
VBox layout = new VBox(20);
layout.setPadding(new Insets(20,5,20,5));
HBox bottomLayout = new HBox(50);
bottomLayout.setPadding(new Insets(20,5,20,5));
bottomLayout.getChildren().addAll(yesButton,noButton);
bottomLayout.setAlignment(Pos.CENTER);
layout.getChildren().addAll(label,bottomLayout);
layout.setAlignment(Pos.CENTER);
Scene scene = new Scene(layout);
window.setScene(scene);
window.showAndWait();
return answer;
}
public static void closeConfirmBox(){
window.close();
}
}
Within my controller class, this is how I designed my MenuItem menuItemExit.
menuItemExit.setOnAction(new EventHandler<ActionEvent>(){
#Override
public void handle(ActionEvent e){
//System.out.println("set stage" + primaryStage);
boolean[] answer;
boolean needToSave = false;
boolean closeApplication = false;
if(saved.get() == false){
answer = ConfirmBox.displayWarning("Warning", "Do you want to save your stuff?");
needToSave = answer[0];
closeApplication = answer[1];
}
if(needToSave == true){
menuItemSave.fire();
}
if(closeApplication== true){
Platform.runLater(new Runnable() {
public void run() {
close();
}
});
}
}
});
primaryStage.setOnCloseRequest(e -> {
e.consume();
menuItemExit.fire();
});
menuItemExit.setAccelerator(new KeyCodeCombination(KeyCode.E, KeyCombination.META_DOWN));
public void close(){
this.primaryStage.close();
}

JRadioButton navigation with arrow keys

I am trying to get a group of JRadioButtons to be navigable using the arrow keys. I was going to implement this manually with KeyListeners, but apparently this behavior is already supposed to work for at least the last 8 years (http://bugs.sun.com/view_bug.do?bug_id=4104452). However, it's not working for me: pressing the arrow keys does nothing. Java version is 7u45 on Windows.
A standalone test case to see what I'm talking about:
import java.awt.*;
import javax.swing.*;
public class Test {
public static void main(final String[] args) {
if (!EventQueue.isDispatchThread()) {
try {
EventQueue.invokeAndWait(new Runnable() {
public void run() {
main(args);
}
});
} catch (Exception e) {
throw new RuntimeException(e);
}
return;
}
try {
//UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
//UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel");
} catch (Throwable t) {
throw new RuntimeException(t);
}
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
ButtonGroup group = new ButtonGroup();
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
JRadioButton rb;
rb = new JRadioButton("Option A");
panel.add(rb);
group.add(rb);
rb = new JRadioButton("Option B");
panel.add(rb);
group.add(rb);
rb = new JRadioButton("Option C");
panel.add(rb);
group.add(rb);
frame.add(panel);
frame.pack();
frame.setVisible(true);
}
}
I have tried using different look & feels, different containers, and different layout managers, but it still does not work.
You need to add the right/left (up/down?) keys to the focus traversal policy of each radio button. For example to add the right/left arrow keys:
Set set = new HashSet( rb.getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS ) );
set.add( KeyStroke.getKeyStroke( "RIGHT" ) );
rb.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, set );
set = new HashSet( rb.getFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS ) );
set.add( KeyStroke.getKeyStroke( "LEFT" ) );
rb.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, set );
Read the section from the Swing tutorial on How to Use the Focus Subsystem for more information.
I believe you can achieve your goal using KeyBindings instead of KeyListeners. In many cases bindings are actually recommended over KeyListeners, as the second ones can generate many problems (frame catching the key activity must be active one etc.)
Thank you everyone for the answers.
I discovered the reason for my confusion. Apparently, when the Sun bug report system says that a bug's status is "Closed" and its "Resolved Date" is "2005-07-19", that doesn't mean the bug is fixed at all. Apparently, it's just logged as a duplicate of some other (newer?) bug. Nearly 16 years since it was first reported it still isn't fixed. Whatever.
The needed behavior is much more subtle than I realized. I experimented in native Windows dialogs in various programs:
Most button-like components: buttons, checkboxes, and radio buttons, implement the arrow keys for focus navigation. In Java this corresponds to the AbstractButton class. (JMenuItem is also a subclass of that, but that has its own distinct arrow key behavior.)
Only radio buttons get selected/checked during this navigation.
Unfocusable (including disabled or invisible) components must be skipped.
Attempting to navigate before the first button in a group or after the last one is inconsistent: on some dialogs it loops from end to end; on others it moves irreversibly onto non-button components; and on yet others it does nothing. I experimented with all these different behaviors and none of them was particularly better than the others.
I implemented a looping behavior below as it felt slightly more fluent. The navigation silently skips past non-AbstractButton components, forming a sort-of separate focus cycle private to buttons. This is dubious but sometimes needed when a set of related checkboxes or radio buttons are mixed with other components. Testing for a common parent component to identify groups would also be a reasonable behavior, but that didn't work in one dialog where I'd used separate components purely for layout reasons (to implement a line break in a FlowLayout).
As suggested I studied up on InputMaps and ActionMaps instead of using a KeyListener. I've always avoided the maps as they seem overcomplicated but I guess I see the advantage of being able to easily override the binding.
This code uses an auxialiary look and feel to install the desired behavior for all AbstractButton components application-wide (which is a nice technique I found out about here). I've tested it with several different dialog boxes and windows and it seems to be okay. If it causes issues I'll update this post.
Call:
ButtonArrowKeyNavigation.install();
once at application startup to install it.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ButtonArrowKeyNavigation {
private ButtonArrowKeyNavigation() {}
public static void install() {
UIManager.addAuxiliaryLookAndFeel(lookAndFeel);
}
private static final LookAndFeel lookAndFeel = new LookAndFeel() {
private final UIDefaults defaults = new UIDefaults() {
#Override
public javax.swing.plaf.ComponentUI getUI(JComponent c) {
if (c instanceof AbstractButton && !(c instanceof JMenuItem)) {
if (c.getClientProperty(this) == null) {
c.putClientProperty(this, Boolean.TRUE);
configure(c);
}
}
return null;
}
};
#Override public UIDefaults getDefaults() { return defaults; };
#Override public String getID() { return "ButtonArrowKeyNavigation"; }
#Override public String getName() { return getID(); }
#Override public String getDescription() { return getID(); }
#Override public boolean isNativeLookAndFeel() { return false; }
#Override public boolean isSupportedLookAndFeel() { return true; }
};
private static void configure(JComponent c) {
InputMap im = c.getInputMap(JComponent.WHEN_FOCUSED);
ActionMap am = c.getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), "focusPreviousButton");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), "focusPreviousButton");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), "focusNextButton");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), "focusNextButton");
am.put("focusPreviousButton", focusPreviousButton);
am.put("focusNextButton", focusNextButton);
}
private static final Action focusPreviousButton = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
move((AbstractButton)e.getSource(), -1);
}
};
private static final Action focusNextButton = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
move((AbstractButton)e.getSource(), +1);
}
};
private static void move(AbstractButton ab, int direction) {
Container focusRoot = ab.getFocusCycleRootAncestor();
FocusTraversalPolicy focusPolicy = focusRoot.getFocusTraversalPolicy();
Component toFocus = ab, loop = null;
for (;;) {
toFocus = direction > 0
? focusPolicy.getComponentAfter(focusRoot, toFocus)
: focusPolicy.getComponentBefore(focusRoot, toFocus);
if (toFocus instanceof AbstractButton) break;
if (toFocus == null) return;
// infinite loop protection; should not be necessary, but just in
// case all buttons are somehow unfocusable at the moment this
// method is called:
if (loop == null) loop = toFocus; else if (loop == toFocus) return;
}
if (toFocus.requestFocusInWindow()) {
if (toFocus instanceof JRadioButton) {
((JRadioButton)toFocus).setSelected(true);
}
}
}
}
Here is my example of JRadioButtons can be navigable using the arrow keys(UP and Down) and modified few codes for you.
public class JRadioButton extends JPanel {
private JRadioButton[] buttons;
public JRadioButtonTest(int row) {
ButtonGroup group = new ButtonGroup();
buttons = new JRadioButton[row];
for (int i = 0; i < buttons.length; i++) {
final int curRow = i;
buttons[i] = new JRadioButton("Option " + i);
buttons[i].addKeyListener(enter);
buttons[i].addKeyListener(new KeyAdapter() {
#Override
public void keyPressed(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_UP:
if (curRow > 0)
buttons[curRow - 1].requestFocus();
break;
case KeyEvent.VK_DOWN:
if (curRow < buttons.length - 1)
buttons[curRow + 1].requestFocus();
break;
default:
break;
}
}
});
group.add(buttons[i]);
add(buttons[i]);
}
}
private KeyListener enter = new KeyAdapter() {
#Override
public void keyTyped(KeyEvent e) {
if (e.getKeyChar() == KeyEvent.VK_ENTER) {
((JButton) e.getComponent()).doClick();
}
}
};
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new JRadioButton(3));
frame.pack();
frame.setVisible(true);
}
}
The core implement method is calling requestFocus() on the correct JRadioButton when an arrow key is called. Extra KeyListener for when the Enter key is pressed.
You can use this KeyListener to your program and add more key.
Good luck!

Javafx 2 click and double click

I would like to know if it was possible to detect the double-click in JavaFX 2 ? and how ?
I would like to make different event between a click and a double click.
Thanks
Yes you can detect single, double even multiple clicks:
myNode.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
if(mouseEvent.getButton().equals(MouseButton.PRIMARY)){
if(mouseEvent.getClickCount() == 2){
System.out.println("Double clicked");
}
}
}
});
MouseButton.PRIMARY is used to determine if the left (commonly) mouse button is triggered the event. Read the api of getClickCount() to conclude that there maybe multiple click counts other than single or double. However I find it hard to distinguish between single and double click events. Because the first click count of the double click will rise a single event as well.
Here is another piece of code which can be used if you have to distinguish between a single- and a double-click and have to take a specific action in either case.
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class DoubleClickDetectionTest extends Application {
boolean dragFlag = false;
int clickCounter = 0;
ScheduledThreadPoolExecutor executor;
ScheduledFuture<?> scheduledFuture;
public DoubleClickDetectionTest() {
executor = new ScheduledThreadPoolExecutor(1);
executor.setRemoveOnCancelPolicy(true);
}
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
StackPane root = new StackPane();
primaryStage.setScene(new Scene(root, 400, 400));
primaryStage.show();
root.setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
if (e.getButton().equals(MouseButton.PRIMARY)) {
dragFlag = true;
}
}
});
root.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
if (e.getButton().equals(MouseButton.PRIMARY)) {
if (!dragFlag) {
System.out.println(++clickCounter + " " + e.getClickCount());
if (e.getClickCount() == 1) {
scheduledFuture = executor.schedule(() -> singleClickAction(), 500, TimeUnit.MILLISECONDS);
} else if (e.getClickCount() > 1) {
if (scheduledFuture != null && !scheduledFuture.isCancelled() && !scheduledFuture.isDone()) {
scheduledFuture.cancel(false);
doubleClickAction();
}
}
}
dragFlag = false;
}
}
});
}
#Override
public void stop() {
executor.shutdown();
}
private void singleClickAction() {
System.out.println("Single-click action executed.");
}
private void doubleClickAction() {
System.out.println("Double-click action executed.");
}
}
Adhering to Java SE 8 lambda expressions would look something like this:
node.setOnMouseClicked(event -> {
if(event.getButton().equals(MouseButton.PRIMARY) && event.getClickCount() == 2) {
handleSomeAction();
}
});
Once you get used to lambda expressions - they end up being more understandable than the original class instantiation and overriding (x) method. -In my opinion-
The response by P. Pandey is the simplest approach which actually distinguishes between single and double click, but it did not work for me. For one, the function "currentTimeMillis" already returns milliseconds, so dividing it by 1000 does not seem to be necessary. The version below worked for me in a more consistent fashion.
#Override
public void handle(MouseEvent t) {
long diff = 0;
currentTime=System.currentTimeMillis();
if(lastTime!=0 && currentTime!=0){
diff=currentTime-lastTime;
if( diff<=215)
isdblClicked=true;
else
isdblClicked=false;
}
lastTime=currentTime;
System.out.println("IsDblClicked()"+isdblClicked);
//use the isdblClicked flag...
}
Not sure if someone still follows this OP or refer it, but below is my version of differentiating single click to double click. While most of the answers are quite acceptable, it would be really useful if it can be done in a proper resuable way.
One of the challenge I encountered is the need to have the single-double click differentiation on multiple nodes at multiple places. I cannot do the same repetitive cumbersome logic on each and every node. It should be done in a generic way.
So I opted to implement a custom EventDispatcher and use this dispatcher on node level or I can apply it directly to Scene to make it applicable for all child nodes.
For this I created a new MouseEvent namely 'MOUSE_DOUBLE_CLICKED", so tthat I am still sticking with the standard JavaFX practises. Now I can include the double_clicked event filters/handlers just like other mouse event types.
node.addEventFilter(CustomMouseEvent.MOUSE_DOUBLE_CLICKED, e->{..<code to handle double_click>..});
node.addEventHandler(CustomMouseEvent.MOUSE_DOUBLE_CLICKED, e->{..<code to handle double_click>..});
Below is the implementation and complete working demo of this custom event dispatcher.
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.*;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class DoubleClickEventDispatcherDemo extends Application {
#Override
public void start(Stage stage) throws Exception {
Rectangle box1 = new Rectangle(150, 150);
box1.setStyle("-fx-fill:red;-fx-stroke-width:2px;-fx-stroke:black;");
addEventHandlers(box1, "Red Box");
Rectangle box2 = new Rectangle(150, 150);
box2.setStyle("-fx-fill:yellow;-fx-stroke-width:2px;-fx-stroke:black;");
addEventHandlers(box2, "Yellow Box");
HBox pane = new HBox(box1, box2);
pane.setSpacing(10);
pane.setAlignment(Pos.CENTER);
addEventHandlers(pane, "HBox");
Scene scene = new Scene(new StackPane(pane), 450, 300);
stage.setScene(scene);
stage.show();
// SETTING CUSTOM EVENT DISPATCHER TO SCENE
scene.setEventDispatcher(new DoubleClickEventDispatcher(scene.getEventDispatcher()));
}
private void addEventHandlers(Node node, String nodeId) {
node.addEventFilter(MouseEvent.MOUSE_CLICKED, e -> System.out.println("" + nodeId + " mouse clicked filter"));
node.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> System.out.println("" + nodeId + " mouse clicked handler"));
node.addEventFilter(CustomMouseEvent.MOUSE_DOUBLE_CLICKED, e -> System.out.println("" + nodeId + " mouse double clicked filter"));
node.addEventHandler(CustomMouseEvent.MOUSE_DOUBLE_CLICKED, e -> System.out.println(nodeId + " mouse double clicked handler"));
}
/**
* Custom MouseEvent
*/
interface CustomMouseEvent {
EventType<MouseEvent> MOUSE_DOUBLE_CLICKED = new EventType<>(MouseEvent.ANY, "MOUSE_DBL_CLICKED");
}
/**
* Custom EventDispatcher to differentiate from single click with double click.
*/
class DoubleClickEventDispatcher implements EventDispatcher {
/**
* Default delay to fire a double click event in milliseconds.
*/
private static final long DEFAULT_DOUBLE_CLICK_DELAY = 215;
/**
* Default event dispatcher of a node.
*/
private final EventDispatcher defaultEventDispatcher;
/**
* Timeline for dispatching mouse clicked event.
*/
private Timeline clickedTimeline;
/**
* Constructor.
*
* #param initial Default event dispatcher of a node
*/
public DoubleClickEventDispatcher(final EventDispatcher initial) {
defaultEventDispatcher = initial;
}
#Override
public Event dispatchEvent(final Event event, final EventDispatchChain tail) {
final EventType<? extends Event> type = event.getEventType();
if (type == MouseEvent.MOUSE_CLICKED) {
final MouseEvent mouseEvent = (MouseEvent) event;
final EventTarget eventTarget = event.getTarget();
if (mouseEvent.getClickCount() > 1) {
if (clickedTimeline != null) {
clickedTimeline.stop();
clickedTimeline = null;
final MouseEvent dblClickedEvent = copy(mouseEvent, CustomMouseEvent.MOUSE_DOUBLE_CLICKED);
Event.fireEvent(eventTarget, dblClickedEvent);
}
return mouseEvent;
}
if (clickedTimeline == null) {
final MouseEvent clickedEvent = copy(mouseEvent, mouseEvent.getEventType());
clickedTimeline = new Timeline(new KeyFrame(Duration.millis(DEFAULT_DOUBLE_CLICK_DELAY), e -> {
Event.fireEvent(eventTarget, clickedEvent);
clickedTimeline = null;
}));
clickedTimeline.play();
return mouseEvent;
}
}
return defaultEventDispatcher.dispatchEvent(event, tail);
}
/**
* Creates a copy of the provided mouse event type with the mouse event.
*
* #param e MouseEvent
* #param eventType Event type that need to be created
* #return New mouse event instance
*/
private MouseEvent copy(final MouseEvent e, final EventType<? extends MouseEvent> eventType) {
return new MouseEvent(eventType, e.getSceneX(), e.getSceneY(), e.getScreenX(), e.getScreenY(),
e.getButton(), e.getClickCount(), e.isShiftDown(), e.isControlDown(), e.isAltDown(),
e.isMetaDown(), e.isPrimaryButtonDown(), e.isMiddleButtonDown(),
e.isSecondaryButtonDown(), e.isSynthesized(), e.isPopupTrigger(),
e.isStillSincePress(), e.getPickResult());
}
}
}
Here is how I have implemented double click
if (e.getEventType().equals(MouseEvent.MOUSE_CLICKED) && !drag_Flag) {
long diff = 0;
if(time1==0)
time1=System.currentTimeMillis();
else
time2=System.currentTimeMillis();
if(time1!=0 && time2!=0)
diff=time2-time1;
if((diff/1000)<=215 && diff>0)
{
isdblClicked=true;
}
else
{
isdblClicked=false;
}
System.out.println("IsDblClicked()"+isdblClicked);
}
Since it is not possible to distinguish between single-click and double-click by default, we use the following approach:
On single-click, we wrap the single-click operation in an abortable runnable. This runnable waits a certain amount of time (i.e., SINGLE_CLICK_DELAY) before being executed.
In the meantime, if a second click, i.e., a double-click, occurs, the single-click operation gets aborted and only the double-click operation is performed.
This way, either the single-click or the double-click operation is performed, but never both.
Following is the full code. To use it, only the three TODO lines have to be replaced by the wanted handlers.
private static final int SINGLE_CLICK_DELAY = 250;
private ClickRunner latestClickRunner = null;
private class ClickRunner implements Runnable {
private final Runnable onSingleClick;
private boolean aborted = false;
public ClickRunner(Runnable onSingleClick) {
this.onSingleClick = onSingleClick;
}
public void abort() {
this.aborted = true;
}
#Override
public void run() {
try {
Thread.sleep(SINGLE_CLICK_DELAY);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (!aborted) {
System.out.println("Execute Single Click");
Platform.runLater(() -> onSingleClick.run());
}
}
}
private void init() {
container.setOnMouseClicked(me -> {
switch (me.getButton()) {
case PRIMARY:
if (me.getClickCount() == 1) {
System.out.println("Single Click");
latestClickRunner = new ClickRunner(() -> {
// TODO: Single-left-click operation
});
CompletableFuture.runAsync(latestClickRunner);
}
if (me.getClickCount() == 2) {
System.out.println("Double Click");
if (latestClickRunner != null) {
System.out.println("-> Abort Single Click");
latestClickRunner.abort();
}
// TODO: Double-left-click operation
}
break;
case SECONDARY:
// TODO: Right-click operation
break;
default:
break;
}
});
}
A solution using PauseTransition:
PauseTransition singlePressPause = new PauseTransition(Duration.millis(500));
singlePressPause.setOnFinished(e -> {
// single press
});
node.setOnMousePressed(e -> {
if (e.isPrimaryButtonDown() && e.getClickCount() == 1) {
singlePressPause.play();
}
if (e.isPrimaryButtonDown() && e.getClickCount() == 2) {
singlePressPause.stop();
// double press
}
});
An alternative to single click vs. double click that I'm using is single click vs. press-and-hold (for about a quarter to a half second or so), then release the button. The technique can use a threaded abortable timer as in some of the code snippets above to distinguish between the two. Assuming that the actual event handling happens on the button release, this alternative has the advantage that single click works normally (i.e., without any delay), and for press-and-hold you can give the user some visual feedback when the button has been held long enough to be released (so there's never any ambiguity about which action was performed).
If you are testing how many mouse buttons (==2) are pressed, do not code it in sub-method! The next is working:
listView.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
if( mouseEvent.getButton().equals(MouseButton.SECONDARY)) {
System.out.println("isSecondaryButtonDown");
mouseEvent.consume();
// ....
}
else
if(mouseEvent.getButton().equals(MouseButton.PRIMARY)){
if(mouseEvent.getClickCount() == 2){
System.out.println("Double clicked");
// mousePressedInListViewDC(mouseEvent);
}
else
if(mouseEvent.getClickCount() == 1){
System.out.println("1 clicked");
mousePressedInListView1C(mouseEvent);
}
}
}
})
;
I ran in the same problem, and what I noticed is that single and double click ARE distinguished with basic :
Button btn = new Button("Double click me too");
btn.setOnMousePressed(mouseEvent -> {
// CLICK catches
if (mouseEvent.getClickCount() == 1) {
System.out.println("Button clicked");
} else if (mouseEvent.getClickCount() == 2)
System.out.println("Button double clicked");
});
But a 'single' click is catched as part of the double click. So you will see on the console :
Using mainly the answer of #markus-weninger, I built up a Class extending Button to expose 2 new EventHandlers :
setOnMouseSingleClicked(EventHandler<MouseEvent> eventHandler)
setOnMouseDoubleClicked(EventHandler<MouseEvent> eventHandler)
So with the full example code bellow, when double clicking on last button, we get :
Keep in mind :
The obvious drawback is that even a single click caught with setOnMouseSingleClicked will be delayed with the singleClickDelayMillis (exposed variable which should be set accordingly to the OS, as mentioned by Kleopatra).
Another noticeable fact, is that I extended Button, and not Node where it should be : The Class where the onMouseClicked(...) is implemented.
As a last comment, I decided to add a new EventHandler rather than using the existing setOnMousePressed, setOnMouseReleased or setOnMouseClicked so that the developer can still fully implement these convenience EventHandlers. For example in order to have immediate response from a click on the button without waiting for the singleClickDelayMillis. But this means that if you implement both, the setOnMouseClicked will be fired even on a double click... beware.
Here comes the code :
import java.util.concurrent.CompletableFuture;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.beans.property.ObjectProperty;
import javafx.event.EventHandler;
import javafx.beans.property.SimpleObjectProperty;
public class DblClickCatchedWithoutSingleClick extends Application {
public class ButtonWithDblClick extends Button {
private long singleClickDelayMillis = 250;
private ClickRunner latestClickRunner = null;
private ObjectProperty<EventHandler<MouseEvent>> onMouseSingleClickedProperty = new SimpleObjectProperty<>();
private ObjectProperty<EventHandler<MouseEvent>> onMouseDoubleClickedProperty = new SimpleObjectProperty<>();
// CONSTRUCTORS
public ButtonWithDblClick() {
super();
addClickedEventHandler();
}
public ButtonWithDblClick(String text) {
super(text);
addClickedEventHandler();
}
public ButtonWithDblClick(String text, Node graphic) {
super(text, graphic);
addClickedEventHandler();
}
private class ClickRunner implements Runnable {
private final Runnable onClick;
private boolean aborted = false;
public ClickRunner(Runnable onClick) {
this.onClick = onClick;
}
public void abort() {
this.aborted = true;
}
#Override
public void run() {
try {
Thread.sleep(singleClickDelayMillis);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (!aborted) {
Platform.runLater(onClick::run);
}
}
}
private void addClickedEventHandler() {
//Handling the mouse clicked event (not using 'onMouseClicked' so it can still be used by developer).
EventHandler<MouseEvent> eventHandler = me -> {
switch (me.getButton()) {
case PRIMARY:
if (me.getClickCount() == 1) {
latestClickRunner = new ClickRunner(() -> {
System.out.println("ButtonWithDblClick : SINGLE Click fired");
onMouseSingleClickedProperty.get().handle(me);
});
CompletableFuture.runAsync(latestClickRunner);
}
if (me.getClickCount() == 2) {
if (latestClickRunner != null) {
latestClickRunner.abort();
}
System.out.println("ButtonWithDblClick : DOUBLE Click fired");
onMouseDoubleClickedProperty.get().handle(me);
}
break;
case SECONDARY:
// Right-click operation. Not implemented since usually no double RIGHT click needs to be caught.
break;
default:
break;
}
};
//Adding the event handler
addEventHandler(MouseEvent.MOUSE_CLICKED, eventHandler);
}
public void setOnMouseSingleClicked(EventHandler<MouseEvent> eventHandler) {
this.onMouseSingleClickedProperty.set(eventHandler);
}
public void setOnMouseDoubleClicked(EventHandler<MouseEvent> eventHandler) {
this.onMouseDoubleClickedProperty.set(eventHandler);
}
public long getSingleClickDelayMillis() {
return singleClickDelayMillis;
}
public void setSingleClickDelayMillis(long singleClickDelayMillis) {
this.singleClickDelayMillis = singleClickDelayMillis;
}
}
public void start(Stage stage) {
VBox root = new VBox();
Label lbl = new Label("Double click me");
lbl.setOnMouseClicked(mouseEvent -> {
// CLICK catches
if (mouseEvent.getClickCount() == 2) {
System.out.println("Label double clicked");
} else if (mouseEvent.getClickCount() == 1)
System.out.println("Label clicked");
});
Button btn = new Button("Double click me too");
btn.setOnMousePressed(mouseEvent -> {
// CLICK catches
if (mouseEvent.getClickCount() == 1) {
System.out.println("Button clicked");
} else if (mouseEvent.getClickCount() == 2)
System.out.println("Button double clicked");
});
ButtonWithDblClick btn2 = new ButtonWithDblClick("Double click me three ;-)");
btn2.setOnMouseSingleClicked(me -> {
System.out.println("BUTTON_2 : Fire SINGLE Click");
});
btn2.setOnMouseDoubleClicked(me -> {
System.out.println("BUTTON_2 : Fire DOUBLE Click");
});
root.getChildren().add(lbl);
root.getChildren().add(btn);
root.getChildren().add(btn2);
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}

Categories

Resources