Javafx - child and parent node mouse event - events

I have got a larger Pane (Parent) to which I have added another smaller Pane (Child). I would like to drag the child Pane without dragging the parent Pane (i.e. register mouse event only on the child and not on the parent).
How can we best implement it?

I managed to find a work around to my problem.
In the Parent Pane: I used mouseEvent.isControlDown()- So I use crtl+mousebutton down to drag the pane.
In the Child Pane: I used !mouseEvent.isControlDown(). So control keystoke is used to determine which part of the event works.
The parent goes like this:
node.addEventFilter(
MouseEvent.MOUSE_PRESSED,
new EventHandler<MouseEvent>() {
public void handle(final MouseEvent mouseEvent) {
if (mouseEvent.isControlDown()) {
// remember initial mouse cursor coordinates
// and node position
dragContext.mouseAnchorX = mouseEvent.getSceneX();
dragContext.mouseAnchorY = mouseEvent.getSceneY();
dragContext.initialTranslateX
= node.getTranslateX();
dragContext.initialTranslateY
= node.getTranslateY();
}
}
});
The Child goes like this:
node.addEventFilter(
MouseEvent.MOUSE_PRESSED,
new EventHandler<MouseEvent>() {
public void handle(final MouseEvent mouseEvent) {
if (!mouseEvent.isControlDown()) {
dragContext.mouseAnchorX = mouseEvent.getSceneX();
dragContext.mouseAnchorY = mouseEvent.getSceneY();
dragContext.initialTranslateX
= node.getTranslateX();
dragContext.initialTranslateY
= node.getTranslateY();
}
}
});

Related

How to change CURSOR to busy and prevent user clicking

I have three COMBO BOXES in my window. It is a family tree application I am developing using JavaFX and SQLIte. Each person has Father, Mother and Spouse info which can be selected from ComboBoxes. ComboBoxes are loaded with data from database.
It takes a while to load these three combo boxes. Actually different delays for different combo boxes.
If I click on a combo box while it is not ready or another combo box is not ready, the screen turns white until the other combo box is ready.
I want to prevent user input on entire window from Mouse, Keyboard and Buttons until entire window is ready to accept user's input. Until then I want change the cursor to something like STOP.
How to do this? Any suggestions?
Thanks,
Hornigold
Run a Task<ObservableList<SomeType>> (or a Task returning some object containing more than a single list) on a seperate Thread. Before starting the thread you disable the root and set the cursor for the scene and when the task finishes you initialize the GUI with the results and reenable the scene again:
#Override
public void start(Stage primaryStage) {
Button loadButton = new Button("load");
ComboBox<String> combo = new ComboBox<>();
VBox root = new VBox(combo, loadButton);
Scene scene = new Scene(root);
loadButton.setOnAction(evt -> {
root.setDisable(true);
// save old cursor to restore after finishing the task
final Cursor oldCursor = root.getScene().getCursor();
scene.setCursor(Cursor.WAIT);
Task<ObservableList<String>> task = new Task<ObservableList<String>>() {
#Override
protected ObservableList<String> call() throws Exception {
ObservableList<String> result = FXCollections.observableArrayList();
for (int i = 0; i < 100; i++) {
result.add(Integer.toString(i));
}
// simulate delay
Thread.sleep(5000);
return result;
}
};
task.setOnSucceeded(e -> {
// use results of task in the GUI
combo.setItems(task.getValue());
// restore cursor and reenable scene
root.setDisable(false);
scene.setCursor(oldCursor);
});
task.setOnFailed(e -> {
// todo: handle exception in Task.call
});
Thread thread = new Thread(task);
thread.start();
});
primaryStage.setScene(scene);
primaryStage.show();
}

Is there a way to set a button onClick function to a function on a prefab that is not in a Scene in Unity?

I have a prefab that instantiate on running the program. The prefab is not already in the scene. On this prefab there is a script with a function that should be called when a button is clicked.The button is in the scene. In the button's inspector I drag and dropped the prefab and chose the function to execute. But on running, I get an exception. Is there a way for the button to reference a function on a prefab that is not in the scene?
Apart from making handler static, you can just find the instance of the button:
public class MyScript: MonoBehaviour
{
void Awake()
{
Button myButton = GetReferenceToButton();
myButton.onClick.AddListener ((UnityEngine.Events.UnityAction) this.OnClick);
}
public void OnClick()
{
Debug.Log("Clicked!");
}
private Button GetReferenceToButton()
{
Button btn = null;
//Find it here
return btn;
}
}
Also you need to cast the delegate to UnityEngine.Events.UnityAction before adding is as listener.
If you have a user interface with multiple buttons in a prefab instantiated programmatically, you can use GetComponentsInChildren to access all the buttons:
public void onUIcreated()
{
// Add event listeners to all buttons in the canvas
Button[] buttons = canvasPrefab.GetComponentsInChildren<Button>();
for (int i = 0; i < buttons.Length; i++)
{
string identifier = buttons[i].name;
buttons[i].onClick.AddListener(delegate { OnButtonTapped(identifier); });
}
}
public void OnButtonTapped(string identifier)
{
Debug.Log("Pressed button:" + identifier);
}

