I'm encountering a problem while attempting to change the text color within a Hyperlink object embedded in a JavaFX Alert (with ExpandableContent).
Furthermore, I'd like to highlight that the CSS file is added to the DialogPane (indeed, background color and other formatting are correctly recognized).
My CSS class: alert_001
Despite several attempts I have been unable to achieve the desired outcome. The text color of the Hyperlink remains unchanged within the Alert object.
DialogPane (dialog-pane)
+- ButtonBar (button-bar)
+--- HBox (container)
+------ Hyperlink (details-button more)(pseudo class state: visited)
+--------- LabeledText (text)
.alert_001 .dialog-pane .button-bar .container .details-button .more .text {
-fx-text-fill: #bbbbbb;
-fx-font-size: 12.0pt;
}
Thank you in advance
src
, Package: my.project
, File: Main.java
)package my.project;
import javafx.application.Application;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage stage) {
final AlertComposer alertComposer = new AlertComposer();
try {
throw new Exception("Test");
} catch (Exception e) {
alertComposer.exception(e);
throw new RuntimeException(e);
}
}
}
src
, Package: my.project
, File: AlertComposer.java
)package my.project;
import javafx.scene.control.Alert;
import javafx.scene.control.DialogPane;
import javafx.scene.control.TextArea;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import java.io.PrintWriter;
import java.io.StringWriter;
public class AlertComposer {
public void exception(Exception e) {
String content = e.getMessage();
Alert alert = this.build(Alert.AlertType.ERROR, "myTitle", "myHeader", content);
GridPane expContent = buildExceptionGridPane(e);
alert.getDialogPane().setExpandableContent(expContent);
alert.showAndWait();
}
private GridPane buildExceptionGridPane(Exception e) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
String exceptionText = sw.toString();
TextArea textArea = new TextArea(exceptionText);
textArea.setEditable(false);
textArea.setWrapText(true);
textArea.setMaxWidth(Double.MAX_VALUE);
textArea.setMaxHeight(Double.MAX_VALUE);
GridPane.setVgrow(textArea, Priority.ALWAYS);
GridPane.setHgrow(textArea, Priority.ALWAYS);
GridPane expContent = new GridPane();
expContent.setMaxWidth(Double.MAX_VALUE);
expContent.add(textArea, 0, 0);
return expContent;
}
public Alert build(Alert.AlertType type, String title, String header, String content) {
Alert alert = build(type);
alert.setTitle(title);
alert.setHeaderText(header);
alert.setContentText(content);
return alert;
}
private Alert build(Alert.AlertType type) {
Alert alert = new Alert(type);
DialogPane dialogPane = alert.getDialogPane();
dialogPane.setMinHeight(Region.USE_PREF_SIZE);
dialogPane.getStylesheets().add(Main.class.getResource("/theme.css").toExternalForm());
dialogPane.getStyleClass().add("alert_001");
return alert;
}
}
resources
, File: theme.css
).alert_001 {
-fx-background-color: #282828;
}
.alert_001 .header-panel {
-fx-background-color: #45494a;
}
.alert_001 .label {
-fx-text-fill: #bbbbbb;
-fx-font-size: 12.0pt;
}
.alert_001 .button {
-fx-border-color: #5e6060;
-fx-border-radius: 10.0 10.0 10.0 10.0;
-fx-background-color: #3c3f41;
-fx-background-radius: 10.0 10.0 10.0 10.0;
-fx-text-fill: #bbbbbb;
-fx-font-size: 14.0pt;
}
The CSS selector is incorrect, for multiple reasons.
Generally, if you have two selectors separated by whitespace, it will match any node for the second selector which is a descendant node of a node matching the first selector. "Descendant node" for JavaFX typically means nodes that have the other node as a parent, or as a parent of a parent, etc (though there are some exceptions to this).
So your selector
.alert_001 .dialog-pane .button-bar .container .details-button .more .text {
}
will match a node with style class text
that is a descendant of a node with style class more
, that is a descendant of a node with style class details-button
, that is a descendant of a node with style class container
, etc.
As you note, the more
style class is applied to the hyperlink that also has style class details-button
. So there is no node with style class more
that is contained in a node with style class details-button
(there is just a single node with both style classes).
Similarly, you add the style class alert_001
to the dialog pane. So there is no node with style class dialog-pane
that is contained in a node with style class alert_001
(there is just a single node, the dialog pane, that has both style classes).
Also note that the .text
selector will select the Text
node that is used to display the text of the hyperlink. Text
nodes do not have a -fx-text-fill
property (see the documentation), but have a -fx-fill
property that is inherited from Shape
.
If you want to select a single node that matches two different selection nodes, you can concatenate the selection rules with no whitespace. So the following will select a node with both style class dialog-pane
and style class alert_001
:
.alert_001.dialog-pane {
/* ... */
}
and similarly the selector
.details-button.more {
/* ... */
}
will select a node with both style class details-button
and style class more
.
So if you want to be really specific in your CSS, you can select the text in the "more" hyperlink with
.alert_001.dialog-pane .button-bar .container .details-button.more .text{
-fx-fill: #bbbbbb;
-fx-font-size: 12.0pt;
}
I don't think you need to be this specific (and it usually makes more sense to style the control (i.e. the hyperlink), not its underlying rendering). So I would simply do
.alert_001 .more {
-fx-text-fill: #bbbbbb;
-fx-font-size: 12.0pt;
}
if you want to specifically select the expand (but not the collapse) button, or
.alert_001 .less {
-fx-text-fill: #bbbbbb;
-fx-font-size: 12.0pt;
}
to style the collapse (but not the expand) button, or
.alert_001 .details-button {
-fx-text-fill: #bbbbbb;
-fx-font-size: 12.0pt;
}
to style the button in either state.
Note also that since the style here is exactly the same as the style you apply to labels in the dialog pane, you can combine these into a single rule:
.alert_001 .label, .alert_001 .details-button {
-fx-text-fill: #bbbbbb;
-fx-font-size: 12.0pt;
}
So the following style sheet achieves what I think you want:
.alert_001 {
-fx-background-color: #282828;
}
.alert_001 .header-panel {
-fx-background-color: #45494a;
}
.alert_001 .label, .alert_001 .details-button {
-fx-text-fill: #bbbbbb;
-fx-font-size: 12.0pt;
}
.alert_001 .button {
-fx-border-color: #5e6060;
-fx-border-radius: 10.0 10.0 10.0 10.0;
-fx-background-color: #3c3f41;
-fx-background-radius: 10.0 10.0 10.0 10.0;
-fx-text-fill: #bbbbbb;
-fx-font-size: 14.0pt;
}
The one caveat here is that the details button CSS style class is not documented, so there is no particular guarantee it will continue to work in future JavaFX releases. The JavaFX team do seem to be quite good about preserving back-compatibility in CSS style names (even for undocumented functionality) when releasing new versions, and there is really no other way to get at these elements anyway.