JavaFX - TextArea 的掩码文本
JavaFX - Mask text of a TextArea
我想知道是否有办法在 JavaFX 中屏蔽 TextArea
的文本。
例如,使用 'bullet' 密码字符(如 PasswordField
)屏蔽文本。对于TextField
,有一种maskText()
方法效果很好。此方法对 TextArea
没有用。
我能做什么?
注意:我希望 getText()
和 setText()
方法必须适用于明文,而不适用于屏蔽文本。就像 PasswordField
一样有效。
编辑
这就是我用来实现结果的方法,但不幸的是没有成功。
我的习惯TextArea
class:
public class PasswordArea extends TextArea {
@Override
protected Skin<?> createDefaultSkin() {
return new PasswordAreaSkin(this); //my custom skin
}
}
用于自定义的自定义皮肤TextArea
:
public class PasswordAreaSkin extends TextAreaSkin {
public PasswordAreaSkin(PasswordArea control) {
super(control);
}
//here I override the maskText method to mask the text
@Override
protected String maskText(String text) {
int n = text.length();
StringBuilder passwordBuilder=new StringBuilder(n);
for(int i = 0; i < n; i++) {
passwordBuilder.append('\u2022'); //append 'bullet' char
}
return passwordBuilder.toString();
}
}
您可以让每个字符都显示为 "bullet",而不是让一个单独的字符串实际成为文本。
这是一个可以做你想做的工作(至少在我能满足你的愿望的范围内……)。它使用 ChangeListener
并在存储原件的同时操纵输入。如需进一步操作或使用,请自行扩展代码。啊,顺便说一下:现在不需要 Skin
,但可以随意应用它,屏蔽在 PasswordArea
中完成。这可能不是最有效的解决方案,但它是有效的(当在 Main.java
中使用时,就像在这个答案末尾发布的那样)。
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.TextArea;
public class PasswordArea extends TextArea {
private StringBuilder original = new StringBuilder();
private StringBuilder masked = new StringBuilder();
public PasswordArea() {
this.textProperty().addListener(new ChangeListener<String>() {
@Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
int oldLength = oldValue.length();
int newLength = newValue.length();
if (newLength == oldLength) {
// obviously an unnecessary case to be checked
} else if (newLength < oldLength) {
// last character deleted, so delete the last one of each, original and masked text
original.delete(newLength, oldLength);
masked.delete(newLength, oldLength);
} else {
// one character added, so just replace that one
char c = newValue.toCharArray()[newLength - 1];
if (Character.isSpaceChar(c)) {
original.append(c);
masked.append(c);
} else if (c == '\u2022') {
} else {
masked.append('\u2022');
original.append(c);
}
}
// this output is just for checking the state of the original
System.out.println(original.toString() + "\t--->\t" + masked.toString());
textProperty().set(masked.toString());
}
});
}
}
这里是Main.java
:
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
public class Main extends Application {
@Override
public void start(Stage primaryStage) {
try {
StackPane root = new StackPane();
Scene scene = new Scene(root,400,400);
PasswordArea passwordArea = new PasswordArea();
root.getChildren().addAll(passwordArea);
primaryStage.show();
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
import javafx.scene.control.TextArea;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class PasswordArea extends TextArea {
private final static List<String> text = new ArrayList<>();
private final static List<KeyCode> allowedKeys = Arrays.asList(KeyCode.ENTER, KeyCode.SPACE, KeyCode.BACK_SPACE, KeyCode.A, KeyCode.B, KeyCode.C, KeyCode.D, KeyCode.E, KeyCode.F,
KeyCode.G, KeyCode.H, KeyCode.I, KeyCode.J, KeyCode.K, KeyCode.L, KeyCode.M, KeyCode.N, KeyCode.O, KeyCode.P, KeyCode.Q, KeyCode.R, KeyCode.S, KeyCode.T, KeyCode.V,
KeyCode.W, KeyCode.X, KeyCode.Y, KeyCode.Z);
public PasswordArea() {
this.setEditable(false);
this.setOnKeyPressed(event -> {
if (!allowedKeys.contains(event.getCode())) {
return;
}
KeyCombination ctrlDelete = new KeyCodeCombination(KeyCode.BACK_SPACE, KeyCombination.CONTROL_DOWN);
if(ctrlDelete.match(event)) {
setPasswordText(getPasswordText());
return;
}
switch (event.getCode()) {
case ENTER:
this.appendText("\n");
text.add("\n");
break;
case SPACE:
this.appendText(" ");
text.add(" ");
break;
case BACK_SPACE:
final int size = this.textProperty().length().get();
if (size > 0) {
this.deleteText(size - 1, size);
text.remove(text.size() - 1);
}
break;
default:
this.appendText("" + '\u2022');
text.add(event.getText());
break;
}
});
}
public String getPasswordText() {
StringBuilder builder = new StringBuilder();
text.forEach(builder::append);
return builder.toString();
}
public void setPasswordText(String setText) {
text.clear();
this.clear();
for (int i = 0; i < setText.length(); i++) {
switch (setText.charAt(i)) {
case ' ':
this.appendText(" ");
break;
case '\n':
this.appendText("\n");
break;
default:
this.appendText("" + '\u2022');
break;
}
}
text.add(setText);
}
}
用法:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Launch extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
PasswordArea area = new PasswordArea();
Scene scene = new Scene(area, 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
}
这就是我得到的,可能是最便宜的方法。
我解决了。
我找到了这个解决方案。它可以工作,但应该在特定条件下进行测试。
不过这是代码,只涉及皮肤
public class PasswordAreaSkin extends TextAreaSkin {
public PasswordAreaSkin(PasswordArea control) {
Text textNode=getTextNode();
textNode.textProperty().addListener(obs -> {
textNode().setText(
maskText(control.textProperty().getValueSafe()));
});
}
@Override
protected String maskText(String text) {
int n = txt.length();
StringBuilder passwordBuilder=new StringBuilder(n);
for(int i = 0; i < n; i++) {
passwordBuilder.append('\u2022'); //append 'bullet' char
}
return passwordBuilder.toString();
}
private Text getTextNode() {
//WARNING: call ONLY in the constructor because
//children list could change
Region content=
((Region)((ScrollPane)getChildren().get(0)).getContent());
Group g=(Group)content.getChildrenUnmodifiable().get(1);
return (Text)g.getChildren().get(0);
}
}
这样,文本在控件中被屏蔽了,但是getText()
return明文和setText()
与明文一起工作,在ui掩盖它(这就是我要找的)
唯一的问题是我绑定到一个实现细节,即 Text 节点在子列表中的位置。
你想要的问题是 TextArea
不是为此功能构建的,至少在 JDK 8(JDK 9 添加了 public 皮肤 API,例如 TextAreaSkin
)。具体来说,它的皮肤 TextAreaSkin
不支持屏蔽机制。
TextFieldSkin
通过将可视文本节点的 textProperty
绑定到组件的 textProperty
来进行屏蔽。因此,对组件的 "real" 文本的任何更改都显示在可视组件的文本中加上适当的掩码修改(maskText
方法):
textNode.textProperty().bind(new StringBinding() {
{ bind(textField.textProperty()); }
@Override protected String computeValue() {
return maskText(textField.textProperty().getValueSafe());
}
});
TextAreaSkin
使用一组 Text
节点作为其视觉效果,尽管在 JDK 中只使用了一个节点 8. 通过监听组件的文本:
textArea.textProperty().addListener(observable -> {
invalidateMetrics();
((Text)paragraphNodes.getChildren().get(0)).setText(textArea.textProperty().getValueSafe());
contentView.requestLayout();
});
我们可以使用它来监听视觉文本的变化并自行更新。下面是一个实现的工作示例。 maskText
方法大部分是从 TextFieldSkin
复制而来的。我们使用反射来访问可视文本表示节点,然后用当前文本更新它(例如,从文本区域构造函数)并注册更新监听器。
public class Test extends Application {
@Override
public void start(Stage stage) throws Exception {
String s = "some times there are\nmore strings\n\nin here";
TextArea ta = new TextArea(s);
ta.setSkin(new TextAreaMaskSkin(ta));
TextArea view = new TextArea();
view.textProperty().bind(ta.textProperty());
Scene scene = new Scene(new HBox(view, ta));
stage.setScene(scene);
stage.show();
}
private static class TextAreaMaskSkin extends TextAreaSkin {
public TextAreaMaskSkin(TextArea textArea) throws Exception {
super(textArea);
Field field = TextAreaSkin.class.getDeclaredField("paragraphNodes");
field.setAccessible(true);
Group group = (Group) field.get(this);
Text text = (Text) group.getChildren().get(0);
text.setText(maskText(textArea.textProperty().getValueSafe()));
text.textProperty().addListener(o -> text.setText(maskText(textArea.textProperty().getValueSafe())));
}
@Override
protected String maskText(String txt) {
int n = txt.length();
StringBuilder passwordBuilder = new StringBuilder(n);
for (int i = 0; i < n; i++) {
if (txt.charAt(i) == '\n') {
passwordBuilder.append('\n');
} else {
passwordBuilder.append(TextFieldSkin.BULLET);
}
}
return passwordBuilder.toString();
}
}
public static void main(String[] args) {
launch(args);
}
}
我想知道是否有办法在 JavaFX 中屏蔽 TextArea
的文本。
例如,使用 'bullet' 密码字符(如 PasswordField
)屏蔽文本。对于TextField
,有一种maskText()
方法效果很好。此方法对 TextArea
没有用。
我能做什么?
注意:我希望 getText()
和 setText()
方法必须适用于明文,而不适用于屏蔽文本。就像 PasswordField
一样有效。
编辑
这就是我用来实现结果的方法,但不幸的是没有成功。
我的习惯TextArea
class:
public class PasswordArea extends TextArea {
@Override
protected Skin<?> createDefaultSkin() {
return new PasswordAreaSkin(this); //my custom skin
}
}
用于自定义的自定义皮肤TextArea
:
public class PasswordAreaSkin extends TextAreaSkin {
public PasswordAreaSkin(PasswordArea control) {
super(control);
}
//here I override the maskText method to mask the text
@Override
protected String maskText(String text) {
int n = text.length();
StringBuilder passwordBuilder=new StringBuilder(n);
for(int i = 0; i < n; i++) {
passwordBuilder.append('\u2022'); //append 'bullet' char
}
return passwordBuilder.toString();
}
}
您可以让每个字符都显示为 "bullet",而不是让一个单独的字符串实际成为文本。
这是一个可以做你想做的工作(至少在我能满足你的愿望的范围内……)。它使用 ChangeListener
并在存储原件的同时操纵输入。如需进一步操作或使用,请自行扩展代码。啊,顺便说一下:现在不需要 Skin
,但可以随意应用它,屏蔽在 PasswordArea
中完成。这可能不是最有效的解决方案,但它是有效的(当在 Main.java
中使用时,就像在这个答案末尾发布的那样)。
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.TextArea;
public class PasswordArea extends TextArea {
private StringBuilder original = new StringBuilder();
private StringBuilder masked = new StringBuilder();
public PasswordArea() {
this.textProperty().addListener(new ChangeListener<String>() {
@Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
int oldLength = oldValue.length();
int newLength = newValue.length();
if (newLength == oldLength) {
// obviously an unnecessary case to be checked
} else if (newLength < oldLength) {
// last character deleted, so delete the last one of each, original and masked text
original.delete(newLength, oldLength);
masked.delete(newLength, oldLength);
} else {
// one character added, so just replace that one
char c = newValue.toCharArray()[newLength - 1];
if (Character.isSpaceChar(c)) {
original.append(c);
masked.append(c);
} else if (c == '\u2022') {
} else {
masked.append('\u2022');
original.append(c);
}
}
// this output is just for checking the state of the original
System.out.println(original.toString() + "\t--->\t" + masked.toString());
textProperty().set(masked.toString());
}
});
}
}
这里是Main.java
:
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
public class Main extends Application {
@Override
public void start(Stage primaryStage) {
try {
StackPane root = new StackPane();
Scene scene = new Scene(root,400,400);
PasswordArea passwordArea = new PasswordArea();
root.getChildren().addAll(passwordArea);
primaryStage.show();
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
import javafx.scene.control.TextArea;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class PasswordArea extends TextArea {
private final static List<String> text = new ArrayList<>();
private final static List<KeyCode> allowedKeys = Arrays.asList(KeyCode.ENTER, KeyCode.SPACE, KeyCode.BACK_SPACE, KeyCode.A, KeyCode.B, KeyCode.C, KeyCode.D, KeyCode.E, KeyCode.F,
KeyCode.G, KeyCode.H, KeyCode.I, KeyCode.J, KeyCode.K, KeyCode.L, KeyCode.M, KeyCode.N, KeyCode.O, KeyCode.P, KeyCode.Q, KeyCode.R, KeyCode.S, KeyCode.T, KeyCode.V,
KeyCode.W, KeyCode.X, KeyCode.Y, KeyCode.Z);
public PasswordArea() {
this.setEditable(false);
this.setOnKeyPressed(event -> {
if (!allowedKeys.contains(event.getCode())) {
return;
}
KeyCombination ctrlDelete = new KeyCodeCombination(KeyCode.BACK_SPACE, KeyCombination.CONTROL_DOWN);
if(ctrlDelete.match(event)) {
setPasswordText(getPasswordText());
return;
}
switch (event.getCode()) {
case ENTER:
this.appendText("\n");
text.add("\n");
break;
case SPACE:
this.appendText(" ");
text.add(" ");
break;
case BACK_SPACE:
final int size = this.textProperty().length().get();
if (size > 0) {
this.deleteText(size - 1, size);
text.remove(text.size() - 1);
}
break;
default:
this.appendText("" + '\u2022');
text.add(event.getText());
break;
}
});
}
public String getPasswordText() {
StringBuilder builder = new StringBuilder();
text.forEach(builder::append);
return builder.toString();
}
public void setPasswordText(String setText) {
text.clear();
this.clear();
for (int i = 0; i < setText.length(); i++) {
switch (setText.charAt(i)) {
case ' ':
this.appendText(" ");
break;
case '\n':
this.appendText("\n");
break;
default:
this.appendText("" + '\u2022');
break;
}
}
text.add(setText);
}
}
用法:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Launch extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
PasswordArea area = new PasswordArea();
Scene scene = new Scene(area, 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
}
这就是我得到的,可能是最便宜的方法。
我解决了。 我找到了这个解决方案。它可以工作,但应该在特定条件下进行测试。 不过这是代码,只涉及皮肤
public class PasswordAreaSkin extends TextAreaSkin {
public PasswordAreaSkin(PasswordArea control) {
Text textNode=getTextNode();
textNode.textProperty().addListener(obs -> {
textNode().setText(
maskText(control.textProperty().getValueSafe()));
});
}
@Override
protected String maskText(String text) {
int n = txt.length();
StringBuilder passwordBuilder=new StringBuilder(n);
for(int i = 0; i < n; i++) {
passwordBuilder.append('\u2022'); //append 'bullet' char
}
return passwordBuilder.toString();
}
private Text getTextNode() {
//WARNING: call ONLY in the constructor because
//children list could change
Region content=
((Region)((ScrollPane)getChildren().get(0)).getContent());
Group g=(Group)content.getChildrenUnmodifiable().get(1);
return (Text)g.getChildren().get(0);
}
}
这样,文本在控件中被屏蔽了,但是getText()
return明文和setText()
与明文一起工作,在ui掩盖它(这就是我要找的)
唯一的问题是我绑定到一个实现细节,即 Text 节点在子列表中的位置。
你想要的问题是 TextArea
不是为此功能构建的,至少在 JDK 8(JDK 9 添加了 public 皮肤 API,例如 TextAreaSkin
)。具体来说,它的皮肤 TextAreaSkin
不支持屏蔽机制。
TextFieldSkin
通过将可视文本节点的 textProperty
绑定到组件的 textProperty
来进行屏蔽。因此,对组件的 "real" 文本的任何更改都显示在可视组件的文本中加上适当的掩码修改(maskText
方法):
textNode.textProperty().bind(new StringBinding() {
{ bind(textField.textProperty()); }
@Override protected String computeValue() {
return maskText(textField.textProperty().getValueSafe());
}
});
TextAreaSkin
使用一组 Text
节点作为其视觉效果,尽管在 JDK 中只使用了一个节点 8. 通过监听组件的文本:
textArea.textProperty().addListener(observable -> {
invalidateMetrics();
((Text)paragraphNodes.getChildren().get(0)).setText(textArea.textProperty().getValueSafe());
contentView.requestLayout();
});
我们可以使用它来监听视觉文本的变化并自行更新。下面是一个实现的工作示例。 maskText
方法大部分是从 TextFieldSkin
复制而来的。我们使用反射来访问可视文本表示节点,然后用当前文本更新它(例如,从文本区域构造函数)并注册更新监听器。
public class Test extends Application {
@Override
public void start(Stage stage) throws Exception {
String s = "some times there are\nmore strings\n\nin here";
TextArea ta = new TextArea(s);
ta.setSkin(new TextAreaMaskSkin(ta));
TextArea view = new TextArea();
view.textProperty().bind(ta.textProperty());
Scene scene = new Scene(new HBox(view, ta));
stage.setScene(scene);
stage.show();
}
private static class TextAreaMaskSkin extends TextAreaSkin {
public TextAreaMaskSkin(TextArea textArea) throws Exception {
super(textArea);
Field field = TextAreaSkin.class.getDeclaredField("paragraphNodes");
field.setAccessible(true);
Group group = (Group) field.get(this);
Text text = (Text) group.getChildren().get(0);
text.setText(maskText(textArea.textProperty().getValueSafe()));
text.textProperty().addListener(o -> text.setText(maskText(textArea.textProperty().getValueSafe())));
}
@Override
protected String maskText(String txt) {
int n = txt.length();
StringBuilder passwordBuilder = new StringBuilder(n);
for (int i = 0; i < n; i++) {
if (txt.charAt(i) == '\n') {
passwordBuilder.append('\n');
} else {
passwordBuilder.append(TextFieldSkin.BULLET);
}
}
return passwordBuilder.toString();
}
}
public static void main(String[] args) {
launch(args);
}
}