JavaFX - TextArea 的掩码文本
Posted
技术标签:
【中文标题】JavaFX - TextArea 的掩码文本【英文标题】:JavaFX - Mask text of a TextArea 【发布时间】:2018-03-19 04:13:18 【问题描述】:我想知道是否有办法在 JavaFX 中屏蔽 TextArea
的文本。
例如,使用像PasswordField
这样的“子弹”密码字符来屏蔽文本。对于TextField
,有一个很好用的maskText()
方法。此方法对TextArea
没有用处。
我能做些什么?
注意:我希望 getText()
和 setText()
方法必须适用于明文,而不是屏蔽文本。就像PasswordField
一样。
编辑 这是我用来达到结果的方法,但不幸的是没有成功。
我的自定义TextArea
类:
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();
【问题讨论】:
由于没有内置方法来屏蔽文本,您必须自己实现一个。为什么以及如何屏蔽TextArea
中的文本?整个文本还是部分文本(例如单个 String
s)?
我希望TextArea
中的所有文本都被PasswordField
等“子弹”字符所掩盖。我试图创建一个自定义的TextArea
,但未成功覆盖maskText()
方法。
你不能重写超类中不存在的方法,可以吗?请在一个最小的工作(或失败)示例中提供您迄今为止编写的代码。我的第一种方法是扩展TextArea
,它有一个成员(可能只是一个String
),它包含真实的文本,并编写一个方法maskText()
,用一个项目符号替换每个字符。
maskText()
方法是 TextInputControlSkin
类的方法我创建了一个自定义 TextArea
,其自定义皮肤扩展了 TextAreaSkin
(这是 TextInputControlSkin
的子类)跨度>
这里提供代码的另一个原因 ;-) 展示你的方法,如果它只是包含一个小错误,你很有可能与这个社区一起解决这个问题。
【参考方案1】:
您想要的问题是 TextArea
不是为此功能而构建的,至少在 JDK 8 中(JDK 9 添加了公共皮肤 API,例如 TextAreaSkin
)。具体来说,它的皮肤TextAreaSkin
不利于掩蔽机制。
TextFieldSkin
通过将可视文本节点的textProperty
绑定到组件的textProperty
来进行屏蔽。因此,对组件“真实”文本的任何更改都会体现在可视组件的文本中以及适当的屏蔽修改(maskText
方法):
textNode.textProperty().bind(new StringBinding()
bind(textField.textProperty());
@Override protected String computeValue()
return maskText(textField.textProperty().getValueSafe());
);
TextAreaSkin
使用一组 Text
节点作为其视觉对象,尽管在 JDK 8 中只使用了 1 个节点。视觉文本的更改是通过侦听组件文本的更改来进行的:
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);
【讨论】:
您使用的是 jdk8 还是 jdk9?因为在 jdk9 中我尝试使用反射,但是在新的模块系统中我得到了一些例外。我还没有完全理解如何纠正反射和模块系统的工作。 @Vin 这是8。你没有提到你使用的是什么版本。 9. 需要打开你要访问的模块。 我发布的解决方案适用于 jdk 8 和 jdk9。唯一的“小故障”是多个明文行转换为单个蒙版行(现在对我来说总比没有好)。 这个也适用于两个版本,你只需要打开模块。我不明白你的“小故障”:你想要发生什么? 我缩进说,如果我写:sometimes\nmore text\nin 字符串,被屏蔽的文本会导致 ********************** *********,人在同一行。我想要的,你解释得很好,因为它不可能发生,是被屏蔽的文本分布在三行上。【参考方案2】:您可以让每个字符都显示为“子弹”,而不是单独的字符串实际上是文本。
【讨论】:
它无法工作,因为我必须使用setText()
方法,其中包含作为文本长度的“项目符号”字符的字符串。但是这种方法会破坏将返回掩码文本的getTex()
方法(我希望getText()
改为返回明文)。此外,在 JavaFX 中,setText()
和 getText()
是最终方法,因此它们不能被覆盖。【参考方案3】:
这是一个可以做你想做的事情(至少在我可以遵循你的愿望的范围内......)。它使用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);
【讨论】:
我可以在此更改侦听器中复制我的maskText()
代码来获得您的代码的结果。问题是 getText()
方法,在这种情况下,返回蒙版文本,而不是我想要的明文。【参考方案4】:
我解决了。 我找到了这个解决方案。它可以工作,但应该在某些条件下进行测试。 然而这是代码,它只涉及皮肤。
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()
返回明文,setText()
使用明文并在 ui 中屏蔽它(这就是我要找的)
唯一的问题是我绑定到一个实现细节,即子列表中 Text 节点的位置。
【讨论】:
【参考方案5】: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();
这就是我得到的,可能是最便宜的方法。
【讨论】:
在文本区域尝试getText()
会得到什么文本?
列表“文本”包含所有键
也许是我的错,我没有指定我希望getText()
和setText()
必须使用明文。就像PasswordField
一样。
哦,我不知道。我添加了 getPasswordText() 和 setPasswordText() 但这也可能不是你想要的。以上是关于JavaFX - TextArea 的掩码文本的主要内容,如果未能解决你的问题,请参考以下文章