How to set JavaFX Dialog Label width - javafx

I'm trying to set the width of a JavaFX Dialog to fit my Text.
I know how to do it, but there is a "Fudge Factor" of 32 that I would like to understand.
Can anyone explain how I can determine the value empirically?
I'm using the Zulu OpenJDK 17 with bundled JavaFX under Windows 10.
Here's some example code:
import static javafx.scene.control.Alert.AlertType.INFORMATION;
import javafx.application.Application;
import javafx.scene.control.*;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class AlertWide extends Application {
private static final String INFO_TEXT = """
A couple of lines...
1) Line 1 is quite short
2) Line 2 is too wide to fit in the standard Alert Label, so it needs to be widened manually, that is to say, once a year
3) the last line
""";
public static void main(final String[] args) {
launch(args);
}
#Override
public void start(final Stage primaryStage) throws Exception {
final var infoWidth = new Text(INFO_TEXT).getBoundsInLocal().getWidth();
final var alert = new Alert(INFORMATION);
; alert.setContentText(INFO_TEXT);
; alert.showAndWait();
setLabelWidth(alert, infoWidth ); alert.showAndWait();
setLabelWidth(alert, infoWidth + 32); alert.showAndWait(); // TODO why 32?
}
private void setLabelWidth(final Alert alert, final double preferredLabelWidth) {
alert.getDialogPane().getChildren().forEach(node -> {
if (node instanceof Label nodeLabel) {
nodeLabel.setPrefWidth(preferredLabelWidth);
}
});
}
}

Having delved into the depths of Dialog, I found a very simple solution.
Rather than iterating through the DialogPane's children, I simply replaced the Label with a new Instance.
P.S. "replaced" is not strictly speaking correct: the built-in DialogPane Label is set to unmanaged & invisible & so takes no part in the rendering.
The following was just fine for my purposes:
import static javafx.scene.control.Alert.AlertType.INFORMATION;
import javafx.application.Application;
import javafx.scene.control.*;
import javafx.stage.Stage;
public class AlertWideSetContent extends Application {
private static final String INFO_TEXT = """
A couple of lines...
1) Line 1 is quite short
2) Line 2 is too wide to fit in the standard Alert Label, so it needs to be widened manually, that is to say, once a year
3) the last line
""";
public static void main(final String[] args) {
launch(args);
}
#Override
public void start(final Stage primaryStage) throws Exception {
final var alert = new Alert(INFORMATION);
; alert.getDialogPane().setContent(new Label(INFO_TEXT));
; alert.showAndWait();
}
}
But, as #kleopatra observed, setting the Preferred Width to -1 (USE_COMPUTED_SIZE) does it too:
import static javafx.scene.control.Alert.AlertType.INFORMATION;
import javafx.application.Application;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.stage.Stage;
public class AlertWideUseComputedSize extends Application {
private static final String INFO_TEXT = """
A couple of lines...
1) Line 1 is quite short
2) Line 2 is too wide to fit in the standard Alert Label, so it needs to be widened manually, that is to say, once a year
3) the last line
""";
public static void main(final String[] args) {
launch(args);
}
#Override
public void start(final Stage primaryStage) throws Exception {
final var alert = new Alert (INFORMATION);
; alert.setContentText(INFO_TEXT );
for (final Node node : alert.getDialogPane().getChildren()) {
if (node instanceof Label nodeLabel) {
nodeLabel.setPrefWidth(Label.USE_COMPUTED_SIZE);
}
}
alert.showAndWait();
}
}

Related

JavaFX ComboBox check input and change it

