I have a problem which I cannot solve with hover effect on Text.
Text t1 = new Text();
Text t2 = new Text();
Text t3 = new Text();
Text t4 = new Text();
When I select one Text I would like to change the background for example to gray. But the tricky part that I don't know how to solve is how I can remove the background color from the previous selected Text? It should work like Toggle button but with Text.
A Text object is a Shape, which defines a much more limited set of properties for styling than a control; for example it has no background. If you want to style the text in the UI, you should probably prefer a Label over Text. The other option would be to wrap the Text in some kind of Region (such as a StackPane) and apply the background to the region. This would complicate your code a little though.
Here are some solutions: the first uses no CSS and uses Labels, just setting their style as needed. The second uses a different approach in which you can use the "selection behavior" already defined in ToggleButton, but make the toggle buttons look like plain text. This is probably the best solution, in that it uses existing functionality and just changes the appearance using CSS. The third is a refinement of the first in which you can factor the style out into a CSS, but implement the selection yourself.
Solution 1: all Java:
Create a property to store the selected text:
ObjectProperty<Label> selectedText = new SimpleObjectProperty<>();
Register a listener with the property to reset the style of the previous selected text and set the style of the new selected text:
selectedText.addListener((obs, oldSelectedText, newSelectedText) -> {
if (oldSelectedText != null) {
oldSelectedText.setStyle("");
}
if (newSelectedText != null) {
newSelectedText.setStyle("-fx-background-color: lightgray;");
}
});
Register mouse listeners with each text to make them the selected text when they are clicked:
Label t1 = new Label("One");
Label t2 = new Label("Two");
Label t3 = new Label("Three");
Label t4 = new Label("Four");
Stream.of(t1, t2, t3, t4).forEach(t ->
t.setOnMouseClicked(event -> selectedText.set(t)));
Solution 2: Use toggle buttons styled to look like plain text:
This would actually be my preferred solution: use the selection behavior already defined for ToggleButton, and just use CSS to change the appearance of the toggle buttons.
ToggleButton t1 = new ToggleButton("One");
ToggleButton t2 = new ToggleButton("Two");
ToggleButton t3 = new ToggleButton("Three");
ToggleButton t4 = new ToggleButton("Four");
ToggleGroup tg = new ToggleGroup();
Stream.of(t1, t2, t3, t4).forEach(t -> t.setToggleGroup(tg));
With the following external style sheet:
.toggle-button, .toggle-button:selected , .toggle-button:hover,
.toggle-button:armed, .toggle-button:focused, .toggle-button:selected:focused {
-fx-color: -fx-base ;
-fx-background-color: transparent ;
-fx-background-insets: 0 ;
-fx-background-radius: 0 ;
-fx-padding: 0 ;
-fx-text-fill: -fx-text-background-color ;
}
.toggle-button:selected, .toggle-button:selected:focused {
-fx-background-color: lightgray ;
}
Solution 3: Java with css pseudoclasses:
Use the property for the selected text as in the first solution, but manipulate the style classes instead of the style directly:
ObjectProperty<Label> selectedText = new SimpleObjectProperty<>();
PseudoClass selected = PseudoClass.getPseudoClass("selected");
selectedText.addListener((obs, oldSelectedText, newSelectedText) -> {
if (oldSelectedText != null) {
oldSelectedText.pseudoClassStateChanged(selected, false);
}
if (newSelectedText != null) {
newSelectedText.pseudoClassStateChanged(selected, true);
}
});
Create the text object, set a style class on them, and register the mouse listener as before:
Label t1 = new Label("One");
Label t2 = new Label("Two");
Label t3 = new Label("Three");
Label t4 = new Label("Four");
Stream.of(t1, t2, t3, t4).forEach(t ->
t.setOnMouseClicked(event -> selectedText.set(t)));
Then use an external style sheet, and apply whatever style you need to the selected text:
.label:selected {
-fx-background-color: lightgray ;
}
With any of these solutions, you can edit the styles to be as you need.
First create a css class.
.text:hover {
-fx-fill: blue;
}
.text:pressed {
-fx-fill: blue;
}
.text-selected {
-fx-fill: blue;
}
Then create a global variable.
Text selectText;
Then add this:
scene.getStylesheets().add(Teste.class.getResource("text.css").toExternalForm();
t1.getStyleClass().add("text");
t2.getStyleClass().add("text");
t1.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent arg0) {
if (selectText != null) {
selectText.getStyleClass().remove("text-selected");
}
t1.getStyleClass().add("text-selected");
selectText = t1;
}
});
t2.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent arg0) {
if (selectText != null) {
selectText.getStyleClass().remove("text-selected");
}
t2.getStyleClass().add("text-selected");
selectText = t2;
}
});
Related
I have a list of records and 2 types of TextFields: filled and empty. I am putting these in a VBox.
for (int i = 0; i < records.size(); i++) {
if (records.get(i).contains("NEW")) {
TextField fillField = new TextField();
vbox.getChildren().add(fillField);
} else {
TextField filledField = new TextField();
filledField.setEditable(false);
filledField.setText(records.get(i));
vbox.getChildren().add(filledField);
}
}
After this the user can fill in the free TextFields. How can I update them inside the VBox?
Then I want to check if any of them are empty(how?), in which case I will fill them with "true".
EDIT:
So I am doing this:
for (int i = 0; i < vbox.getChildren().size(); i++) {
if (((TextField) vbox.getChildren().get(i)).getText()==null) {
TextField filledField = new TextField("true");
((TextField) vbox.getChildren().get(i)).setText("true");
//System.out.println(((TextField)vbox.getChildren().get(i)).getText());
}
}
My problem is that when I am printing in the console, I do see true when the field is empty. But in my application, the field remains empty.
Do I need to update vbox or something, after I update all the fields or?
The text of a TextField only becomes null, if you set the property to this value. This is a bad idea though, since you'd need to check for null and the empty string (the latter being the result of adding some chars and deleting them from the TextField).
In this case the simplest solution would be not to do this and use String.isEmpty for the checks:
for (String record : records) {
TextField textField = new TextField();
if (!record.contains("NEW")) {
textField.setEditable(false);
textField.setText(record);
}
vbox.getChildren().add(textField);
}
for (Node child : vbox.getChildren()) {
TextField tf = (TextField) child;
if (tf.getText().isEmpty()) {
tf.setText("true");
}
}
Consider the following example:
class MainView : View("Example") {
val someBooleanProperty: SimpleBooleanProperty = SimpleBooleanProperty(true)
override val root = borderpane {
paddingAll = 20.0
center = button("Change bg color") {
action {
// let's assume that new someBooleanProperty value is updated
// from some API after button clicked
// so changing style of the borderpane in action block
// of the button is not the solution
someBooleanProperty.value = !someBooleanProperty.value
}
}
}
}
class Styles : Stylesheet() {
companion object {
val red by cssclass()
val green by cssclass()
}
init {
red { backgroundColor += Color.RED }
green { backgroundColor += Color.GREEN }
}
}
How can I dynamically change the background color of borderpane depending on someBooleanProperty (e.g RED when true and GREEN when false)? Is there possibility to bind CSS class to a property? Is there any solution to do that without using CSS (meaning inside style block etc)
If you want to toggle a class (add or remove a class based on a boolean property), you can use the Node.toggleClass(CssRule, ObservableValue<Boolean>) function.
val someBooleanProperty = SimpleBooleanProperty(true)
...
borderpane {
toggleClass(Styles.red, someBooleanProperty)
toggleClass(Styles.green, someBooleanProperty.not())
}
If, on the other hand, you want to bind to a changing class value, you can use the Node.bindClass(ObservableValue<CssRule>) function.
val someClassyProperty = SimpleObjectProperty(Styles.red)
...
borderpane {
bindClass(someClassyProperty)
}
You can then set the class to whatever you want.
I have a TreeTableView control in my view, which has a column, which is composed of two labels. That is during runtime a cell is rendered by displaying two labels:
class ItemCell extends TreeTableCell[InventoryModelItem, InventoryModelItem] {
val hbox = new HBox()
val actionLabel = new Label()
actionLabel.styleClass.add("label-hotkey")
val itemLabel = new Label()
hbox.children.addAll(actionLabel, itemLabel)
setGraphic(hbox)
override def updateItem(item: InventoryModelItem, empty: Boolean): Unit = {
if (!empty && item != null) {
actionLabel.setText(item.action.getOrElse(""))
itemLabel.setText(item.displayName)
}
//setTextFill(Color.BLUE);
//setStyle("-fx-background-color: yellow");
}
}
What I wanna achieve is that the action label is rendered with a different style. Thus I set it via actionLabel.styleClass.add("label-hotkey"). The css style is recognized in general:
.label-hotkey {
-fx-font-size: 11pt;
-fx-font-family: "Droid Sans Mono";
-fx-text-fill: yellow;
-fx-opacity: 0.6;
}
For instance the -fx-font-size is taken into account during rendering. The issue is that -fx-text-fill is ignored, so the color of the label is not yellow.
So, what I am doing wrong here ?
EDIT: Even setTextFill(Color.BLUE) has no effect (see commented section in coding)
The title says everything. I want to change the color of the prompt text of a not editable combobox, so that the text has the same color like the prompt text of a editable combobox.
In my CSS-file I tried to use -fx-prompt-text-fill in .combo-box, .combo-box-base, .combo-box-base .text-field and .combo-box-base .text-input, but nothing worked.
What styleclass do I have to use?
When the ComboBox is not editable, there is no TextField, and the property -fx-prompt-text-fill is no longer valid, since the control displayed instead, a ListCell, doesn't extend TextInputControl.
In order to set the style of this cell, we can provide our custom styled ListCell:
#Override
public void start(Stage primaryStage) {
ComboBox comboBox = new ComboBox();
comboBox.getItems().addAll("Item 1", "Item 2", "Item 3");
comboBox.setPromptText("Click to select");
comboBox.setEditable(false);
comboBox.setButtonCell(new ListCell(){
#Override
protected void updateItem(Object item, boolean empty) {
super.updateItem(item, empty);
if(empty || item==null){
// styled like -fx-prompt-text-fill:
setStyle("-fx-text-fill: derive(-fx-control-inner-background,-30%)");
} else {
setStyle("-fx-text-fill: -fx-text-inner-color");
setText(item.toString());
}
}
});
Scene scene = new Scene(new StackPane(comboBox), 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
}
Hi I do beleive that I m providing a better solution
First
in your CSS file create the following
.input .text-field {
-fx-prompt-text-fill: #a0a0a0; // or any color you want
}
than in the scene builder set your combobox class to input after attaching the CSS file
That works like a sharm for me
Is there any way to change the selection bar text color in a list view?
Preferably using CSS. In a TableView, you can use:
-fx-selection-bar-text: white;
But this does not work for a ListView.
UPDATE: The above case happens when using CellFactories to render the cells.
lvRooms.setCellFactory(new Callback<ListView<String>, ListCell<String>>() {
#Override public ListCell<String> call(ListView<String> list) {
return new RoomCell();
}
});
In the Cell Factory class, I'd gladly cover the case when the row is selected.
But: It is called just once at the beginning, not every time the selection bar is moved, and therefore the isSelected() method always renders false.
UPDATE 2: This is the RoomCell implementation:
class RoomCell extends ListCell<String> {
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (item != null) {
Log.debug("RoomCell called, item: "+item);
final Label lbl = new Label(item); // The room name will be displayed here
lbl.setFont(Font.font("Segoe UI", FontWeight.BOLD, 18));
lbl.setStyle("-fx-text-fill: black");
//lbl.setTextFill(isSelected()?Color.WHITE: Color.BLACK);
if (isSelected()) // This is always false :(
lbl.setStyle("-fx-text-fill: yellow");
if (Rooms.getBoolean(item, "OwnerStatus")) {
lbl.setEffect(new DropShadow(15, Color.BLUEVIOLET));
lbl.setGraphic(new ImageView(
new Image(getClass().getResourceAsStream("images/universal.png"))));
} else {
lbl.setGraphic(new ImageView(
new Image(getClass().getResourceAsStream("images/yin-yang.png"))));
lbl.setEffect(new DropShadow(15, Color.WHITE));
}
setGraphic(lbl);
}
}
}
-fx-selection-bar-text is a color palette (not css property) defined in a root default CSS selector, which is selector of the Scene. I don't know how are you using it but if you define it (globally since it is scene's selector) like:
.root{
-fx-selection-bar-text: red;
}
in your CSS file then all controls' css properties using -fx-selection-bar-text will be red. ListView will be affected as well (see commented out original usages below).
However if you want to customize the ListView's style only, override the default properties this way
(Note: only -fx-text-fill are overriden. Original values are commented out, where -fx-selection-bar-text is used):
/* When the list-cell is selected and focused */
.list-view:focused .list-cell:filled:focused:selected {
-fx-background-color: -fx-focus-color, -fx-cell-focus-inner-border, -fx-selection-bar;
-fx-background-insets: 0, 1, 2;
-fx-background: -fx-accent;
/* -fx-text-fill: -fx-selection-bar-text; */
-fx-text-fill: red;
}
/* When the list-cell is selected and selected-hovered but not focused.
Applied when the multiple items are selected but not focused */
.list-view:focused .list-cell:filled:selected, .list-view:focused .list-cell:filled:selected:hover {
-fx-background: -fx-accent;
-fx-background-color: -fx-selection-bar;
/* -fx-text-fill: -fx-selection-bar-text; */
-fx-text-fill: green;
}
/* When the list-cell is selected, focused and mouse hovered */
.list-view:focused .list-cell:filled:focused:selected:hover {
-fx-background: -fx-accent;
-fx-background-color: -fx-focus-color, -fx-cell-focus-inner-border, -fx-selection-bar;
-fx-background-insets: 0, 1, 2;
/* -fx-text-fill: -fx-selection-bar-text; */
-fx-text-fill: yellow;
}
These CSS properties and more are avaliable in built-in caspian.css.
UPDATE: I strongly advice you to read the Cell API. From there
... We represent extremely large data sets using only very few Cells.
Each Cell is "recycled", or reused.
Be warned about the different String items may use the same cell, ending with misleading visual effects/renderings, like isSelected() in your code. Additionally in API it says
Because by far the most common use case for cells is to show text to a
user, this use case is specially optimized for within Cell. This is
done by Cell extending from Labeled. This means that subclasses of
Cell need only set the text property, rather than create a separate
Label and set that within the Cell.
So I refactored your code as follows.
class RoomCell extends ListCell<String> {
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (item != null) {
Log.debug("RoomCell called, item: "+item);
setFont(Font.font("Segoe UI", FontWeight.BOLD, 18));
ImageView iView = new ImageView();
if (Rooms.getBoolean(item, "OwnerStatus")) {
iView.setEffect(new DropShadow(15, Color.BLUEVIOLET));
iView.setImage(new Image(getClass().getResourceAsStream("images/universal.png")));
} else {
iView.setEffect(new DropShadow(15, Color.WHITE));
iView.setImage(new Image(getClass().getResourceAsStream("images/yin-yang.png")));
}
setGraphic(iView); // The image will be displayed here
setText(item); // The room name will be displayed here
}
}
}
All -fx-text-fill styles of the cell's text will change according to definitions in CSS file.
Now here is a trade-off between cell's text dropshadow effect and its fill colors from CSS file:
-- if you want to use dropshadow effect, you should go like current way, namely creating label, setting its text, give dorpshadow effect to the label and setGraphic(label). However this time you will not prefer to set the text (setText(item)) of the cell thus text color styles in CSS file will have no effect.
-- On other hand, if you prefer the code that I have refactored, then you should to disable -fx-background-color of the cell (which extends Labeled) by setting it to transparent or null and set the -fx-effect to dropshadow in CSS file to be able to apply dropshadow effect to the text directly. Clearing the background of the cell is not the preferred way either IMO. An explanation by the code:
Label lbl = new Label("This text will have a dropshadow on itself directly");
lbl.setEffect(new DropShadow(15, Color.BLUE));
Label another_lbl = new Label("This text will have a dropshadow applied on the background bounds, not to text");
another_lbl.setEffect(new DropShadow(15, Color.BLUE));
another_lbl.setStyle("-fx-background-color:gray");
Test them to see the difference. That's all.