JavaFX: handle key combination and mouse event simultaneously - java

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
}

Related

How to implement CAPS LOCK alert bubble on password field in JavaFX?

I’m trying to implement a caps lock alert on password field. If caps lock is ON then the bubble will appear below the password field. I’ve searched a lot but didn’t get any solution that how can I implement such bubble on input fields in JavaFX. I’ve found some source code to get the caps lock state.
boolean isOn=Toolkit.getDefaultToolkit().getLockingKeyState(KeyEvent.VK_CAPS_LOCK);
scene.setOnKeyReleased( event -> {
if ( event.getCode() == KeyCode.CAPS ) {
System.out.println("Capslock pressed");
System.out.println("Capslock state: " + isOn);
}
});
But my problem is how to implement the bubble alert on text field.
Here you can see what I have to do.
It would be helpful if you suggest me some possible ways as I’m new in JavaFX. Is there any JavaFX library to do such bubble alert on input fields?
It sounds like you have figured out how to get the input state you could try something like this for the listener
public class Main extends Application {
private Label capsLabel = new Label("Caps is ON");
private boolean capsIsOn;
#Override
public void start(Stage stage) {
System.out.println(Toolkit.getDefaultToolkit().getLockingKeyState(20));
//Try adding this line to get state on startup
capsLabel.setVisible(Toolkit.getDefaultToolkit().getLockingKeyState(20));
TextField textField = new TextField();
//Also try adding this line and to check again so when the field
//is selected it will check again
textField.setOnMouseClicked(event -> capsLabel.setVisible(Toolkit.getDefaultToolkit().getLockingKeyState(20)));
textField.setOnKeyReleased(keyEvent -> {
if(keyEvent.getCode().toString().equals("CAPS")){
capsIsOn = !capsIsOn;
capsLabel.setVisible(capsIsOn);
}
});
VBox vBox = new VBox();
vBox.getChildren().addAll(textField, capsLabel);
stage = new Stage();
stage.setScene(new Scene(vBox));
stage.show();
}
public static void main(String[] args) { launch(args); }
}
Alternatively you could set this on a timer and have it constantly checking personally I don't like the idea of constant use of computer resources but its not my project.
JavaFX doesn’t have any way to detect CapsLock. In theory, you could install a Scene-wide listener, but that wouldn’t catch when the state changes while other applications have focus.
Mixing AWT/Swing and JavaFX is perilous, because each has its own thread on which nearly all of its methods must be executed. Since CapsLock needs to be polled anyway, it makes sense to use javax.swing.Timer, which both executes an action regularly and ensures that action is run in the proper thread (the AWT event dispatch thread):
BooleanProperty capsLockOn = new SimpleBooleanProperty();
EventQueue.invokeLater(() -> {
Timer timer = new Timer(500, e -> {
boolean state = Toolkit.getDefaultToolkit().getLockingKeyState(
KeyEvent.VK_CAPS_LOCK);
Platform.runLater(() -> capsLockOn.set(state));
});
timer.start();
Platform.runLater(() -> {
Window window = passwordField.getScene().getWindow();
window.setOnShown(e -> EventQueue.invokeLater(timer::restart));
window.setOnHidden(e -> EventQueue.invokeLater(timer::stop));
});
});
Region message = new BorderPane(new Label("Caps Lock is on"));
message.setStyle(
"-fx-background-color: #f4f4f4;" +
"-fx-border-color: black;" +
"-fx-border-width: 1px;" +
"-fx-padding: 1em 1em 0.75em 1em;" +
"-fx-shape: 'M 0 10 h 20 l 10 -10 l 10 10 h 150 v 90 h -190 z';"
);
Popup capsLockWarning = new Popup();
capsLockWarning.getContent().add(message);
capsLockOn.addListener((o, wasOn, on) -> {
if (on) {
Point2D location =
passwordField.localToScreen(-15, passwordField.getHeight());
capsLockWarning.show(passwordField,
location.getX(), location.getY());
} else {
capsLockWarning.hide();
}
});

Double tab change event on tab closing in JavaFX

