JavaFX ScrollPane PannableProperty with TitledPane - javafx

I'm building a GUI application with javafx that needs PannableProperty from the ScrollPane to work when the user drag the content of it from anywhere.
In oracle docs they say about the "pannableProperty":
Specifies whether the user should be able to pan the viewport by using
the mouse. If mouse events reach the ScrollPane (that is, if mouse
events are not blocked by the contained node or one of its children)
then pannable is consulted to determine if the events should be used
for panning.
So my problem is the mouse event cannot reach the ScrollPane..
Anyone has a clue how to make it possible?
this is a simple code to test it:
ScrollPane root = new ScrollPane();
root.setHbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS);
root.setVbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS);
root.setPannable(true);
VBox v = new VBox(10);
TitledPane c1 = new TitledPane("test: ", new HBox(new Label("test: "), new TextField()));
HBox c2 = new HBox(new Label("we are just in HBox "), new TextField());
Label c3 = new Label("I'm just a label and pannableProperty works here");
TitledPane c4 = new TitledPane("test4", new HBox(new Label("test: "), new TextField()));
AnchorPane c5 = new AnchorPane();
c5.setPrefSize(100, 100);
v.getChildren().addAll(c1, c2, c3, c4, c5);
root.setContent(v);
Scene scene = new Scene(root, 300, 300);
primaryStage.setScene(scene);
primaryStage.show();

