JavaFX - How to override Textfield focus event behavior? [duplicate] - javafx

The default behaviour is that the prompt text in the field is erased as the field is being focused. That is when the marker is in the field.
Is it possible to configure the textfield so the prompt text is only erased when the user have started typing?
Otherwise I need to add a label beside/over each textfield for description of the value in it.

I know it's a bit old, but I needed it myself and this is still very relevant.
I will complete jewelsea's answer and give a simpler solution.
Background
Apparently this was the default behavior of Java(FX) (prompt text in a TextField was cleared only when the user starts typing).
But then, following a request (or a bug report) in the JIRA system,
Java changed this behavior (and made the default to clear the text when the TextField gets focus).
You can review this bug report here.
Solution
To get back to the old default (and nicer behavior IMO), all you need to do is to add the following line(s) of code.
In case your application interface is written using proper Java code.
Java Code:
textField.setStyle("-fx-prompt-text-fill: derive(-fx-control-inner-background, -30%);");
Where textField is your TextField component.
And in case your application interface is written using FXML and CSS, add the following to your CSS file.
JavaFX FXML (CSS):
.text-input, .text-input:focused {
-fx-prompt-text-fill: derive(-fx-control-inner-background, -30%);
}
--------------------------------------------------------------------------------
Cleared-on-Focus Behavior
Currently it's the default behavior (the text-field's prompt text is cleared when the text-field gets focus),
so you don't need to do anything to get this behavior,
but in case Java will decide to go back to the cleared-on-typing behavior,
and you want to get the cleared-on-focus behavior, this is how to:
In case of proper Java code - it's a bit tricky since you can't define behavior of pseudo-classes directly.
Java Code (using bindings):
textField.styleProperty().bind(
Bindings
.when(textField.focusedProperty())
.then("-fx-prompt-text-fill: transparent;")
.otherwise("-fx-prompt-text-fill: derive(-fx-control-inner-background, -30%);"));
or -
Java Code (using events):
textField.focusedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if (newValue) {
textField.setStyle("-fx-prompt-text-fill: transparent;");
} else {
textField.setStyle("-fx-prompt-text-fill: derive(-fx-control-inner-background, -30%);");
}
}
});
JavaFX FXML CSS:
Add the following to your CSS file.
.text-input {
-fx-prompt-text-fill: derive(-fx-control-inner-background, -30%);
}
.text-input:focused {
-fx-prompt-text-fill: transparent;
}
Hope this helped...

