JavaFX: Reacting to Single, Double, and Triple Click - javafx-8

I am trying to configure certain actions in response to number of mouse clicks. It seems to me that the single click and triple click get detected and applied. but the double click does not really work.
I tried to do something like:
if (doubleClick)
else if (tripleClick)
else if (singleClick).
The order of checking did not help either, the action for the double click never gets triggered because that of the single click get triggered first. Any ideas on how to do this?

Assuming you are doing something like
if (mouseEvent.getClickCount()==1)
etc, then it's probably not doing what you think. MouseEvent.getClickCount() just returns the number of clicks that have occurred in a "small" region and in a "small" amount of time. "Small" is (deliberately) not defined.
So a double click is just two clicks. The first returns 1 for getClickCount(), then second returns 2. Similarly, a triple click is three clicks: the first returns 1, the next 2, the third 3. You can test this with a very simple piece of code:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
public class ClickCountTest extends Application {
#Override
public void start(Stage primaryStage) {
Pane root = new Pane();
root.setOnMouseClicked(event -> System.out.println(event.getClickCount()));
primaryStage.setScene( new Scene(root, 250, 150) );
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
If you click, you'll see the output "1". If you double click, you'll see a "1" followed by a "2".
There's no built-in way to ignore the first click if it's part of a double (or triple) click. The issue, obviously, is that at the time of the first click, there's no way of knowing if another click is going to come without some kind of timing, which gets pretty complicated and would force a delay on responding to any mouse click.
There was some talk a while back about implementing an onClickSequenceFinished type of event, so that instead of listening for mouse click events, you could listen for the click sequence finished event and then query that event to find the number of clicks. In the end, it was decided not to support this as the use case was not considered good UI programming practice.
The reason for that is that it's a pretty bad idea for, say, a double click to exclude the action of a single click. If the user is just too slow with their double click, then they will inadvertently invoke the single click action (twice). So if you are supporting both double click and single click actions, then the actions should be chosen so that it makes sense for the single-click action to be invoked any time the double-click action is invoked. The typical example is a ListView, where double-clicking a list element opens a "details" editor, and single-clicking selects the item. It makes sense for the item being edited to also be selected, so the double-click action implies the single-click action.
Put another way, it's considered a bad design if a double click action is designed to exclude a single click action, and that idiom is not directly supported. You should consider using modifier keys instead of click counts for this kind of distinction.
Update:
If you really want to distinguish events by click count like this (and I really don't recommend it), then you can use something like a PauseTransition to implement the timer. Something like:
import javafx.animation.PauseTransition;
import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import javafx.util.Duration;
public class ClickCountTest extends Application {
#Override
public void start(Stage primaryStage) {
// This value may need tuning:
Duration maxTimeBetweenSequentialClicks = Duration.millis(500);
PauseTransition clickTimer = new PauseTransition(maxTimeBetweenSequentialClicks);
final IntegerProperty sequentialClickCount = new SimpleIntegerProperty(0);
clickTimer.setOnFinished(event -> {
int count = sequentialClickCount.get();
if (count == 1) System.out.println("Single click");
if (count == 2) System.out.println("Double click");
if (count == 3) System.out.println("Triple click");
if (count > 3) System.out.println("Multiple click: "+count);
sequentialClickCount.set(0);
});
Pane root = new Pane();
root.setOnMouseClicked(event -> {
sequentialClickCount.set(sequentialClickCount.get()+1);
clickTimer.playFromStart();
});
primaryStage.setScene( new Scene(root, 250, 150) );
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
This is also probably a good use case for Tomas Mikula's ReactFX framework, (also see his blog post).

If you want to detect each of the different click counts, you could use a switch statement.
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
/**
*
* #author jeffreyguenther
*/
public class ListTest extends Application{
#Override
public void start(Stage stage) throws Exception {
Circle c = new Circle();
c.setRadius(100);
c.setCenterX(100);
c.setCenterY(100);
c.setFill(Color.AQUA);
c.setStroke(Color.BLACK);
c.setStrokeWidth(3);
c.setOnMousePressed((e) ->{
switch(e.getClickCount()){
case 1:
System.out.println("One click");
break;
case 2:
System.out.println("Two clicks");
break;
case 3:
System.out.println("Three clicks");
break;
}
});
stage.setScene(new Scene(new Group(c)));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Then as you single, double, and triple click this output is given:
One click
Two clicks
Three clicks
You can use the case statements to determine which counts you want to handle. For example, if you only want to handle the double and triple clicks, you can remove the first case.

Related

Ending an application(game) and exit or and start a new one

So how does one end an application/game on a button click and exit as if the window red close symbol (X) has been clicked on or better still
how does one end the current application and without closing the whole window / stage starts a new one ?
so for example we have something like
public class Main extends Application
{
public Scene scene ;
private parent createContent()
{
// root pane, nodes and everything is here
//which makes up the game
//return root;
}
#Override
public void start(Stage stage)
{
scene = new Scene(createContent());
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {launch(args); }
}
So at the end of the current game, the user should be giving the option to start a new game or to exit the application completely by clicking on buttons. If he should click on exit game then the game should close as if he has pressed on the window red x close symbol.
If the user should click on start a new game, then the prefered behavior will be for method
private parent createContent()
to start all over again, but of course all stages and nodes created in the previous calls of createContent() should be eliminated..
How can this be done?
I have the similar workflow in my project and I implemented the next way.
Register a handler for OnCloseRequest:
stage.setOnCloseRequest(windowEvent -> appToBeClosed(stage, windowEvent));
Below methods to show a dialog with a question. Only if an user decided to stay you should consume the current window event and do something otherwise the application will be closed:
private void appToBeClosed(Stage notUsedStage, WindowEvent windowEvent) {
if (hasNotSavedEvents()) {
final Optional<ButtonType> userResponse = alertAboutNotSavedChangedEvents(
"alert.changed.header", "alert.changed.content");
if (userResponse.isPresent() && userResponse.get() == ButtonType.NO) {
windowEvent.consume();
}
}
}
private Optional<ButtonType> alertAboutNotSavedChangedEvents(String headerResourceKey,
String contentResourceKey) {
final Alert alert = new Alert();
// TODO prepare alert as you wish...
return alert.showAndWait();
}
I hope the main idea is clear and you will be able to adopt it to your project.
Let me know your questions.

Switching scenes the FXML way (SceneBuilder)

I have two scenes, scene 1 has a Label on it that simply reads "This is scene 1", it also has a button on it with the text "Press me to go to scene 2". scene 2 is similar to scene 1 but the Label and text on scene 2 say the opposite.
The problem is very simple, or at least should be. I am able to do this the javaFX way but cannot seem to do it the FXML way.
I have a main class -
import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
public class ManinApp extends Application
{
Stage primaryStage;
private AnchorPane rootLayout;
public static void main(String [] args)
{
launch(args);
}
public void start(Stage primaryStage)
{
this.primaryStage = primaryStage;
this.primaryStage.setTitle("Two Scenes");
initRootLayout();
//showSecondScene();
}
public void initRootLayout()
{
try
{
FXMLLoader loader = new FXMLLoader();
loader.setLocation(ManinApp.class.getResource("Scene1.fxml"));
rootLayout = (AnchorPane) loader.load();
Scene scene = new Scene(rootLayout);
primaryStage.setScene(scene);
primaryStage.show();
}
catch(IOException e)
{
e.printStackTrace();
}
}
/**
public void showSecondScene()
{
try
{
FXMLLoader loader = new FXMLLoader();
loader.setLocation(ManinApp.class.getResource("Scene2.fxml"));
AnchorPane secondScene = (AnchorPane)loader.load();
rootLayout.getChildren().add(secondScene);
}
catch(IOException e)
{
e.printStackTrace();
}
}
*/
public Stage getPrimaryStage()
{
return primaryStage;
}
}
the showSecondScene() has been commented out for now. My understanding is that you also need a Controller class to wire up the code to SceneBuilder?
the solution the FX way was
btnscene1.setOnAction(e ->
{
if(e.getSource() == btnscene1)
thestage.setScene(scene2);
else
thestage.setScene(scene1);
});
btnscene2.setOnAction(e ->
{
if(e.getSource()==btnscene2)
thestage.setScene(scene1);
else
thestage.setScene(scene2);
});
apologies for the formatting!
how am I able to do this using a controller class from which i am able to use the primary stage and two scene declared in my main class?
i hope it makes sense
I think your are doing quite well. FXML (and SceneBuilder) are used correctly here.
I would suggest few things:
Use a root container (e.g. StackPane) to host either scene1 or scene2
(better names would be layout1 / layout2). You don't need to use
different Scene here.
Load both fxml files at init time (or lazy loading if needed)
switch from one to the other by removing the content of the root container, and adding the other one.
Now, if the layouts are big, with a lot of css involved, and you need to switch very often from layout1 to layout2, you may want to add both layout in the root container. Then, use:
setVisible()
setManaged()
... on the root of the layout you want to hide / show.
Doing this, you avoid the layout and css steps that is done as soon as you add a node in the scene graph.
While I technically understand, what you want to achieve, I'm still lost about the reason behind it.
If you just want to switch the "main" content of the window, use a StackPane as the root, add multiple Layouts to that stack, and solve your problem by switching the one you want to work on #toFront().
Normally the Layouts on the stack are transparent (except for the controls like buttons and so on, of course), so you would either need to set a background of the stacked Layouts OR (which I would prefer) toggle the visibility of the one in the back (or set opaqueness to 0, or something like that).

Setting a textfield in a class in but displaying a number?

I'm learn as3, and building some exercises from the adobe online tutorials. I'm trying to do a very simple task and I think my code is exactly the same as the tutoriala nd it's not working! I just can't work it out...
I'm trying to change the textfield in a button from that buttons class.
The button is called GameButton, the textfield is called blabel and is classic text and dynamic text. Here is the code for the GameButton class. Instead of displaying "Click" as below it just changes the label to the number 1. The trace statement is working etc it is going there, but the text isn't passing through or something. Please help!!!
package {
import flash.display.MovieClip;
public class GameButton extends MovieClip {
public function GameButton() {
trace("Gamebutton has been created");
this.blabel.text = "Click";
stop();
}
}
}
The long and short of it is you can create the button in code, or else you can try listening for added to stage events coming from the parent object you're adding the children to (maybe the stage, maybe another DisplayObjectContainer). The problem with the listening method is I'm not sure how you would know which child just dispatched the event without making some messy code. I think the first option is generally easier and makes more sense, the only caveat is that you have to place the instances using x/y coordinates or apply scaleX, scaleY to stretch or shrink objects instead of doing it using the authoring tool. You can still use the drag and drop parts of flash to figure out coordinates and build individual movie clips etc.
Enough talk on to some code:
package
{
import flash.display.MovieClip;
public class GameButton extends MovieClip {
private var blabel:TextField; //This can be any display object class or a class that extends from a display object class (Sprite, MovieClip, MyCustomButton etc.)
public function GameButton() {
blabel = new TextField(); //The type after new, should be the same, or a sub-class (extension) of the type used in the variable declaration above
addChild(blabel);
//blabel.x=10; //optional over ten pixels from left
//blabel.y=10; //optional down ten pixels from top
//blabel.scaleX=.5; //optional half the width
//blabel.scaleY=2; //optional 2 times taller
trace("Gamebutton has been created");
blabel.text = "Click";
stop();
}
}
}

How to restrict the minimum size of the window for Eclipse e4

I am making an application based on Eclipse e4 framework. I was wondering how the minimal size of the application window can be controlled. There seems no properties can be defined in e4xmi file for this purpose.
Does anyone know how to do it?
I found a thread in Eclipse Community Forum (http://www.eclipse.org/forums/index.php/t/244875/) saying it can be achieved by creating my own renderer. How can I do that exactly?
Thank you very much :)
Assuming you are using the built-in SWT Renderers, you can also listen for the creation of your E4 MWindow elements and gain access to the underlying SWT Shell. In this example the listener is registered in an AddOn, which you can add to your e4xmi.
import javax.annotation.PostConstruct;
import org.eclipse.e4.core.services.events.IEventBroker;
import org.eclipse.e4.ui.model.application.ui.basic.MWindow;
import org.eclipse.e4.ui.workbench.UIEvents;
import org.eclipse.swt.widgets.Shell;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventHandler;
public class MinSizeAddon {
#PostConstruct
public void init(final IEventBroker eventBroker) {
EventHandler handler = new EventHandler() {
#Override
public void handleEvent(Event event) {
if (!UIEvents.isSET(event))
return;
Object objElement = event.getProperty(UIEvents.EventTags.ELEMENT);
if (!(objElement instanceof MWindow))
return;
MWindow windowModel = (MWindow)objElement;
Shell theShell = (Shell)windowModel.getWidget();
if (theShell == null)
return;
theShell.setMinimumSize(400, 300);
}
};
eventBroker.subscribe(UIEvents.UIElement.TOPIC_WIDGET, handler);
}
}
Note, that this will be executed for any MWindow in your application, and there can be more of them (i.e. when an MPart is detached from the MPartStack into a seperate window). If you want to limit the execution to specific MWindows, I recommend to add a tag to the window in the e4xmi and check for this tag before setting the minimum size.
If anyone is still looking to do this in an e4 application and doesn't want to roll their own renderer, you can simply do the following in the post-construct of your part class:
#PostConstruct
public void postConstruct(Composite parent) {
parent.getShell().setMinimumSize(300, 300);
//...
}
The parent Composite passed in by the framework gives you access to the Shell, which lets you set the minimum size. This stops the application from being resized to less than the specified minimum size (in pixels).

MouseDown events are not delivered until MouseUp when a Drag Source is present

I have a mouse listener. It has some code to respond to mouseUp and mouseDown events. This works correctly.
However, as soon as I add a DragSource, my mouseDown event is no longer delivered -- until I release the mouse button!
This is trivial to reproduce - below is a simple program which contains a plain shell with just a mouse listener and a drag listener. When I run this (on a Mac), and I press and hold the mouse button, nothing happens - but as soon as I release the mouse button, I instantly see both the mouse down and mouse up events delivered. If I comment out the drag source, then the mouse events are delivered the way they should be.
I've searched for others with similar problems, and the closest I've found to an explanation is this:
https://bugs.eclipse.org/bugs/show_bug.cgi?id=26605#c16
"If you hook drag detect, the operating system needs to eat mouse events until it determines that you have either dragged or not."
However, I don't understand why that's true -- why must the operating system eat mouse events to determine if I have a drag or not? The drag doesn't start until I have a mouse -move- event with the button pressed.
More importantly: Can anyone suggest a workaround? (I tried dynamically adding and removing my drag source when the mouse is pressed, but then I couldn't get drag & drop to function properly since it never saw the initial key press - and I can't find a way to programmatically initiate a drag.)
Here's the sample program:
package swttest;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DragSource;
import org.eclipse.swt.dnd.DragSourceEvent;
import org.eclipse.swt.dnd.DragSourceListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
public class SwtTest {
public static void main(String[] args) {
final Display display = new Display();
final Shell shell = new Shell(display);
shell.addMouseListener(new MouseListener() {
public void mouseUp(MouseEvent e) {
System.out.println("mouseUp");
}
public void mouseDown(MouseEvent e) {
System.out.println("mouseDown");
}
public void mouseDoubleClick(MouseEvent e) {
System.out.println("mouseDoubleClick");
}
});
DragSourceListener dragListener = new DragSourceListener() {
public void dragFinished(DragSourceEvent event) {
System.out.println("dragFinished");
}
public void dragSetData(DragSourceEvent event) {
System.out.println("dragSetData");
}
public void dragStart(DragSourceEvent event) {
System.out.println("dragStart");
}
};
DragSource dragSource = new DragSource(shell, DND.DROP_COPY | DND.DROP_MOVE);
dragSource.addDragListener(dragListener);
shell.pack();
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
}
}
To answer your specific question about why this happens -- on Cocoa we don't consider a drag to have started until the mouse has moved a few pixels. This ensures against 'accidental' drags if you're sloppy with the clicks. On Linux and Win32 the window toolkit can do the drag detection. If you just hold down the button the detection times out and the mouse down is delivered. On Cocoa we have no time out, which is why nothing happens until the drag is detected or a mouse up happens.
That's a lot of detail, but the conclusion is that the behavior is inconsistent, and we should always be able to deliver the mouse down immediately, without waiting for the drag detection to complete.
I don't see a workaround, since this is happening before the Control sees the event.
See this bug which has patches for win32, gtk and cocoa SWT.
I had faced the same problem and found a solution. Once you attach a DragSource to your custom widget, the event loop will be blocked in that widget's mouse down hook and will eat mouse move events to detect a drag. (I've only looked into the GTK code of SWT to find this out, so it may work a little differently on other platforms, but my solution works on GTK, Win32 and Cocoa.) In my situation, I wasn't so much interested in detecting the mouse down event right when it happened, but I was interested in significantly reducing the drag detection delay, since the whole purpose of my Canvas implementation was for the user to drag stuff. To turn off the event loop blocking and built-in drag detection, all you have to do is:
setDragDetect(false);
In my code, I am doing this before attaching the DragSource. As you already pointed out, this will leave you with the problem that you can't initiate a drag anymore. But I have found a solution for that as well. Luckily, the drag event generation is pure Java and not platform specific in SWT (only the drag detection is). So you can just generate your own DragDetect event at a time when it is convenient for you. I have attached a MouseMoveListener to my Canvas, and it stores the last mouse position, the accumulated drag distance and whether or not it already generated a DragDetect event (among other useful things). This is the mouseMove() implementation:
public void mouseMove(MouseEvent e) {
if (/* some condition that tell you are expecting a drag*/) {
int deltaX = fLastMouseX - e.x;
int deltaY = fLastMouseY - e.y;
fDragDistance += deltaX * deltaX + deltaY * deltaY;
if (!fDragEventGenerated && fDragDistance > 3) {
fDragEventGenerated = true;
// Create drag event and notify listeners.
Event event = new Event();
event.type = SWT.DragDetect;
event.display = getDisplay();
event.widget = /* your Canvas class */.this;
event.button = e.button;
event.stateMask = e.stateMask;
event.time = e.time;
event.x = e.x;
event.y = e.y;
if ((getStyle() & SWT.MIRRORED) != 0)
event.x = getBounds().width - event.x;
notifyListeners(SWT.DragDetect, event);
}
}
fLastMouseX = e.x;
fLastMouseY = e.y;
}
And that will replace the built-in, blocking drag detection for you.