,我试图将文本流覆盖在我的文本框架顶部以获得可着色和可选的文本。
<StackPane>
<!-- TextArea for selectable text. -->
<TextArea fx:id="dialog"
minHeight="-Infinity"
text="Label"
wrapText="true"
editable="false"
focusTraversable="false">
<HBox.margin>
<Insets left="82.0"
right="7.0" />
</HBox.margin>
<StackPane.margin>
<Insets left="82.0"
right="7.0" />
</StackPane.margin>
<padding>
<Insets bottom="6.0"
left="6.0"
right="6.0"
top="6.0" />
</padding></TextArea>
<!-- TextFlow for colored text. -->
<TextFlow fx:id="textFlow"
styleClass="text-flow"
mouseTransparent="true"
lineSpacing="1.2">
<HBox.margin>
<Insets left="82.0"
right="7.0" />
</HBox.margin>
<StackPane.margin>
<Insets left="82.0"
right="7.0" />
</StackPane.margin>
<padding>
<Insets bottom="3.0"
left="18.8"
right="18.8"
top="13.8" />
</padding></TextFlow>
</StackPane>
提前感谢您!
正如@Jewelsea的评论中提到的那样,我认为最简单的方法是使用WebView来获取所需的功能。having说,如果您对使用HTML或WebView不太感兴趣,则可以尝试使用自定义的TextFlow布局的下面方法。
一般的想法是
我们在Textflow儿童中包括一个不受管理的文本area,它总是位于TextFlow中的所有节点的顶部 textarea造型被定制为具有透明背景,并与基础文本节点保持一致
当您在Textarea中选择任何文本时,您似乎正在选择文本流中的多色文本节点中的文本。
这只是一个建议或想法,您需要进行更多的精细调整或重构才能使其变得完全正常。
我希望以下演示能为您提供一些如何处理的想法:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import java.util.List;
public class SelectableTextFlowExample extends Application {
public static void main(String[] args) {
launch(args);
}
public void start(Stage primaryStage) {
primaryStage.setTitle("JavaFX Selectable TextFlow Example");
// Approach #1 : Using WebView
WebView webView = new WebView();
webView.setMaxWidth(200);
webView.setMinHeight(Region.USE_PREF_SIZE);
String content = "<span style='color:red;'>Hello</span> <span style='color:green'>World!</span> <span style='color:red;'>ContinousBigWord</span>";
webView.getEngine().loadContent(content, "text/html");
WebViewFitContent wv = new WebViewFitContent(content);
// Approach #2 : TextFlow with TextArea/TextField
TextFlow flow = new TextFlow();
TextField t1 = buildTextField("Hello");
TextField t2 = buildTextField("W");
TextField t3 = buildTextField("orld!");
flow.getChildren().addAll(t1, t2, t3);
// Approach #3 : Custom TextFlow layout
SelectableTextFlow stf = new SelectableTextFlow();
Text txt1 = new Text("Hello ");
txt1.setStyle("-fx-fill:red;");
Text txt2 = new Text("World! ");
txt2.setStyle("-fx-fill:green;");
Text txt3 = new Text("ContinousBigWord");
txt3.setStyle("-fx-fill:red;");
stf.getChildren().addAll(txt1, txt2, txt3);
VBox vBox = new VBox(buildBox("Using WebView", wv),
buildBox("Using TexFlow with TextArea/TextField", flow),
buildBox("Using Custom TextFlow", stf));
vBox.setSpacing(15);
vBox.setPadding(new Insets(10));
Scene scene = new Scene(vBox, 400, 300);
primaryStage.setScene(scene);
primaryStage.show();
}
private VBox buildBox(String title, Node node) {
VBox box = new VBox();
box.setAlignment(Pos.CENTER_LEFT);
box.setSpacing(5);
Label label = new Label(title);
label.setStyle("-fx-font-size:16px;-fx-font-weight:bold;");
box.getChildren().addAll(label, node);
return box;
}
private TextField buildTextField(String k) {
MyTextField t = new MyTextField();
t.textProperty().addListener((obs, old, val) -> t.setWidthMask(val));
t.setText(k);
t.setEditable(false);
t.setStyle("-fx-padding:0px;-fx-background-color:transparent;-fx-background-insets:0;");
t.setMinWidth(Region.USE_PREF_SIZE);
return t;
}
class SelectableTextFlow extends TextFlow {
private final static String CSS = "data:text/css," +
"""
.selectable-text-flow .text-area,
.selectable-text-flow .text-area .scroll-pane,
.selectable-text-flow .text-area .scroll-pane .viewport,
.selectable-text-flow .text-area .scroll-pane .viewport .content,
.selectable-text-flow .text-area .scroll-pane .scroll-bar,
.selectable-text-flow .text-area .scroll-pane .scroll-bar .increment-arrow,
.selectable-text-flow .text-area .scroll-pane .scroll-bar .decrement-arrow,
.selectable-text-flow .text-area .scroll-pane .scroll-bar .increment-button,
.selectable-text-flow .text-area .scroll-pane .scroll-bar .decrement-button,
.selectable-text-flow .text-area > .scroll-pane > .corner{
-fx-background-color: transparent !important;
-fx-border-color: transparent !important;
-fx-padding: 0px;
-fx-background-insets: 0px !important;
}
.selectable-text-flow .text-area .scroll-pane .scroll-bar{
-fx-max-width:0px;
-fx-max-height:0px;
}
.selectable-text-flow .text-area{
-fx-text-fill: transparent;
}
""";
TextArea textArea = new TextArea();
public SelectableTextFlow() {
getStyleClass().add("selectable-text-flow");
textArea.setWrapText(true);
textArea.setManaged(false);
textArea.setEditable(false);
getChildren().add(textArea);
getStylesheets().add(CSS);
}
@Override
protected void layoutChildren() {
StringBuilder txt = new StringBuilder();
List<Node> managed = getManagedChildren();
for (Node node : managed) {
if (node instanceof Text) {
Text text = (Text) node;
txt.append(text.getText());
}
}
textArea.setText(txt.toString());
textArea.toFront();
super.layoutChildren();
textArea.setLayoutX(-3); // need to do some fine alignment dynamically
textArea.setLayoutY(-3);
textArea.resize(getWidth(), getHeight() + 6);
}
}
class MyTextField extends TextField {
/**
* Field width mask.
*/
private String widthMask;
public final void setWidthMask(final String aWidthMask) {
widthMask = aWidthMask;
if (widthMask != null && !widthMask.isEmpty()) {
/* To indicate that the preferred dimensions should be used for that max and/or min constraint. */
setMinWidth(USE_PREF_SIZE);
setMaxWidth(USE_PREF_SIZE);
}
}
@Override
protected final double computePrefWidth(final double height) {
if (widthMask != null && !widthMask.isEmpty()) {
/* If the prefWidth is not yet determined, compute and set the prefWidth. */
if (getPrefWidth() == USE_COMPUTED_SIZE) {
setPrefWidth(computeWidth(this, widthMask));
}
return getPrefWidth();
}
return super.computePrefWidth(height);
}
}
/**
* Computes the width of a text field based on the string mask provided
*
* @param textField The text field to evaluate
* @param textMask the text mask with which to calculate the max required width
* @return the required width of the text field
*/
public static double computeWidth(final TextField textField, final String textMask) {
/*
* Getting the last Text node to ensure that the text node is related to the actual text and not the prompt
* text.
*/
final Text text = (Text) textField.lookup(".text");
final String cache = text.getText();
/*
* Unbinding the text property to ensure that it will not trigger the listeners of textField when computing
* the mask width.
*/
text.textProperty().unbind();
text.setText(textMask);
final double prefWidth =
Math.ceil(textField.getInsets().getLeft())
+ text.getLayoutBounds().getWidth()
+ Math.ceil(textField.getInsets().getRight())
+ 2.0;
text.setText(cache);
/* Setting back the binding when the mask width computing is done. */
text.textProperty().bind(textField.textProperty());
return prefWidth;
}
}