I have the following program:
public class MainClass extends Application {
public static void main(String[] arg) {
launch(arg);
}
#Override
public void start(Stage primaryStage) throws Exception {
TabPane tabPane = new TabPane();
tabPane.getSelectionModel().selectedItemProperty().addListener((ov, oldTab, newTab) -> {
System.out.println("Tab change: " + oldTab + "/" + newTab);
});
Tab tab = new Tab("Test tab");
tab.setOnCloseRequest((event) -> {
System.out.println("Removing tab");
event.consume();
//I need to remove tab manually
tabPane.getTabs().remove(tab);
});
System.out.println("Adding tab");
tabPane.getTabs().add(tab);
Group root = new Group(tabPane);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
}
When I run it and click close icon on Tab I have the following output of the program:
Adding tab
Tab change: null/javafx.scene.control.Tab#70b1aa69
Removing tab
Tab change: javafx.scene.control.Tab#70b1aa69/null
Tab change: null/javafx.scene.control.Tab#70b1aa69
As you see I get two Tab change events when I closing tab but I need only one. How to fix it?
Interesting bug - was puzzled as to why/how the removed tab can still be the selected tab even though no longer in the tabs list.
First question was, where exactly the selection happens: that's done in the mousePressedHandler installed by the TabHeaderSkin
setOnMousePressed(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent me) {
if (getTab().isDisable()) {
return;
}
if (me.getButton().equals(MouseButton.MIDDLE)) {
if (showCloseButton()) {
Tab tab = getTab();
if (behavior.canCloseTab(tab)) {
removeListeners(tab);
behavior.closeTab(tab);
}
}
} else if (me.getButton().equals(MouseButton.PRIMARY)) {
behavior.selectTab(getTab());
}
}
});
But then: how comes that this handler is still active after removal of the tab (and hopefully its visuals as well)? The cleanup of the visual parts is the task of TabPaneSkin, it listens to the tabs list and removes the TabHeaderSkin (aka: the component that shows the tab above its content). But the cleanup is not immediately complete for two reasons:
fade-out animation keeps the header alive until the animation is ready, that's fine
header's internal cleanup (messaged via header.removeListeners) is incomplete, as it removes children and listeners, but fails to remove the mouseHandler - and that's the bug.
Code from TabHeaderSkin:
private void removeListeners(Tab tab) {
listener.dispose();
inner.getChildren().clear();
getChildren().clear();
// following line is missing:
setOnMousePressed(null)
}
A way to hack around is to register our own listener on tabs, and force the handler to null on removal. Note: the listener must be notified after core did its job, so either install in a custom skin or after the tabPane's skin has been set.
To illustrate, I modified your example accordingly:
public class TabPaneRemoveSelected extends Application {
public static void main(String[] arg) {
launch(arg);
}
public static class MyTabSkin extends TabPaneSkin {
public MyTabSkin(TabPane pane) {
super(pane);
pane.getTabs().addListener(this::tabsChanged);
}
protected void tabsChanged(Change<? extends Tab> c) {
while (c.next()) {
if (c.wasRemoved()) {
// lookup all TabHeaderSkins
Set<Node> tabHeaders = getSkinnable().lookupAll(".tab");
tabHeaders.stream()
.filter(p -> p instanceof Parent)
.map(p -> (Parent) p)
.forEach(p -> {
// all children removed indicates being in the process
// of being removed
if (p.getChildrenUnmodifiable().size() == 0) {
// complete removeListeners
p.setOnMousePressed(null);
}
}
);
}
}
}
}
#Override
public void start(Stage primaryStage) throws Exception {
TabPane tabPane = new TabPane() {
#Override
protected Skin<?> createDefaultSkin() {
return new MyTabSkin(this);
}
};
tabPane.getSelectionModel().selectedItemProperty().addListener((ov, oldTab, newTab) -> {
System.out.println("Tab change: " + oldTab + "/" + newTab );
});
Tab tab = new Tab("Test tab");
Tab second = new Tab("second");
installHandler(tabPane, tab, second);
installHandler(tabPane, second);
System.out.println("Adding tab");
tabPane.getTabs().addAll(tab, second);
Group root = new Group(tabPane);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
protected void installHandler(TabPane tabPane, Tab... tab) {
for (Tab tab2 : tab) {
tab2.setOnCloseRequest((event) -> {
System.out.println("Removing tab");
event.consume();
//I need to remove tab manually
tabPane.getTabs().remove(tab2);
});
}
}
}
It seems to be a bug so I opened a bug issue http://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8189424 (https://bugs.openjdk.java.net/browse/JDK-8189424) and accept this answer (as soon as SO lets me do it).

How do i connect two nodes with a line

How do I connect a node which lies in the first container to another node in the second container, i.e if i have a pane which has a sub node in it how will i connect it to another pane sub node, I've only manged to connect nodes that are is the same container but that's is not what i need, i want something like this.
enter code here
public class Main extends Application {
static Pane root = new Pane();
#Override
public void start(Stage primaryStage) throws Exception{
Circuit c1 = new Circuit(10,10);
Circuit c2 = new Circuit(200,10);
Circuit c3 = new Circuit(10,200);
Circuit c4 = new Circuit(200,200);
root.getChildren().addAll(c1, c2, c3, c4);
primaryStage.setScene(new Scene(root, 300, 275));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The circuit Class
enter code here
import static sample.Main.root;
public class Circuit extends Pane{
Circuit(int LOCATION_X, int LOCATION_Y){
setStyle("-fx-background-color: red");
setPrefSize(150,150);
setLayoutX(LOCATION_X);
setLayoutY(LOCATION_Y);
createCircle cir = new createCircle();
cir.setLayoutX(75);
cir.setLayoutY(75);
// register handlers
cir.setOnDragDetected(startHandler);
cir.setOnMouseDragReleased(dragReleaseHandler);
cir.setOnMouseDragEntered(dragEnteredHandler);
// add info allowing to identify this node as drag source/target
cir.setUserData(Boolean.TRUE);
getChildren().add(cir);
root.setOnMouseReleased(evt -> {
// mouse released outside of a target -> remove line
root.getChildren().remove(startHandler.line);
startHandler.line = null;
});
root.setOnMouseDragged(evt -> {
if (startHandler.line != null) {
Node pickResult = evt.getPickResult().getIntersectedNode();
if (pickResult == null || pickResult.getUserData() != Boolean.TRUE) {
// mouse outside of target -> set line end to mouse position
startHandler.line.setEndX(evt.getX());
startHandler.line.setEndY(evt.getY());
}
}
});
}
class DragStartHandler implements EventHandler<MouseEvent> {
public Line line;
#Override
public void handle(MouseEvent event) {
if (line == null) {
Node sourceNode = (Node) event.getSource();
line = new Line();
Bounds bounds = sourceNode.getBoundsInParent();
// start line at center of node
line.setStartX((bounds.getMinX() + bounds.getMaxX()) / 2);
line.setStartY((bounds.getMinY() + bounds.getMaxY()) / 2);
line.setEndX(line.getStartX());
line.setEndY(line.getStartY());
sourceNode.startFullDrag();
root.getChildren().add(0, line);
}
}
}
DragStartHandler startHandler = new DragStartHandler();
EventHandler<MouseDragEvent> dragReleaseHandler = evt -> {
if (evt.getGestureSource() == evt.getSource()) {
// remove line, if it starts and ends in the same node
root.getChildren().remove(startHandler.line);
}
evt.consume();
startHandler.line = null;
};
EventHandler<MouseEvent> dragEnteredHandler = evt -> {
if (startHandler.line != null) {
// snap line end to node center
Node node = (Node) evt.getSource();
Bounds bounds = node.getBoundsInParent();
startHandler.line.setEndX((bounds.getMinX()+bounds.getMaxX())/2);
startHandler.line.setEndY((bounds.getMinY()+bounds.getMaxY())/2);
}
};
}
The point from where the wire will drawn out and connected to
enter code here
public class createCircle extends Circle {
createCircle(){
super(25, Color.BLACK.deriveColor(0, 1, 1, 0.5));
}
}
You are mixing coordinate systems.
Bounds bounds = sourceNode.getBoundsInParent();
will give you the bounds of sourceNode in the coordinate system of the sourceNode's parent, which is going to be the current Circuit instance (if I read your code correctly). However, you are using those bounds to calculate the coordinates of the line, which is placed in the root node, so you need the coordinates in the coordinate system of the root.
You can transform the coordinates by doing something like
Bounds boundsInScene = sourceNode.localToScene(sourceNode.getBoundsInLocal());
Bounds boundsInRoot = root.sceneToLocal(boundsInScene);
Now boundsInRoot represents the bounds of sourceNode in the coordinate system of root, so you can use it to compute the coordinates of the line. There are likely similar transforms you need to make throughout the code.

Stop Win+R from opening run tool

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.

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();
}

Categories

Resources