Solution
This example will allow TextFields in JavaFX whose prompt behaviour is to show the prompt when the field is empty, even if the field has focus. The solution is a combination of custom CSS and a custom TextField class, which manipulates the css styles of the TextField.
persistent-prompt.css
.persistent-prompt:focused {
-fx-prompt-text-fill: derive(-fx-control-inner-background,-30%);
}
.no-prompt {
-fx-prompt-text-fill: transparent !important;
}
PersistentPromptTextField.java
import javafx.scene.control.TextField;
public class PersistentPromptTextField extends TextField {
PersistentPromptTextField(String text, String prompt) {
super(text);
setPromptText(prompt);
getStyleClass().add("persistent-prompt");
refreshPromptVisibility();
textProperty().addListener(observable -> refreshPromptVisibility());
}
private void refreshPromptVisibility() {
final String text = getText();
if (isEmptyString(text)) {
getStyleClass().remove("no-prompt");
} else {
if (!getStyleClass().contains("no-prompt")) {
getStyleClass().add("no-prompt");
}
}
}
private boolean isEmptyString(String text) {
return text == null || text.isEmpty();
}
}
PromptChanger.java
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class PromptChanger extends Application {
#Override
public void start(Stage stage) throws Exception {
TextField textField1 = new PersistentPromptTextField("", "First name");
TextField textField2 = new PersistentPromptTextField("", "Last name");
VBox layout = new VBox(
10,
textField1,
textField2
);
layout.setPadding(new Insets(10));
Scene scene = new Scene(layout);
scene.getStylesheets().add(
getClass().getResource(
"persistent-prompt.css"
).toExternalForm()
);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}
How prompt handling is currently implemented in JavaFX 8
Default CSS for JavaFX 8 (modena.css) for controlling the prompt text is as follows:
.text-input {
-fx-prompt-text-fill: derive(-fx-control-inner-background,-30%);
}
.text-input:focused {
-fx-prompt-text-fill: transparent;
}
This will make the prompt text transparent whenever a field is focused, even if the field has no data in it.
In Comparison to HTML
HTML input has a placeholder, which is specified as follows:
User agents should present this hint to the user . . . when the element's value is the empty string or the control is not focused (or both).
You can try this functionality in your browser at this test link.
I think the argument against this behaviour for JavaFX "Prompt text should be cleared when focus is grabbed to signal readiness to receiving user input" is moot because focused text fields get a clearly visible focus ring, so the user knows that the control is ready to receive input even when the prompt text is displayed.
I think JavaFX should by default operate the same way as most HTML user agents in this respect. Feel free to create a Tweak request in the JavaFX issue tracker to request that the input prompt in JavaFX work similarly to HTML implementations (if there isn't an issue created for this already).
Alternative
Glisten
The third-party Gluon Glisten has a custom TextField control can have the following attributes:
Float Text- A place-holder text inside a TextField which is transitioned to the top of the TextField when focus is received by it.
The nice thing about the Glisten-based TextField is that the prompt text is always visible, whether or not the user has typed anything into the control.
MaterialFX
MaterialFX also provides a variety of alternative implementations for text field prompts, similar to the Glisten operation, but matching the Google Material design. To see samples, click on the “Fields” demo option from the preview gifs.

You could configure the '-fx-prompt-text-fill' key in '.text-field:focused' area like in '.text-field' area (css):
`.text-field {
-fx-prompt-text-fill: derive(-fx-control-inner-background,-30%);
}
.text-field:focused {
-fx-prompt-text-fill: derive(-fx-control-inner-background,-30%);
}`
and add the 'promptText' entry in fxml file:
<TextField fx:id="XY_Id" promptText="First name">
That is all and works for me.

I am sure there may be several approaches and tricks to achieve what you're asking for, but, you are overriding a default, expected and standard UI behavior when you do so. Prompt text should be cleared when focus is grabbed to signal readiness to receiving user input, not after user starts typing.

Assuming you're setting prompt text via:
#FXML TextField tf;
public void someMethod() {
tf.setPromptText("This is the prompt text");
}
You can instead use tf.setText(String str) to set initial text.
Then, in the initialize method of your controller, add:
tf.setOnKeyTyped(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
tf.setText(null);
}
});
A link that you can refer to for additional help: Click me!

textField.setStyle("-fx-prompt-text-fill: derive(-fx-control-inner-background, -30%);");
or
textField.styleProperty().bind(
Bindings.createStringBinding(
()->"-fx-prompt-text-fill: derive(-fx-control-inner-background, -30%);"));

Related

Issue with ComboBox focus in FX 19