RecyclerView Click event

I have created a RecyclerView adapter and I'm trying to start an activity when a row is clicked:
public override OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
MyViewHolder viewHolder = (MyViewHolder)holder;
viewHolder.MyView.Click += (sender, e) =>
{
var context = viewHolder.MyView.Context;
var intent = new Intent(context, typeof(DetailActivity));
context.StartActivity(intent);
}
}
When I click the first row it will take me to the activity like I want. If I scroll down so that the first row is rebound and then scroll back to the top again and then click the first row then my Click event fires twice. Once for the first row that was bound and then again for a row that was bound when I scrolled.
Is there an event you need to handle to unregister the click events?
I believe the standard pattern is to setup your clickhandlers in the constructor of the ViewHolder. Then in OnBindViewHolder, you update the Views/Data inside the ViewHolder.
Something like this (not compiled code):
Adapter:
public override OnBindViewHolder()
{
MyViewHolder viewHolder = (MyViewHolder)holder;
viewHolder.SetData(whatever data you care about);
}
MyViewHolder:
public MyViewHolder(View view) : base(view)
{
MainView = view;
MainView.Click += (sender, e) =>
{
var context = MainView.Context;
var intent = new Intent(context, typeof(DetailActivity));
context.StartActivity(intent);
}
}
Doing it this way keeps the Adapter cleaner by putting business logic in the ViewHolder, and also prevents your click handlers from being constantly setup and torn down as you scroll.

JavaFX 2.2: Hooking Slider Drag n Drop Events

I am trying to catch the events on the JavaFX Slider especially the one which indicates that the drag stopped and was released. At first I used the valueProperty with mock-up code like this
slider.valueProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> ov, Number oldValue, Number newValue) {
log.fine(newValue.toString());
}
});
but with this it update too often. So I searched within SceneBuilder and the API and found some interessting like
slider.setOnMouseDragReleased(new EventHandler<MouseDragEvent>() {
#Override
public void handle(MouseDragEvent event) {
System.out.println("setOnMouseDragReleased");
}
});
but they never get fired. There only some like setOnMouseReleased I get some output, but this for example count for the whole Node like the labels etc.
So my question is, which is the correct hook to know the value is not changing anymore (if possible after release of the mouse like drag'n'drop gesture) and maybe with a small example to see its interfaces working.
Add a change listener to the slider's valueChangingProperty to know when the slider's value is changing, and take whatever action you want on the value change.
The sample below will log the slider's value when it starts to change and again when it finishes changing.
import javafx.application.Application;
import javafx.beans.value.*;
import javafx.geometry.*;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;
public class SliderChangeLog extends Application {
private final ListView<String> startLog = new ListView<>();
private final ListView<String> endLog = new ListView<>();
#Override public void start(Stage stage) throws Exception {
Pane logsPane = createLogsPane();
Slider slider = createMonitoredSlider();
VBox layout = new VBox(10);
layout.setAlignment(Pos.CENTER);
layout.setPadding(new Insets(10));
layout.getChildren().setAll(
slider,
logsPane
);
VBox.setVgrow(logsPane, Priority.ALWAYS);
stage.setTitle("Slider Value Change Logger");
stage.setScene(new Scene(layout));
stage.show();
}
private Slider createMonitoredSlider() {
final Slider slider = new Slider(0, 1, 0.5);
slider.setMajorTickUnit(0.5);
slider.setMinorTickCount(0);
slider.setShowTickMarks(true);
slider.setShowTickLabels(true);
slider.setMinHeight(Slider.USE_PREF_SIZE);
slider.valueChangingProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(
ObservableValue<? extends Boolean> observableValue,
Boolean wasChanging,
Boolean changing) {
String valueString = String.format("%1$.3f", slider.getValue());
if (changing) {
startLog.getItems().add(
valueString
);
} else {
endLog.getItems().add(
valueString
);
}
}
});
return slider;
}
private HBox createLogsPane() {
HBox logs = new HBox(10);
logs.getChildren().addAll(
createLabeledLog("Start", startLog),
createLabeledLog("End", endLog)
);
return logs;
}
public Pane createLabeledLog(String logName, ListView<String> log) {
Label label = new Label(logName);
label.setLabelFor(log);
VBox logPane = new VBox(5);
logPane.getChildren().setAll(
label,
log
);
logPane.setAlignment(Pos.TOP_LEFT);
return logPane;
}
public static void main(String[] args) { launch(args); }
}
There could be times when you want to know when the user is moving the slider versus the slider value changing due to a binding to a property. One example is a slider that is used on a media player view to show the media timeline. The slider not only displays the time but also allows the user to fast forward or rewind. The slider is bound to the media player's current time which fires the change value on the slider. If the user moves the slider, you may want to detect the drag so as to stop the media player, have the media player seek to the new time and resume playing. Unfortunately the only drag event that seems to fire on the slider is the setOnDragDetected event. So I used the following two methods to check for a slider drag.
slider.setOnDragDetected(new EventHandler<Event>() {
#Override
public void handle(Event event) {
currentPlayer.pause();
isDragged=true;
}
});
slider.setOnMouseReleased(new EventHandler<Event>() {
#Override
public void handle(Event event) {
if(isDragged){
currentPlayer.seek(Duration.seconds((double) slider.getValue()));
currentPlayer.play();
isDragged=false;
}
}
});
jewelsea's answer was very helpful for setting me on the right track, however if "snapToTicks" is on, undesired behavior results. The "end" value as captured by jewelsea's listener is before the snap takes place, and the post-snap value is never captured.
My solution sets a listener on value but uses valueChanging as a sentinel. Something like:
slider.valueProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(
ObservableValue<? extends Number> observableValue,
Number previous,
Number now) {
if (!slider.isValueChanging()
|| now.doubleValue() == slider.getMax()
|| now.doubleValue() == slider.getMin()) {
// This only fires when we're done
// or when the slider is dragged to its max/min.
}
}
});
I found that checking for the max and min value was necessary to catch the corner case where the user drags the slider all the way past its left or right bounds before letting go of the mouse. For some reason, that doesn't fire an event like I'd expect, so this seems like an okay work-around.
Note: Unlike jewelsea, I'm ignoring the starting value for the sake of simplicity.
Note 2: I'm actually using ScalaFX 2, so I'm not sure if this Java translation compiles as-written.