I am trying to build a Combobox in JavaFX that should work as followed.
The user should only type numbers in, but the typed numbers have to be formattet.
ex. 111-111-1111.
So if the user types in three numbers a - should be added automatically.
I figured out how to do that.
I blocked everything but numbers with a TexFormatter.
Now the main Part of the problem comes the part after this.
I added a keyevent.key_released place the caret at the end when a key is released.
But if the user is typing too fast it won't work.
Most of the time it works just fine like that, but when is the user every doing something only the way I expect?
I could not find another way to get the actual value of the combobox, because it seems to refresh after hitting enter or so.
Adding the - in the Textformatter resultet in the programm listening to itself and I wasn't able to place the caret at the end position.
import java.util.function.UnaryOperator;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.TextFormatter;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
/**
*
* #author
*/
public class tester extends Application{
ComboBox<String> combo = new ComboBox<String>();
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
System.out.println("test");
HBox root = new HBox();
//
UnaryOperator<TextFormatter.Change> filter = new UnaryOperator<TextFormatter.Change>() {
#Override
public TextFormatter.Change apply(TextFormatter.Change t) {
System.out.println(t.getText());
if (t.isReplaced())
if(t.getText().matches("[^0-9]")) {
t.setText(t.getControlText().substring(t.getRangeStart(), t.getRangeEnd()));}
if (t.isAdded()) {
// Add in Formatter //
if (t.getControlText().length() == 2 || t.getControlText().length() == 6 && t.getText().matches("[0-9]")){
t.setText(t.getText() + "-");
combo.getEditor().end();
}
//
if (t.getText().matches("[^0-9]")) {
t.setText("");}
}
return t;
}
};
combo.setEditable(true);
combo.getEditor().setTextFormatter(new TextFormatter<>(filter));
combo.getEditor().addEventFilter(KeyEvent.KEY_RELEASED, e -> {
combo.getEditor().end();
});
root.getChildren().add(combo);
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
}

JavaFX TextField listener gives java.lang.IllegalArgumentException: The start must be <= the end

So I am writing a javafx program to manipulate the individual bits in a byte. I have a textfield for each bit. I want to implement a changelistener on the textfields so one cannot enter anything but a 0 or a 1. It works fine if the field is empty and the user tries to enter a letter, but if there is already a 0 or 1 in it it throws an exception and I dont understand why.
Here is my code:
public class Task03Controller implements Initializable {
#FXML private TextField zeroTextField, oneTextField, twoTextField, threeTextField,
fourTextField, fiveTextField, sixTextField, sevenTextField;
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
zeroTextField.textProperty().addListener((observable, oldValue, newValue) -> {
if(!zeroTextField.getText().equals("0") && !zeroTextField.getText().equals("1"))
zeroTextField.clear();
else if(zeroTextField.getText().length() > 1)
zeroTextField.setText(zeroTextField.getText().substring(0, 0));
});
}
}
Using the same idea as the duplicate. You need to define a regular expression that matches binary numbers.
I am using "\\b[01]+\\b" to define binary numbers and "" to define an empty TextField.
MCVE
import java.util.function.UnaryOperator;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.TextFormatter.Change;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class TestingGroundsTwo extends Application
{
public static void main(String[] args)
{
launch(args);
}
#Override
public void start(Stage stage)
{
UnaryOperator<Change> binaryFilter = change -> {
String newText = change.getControlNewText();
if (newText.matches("\\b[01]+\\b") || newText.matches("")) {
return change;
}
return null;
};
TextField textField = new TextField();
textField.setTextFormatter(new TextFormatter<>(binaryFilter));
stage.setTitle("Hello World!");
Scene scene = new Scene(new StackPane(textField), 750, 125);
scene.setFill(Color.GHOSTWHITE);
stage.setScene(scene);
stage.show();
}
}

JavaFX Spinner units