I am encountering some strange issues after upgrading to latest JavaFX version (19).
If I set the last option as the value in ComboBox, then on first opening, the dropdown will not hide if I select any option. After that, the dropdown works as usual. The strange part is this only happens if I set the last option as value. When I set any other option apart from last option, it is working well.
In the above example, you can notice that:
If "Four" is set: the dropdown does not hide even when I select different options (only for first time).
If "Three" is set: the dropdown works as usual.
When I tried to investigate the root cause, I am almost concluded that it is because of the new feature "focusWithIn". Because that is where it is causing the issue to happen.
But on further investigation, I also noticed that the focus got messed up. In the above gif, you can also notice that the "focused" style is not applied on ComboBox if I set last option as value (again only for first time till I moved focus to another node and when I am back, it works as usual). Whereas the focused style works well if I set a different value.
When I tried to put some logs on focused property, below is the output when I focus on ComboBox (with last option as value):
ComboBox focused : true
ComboBox focused : false
The focus is immediately turned off!!
The only thing that confuses me is "Why only with last option??". I know issues/bugs will occur in general. But this particual relation with last option, I couldn't get it :)
Anyway, I tried different ways to make the dropdown hiding and focus to work, but none worked. Do any of you have any suggestions(workaround) to let this fix.
Below is the working demo:
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class ComboBoxDemo extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
ComboBox<String> comboBox = new ComboBox<>();
comboBox.getItems().addAll("One", "Two", "Three", "Four");
comboBox.getSelectionModel().select("Four"); // Change value to any other options, it works
comboBox.focusedProperty().addListener((obs, old, focused) -> {
System.out.println("ComboBox focused : " + focused+", showing: "+comboBox.isShowing());
// ATTEMPT #2 : Requesting the focus by conditional checking (DIDN'T WORKED)
if(comboBox.isShowing() && !focused){
comboBox.requestFocus();
}
});
// ATTEMPT #1 : Requesting the focus after the dropdown is shown (DIDN'T WORKED)
comboBox.setOnShown(e -> {
System.out.println("ComboBox shown...");
comboBox.requestFocus();
});
VBox root = new VBox(new CheckBox("Just for focus grabbing"), comboBox);
root.setAlignment(Pos.CENTER);
root.setSpacing(20);
Scene scene = new Scene(root, 300, 200);
primaryStage.setTitle("ComboBox FX " + System.getProperty("javafx.runtime.version"));
primaryStage.setScene(scene);
primaryStage.show();
}
}
Definitely a bug in JavaFX 19.
I tested a bunch of things, and it seems that in order to set an initial selection for a ComboBox at the time of generation, it requires two ticks:
ComboBox<String> comboBox = new ComboBox<>();
comboBox.getItems().addAll("One", "Two", "Three", "Four");
initializeComboBox(comboBox, 3);
private void initializeComboBox(ComboBox comboBox, int intialIndex) {
Platform.runLater(()->{
Platform.runLater(()->{
comboBox.getSelectionModel().select(intialIndex);
});
});
}
I cannot answer why it is required for the last item only. That will be a job for the developers. However, this will work as a workaround in the meantime for this particular use case in this version.

Vaadin 14 - Show errors at file upload like at any other input field

