JavaFX - TextArea的掩码文本

时间:2017-10-07 14:22:45

标签: java javafx textarea

我想知道是否有办法掩盖JavaFX中TextArea的文本。 例如,使用{bullet}密码字符PasswordField屏蔽文本。对于TextFieldmaskText()方法效果很好。此方法对TextArea无效。 我能做什么?

注意:我希望getText()setText()方法必须使用明文,而不是屏蔽文本。就像PasswordField一样。

EDIT
这是我用来实现结果的方法,但不幸的是没有成功。

我的自定义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();
    }
}

5 个答案:

答案 0 :(得分: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);
    }
}

enter image description here

答案 1 :(得分:0)

你可以让每个角色都显示为“子弹”而不是一个单独的字符串实际上是文本。

答案 2 :(得分:0)

这是一项可以满足你想要的工作(至少我可以按照你的愿望......)。它使用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);
    }
}

答案 3 :(得分:0)

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();
    }

}

这就是我得到的,可能是最便宜的方式。

答案 4 :(得分:0)

我解决了。 我找到了这个解决方案它可以工作,但它应该在某些条件下进行测试。 然而,这是代码,它只涉及皮肤。

import argparse
import sys
import time

def createParser ():
    parser = argparse.ArgumentParser()
    parser.add_argument ('-floor', '--floor')

    return parser

parser = createParser()
namespace = parser.parse_args(sys.argv[1:])

def main():
  floor = namespace.floor
  while True:
    print(floor)
    time.sleep(1)
main()

通过这种方式,文本在控件中被屏蔽,但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()使用明文和ui掩码(这就是我要找的内容) )

唯一的问题是我绑定了一个实现细节,即子节点列表中Text节点的位置。