I would like to set a units to a spinner which is accepting only numbers.
Ideally the spinner textfield would display the units right next to the number itself (example: 2500 ms or 125 % etc.).
Is there a way to set a format to the textfield?
I thought it would be fairly easy but I can't find a way.
Thanks, Jan
The spinner value factory has a converter that is used to convert the value to and from the text value displayed or entered in the editor. You can use this to configure how the value is displayed:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Spinner;
import javafx.scene.control.SpinnerValueFactory;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.StringConverter;
public class SpinnerWithUnits extends Application {
#Override
public void start(Stage primaryStage) {
Spinner<Integer> spinner = new Spinner<>();
spinner.setEditable(true);
SpinnerValueFactory.IntegerSpinnerValueFactory valueFactory = new SpinnerValueFactory.IntegerSpinnerValueFactory(0, 10000, 0, 100);
valueFactory.setConverter(new StringConverter<Integer>() {
#Override
public String toString(Integer value) {
return value.toString()+" ms";
}
#Override
public Integer fromString(String string) {
String valueWithoutUnits = string.replaceAll("ms", "").trim();
if (valueWithoutUnits.isEmpty()) {
return 0 ;
} else {
return Integer.valueOf(valueWithoutUnits);
}
}
});
spinner.setValueFactory(valueFactory);
StackPane root = new StackPane(spinner);
primaryStage.setScene(new Scene(root, 400, 400));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Of course, you probably want to use a more appropriate type than Integer as the type of the spinner: in this case it would probably make more sense to use a Duration. You can still use a converter on the (now custom) spinner value factory to display the value formatted as you need.

How to update the value of Event handler in thread?

I'd like to update the value of text in thread(ScheduledService) when I push the button. I can update the value of text in GUI but the value isn't capable of updating it in thread(ScheduledService).
How should I do to update the value of text in thread(ScheduledService) area?
[Procesure]
(1)when I input the value of text and push the button,the value is shown in GUI
by the following code(in EventHandler)
label.setText(text);
Value = Integer.parseInt( text);
(2)I want to pass the value of "Value" to thread by the following
recieve(Value);
(3) the value of "Value" is shown by the following code
System.out.println(a);
But , the value of "Value" is not update .it is still "0".
the value of "0" is the initial value of "Value".
public Integer Value = 0;
My Code is the following:
package javafxapplication16;
import java.io.IOException;
import javafx.application.Application;
import javafx.concurrent.ScheduledService;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonBuilder;
import javafx.scene.control.Label;
import javafx.scene.control.LabelBuilder;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFieldBuilder;
import javafx.scene.layout.HBox;
import javafx.scene.layout.HBoxBuilder;
import javafx.scene.text.Font;
import javafx.scene.text.FontPosture;
import javafx.stage.Stage;
import javafx.stage.StageBuilder;
public class JavaFXApplication16 extends Application {
public Label label;
public Integer Value = 0;
public Button bt_co;
public TextField tx;
#Override
public void start(Stage stage) throws Exception{
tx = TextFieldBuilder.create().text("").build();
Font font = Font.font("Arial",FontPosture.ITALIC,20);
label = LabelBuilder.create().text("value")
.alignment(Pos.CENTER)
.font(font)
.prefWidth(200).build();
bt_co = ButtonBuilder.create().text("")
.prefWidth(200)
.alignment(Pos.CENTER)
.id("")
.build();
HBox root = HBoxBuilder.create().spacing(100).children(tx,label,bt_co).build();
Scene scene = new Scene(root);
scene.addEventHandler(ActionEvent.ACTION,actionHandler);
recieve(Value); // pass the value of "Value" to thread
stage = StageBuilder.create().width(640).height(640).scene(scene).title(" ").build();
stage.show();
}
/* thread */
private void recieve(int a ) throws IOException {
ScheduledService<Boolean> ss = new ScheduledService<Boolean>()
{
#Override
protected Task<Boolean> createTask()
{
Task<Boolean> task = new Task<Boolean>()
{
#Override
protected Boolean call() throws Exception
{
System.out.println(a);
return true;
};
};
return task;
}
};
ss.start();
}
/* Event Handler */
EventHandler<ActionEvent> actionHandler = new EventHandler<ActionEvent>(){
public void handle (ActionEvent e){
Button src =(Button)e.getTarget();
String text = tx.getText();
label.setText(text);
Value = Integer.parseInt( text);
}
};
public static void main(String[] args) {
launch(args);
}
}
Parameter values are evaluated right before the method is invoked. It's that value that is passed, not some value that may be written to some field involved in the expression used as function parameter.
This means what happens is
UI is build.
Event handler is added
receive is executed and the value currently stored in the Value field is passed as parameter, which is 0 at this time.
(ScheduledService prints 0 repeatedly)
At some time the event handler is executed writing a new value to the Value field
(ScheduledService continues printing 0, since this is the value of a)
For any different behavior you'd need to use a expression inside the task that actually changes it's value, e.g.
volatile Integer Value;
...
receive();
...
private void recieve() /* throws IOException */ {
ScheduledService<Boolean> ss = new ScheduledService<Boolean>()
{
#Override
protected Task<Boolean> createTask()
{
Task<Boolean> task = new Task<Boolean>()
{
#Override
protected Boolean call() throws Exception
{
System.out.println(a);
return true;
};
};
return task;
}
};
ss.start();
}
BTW: You should get rid of those uses of builders. They are all deprecated in JavaFX 8, not even documented in the javadocs and will probably be removed in JavaFX 9.

How do I get the character index at a given coordinate in a Text-node?

In JavaFX, how can I get the index of the character (in a javafx.scene.text.Text object) under the mouse pointer from a mouse clicked event? Or more generally, how do I get the index of the character located at a (x, y) coordinate in a javafx.scene.text.Text node?
I have managed to find the index using Node.queryAccessibleAttribute(), but this feels somewhat like a hack and not the proper use of the JavaFX APIs:
import javafx.application.Application;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import static javafx.scene.AccessibleAttribute.OFFSET_AT_POINT;
public class TextClicked extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
final Text textNode = new Text("Hello, world!");
textNode.setOnMouseClicked(event -> {
final int idx = (int) textNode.queryAccessibleAttribute(OFFSET_AT_POINT, new Point2D(event.getScreenX(), event.getScreenY()));
System.out.println("Character clicked: " + textNode.getText().charAt(idx));
});
primaryStage.setScene(new Scene(new HBox(textNode)));
primaryStage.show();
}
}
JavaFX doesn't really encourage a functionality where you register an event handler with a control, and then inspect low-level details of the event (such as the mouse coordinates) in order to deduce semantic information about the event. The preferred approach is to register listeners with individual contained nodes in the scene graph. For example, whereas in Swing you might register a listener with a JList and then locationToIndex(...) method to get the index of the item in the JList, in JavaFX you are encouraged to register listeners with the individual ListCells instead of the ListView itself.
So the "idiomatic" way to do this is probably to create a TextFlow and add individual Texts to it. To determine which Text is clicked, you would register listeners with each of the individual texts.
You could create a reusable class to encapsulate this functionality, of course, exposing as much API of the enclosed TextFlow as you need.
Here's a fairly basic example:
import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.event.Event;
import javafx.event.EventType;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.input.InputEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
import javafx.stage.Stage;
public class ClickableTextExample extends Application {
#Override
public void start(Stage primaryStage) {
ClickableText text = new ClickableText("The quick brown fox jumped over the lazy dog.");
text.asNode().addEventHandler(TextClickEvent.CLICKED, e -> {
System.out.println("Click on "+e.getCharacter()+" at "+e.getIndex());
});
StackPane root = new StackPane(text.asNode());
Scene scene = new Scene(root, 350, 120);
primaryStage.setScene(scene);
primaryStage.show();
}
public static class ClickableText {
private final StringProperty text = new SimpleStringProperty();
public StringProperty textProperty() {
return text ;
}
public String getText() {
return textProperty().get();
}
public void setText(String text) {
textProperty().set(text);
}
private final TextFlow textFlow ;
public ClickableText(String text) {
textFlow = new TextFlow();
textProperty().addListener((obs, oldText, newText) ->
rebuildText(newText));
setText(text);
}
public Node asNode() {
return textFlow ;
}
private void rebuildText(String text) {
List<Text> textNodes = new ArrayList<>();
for (int i = 0; i < text.toCharArray().length; i++) {
char c = text.charAt(i);
Text textNode = new Text(Character.toString(c));
textNodes.add(textNode);
registerListener(textNode, i);
}
textFlow.getChildren().setAll(textNodes);
}
private void registerListener(Text text, int index) {
text.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> {
TextClickEvent event = new TextClickEvent(text.getText().charAt(0), index, textFlow, textFlow);
Event.fireEvent(textFlow, event);
});
}
}
public static class TextClickEvent extends InputEvent {
private final char c ;
private final int index ;
public static final EventType<TextClickEvent> CLICKED = new EventType<TextClickEvent>(InputEvent.ANY);
public TextClickEvent(char c, int index, Node source, Node target) {
super(source, target, CLICKED);
this.c = c ;
this.index = index ;
}
public char getCharacter() {
return c ;
}
public int getIndex() {
return index ;
}
}
public static void main(String[] args) {
launch(args);
}
}
Another solution, which you may or may not regard as a hack, would be to use a non-editable text field and style it to look like a Text (or Label). Then you can check the caretPosition after the user clicks.
This code is based on Copiable Label/TextField/LabeledText in JavaFX
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.scene.control.TextField;
import javafx.stage.Stage;
public class TextClicked extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
final TextField textNode = new TextField("Hello, world!");
textNode.setEditable(false);
textNode.getStyleClass().add("clickable-text");
textNode.setOnMouseClicked(event -> {
final int idx = Math.max(0, textNode.getCaretPosition() - 1);
System.out.println("Character clicked: " + textNode.getText().charAt(idx));
});
Scene scene = new Scene(new HBox(textNode));
scene.getStylesheets().add("clickable-text.css");
primaryStage.setScene(scene);
primaryStage.show();
}
}
and then
clickable-text.css:
.clickable-text, .clickable-text:focused {
-fx-background-color: transparent ;
-fx-background-insets: 0px ;
}

Resources