Another tricky one :-)
The default Skin implementation of the TitledPane is a subclass of SkinBase and the default constructor (which gets invoked by TitledPaneSkin) does this (shortened version):
protected SkinBase(final C control) {
// Default behavior for controls is to consume all mouse events
consumeMouseEvents(true);
}
So we need to reverse this, unfortunately you have to do reflection for this:
TitledPane c1 = new TitledPane("test: ", new HBox(new Label("test: "), new TextField()));
c1.skinProperty().addListener((w,o,n)-> {
if(n instanceof SkinBase) {
SkinBase<?> skinbase = (SkinBase<?>) n;
try {
Method m = SkinBase.class.getDeclaredMethod("consumeMouseEvents", Boolean.TYPE);
AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
boolean wasAccessible = m.isAccessible();
try {
m.setAccessible(true);
m.invoke(skinbase, false);
return null;
}
catch(ReflectiveOperationException e) { throw new IllegalStateException(e); }
finally {
m.setAccessible(wasAccessible);
}
});
} catch (ReflectiveOperationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
And now it should work, at least it does in my test application.
EDIT #1:
Doing this resets the focus during mouse operations, which renders the TitledPane somewhat unusable. So we are now messing with the focus system:
ScrollPane root = new ScrollPane();
root.setFocusTraversable(false);
Scene scene = new Scene(root, 300, 300);
scene.focusOwnerProperty().addListener((w,o,n)->
if(n == root && o != null) {
o.requestFocus();
}
});
Basically what we are doing here is that we re-focus the previously focussed component if the newly focused element is the ScrollPane.

Related

VBox .getHeight() Not Changing until after .setOnAction Finishes [duplicate]

This question already has answers here:
Get the height of a node in JavaFX (generate a layout pass)
(2 answers)
Closed 3 years ago.
So I'm not sure how to calculate the height of the node during the .setOnAction event I have tried .requestLayout()/.applyCss() not sure what else to try I am trying to find the height of the vBox after adding a node but it is only printing the height of the node before the new one was added
public class Main extends Application {
#Override
public void start(Stage stage) {
VBox vBoxContainer = new VBox();
vBoxContainer.setAlignment(Pos.CENTER);
vBoxContainer.setPrefSize(200,200);
VBox vBox = new VBox();
vBox.setAlignment(Pos.CENTER);
for (int i = 0; i < 5; i++)
vBox.getChildren().add(new Label("newLabel"));
vBoxContainer.getChildren().add(vBox);
Button button = new Button("Add Label");
button.setOnAction(event -> {
System.out.println("Height Before new Label:"+vBox.getHeight());
vBox.getChildren().add(new Label("newLabel"));
//here is where I was adding code to produce expected result
System.out.println("Height After new Label:"+vBox.getHeight());
});
Button checkButton = new Button("Print VBox Height");
checkButton.setOnAction(event -> System.out.println("VBox Height:"+vBox.getHeight()));
vBoxContainer.getChildren().addAll(button, checkButton);
stage.setScene(new Scene(vBoxContainer));
stage.show();
}
}
Run the example and Click the button that adds a Label to the vBox and it outputs
Actual Result:
Height Before new Label:85.0
Height After new Label:85.0
Expected Result:
Height Before new Label:85.0
Height After new Label:102.0
But if you then click the Print VBox Height Button it will show the correct height of:
VBox Height:102.0
You can try adding a listener to the VBox's height property.
VBox vBoxContainer = new VBox();
vBoxContainer.setAlignment(Pos.CENTER);
vBoxContainer.setPrefSize(200, 200);
VBox vBox = new VBox();
vBox.setAlignment(Pos.CENTER);
vBoxContainer.getChildren().add(vBox);
vBox.heightProperty().addListener((observable, oldValue, newValue) -> {
System.out.println("Height changed to: " + newValue.doubleValue());
if(newValue.doubleValue() > 100)
{
//do something!
}
});
for (int i = 0; i < 5; i++) {
vBox.getChildren().add(new Label("newLabel"));
}
Button button = new Button("Add Label");
button.setOnAction(event -> {
vBox.getChildren().add(new Label("newLabel"));
});
Button checkButton = new Button("Print VBox Height");
checkButton.setOnAction(event -> System.out.println("VBox Height:" + vBox.getHeight()));
vBoxContainer.getChildren().addAll(button, checkButton);
stage.setScene(new Scene(vBoxContainer));
stage.show();
requestLayout does not actually do a layout pass. It simply tells JavaFX, that a layout pass is required which will result in JavaFX doing the layout pass some time after your method returns. To do the layout yourself, you need to call layout yourself, i.e. change the logic in the event handler like this:
button.setOnAction(event -> {
System.out.println("Height Before new Label:"+vBox.getHeight());
vBox.getChildren().add(new Label("newLabel"));
// manually doing layout on the root here
vBoxContainer.applyCss();
vBoxContainer.layout();
System.out.println("Height After new Label:"+vBox.getHeight());
});
Note that I do the layout pass for the root, since the ancestor layouts can also be involved in determining the actual size of a Node...

JavaFX custom dialog set Layout of node

We created a Custom Dialog without an FXML file. We are using JavaFX 8.
The dialog loads and functions as expected but we can not move the Buttons and the TextField to enhance the styling.
We have tried to use tf.setLayoutY(50) this has no effect.
We used this tf.setPromptText("This Works ?") and it works.
We would rather not use css to accomplish this styling.
And we will consider a FXML file if we can keep the two event handlers that force data to be entered in the TextField.
So the question is: How to style this Custom Dialog?
The code is a mess as it includes some concepts we tried:
public void CustomDialog() {
Dialog dialog = new Dialog<>();
dialog.setResizable(false);
final Window window = dialog.getDialogPane().getScene().getWindow();
stage = (Stage) window;
stage.setMinHeight(600);
stage.setMinWidth(400);
TextField tf = new TextField();
tf.setLayoutX(10);
tf.setLayoutY(50);
dialog.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL);
dialog.getDialogPane().getChildren().add(tf);
dialog.getDialogPane().setContent(tf);
// Create an event filter that consumes the action if the text is empty
EventHandler<ActionEvent> filter = event -> {
if (tf.getText().isEmpty()) {
event.consume();
}
};
// lookup the buttons
ButtonBase okButton = (Button) dialog.getDialogPane().lookupButton(ButtonType.OK);
Button cancelButton = (Button) dialog.getDialogPane().lookupButton(ButtonType.CANCEL);
// add the event-filter
okButton.addEventFilter(ActionEvent.ACTION, filter);
cancelButton.addEventFilter(ActionEvent.ACTION, filter);
stage.setOnCloseRequest(event -> {
if (tf.getText().isEmpty()) {
event.consume();
}
}
//Scene scene = new Scene(root);
//dialogStage.setScene(scene);
dialog.initModality(Modality.APPLICATION_MODAL);
//dialogStage.setAlwaysOnTop(true);
//dialogStage.setResizable(false);
tf.setPromptText("This Works ?");
tf.requestFocus();// This does not work
dialog.showAndWait();
}
Grendel we enhanced your answer so anyone who comes by and sees the code you posted in your question will understand as you said it was a mess
Your posted answer was real old school but less work perhaps than building a FXML file
Besides it is good to know some old school tricks
public void NewDialog(){
Label lblAmt = new Label("Enter Amount");
Button btnOK = new Button("OK");
TextField txtAmt = new TextField();
AnchorPane secondaryLayout = new AnchorPane();
secondaryLayout.setStyle("-fx-border-color:red;-fx-border-width:10px; -fx-background-color: lightblue;");
secondaryLayout.getChildren().addAll(lblAmt,btnOK,txtAmt);
lblAmt.setLayoutX(30);
lblAmt.setLayoutY(30);
txtAmt.setLayoutX(164);
txtAmt.setLayoutY(25);
txtAmt.setMaxWidth(116);
btnOK.setLayoutX(190);
btnOK.setLayoutY(100);
btnOK.setStyle("-fx-font-size: 18px;-fx-font-weight: bold;");
lblAmt.setStyle("-fx-font-size: 18px;-fx-font-weight: bold;");
txtAmt.setStyle("-fx-font-size: 18px;-fx-font-weight: bold;");
Scene secondScene = new Scene(secondaryLayout, 300, 180);
EventHandler<ActionEvent> filter = event -> {
if(txtAmt.getText().isEmpty()) {
event.consume();
}
};
// New window (Stage)
Stage newWindow = new Stage();
newWindow.initStyle(StageStyle.UNDECORATED);
//newWindow.initModality(Modality.APPLICATION_MODAL);
newWindow.setResizable(false);
newWindow.setTitle("Second Stage");
newWindow.setScene(secondScene);
btnOK.addEventHandler(ActionEvent.ACTION,filter);
btnOK.setOnAction(evt -> {
String str = txtAmt.getText();
System.out.println("################ str "+str);
if(txtAmt.getText().equals("")) {
evt.consume();
txtAmt.requestFocus();
}else{
newWindow.close();
}
});
newWindow.setOnCloseRequest(event -> {
if(txtAmt.getText().isEmpty()) {
event.consume();
}
});
txtAmt.requestFocus();
newWindow.showAndWait();
}

How to start animation from List View on mouse click - JAVAFX

I created scene, animations, list view, my problem is how to make animations play when I click on them in list view, and also, I need to create so more then one animation can play in one time.
Here is my code:
Group group = new Group();
Circle circle = new Circle(50, 300, 50);
circle.setFill(Color.RED);
TranslateTransition translate = new TranslateTransition();
translate.setByX(400);
translate.setDuration(Duration.millis(1000));
translate.setCycleCount(500);
translate.setAutoReverse(true);
translate.setNode(circle);
FadeTransition fade = new FadeTransition();
fade.setDuration(Duration.millis(1000));
fade.setFromValue(10);
fade.setToValue(0.1);
fade.setCycleCount(500);
fade.setAutoReverse(true);
fade.setNode(circle);
ScaleTransition transition = new ScaleTransition();
transition.setByX(1);
transition.setByY(1);
transition.setDuration(Duration.millis(1000));
transition.setCycleCount(500);
transition.setAutoReverse(true);
transition.setNode(circle);
ListView listView = new ListView();
listView.setPrefWidth(120);
listView.setPrefHeight(90);
listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
listView.getItems().add("Translate Transition");
listView.getItems().add("Fade Transition");
listView.getItems().add("Scale Transition");
group.getChildren().addAll(circle, listView);
Scene scene = new Scene(group, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
So, I have problem just with:
How to start animation(can be more then one animation in same time) when I click on some animation in list view.
You can add a listener to your listview that listens when items are selected. Something like this:
listView.getSelectionModel().selectedItemProperty().addListener((obs, ov, nv) -> {
if(nv != null && "Translate Transition".equals(nv)){
translate.play();
}
//Etc...
});

JavaFX 2.0 Toolbar-button load fxml

I have a toolbar with several buttons. I want several of this buttons to load different FXML-files. The way it is done now is by rwiting the fxml file in Java code and inserting it everytime it get's called. It would be a much better choice if I could just call the FXML-file and get the correct scene. This is an example where i load a settings view:
public void showModelSettings(){
clearPane();
GridPane gridPane = new GridPane();
ColumnConstraints cc1 = new ColumnConstraints();
cc1.setPercentWidth(50);
ColumnConstraints cc2 = new ColumnConstraints();
cc2.setPercentWidth(50);
gridPane.getColumnConstraints().addAll(cc1,cc2);
RowConstraints rc1 = new RowConstraints();
rc1.setPercentHeight(30);
RowConstraints rc2 = new RowConstraints();
rc2.setPercentHeight(70);
gridPane.getRowConstraints().addAll(rc1,rc2);
//iwModel.setImage(new Image("C:\Users\Public\Pictures\Sample Pictures\Desert.jpg"));
gridPane.setConstraints(cbModel, 0, 0);
gridPane.setConstraints(btnImageChooser, 1, 0);
gridPane.setConstraints(iwModel, 0, 1, 2, 1);
gridPane.getChildren().addAll(cbModel, btnImageChooser,iwModel);
mainPanel.getChildren().add(gridPane);
}
I have used tabpane at an earlier date, the handy thing with this is that it can load "content" which in a way is a actionlistener that can load fxml-files. I want this functionality for my toolbar buttons.
all help will be appreciated greatly!
Thanks :)
I think maybe I solved it, can't check yet because my project has several errors in other packages. But here is the code I believe will work:
private Parent replaceSceneContent(String fxml) throws Exception {
Parent page = (Parent) FXMLLoader.load(InventorySystem.class.getResource("/hist/inventory/gui/fxml/"+fxml), ResourceBundle.getBundle("MessageBundle",localSettings.getLocale()), new JavaFXBuilderFactory());
Scene scene = stage.getScene();
if(scene == null) {
scene = new Scene(page, 340, 280);
scene.getStylesheets().add("hist/inventory/gui/startStyle.css"); //Endre styles?
//stage.setResizable(false); //Må kanskje flyttes
stage.setScene(scene);
Organizer.getWindowFitter().setScene(scene);
} else {
stage.getScene().setRoot(page);
}
stage.sizeToScene();
return page;
}
public void setScene(String scene) throws Exception {
replaceSceneContent(scene);
}

How to create a modal window in JavaFX 2.1

I can't figure out how to create a modal window in JavaFX. Basically I have file chooser and I want to ask the user a question when they select a file. I need this information in order to parse the file, so the execution needs to wait for the answer.
I've seen this question but I've not been able to find out how to implement this behavior.
In my opinion this is not good solution, because parent window is all time active.
For example if You want open window as modal after click button...
private void clickShow(ActionEvent event) {
Stage stage = new Stage();
Parent root = FXMLLoader.load(
YourClassController.class.getResource("YourClass.fxml"));
stage.setScene(new Scene(root));
stage.setTitle("My modal window");
stage.initModality(Modality.WINDOW_MODAL);
stage.initOwner(
((Node)event.getSource()).getScene().getWindow() );
stage.show();
}
Now Your new window is REALY modal - parent is block.
also You can use
Modality.APPLICATION_MODAL
Here is link to a solution I created earlier for modal dialogs in JavaFX 2.1
The solution creates a modal stage on top of the current stage and takes action on the dialog results via event handlers for the dialog controls.
JavaFX 8+
The prior linked solution uses a dated event handler approach to take action after a dialog was dismissed. That approach was valid for pre-JavaFX 2.2 implementations. For JavaFX 8+ there is no need for event handers, instead, use the new Stage showAndWait() method. For example:
Stage dialog = new Stage();
// populate dialog with controls.
...
dialog.initOwner(parentStage);
dialog.initModality(Modality.APPLICATION_MODAL);
dialog.showAndWait();
// process result of dialog operation.
...
Note that, in order for things to work as expected, it is important to initialize the owner of the Stage and to initialize the modality of the Stage to either WINDOW_MODAL or APPLICATION_MODAL.
There are some high quality standard UI dialogs in JavaFX 8 and ControlsFX, if they fit your requirements, I advise using those rather than developing your own. Those in-built JavaFX Dialog and Alert classes also have initOwner and initModality and showAndWait methods, so that you can set the modality for them as you wish (note that, by default, the in-built dialogs are application modal).
You can create application like my sample. This is only single file JavaFX application.
public class JavaFXApplication1 extends Application {
#Override
public void start(Stage primaryStage) {
Button btn = new Button();
btn.setText("Say 'Hello World'");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
Stage stage;
stage = new Stage();
final SwingNode swingNode = new SwingNode();
createSwingContent(swingNode);
StackPane pane = new StackPane();
pane.getChildren().add(swingNode);
stage.initModality(Modality.APPLICATION_MODAL);
stage.setTitle("Swing in JavaFX");
stage.setScene(new Scene(pane, 250, 150));
stage.show();
}
});
StackPane root = new StackPane();
root.getChildren().add(btn);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
private void createSwingContent(final SwingNode swingNode) {
SwingUtilities.invokeLater(() -> {
try {
Path currentRelativePath = Paths.get("");
String s = currentRelativePath.toAbsolutePath().toString();
JasperDesign jasperDesign = JRXmlLoader.load(s + "/src/reports/report1.jrxml");
String query = "SELECT * FROM `accounttype`";
JRDesignQuery jrquery = new JRDesignQuery();
jrquery.setText(query);
jasperDesign.setQuery(jrquery);
JasperReport jasperReport = JasperCompileManager.compileReport(jasperDesign);
JasperPrint JasperPrint = JasperFillManager.fillReport(jasperReport, null, c);
//JRViewer viewer = new JRViewer(JasperPrint);
swingNode.setContent(new JRViewer(JasperPrint));
} catch (JRException ex) {
Logger.getLogger(AccountTypeController.class.getName()).log(Level.SEVERE, null, ex);
}
});
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}

Resources