Brief question first:
How can I show an upload error message in the same style of all the other input fields?
Details:
Vaadin 14.1.5 offers an upload-element: https://vaadin.com/components/vaadin-upload/java-examples
I created an upload field with this code:
MemoryBuffer buffer = new MemoryBuffer();
Upload upload = new Upload(buffer);
A failure message for too large file size is enforce by this line:
upload.setMaxFileSize(1);
Translation is done with UploadI18N (see https://vaadin.com/api/platform/14.1.5/com/vaadin/flow/component/upload/UploadI18N.html ):
upload.setI18n(buildMyUploadI18N(Locale.GERMAN));
And with all the listeners I can receive and show error messages at server-side, e.g. for rejection:
upload.addFileRejectedListener(new ComponentEventListener<FileRejectedEvent>() {
#Override
public void onComponentEvent(FileRejectedEvent event) {
Notification.show(event.getErrorMessage());
}
});
This code works fine and the system shows a notification message to the user when the file to upload is too large.
But: this validation-message-behavior differs from what the user is used to: Red text next to the input field (see screenshot).
How can I show an upload error message in the same style of all the other input fields?
Another option is to use a Paragraph, as in the example page you have linked. The source code for it could be found here: addRejectedListener
So once rejected listener is called, add a paragraph with a red text and possibly set background of upload to pink(or any other color/style you would like it to have). Below is a simplified version of the approach (You should put styles to a separate css file and set/remove a class to upload)
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.HasComponents;
import com.vaadin.flow.component.HtmlComponent;
import com.vaadin.flow.component.Tag;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.Label;
import com.vaadin.flow.component.html.Paragraph;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.upload.Upload;
import com.vaadin.flow.component.upload.receivers.MemoryBuffer;
import com.vaadin.flow.router.Route;
#Route("multiuploadView")
public class MultiFileUploadView extends VerticalLayout {
Div output = new Div();
Upload upload;
public MultiFileUploadView(){
MemoryBuffer buffer = new MemoryBuffer();
upload = new Upload(buffer);
upload.setMaxFiles(1);
upload.setDropLabel(new Label("Upload a 300 bytes file in .csv format"));
upload.setAcceptedFileTypes("text/csv");
upload.setMaxFileSize(300);
upload.addFileRejectedListener(event -> {
Paragraph component = new Paragraph();
showOutput(event.getErrorMessage(), component, output);
});
add(upload,output);
}
private void showOutput(String text, Component content,
HasComponents outputContainer) {
outputContainer.removeAll();
HtmlComponent p = new HtmlComponent(Tag.P);
p.getElement().setText(text);
p.getElement().getStyle().set("color","red");
upload.getElement().getStyle().set("background","pink");
outputContainer.add(p);
outputContainer.add(content);
}
}
And this looks like this:
But, otherwise, I would say that your workaround is pretty much what one can do :)
My current workaround is as follows:
The system creates a disabled Textfield in addition to the Upload-element
TextField filenameField = new TextField();
filenameField.setEnabled(false);
Every error listener at the Upload-element then sets the error message at the TextField:
upload.addFileRejectedListener(new ComponentEventListener<FileRejectedEvent>() {
#Override
public void onComponentEvent(FileRejectedEvent event) {
filenameField.setInvalid(true);
filenameField.setErrorMessage(event.getErrorMessage());
}
});
and
upload.addFailedListener(new ComponentEventListener<FailedEvent>() {
#Override
public void onComponentEvent(FailedEvent event) {
filenameField.setInvalid(true);
filenameField.setErrorMessage(event.getReason().getMessage());
}
});
and reset invalid-status and set new filename at success:
upload.addSucceededListener(event -> {
filenameField.setValue(StringUtils.trimToEmpty(event.getFileName()));
filenameField.setInvalid(false);
});
This is how it looks like - not my preferred solution (because the filename is not errorneous but the file size), but for the moment this is OK for me:

JavaFX TreeView: remove expand / collapse button (disclosure node) & functionality

I want to have a TreeView that has all of its children permanently expanded, and I don't want the user to be able to expand or collapse any of the children.
To do this I've found that I need to do the following:
Remove icon with CSS (Done)
Change expand and collapse image TreeView JavaFX 2.2
[edit] Above link should be used to change image; to remove completely, use this solution: https://stackoverflow.com/a/27831191/4430591
Remove double click functionality (Done)
Disable TreeItem's default expand/collapse on double click JavaFX 2.2
[edit] Remove ability to collapse / expand using keyboard arrrow keys (Done)
Given in José Pereda's solution below ( https://stackoverflow.com/a/27831085/4430591 )
[edit] Remove ability to right click for a ContextMenu (Done)
Given in José Pereda's solution below ( https://stackoverflow.com/a/27831085/4430591 )
Remove icon's clickablity (How do I do this?)
[edit] solution: https://stackoverflow.com/a/27831191/4430591
Even though the icon is no longer visible, it's still clickable. I don't see any way of filtering this; I only see ways to be able to respond to it after the fact.
Also, if I'm missing anything else that I need to do to ensure this functionality, please let me know.
I feel quite silly. I think this was mostly just a matter of not knowing what that darn arrow was called. Apparently it's a disclosureNode? Maybe that's common knowledge.
In the custom defined TreeCell, all I did was add this line in the updateItem method:
setDisclosureNode(null);
The solution to avoid modifying the skin or the default behavior is more simple if we trap the clicks before they are dispatched, and consume the right ones.
For that we can use an EventDispatcher, to filter both the mouse pressed and the right click over the arrows, which are StackPane nodes:
class CellEventDispatcher implements EventDispatcher {
private final EventDispatcher original;
public CellEventDispatcher(EventDispatcher original) {
this.original = original;
}
#Override
public Event dispatchEvent(Event event, EventDispatchChain tail) {
if (event.getEventType().equals(MouseEvent.MOUSE_PRESSED) ||
event.getEventType().equals(ContextMenuEvent.ANY)){
event.consume();
}
if(event instanceof KeyEvent && event.getEventType().equals(KeyEvent.KEY_PRESSED)){
if((((KeyEvent)event).getCode().equals(KeyCode.LEFT) ||
((KeyEvent)event).getCode().equals(KeyCode.RIGHT))){
event.consume();
}
}
return original.dispatchEvent(event, tail);
}
}
Now we apply our custom dispatcher to the tree view:
#Override
public void start(Stage primaryStage) {
TreeView<String> tree = new TreeView<>();
...
EventDispatcher treeOriginal = tree.getEventDispatcher();
tree.setEventDispatcher(new CellEventDispatcher(treeOriginal));
Scene scene = new Scene(tree);
primaryStage.setScene(scene);
primaryStage.show();
}
This will consume any click (left or right) over the arrows on the tree.
EDIT
Added to the event dispatcher class the case where the user uses the keyboard to traverse the tree view, consuming the collapse/expand events with arrow LEFT or RIGHT.

