I'm trying to use a TranslateTransition object in JavaFX to move an onscreen object in a LOGO program I am building. I have an onscreen TurtleDisplay object, which extends ImageView, and this is what I'm trying to move. The code to move it is here:
public void drawTurtle(TurtleData currentData) {
TurtleImage inList = getTurtleImage(currentData);
if (inList==null) {
TurtleImage temp = new TurtleImage(currentData.getX(),
currentData.getY(), currentData.getHeading(), turtleImage);
myTurtlesGroup.getChildren().add(temp);
myTurtlesList.add(temp);
}
else {
TranslateTransition tt = new TranslateTransition(Duration.seconds(3),inList);
tt.setFromX(inList.getX());
tt.setFromY(inList.getY());
tt.setToX(inList.getX()+currentData.getX());
tt.setToY(inList.getY()+currentData.getY());
tt.setCycleCount(Timeline.INDEFINITE);
tt.play();
}
}
This code, which is part of the front end, is called from the back end via a Listener on an ObservableList. The backend contains this ObservableList of TurtleData objects that contain the information necessary to move a turtle on screen -- which turtle to move, the coordinate to move to, and the rotation of the turtle. The code to call this is here:
ObservableList<TurtleData> myTurtles = FXCollections
.observableArrayList();
myTurtles.addListener(new ListChangeListener<TurtleData>() {
#Override
public void onChanged(Change<? extends TurtleData> c) {
myDisplay.getSelectedWorkspace().getTV().clearTurtles();
while (c.next()) {
for (TurtleData addItem : c.getAddedSubList()) {
myDisplay.getSelectedWorkspace().getTV().drawTurtle(addItem);
}
}
}
});
I have stepped through with a debugger and ensured that this code is called -- specifically, the tt.play() line is run. Nothing moves on screen. Does anyone have any idea what is wrong? Do I need to setup an Animation Timeline? Thank you for any help!
Related
I'm writing a JavaFX program with a TableView called 'table' and 2 buttons called 'previous' & 'next'.
Here is part of the code:
previous.setOnAction(event -> {
table.getSelectionModel().selectPrevious();
});
next.setOnAction(event -> {
table.getSelectionModel().selectNext();
});
However, if I keep pressing the buttons, the table will not scroll automatically to keep the selected item visible. So I modified the code like this :
previous.setOnAction(event -> {
table.getSelectionModel().selectPrevious();
table.scrollTo(table.getSelectionModel().getSelectedItem());
});
next.setOnAction(event -> {
table.getSelectionModel().selectNext();
table.scrollTo(table.getSelectionModel().getSelectedItem());
});
But it will always try to keep the selected item at the top of the visible region. If I keep pressing 'next'. The selected item will stay at the top instead of staying at the bottom.
I want to mimic the natural behavior of a tableview in the way that if I press up or down on the keyboard with something selected, the tableview will scroll automatically to keep the selected item visible.
How should I modify the code to make the auto scrolling more natural when I press the buttons?
Thanks
The problem is
missing fine-grained control of scrollTo target location on application level
the (somewhat unfortunate) implementation of virtualizedControl.scrollTo(index) which (ultimately) leads to calling flow.scrollToTop(index)
There's a long-standing RFE (reported 2014!) requesting better control from application code. Actually, VirtualFlow has public methods (scrollToTop, scrollTo, scrollPixels) providing such, only they are not passed on to the control layer (getVirtualFlow in VirtualContainerBase is final protected), so can't be overridden in a custom skin. Since fx12, we can hack a bit, and expose the onSelectXX of Tree/TableViewSkin and use those, either directly in application code (example below) or in a custom TableView.
Example code:
public class TableSelectNextKeepVisible extends Application {
/**
* Custom table skin to expose onSelectXX methods for application use.
*/
public static class MyTableSkin<T> extends TableViewSkin<T> {
public MyTableSkin(TableView<T> control) {
super(control);
}
/**
* Overridden to widen scope to public.
*/
#Override
public void onSelectBelowCell() {
super.onSelectBelowCell();
}
/**
* Overridden to widen scope to public.
*/
#Override
public void onSelectAboveCell() {
super.onSelectAboveCell();
}
}
private Parent createContent() {
TableView<Locale> table = new TableView<>(FXCollections.observableArrayList(Locale.getAvailableLocales())) {
#Override
protected Skin<?> createDefaultSkin() {
return new MyTableSkin<>(this);
}
};
TableColumn<Locale, String> country = new TableColumn<>("Column");
country.setCellValueFactory(new PropertyValueFactory<>("displayLanguage"));
table.getColumns().addAll(country);
Button next = new Button("next");
next.setOnAction(e -> {
table.getSelectionModel().selectNext();
// scrolls to top
// table.scrollTo(table.getSelectionModel().getSelectedIndex());
((MyTableSkin<?>) table.getSkin()).onSelectBelowCell();
});
Button previous = new Button("previous");
previous.setOnAction(e -> {
table.getSelectionModel().selectPrevious();
// scrolls to top
// table.scrollTo(table.getSelectionModel().getSelectedIndex());
((MyTableSkin<?>) table.getSkin()).onSelectAboveCell();
});
BorderPane content = new BorderPane(table);
content.setBottom(new HBox(10, next, previous));
return content;
}
#Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
try using getSelectedIndex as follows instead of using getSelectedItem
previous.setOnAction(event -> {
table.getSelectionModel().selectPrevious();
table.scrollTo(table.getSelectionModel().getSelectedIndex());
});
Platform.runLater( () -> TABLE_NAME.scrollTo(TABLE_INFORMATION_LIST.getList().size()-index) );
should work if you call it whenever you add information to the table.
This is the background for my question:
I have a GUI with an accordion with many TitledPanes, and each Titledpane contains a spreadsheetView from the controlsFX package.
There is a search-function in the code, where a Titledpane is opened and a specific cell in the spreadsheetView is opened for text input using the edit method of the spreadsheetcell type.
If the TitledPane is already open, this works fine, but if it must open first then the call of the edit-method fails. (The program is actually written in scalafx, but I don't think that matters here because scalafx is just a wrapper of javaFX and calls all the javaFX methods.)
Someone from the scalafx user group found out, that when I put in a wait time of 350ms (The animation time of the TitledPane is 300ms) then the call of 'edit' on the cell succeeds. He thought that the call fails, when the rendering of the content of the TitledPane is not complete.
This is also true when I turn the animation for the TitledPane off. In this case, it is sufficient to wait for 50ms, which does not work when animation is on.
Anyway - I am concerned about just waiting 350ms and hoping that this will always work. Which brings me back to the question: How can I tell that the rendering inside the TitledPane (or the spreadsheetView?) is complete so that I can safely call my edit method on the spreadsheetView?
Astonishingly, that doesn't seem to be supported.
The property that changes during the expand/collapse phase is the content's height: so a hack around might be to listen to it and start editing when fully expanded (which is a bit hacky in itself, could change due to layout constraints as well).
The example below simply initializes the fully expanded height after showing, listens to content's height property and starts editing when it reaches the fully expanded height.
The code:
public class TitledPaneEndOfExpansion extends Application {
private DoubleProperty expandedHeight = new SimpleDoubleProperty();
private TitledPane titled = new TitledPane();
private Parent createContent() {
titled.setText("Titled");
ListView<String> list = new ListView<>(FXCollections.observableArrayList("some", "content"));
list.setEditable(true);
list.setCellFactory(TextFieldListCell.forListView());
titled.setContent(list);
list.heightProperty().addListener((src, ov, nv) -> {
if (nv.doubleValue() == expandedHeight.get()) {
list.edit(0);
}
});
BorderPane content = new BorderPane(titled);
return content;
}
#Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.setTitle(FXUtils.version());
stage.show();
expandedHeight.set(((Region) titled.getContent()).getHeight());
}
public static void main(String[] args) {
launch(args);
}
}
Basically I like kleopatras idea, but unfortunately I can't figure out if this works for me or not.
At first I had some problems reading the code - only because my java knowledge is very limited. So I transferred it to scala. When I run it there, the call to edit works only sometimes (after startup it does not, when i clicked into a cell to edit it does). So I added a button that also calls edit - and it had the same behavior. So calling edit in general seems to have a problem in scalafx. But I learned something interesting here. I will now wait a few more days to see if anyone can think of anything else. If not then I will accept kleopatras solution.
For my own reference I add my not working scala-code here:
import scalafx.Includes._
import scalafx.application.JFXApp
import scalafx.beans.property.DoubleProperty
import scalafx.beans.value.ObservableValue
import scalafx.collections.ObservableBuffer
import scalafx.event.ActionEvent
import scalafx.scene.Scene
import scalafx.scene.control.cell.TextFieldListCell
import scalafx.scene.control.{Button, ListView, TitledPane}
import scalafx.scene.layout.BorderPane
object TitledPaneEndOfExpansion extends JFXApp {
val expandedHeight = new DoubleProperty()
val data: ObservableBuffer[String] = new ObservableBuffer[String]() ++= List("some", "content", "for", "testing")
stage = new JFXApp.PrimaryStage {
title = "JavaFX: edit after rendering test"
val list: ListView[String] = new ListView[String](data) {
editable = true
cellFactory = TextFieldListCell.forListView()
height.onChange { (source: ObservableValue[Double, Number], oldValue: Number, newValue: Number) =>
println("old height is: " + oldValue.doubleValue() + " new height is: " + newValue.doubleValue())
if (newValue.doubleValue() == expandedHeight.value) {
edit(1)
}
}
}
val titled: TitledPane = new TitledPane {
text = "titled"
content = list
}
scene = new Scene {
root = new BorderPane {
center = titled
bottom = new Button() {
text = "edit cell 1"
onAction = { _: ActionEvent => list.edit(1) }
}
}
}
expandedHeight.value = 400
list.edit(1)
}
}
I've developed an Android application using Libgdx in 3D space to render some buildings with interaction buttons to help user navigating in the environment. For example, left and right buttons to move the camera in left and right direction. While pushing a button causes to execute the code once, I've used this trick to keep executing the code as the user holding the button down.
private void createStage() {
stage = new Stage();
intervalTime = 15L;
buttonLeft = new TextButton("", leftStyle);
buttonLeft.addListener(new InputListener() {
// repeat an action with ScheduledExecutorService
final Runnable leftRunnable = new Runnable() {
#Override
public void run() {
Vector3 dir = new Vector3();
dir.fromString(cam.direction.toString()).scl(0.5f);
cam.position.add(dir.z, 0, -dir.x); // camera moves to left
cam.update();
}
};
// add on thread to object
final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
ScheduledFuture<?> future; // future schedule to run and stop task
#Override
public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) {
future = executor.scheduleAtFixedRate(leftRunnable, 0L, intervalTime, TimeUnit.MILLISECONDS);
return true;
}
#Override
public void touchUp(InputEvent event, float x, float y, int pointer, int button) {
super.touchUp(event, x, y, pointer, button);
//leftFlag = false;
if (future != null) {
future.cancel(true);
}
}
});
This method is invoked in the create() function of AndroidListener and the stage will be drawn in render() function as well. There are about 12 buttons which use the same approach but it gives me some lagging in rendering process while the user holds the buttons down for seconds or pushing two buttons simultaneously. Is there something wrong with the approach or is it an appropriate structure for executing a code frequently?
Camera is not a thread-safe class, so you would need to use synchronization if modifying it from your background thread.
That said, updating a camera is a trivial operation, so multi-threading is adding a lot of needless complexity. You're generating a fair amount of garbage, although I don't know if that's the only reason you're seeing some lagging.
Here's how I'd do it more simply.
stage = new Stage();
float camSpeed = 0.5f / 15; // Units per ms
float camDisp = camSpeed * Gdx.graphics.getDeltaTime();
buttonLeft = new TextButton("", leftStyle){
public void act(float delta){
super.act(delta);
if (isPressed()){
camera.position.add(camera.direction.z * camDisp,
0,
-camera.direction.x * camDisp);
camera.update();
}
}
}
Not quite sure what you're doing with the camera direction, but I tried to copy the same behavior. If I just wanted to pan the camera to the left, I'd do it like this. The temp variable is to avoid instantiating objects and triggering GC.
private static final Vector3 TMP = new Vector3();
//...
TMP.set(camera.direction).crs(camera.up); // right vector of camera
camera.position.add(TMP.scl(-camDisp));
Do you know how to wait for the user's input in a for loop? I don't mean the showAndWait() method, because I am not opening a new dialogue stage for the user. So for example, each round of the for loop should be waiting for the user to push a button before going ahead with the next round.
How is it possible? Many thanks!
UPDATE:
Now it came to my mind, that it would work with a while(buttonNotPressed){} but is it a good solution? I mean the while loop is running in this case as crazy until the user won't push the button. Or doest it work somehow similarly with wait methods?
Imagine it as a session:
User starts session with handleStart() You give the user 5 questions, one after one. In every iteration, the user can answer the upcoming question and he can save or submit the answer by handleSaveButton() You process the answer as you want, and go ahead with the next iteration. The point is, that the iteration must stop, until the save button hasn't been pressed.
Don't do it like that. The FX toolkit, like any event-driven GUI toolkit, already implements a loop for the purposes of rendering the scene graph and processing user input each iteration.
Just register a listener with the button, and do whatever you need to do when the button is pressed:
button.setOnAction(event -> {
// your code here...
});
If you want the action to change, just change the state of some variable each time the action is performed:
private int round = 0 ;
// ...
button.setOnAction(event -> {
if (round < 5) {
System.out.println("Round "+round);
System.out.println("User's input: "+textArea.getText());
round++ ;
}
});
I recently ran into a similar problem where I wanted something to be executed with an interval (if that's what you mean), until the user fired an event. I found 3 ways to do this:
UPDATE
You should use the stop/cancel method for the custom runnable and timer or else the thread will still be running when you exit the application. Timeline seems do it by itself.
Using a Timer:
Timer timer = new Timer();
TimerTask task = new TimerTask() {
#Override
public void run() {
System.out.println("Printed every second.");
}
};
timer.scheduleAtFixedRate(task, 0, 1000);
//timer.cancel();
With a TimeLine:
Timeline tl = new Timeline(new KeyFrame(Duration.millis(1000), e -> {
System.out.println("Timeline");
}));
tl.setCycleCount(Timeline.INDEFINITE);
tl.play();
//tl.stop();
Or making your own runnable class:
public class Runner implements Runnable {
private final Thread thread = new Thread(this);
private boolean run;
#Override
public void run() {
while(run) {
try {
System.out.println("Printed from loop");
Thread.sleep(1000);
} catch (InterruptedException e) {
run = false;
}
}
}
public void start() {
run = true;
thread.start();
}
public void stop() {
if(run) {
thread.interrupt();
System.out.print("Thread has stopped.");
}
}
}
And then when a person clicks fx. a button the event would stop using the example James_D posted:
Button btn = new Button("Button");
btn.setOnAction(e -> {
timer.cancel();
tl.stop();
runner.stop();
});
In my case, for inside for, had to create 2 index in class, use:
//start method
Timer timer = new Timer();
TimerTask task = new TimerTask()
{
public void run()
{
Platform.runLater(()->{
//... code to run after time, calling the same mehtod, with condition to stop
});
}
};
timer.schedule(task, time);
//end method
Had to use recursive method, incrementing the index with conditions, cause the tasks were been schedule all at the same time, without wait time.
I do not know if it is rigth, but was the solution that i found.
Hope it helps.
ALTERNATIVE SOLUTION W/O PAUSING:
I'm creating a game where I want the user to pick the game difficulty before the game starts. Instead of trying to pause the program midway through, I just put the next step of the code in a separate method which you call once a button is clicked:
private static difficulty;
public static void main(String[] args) {
try {
Application.launch(args);
} catch (UnsupportedOperationException e) {
}
}
public void start(Stage startStage) {
HBox buttons = new HBox();
Button easyButton = new Button("Easy");
Button mediumButton = new Button("Medium");
Button hardButton = new Button("Hard");
buttons.getChildren().addAll(easyButton, mediumButton, hardButton);
buttons.setAlignment(Pos.CENTER);
hbox.getChildren().addAll(buttons);
Scene startScene = new Scene(buttons, 200, 200);
startStage.setScene(startScene);
startStage.show(); // MENU
EventHandler<ActionEvent> playEasy = new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent) {
difficulty = 1; // SET DIFFICULTY
startStage.close(); // CLOSE MENU
play(); // RUN GAME ON EASY
}
};
EventHandler<ActionEvent> playMedium = new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent) {
difficulty = 2; // SET DIFFICULTY
startStage.close(); // CLOSE MENU
play(); // RUN GAME ON MEDIUM
}
};
EventHandler<ActionEvent> playHard = new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent) {
difficulty = 3; // SET DIFFICULTY
startStage.close(); // CLOSE MENU
play(); // RUN GAME ON HARD
}
};
easyButton.setOnAction(playEasy);
mediumButton.setOnAction(playMedium);
hardButton.setOnAction(playHard);
}
public void play() {
// WRITE GAME CODE HERE
}
To solve your specific problem, you could probably pass the startStage into the play method and then just update the scene there...but regardless I do hope this helps someone whos having trouble on how to use buttons! :)
How can I get my actual object out of the Dragboard with getContent()? Is there any possibility to specify the type? Todo and Doing are FlowPanes - MyRectangle is an exammple of a custom component.
What I want to have:
Put an Object eg. an Rectangle with height, filled color etc. on the clipboard and get that object back from the board with height, color etc....
private static final DataFormat itemFormat = new DataFormat("custom.item");
MyRectangle myRectangle = generateRectangle();
myRectangle.setOnDragDetected(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
Dragboard db = myRectangle
.startDragAndDrop(TransferMode.MOVE);
ClipboardContent content = new ClipboardContent();
content.put(taskFormat, myRectangle);
// Rectangle has height
System.out.println(myRectangle.getHeight());
TaskItem task = new TaskItem();
task.setTime(6);
content.put(itemFormat, task);
db.setContent(content);
event.consume();
}
});
myRectangle.setOnDragDone(new EventHandler<DragEvent>() {
public void handle(DragEvent event) {
event.consume();
}
});
doing.setOnDragOver(new EventHandler<DragEvent>() {
public void handle(DragEvent event) {
if (event.getGestureSource() != doing) {
event.acceptTransferModes(TransferMode.MOVE);
}
event.consume();
}
});
doing.setOnDragEntered(new EventHandler<DragEvent>() {
public void handle(DragEvent event) {
event.consume();
}
});
doing.setOnDragExited(new EventHandler<DragEvent>() {
public void handle(DragEvent event) {
event.consume();
}
});
doing.setOnDragDropped(new EventHandler<DragEvent>() {
public void handle(DragEvent event) {
final Dragboard db = event.getDragboard();
boolean success = false;
if (db.hasContent(taskFormat)) {
MyRectangle rect2 = (MyRectangle) db.getContent(taskFormat);
System.out.println(rect2.getHeight());
todo.getChildren().remove(rect2);
doing.getChildren().add(rect2);
success = true;
// doing.getChildren().add(rectangle);
}
event.setDropCompleted(success);
event.consume();
}
});
private MyRectangle generateRectangle() {
final MyRectangle rect2 = new MyRectangle(0, 0, 10, 10);
rect2.setId("app");
rect2.setArcHeight(8);
rect2.setWidth(80);
rect2.setArcWidth(8);
rect2.setStrokeWidth(1);
rect2.setStroke(Color.WHITE);
rect2.setHeight(60);
return rect2;
}
Well, you really should be thinking about having a representation of the data (not the view of the data) which is the object that is dragged and dropped. You register a handler to detect the drag with a node (view of the data), set the data into the dragboard, and then on a successful drop create another view of the same data where you dropped it. Remove the previous Node for a move, don't remove it for a copy.
Unfortunately, it doesn't work.
See this
and vote for this bug/feature, which would allow you to do this directly.
Currently you can only place Java objects on the drag board if they implement Serializable. Since the JavaFX Properties do not implement Serializable, any class that uses those for data representation (which, imho, is any class you'd want to use to represent data you want to drag and drop around an application). And even if your class is Serializable, as I understand it, the object is serialized on placing it in the dragboard and deserialized when you remove it, which means you'd get a new object when you dragged and dropped, not a reference to the same object: that's probably not what you want. (If you copy something by drag and drop, then edit it, you probably want both copies to respect the edit.)
So, for now, I think the solution is to create a local repository of some kind and store the dragged object in it. This might be as simple as just an ObjectProperty<?> currentlyDraggedObject, or something more complex like the LocalDragboard I implemented at the bottom of the discussion referenced earlier. (This is nothing more than copying the code you'll find if you google "standard example of a typesafe heterogeneous container".)
I have to say, I find the way drag and drop is done a bit weird. Almost everything in JavaFX 2 and later was written in a very modern style of Java, with (almost) everything using generics very comfortably, some very nice concurrency APIs that were designed for the newer high-level concurrency APIs, all the event handling designed with an eye to more recent language developments such as lambda expressions and streams. The Bindings API even seems to tip its hat slightly towards the whole reactive programming movement. But drag and drop seems to have been designed as though the only data we would ever want to transfer by drag gestures were Strings, Images, and Files. It's as though the designers of the DnD API hadn't really got their heads around the idea that programmers would want to, you know, develop their own data representation classes.
So in the midst of this very modern-looking GUI framework, you have a DnD API that looks like it was designed in the late 90s (if that). Very strange.