javafx HTMLEditor scrollpane scrolls on Space Key

I have a VBox inside a ScrollPane wich contains a HTMLEditor and other stuff.
When I type text inside the HTMLEditor each time I hit the Space Bar, I get a whitespace inside the editor as expected but also the Scrollpane scrolls down. First I worked around this by adding a EventFilter to the Scrollpane and consume the KEY_PRESSED event. But now I need this event inside the HTMLEditor.
So my question: is there any Flag to tell the Scrollpane not to scroll on KeyCode.SPACE or is there a way to route the input Focus/ Key Events only to the HTMLEditor, bypassing the Scrollpane? Or a way to filter this event only on the Scrollpane?
You can reproduce this also with javafx Scene Builder:
Scrollpane->VBox(larger than Scrollpane so Scrollbars appear)->2*HTMLEditor, Preview in Window, hit the Space Bar.
Solved:
Added an EventFilter to the HTMLEditor, which consumes the KeyCode.SPACE on KEY_PRESSED.
htmlEditor.addEventFilter( KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
if (event.getEventType() == KeyEvent.KEY_PRESSED){
// Consume Event before Bubbling Phase, -> otherwise Scrollpane scrolls
if ( event.getCode() == KeyCode.SPACE ){
event.consume();
}
}
}
});
I just ran into a similar problem. What I did was to pass the filtered event on to my event handler method directly before consuming it. For your case, it would look something like this (assume you have an KeyEvent handler method that you've named onKeyPressed()):
htmlEditor.setOnKeyPressed(new EventHandler<KeyEvent>() {#Override public void handle(KeyEvent t) { onKeyPressed(t); }});
scrollPane.addEventFilter(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent t) {
if(t.getCode() == KeyCode.SPACE) {
onKeyPressed(t);
t.consume();
}
}
});
Create your own widget that extends the HTMLEditor and add a listener for the pressed event.
setOnKeyPressed(event -> {
if (event.getCode() == KeyCode.SPACE
|| event.getCode() == KeyCode.TAB ) {
// Consume Event before Bubbling Phase, -> otherwise Scrollpane scrolls
event.consume();
}
});

Resources