Removing a blinking cursor in Flex Flash Player

I have a MX TextInput field on my form. As one of our user has seizure problems with blinking cursors, I am trying to disable it but without success. Through Control Panel, I have been able to prevent a blinking cursor in Office Apps and on the Web browsers but not with the Flex Application which uses the Flash Player. Has anyone come across this issue and have a solution?
Here's a simple solution that removes the cursor all together. I'm not sure if you want to remove the cursor (feasible) or stop the cursor from blinking (seems less feasible).
It works by setting the underlying TextField object's selectable property to false. The MX TextInput class has it's own selectable property, however, the code in TextInput also requires the editable property to be false to disable selection. So you need to extend TextInput to work around that.
The underlying TextField doesn't expose any properties to stop the cursor from blinking (that I'm aware of). TextField is one of Flash Player's built in classes, so the chances of modifying this low level behavior seem slim.
This obviously breaks the ability to copy/paste in the TextInput. You might have to devise a way to temporarily enable selection to support copy/paste or selecting text in general.
package
{
import mx.controls.TextInput;
public class CustomTextInput extends TextInput
{
public function CustomTextInput()
{
}
private var _hideCursor:Boolean = true;
private var hideCursorChanged:Boolean = true;
public function get hideCursor():Boolean
{
return _hideCursor;
}
public function set hideCursor(value:Boolean):void
{
if (value == hideCursor)
{
return;
}
hideCursorChanged = true;
_hideCursor = value;
invalidateProperties();
}
override protected function commitProperties():void
{
super.commitProperties();
if (hideCursorChanged)
{
hideCursorChanged = false;
textField.selectable = !_hideCursor;
}
}
}
}

Why is 'textField' not instantiated when I subclass TextArea in Flex?

I'm experimenting with TextArea and inheritance to implement some additional functionality on the protected textField property.
Unfortunately, when I create a new instance of the subclass, this property is set to null. I'm probably misunderstanding the way super() works, but I thought it would have been instantiated after the constructor finished.
Here's a small snippet of code which extends TextArea:
public final class ExtTextArea extends TextArea {
public function ExtTextArea() {
super();
}
public function testTextField():void {
if (textField == null)
Alert.show("null!");
}
}
}
The invoking code is simple:
var extTextArea:ExtTextArea = new ExtTextArea();
extTextArea.testTextField();
The Alert in ExtTestArea appears every time I run this code.
Why is this? Is there something more I need to do to access the textField property?
Since textField is "the internal UITextField that renders the text of this TextArea" I believe it will remain null until you add it to the display via .addChild(...). I ran a quick test to verify that once I've added it to the display, it is no longer null. You might want to add an event handler to the "creation complete" event and adjust it at that point (I think).
The Flex SDK comes with source code, so you can take a peek and see when this field is initialized. It is not initialized in the constrcutor, but you will see that a new TextField instantiated by createChildren(), which is called when the component is added to a layout container.

Resources