I am in the process of teaching myself JavaFX. Coming from the Swing world there are a lot of similarities between the 2. Especially event processing. Part of my process is to try and mimic an existing application as closely as possible. One of the things I am doing is creating a dialog that will allow the user to select a font to use. There is a text field for them to type in the font name and a list where they can scroll and select one. When they start typing the list will automatically scroll to through the list to start matching what the user is typing. I am also trying to populate the text field with the currently matched font name and then highlight the portion that the user has not typed yet so they can continue to type until the correct match is found.
For example if the user types the letter 't' on Windows the first font found is Tahoma. So the text field will be set to Tahoma and the carat will be positioned right after the 'T' and the 'ahoma' will be highlighted. What happens instead is that the field is populated with Tahoma and the carat is positioned at the end and nothing is highlighted. So it is like it is ignoring the 2 lines of code for positioning and highlighting or the event processor is causing my calls to JavaFX libraries to be run out of order.
I think this may be a bug with JavaFX but it could also be my misunderstanding of the event system. Please let me know which one and why.
Here is a complete sample code showing the problem. Just start typing in the text field to try it out.
package test;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class TestTyping extends Application {
ChangeListener<String> textChange;
#Override
public void start(Stage primaryStage) throws Exception {
BorderPane root = new BorderPane();
TextField text = new TextField();
root.setTop(text);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
textChange = (observable, oldValue, newValue) -> {
text.textProperty().removeListener(textChange);
for (String family : Font.getFamilies()) {
if (family.equalsIgnoreCase(newValue) || family.toLowerCase().startsWith(newValue.toLowerCase())) {
text.setText(family);
text.positionCaret(newValue.length());
text.selectEnd();
break;
}
}
text.textProperty().addListener(textChange);
};
text.textProperty().addListener(textChange);
}
public static void main(String... args) {
launch(args);
}
}
Wrap caret position and select end into Platform.runLater. The problem is in events order. I don't know correct details about this issue so I will not provide you a detailed answer, only solution.
Platform.runLater(()-> {
text.positionCaret(newValue.length());
text.selectEnd();
});
Here's an alternative approach entirely, which uses a TextFormatter to modify changes to the text. The advantage here is that it doesn't rely on the "timing" of various property changes with respect to event handling, which is not documented and thus could possibly change in later JavaFX versions. It also avoids the slightly ugly "remove the listener and add it back" idiom.
import java.util.function.UnaryOperator;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.TextFormatter.Change;
import javafx.scene.layout.BorderPane;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class TestTyping extends Application {
ChangeListener<String> textChange;
#Override
public void start(Stage primaryStage) throws Exception {
BorderPane root = new BorderPane();
TextField text = new TextField();
root.setTop(text);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
UnaryOperator<Change> filter = c -> {
// for delete, move the caret, or change selection, don't modify anything...
if (c.getText().isEmpty()) {
return c ;
}
for (String family : Font.getFamilies()) {
if (family.toLowerCase().startsWith(c.getControlNewText().toLowerCase())) {
c.setText(family.substring(c.getRangeStart(), family.length()));
c.setAnchor(c.getControlNewText().length());
break ;
}
}
return c ;
};
text.setTextFormatter(new TextFormatter<String>(filter));
}
public static void main(String... args) {
launch(args);
}
}
Related
I have a scene with a choice box. the aim is to get all available system fonts to display in the choice box, I kinda feel I'm on the right path as so far I have managed to get 1 to display in the choice box, but why just the 1?
here is the code -
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.SingleSelectionModel;
import javafx.scene.layout.Pane;
import javafx.scene.text.Font;
public class ChoiceBoxFonts extends Application
{
ObservableList<String> fontType;
ChoiceBox<String> fonts;
public static void main(String [] args)
{
Application.launch(args);
}
public void start(Stage primaryStage)
{
Pane root = new Pane();
Font.getFamilies().stream().forEach(i ->{
fontType =
FXCollections.observableArrayList(i
);
});
// New choicebox with observable arraylist fontType
fonts = new ChoiceBox<String> (fontType);
//SingleSelectionModel<String> selMod = fonts.getSelectionModel();
root.getChildren().add(fonts);
Scene scene = new Scene(root,200,200);
primaryStage.setScene(scene);
primaryStage.show();
}
}
The goal of the experiment is to be able to select a font from the choice box and change the font of a text object with that selection.
Also, is there a better UI to be able to do such a thing? If there are a bucket load of fonts, that choice box is going to be very long!
You just need
fontType = FXCollections.observableArrayList(Font.getFamilies());
instead of the iteration you have.
If there are a bucket load of fonts, that choice box is going to be very long!
I would probably consider a ListView.
really liking JavaFX but have come across this problem and wondered if it was a bug.
The ScrollBar.setOnMousePressed() doesn't seem to fire when it has been initialised with a handler. The code below demonstrates the problem:-
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollBar;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Play extends Application {
public static void main(String[] args) {
launch(args);
}
private static int cnt;
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Bug?");
Button btn = new Button("This text will get replaced by the event handlers");
ScrollBar scrollBar = new ScrollBar();
// When pressing and releasing the ScrollBar thumb, we only get decrements
// If you replace the ScrollBar with say a Button, then the code below works as you might expect.
scrollBar.setOnMousePressed( event -> btn.setText("X" + cnt++));
scrollBar.setOnMouseReleased( event -> btn.setText("X" + cnt--));
VBox root = new VBox();
root.getChildren().add(btn);
root.getChildren().add(scrollBar);
primaryStage.setScene(new Scene(root, 350, 250));
primaryStage.show();
}
}
Note, Im running on JDK 1.8.0_66 64 Bit on Microsoft Windows 10.
A simple workaround, as suggested by James_D, is to use EventFilters instead of setOnMousePressed(), as follows:-
So,
scrollBar.addEventFilter(MouseEvent.MOUSE_PRESSED,
event -> btn.setText("X" + cnt++));
instead of
scrollBar.setOnMousePressed( event -> btn.setText("X" + cnt++));
I believe .setOnMousePressed() should work, but doesn't because of a bug in the library. I've raised with oracle and will update the answer once oracle clarifies.
I am well aware of the Java FX Node API which states:
A disabled Node does not receive mouse or key events.
So, I am trying to come up with a work around. In our old Swing application we used to allow users to double click on a disabled web/email field (Formatted TextField) to open a link to the page or their native mail client. I am hoping to simulate this behavior in FX. Instead of calling:
setDisable(true);
I am now calling:
setEditable(false);
The only remaining issue is that I would like to style the Node as if it were disabled, or at minimum disable text selection.
Is there a simple way to get the exact style of the Node when it is disabled, or will I need to create my own CSS class? (Unfortunately my CSS knowledge is relatively weak).
I can't see an "easy" way to do this with CSS, without replicating the default rules for a disabled text field (but maybe someone else has a trick for that).
Here's a completely different approach, though. When the text field is disabled, it doesn't receive mouse events, so any mouse events will just "drop through" to the node below it in Z-order. So if you wrap the text field in some pane, and register a mouse handler with the pane, that mouse handler will get invoked if the text field is disabled.
SSCCE:
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TextField;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class DIsabledTextFieldEventTest extends Application {
#Override
public void start(Stage primaryStage) {
TextField textField = new TextField();
StackPane textFieldHolder = new StackPane(textField);
CheckBox disableTextField = new CheckBox("Disable text field");
textField.disableProperty().bind(disableTextField.selectedProperty());
textFieldHolder.setOnMouseClicked(e -> {
if (e.getClickCount() == 2) {
System.out.println("Double click on disabled text field!");
}
});
VBox root = new VBox(10, disableTextField, textFieldHolder);
root.setAlignment(Pos.CENTER);
Scene scene = new Scene(root, 350, 120);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Is there a simple way to get the exact style of the Node when it is disabled, or will I need to create my own CSS class?
If you do the below to your non-editable text field, it looks exactly like a disabled text field (tested on my MacBook).
textField.setStyle("-fx-opacity:0.5");
I'm trying to learn JavaFX. To do so I've been attempting to make a text editor that includes multiple line text box support, as well as the possibility of having syntax highlighting down the road.
Currently, the biggest problem I've been facing is that the ScrollPane I've been encapsulating all my FlowPanes in won't resize according to the size of the Pane it's in. I've been researching this problem for about half a week now and simply cannot get the ScrollPane to just fill the window it's in. The code below displays a JavaFX stage that has working keyboard input and the ScrollPane is always the same size no matter what. Thanks to all in advance!
Here's my Main:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Launcher extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setScene(new Scene(new DynamicTextBox(),500,500));
primaryStage.show();
}
}
TextBox class:
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.geometry.Bounds;
import javafx.geometry.Orientation;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.Pane;
import javafx.scene.text.Text;
public class DynamicTextBox extends Pane {
//currentLinePane is made to handle all the direct user inputs
//multiLinePane, while not really used yet will create a new line when the enter key is struck.
private FlowPane currentLinePane, multiLinePane;
private ScrollPane editorScroller;
public DynamicTextBox() {
super();
currentLinePane = new FlowPane(Orientation.HORIZONTAL);
multiLinePane = new FlowPane(Orientation.VERTICAL);
multiLinePane.getChildren().add(currentLinePane);
editorScroller = new ScrollPane(multiLinePane);
editorScroller.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED);
editorScroller.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
editorScroller.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
configureInput(event);
}
});
super.getChildren().add(editorScroller);
editorScroller.requestFocus();
}
private void configureInput(KeyEvent event) {
currentLinePane.getChildren().add(new Text(event.getText()));
}
}
You're using
ScrollPane.ScrollBarPolicy.AS_NEEDED
which, according to the docs at Oracle, "Indicates that a scroll bar should be shown when required." Instead, use
ScrollPane.ScrollBarPolicy.ALWAYS
alternatively, recall these are constants. you can get the height of the parent using boundsInParent: https://docs.oracle.com/javafx/2/api/javafx/scene/Node.html#boundsInParentProperty
alternatively, you can use getParent() to get the parent and then get its height using computeMinWidth() https://docs.oracle.com/javafx/2/api/javafx/scene/Node.html#getParent()
I'm currently learning to create Graphical Interfaces using JavaFX as i feel like it's
more powerful than Swing and easier to code by hand instead of having to resort to
GUIBuilder.
While this also includes Swing i have read quite a few tutorials so far and I always see
all the code being written in either the main() or start() methods.
For example this code example from java.about.com:
//Imports are listed in full to show what's being used
//could just import javafx.*
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;
public class ApplicationWindow extends Application {
//JavaFX applicatoin still use the main method.
//It should only ever contain the call to the launch method
public static void main(String[] args) {
launch(args);
}
//starting point for the application
//this is where we put the code for the user interface
#Override
public void start(Stage primaryStage) {
//The primaryStage is the top-level container
primaryStage.setTitle("example Gui");
//The BorderPane has the same areas laid out as the
//BorderLayout layout manager
BorderPane componentLayout = new BorderPane();
componentLayout.setPadding(new Insets(20,0,20,20));
//The FlowPane is a conatiner that uses a flow layout
final FlowPane choicePane = new FlowPane();
choicePane.setHgap(100);
Label choiceLbl = new Label("Fruits");
//The choicebox is populated from an observableArrayList
ChoiceBox fruits = new ChoiceBox(FXCollections.observableArrayList("Asparagus", "Beans", "Broccoli", "Cabbage"
, "Carrot", "Celery", "Cucumber", "Leek", "Mushroom"
, "Pepper", "Radish", "Shallot", "Spinach", "Swede"
, "Turnip"));
//Add the label and choicebox to the flowpane
choicePane.getChildren().add(choiceLbl);
choicePane.getChildren().add(fruits);
//put the flowpane in the top area of the BorderPane
componentLayout.setTop(choicePane);
final FlowPane listPane = new FlowPane();
listPane.setHgap(100);
Label listLbl = new Label("Vegetables");
ListView vegetables = new ListView(FXCollections.observableArrayList("Apple", "Apricot", "Banana"
,"Cherry", "Date", "Kiwi", "Orange", "Pear", "Strawberry"));
listPane.getChildren().add(listLbl);
listPane.getChildren().add(vegetables);
listPane.setVisible(false);
componentLayout.setCenter(listPane);
//The button uses an inner class to handle the button click event
Button vegFruitBut = new Button("Fruit or Veg");
vegFruitBut.setOnAction(new EventHandler() {
#Override
public void handle(ActionEvent event) {
//switch the visibility for each FlowPane
choicePane.setVisible(!choicePane.isVisible());
listPane.setVisible(!listPane.isVisible());
}
});
componentLayout.setBottom(vegFruitBut);
//Add the BorderPane to the Scene
Scene appScene = new Scene(componentLayout,500,500);
//Add the Scene to the Stage
primaryStage.setScene(appScene);
primaryStage.show();
}
}
But coding everything in the main() or start() goes against principles i learnt at school
and also seems like a bad thing to do too me.
As i always used to code splitting everything up in different methods and than keeping a clean main() by only calling methods to do stuff, so it's easy to read up on the programs execution path. As i see all tutorials etc. going against this by putting al their GUI code in the main or start methods i started wondering if this is conventional to do regarding GUI code or should i still try to put everything under custom methods and keep a clean main()?
Also could anyone share neat tutorials regarding JavaFX? I can't seem to find much in this matter.
EDIT: I should really just ask for a good example on how a class hierarchy of an advanced application interface looks like to get some ideas.
Hope this was a clear question,
thanks in advance.
Jasper.
You could create a GUI class that holds all of your GUI code together, or simply create a GUI method that resides below main. Then, in main, you could call:
public static void main(String[] args)
{
launch(args);
}
public void start()
{
launchGUI(Stage primaryStage)
}
And then create your launchGUI method either in the same class, or create a new class specifically for the GUI and them use import to import it into the class containing main/start.