JavaFX 8 TableView 带有用于显示/隐藏密码的切换按钮

标签 java javafx javafx-8

我正在尝试为每一行创建一个带有按钮的 TableView,以显示/隐藏该特定行的密码。我一直用this问题以弄清楚,但我现在真的很难过。所以我的问题是我怎样才能让它发挥作用?我做错了什么?

这是我到目前为止所得到的。

Person.java

import javafx.beans.property.SimpleStringProperty;

public class Person {
    private final SimpleStringProperty name;
    private final SimpleStringProperty password;
    private final SimpleStringProperty maskedPassword;

    public Person(String n, String p) {
        this.name = new SimpleStringProperty(n);
        this.password = new SimpleStringProperty(p);
        this.maskedPassword = new SimpleStringProperty(maskPass(p));
    }

    private String maskPass(String password) {
        String output = "";
        for(int i = 0; i < password.length(); i++) {
            output += "\u2022";
        }
        return output;
    }

    public String getName() {
        return this.name.get();
    }

    public void setName(String n) {
        this.name.set(n);
    }

    public String getPassword() {
        return this.password.get();
    }

    public void setPassword(String p) {
        this.password.set(p);
        this.maskedPassword.set(maskPass(p));
    }

    public String getMaskedPassword() {
        return this.maskedPassword.get();
    }
}

和Main.java

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Callback;

public class Main extends Application{

    private final TableView<Person> table = new TableView<Person>();
    private final ObservableList<Person> data = FXCollections.observableArrayList(
                new Person("Moses Kiptanui", "pas$w0rd"),
                new Person("Bob Geldof", "hunter2"),
                new Person("Steve Finnan", "skadjhf"),
                new Person("Don Bradman", "CRICKET!"),
                new Person("Fetty Wap", "seventeen38")
            );

    @Override
    public void start(Stage stage) throws Exception {
        stage.setTitle("Table Experiment");

        TableColumn<Person, String> nameCol = new TableColumn<Person, String>("Name");
        nameCol.setMinWidth(100);
        nameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("name"));

        TableColumn<Person, String> passwordCol = new TableColumn<Person, String>("Password");
        passwordCol.setMinWidth(100);
        passwordCol.setCellValueFactory(new PropertyValueFactory<Person, String>("maskedPassword"));

        TableColumn<Person, Person> btnCol = new TableColumn<Person, Person>("Show/Hide");
        btnCol.setCellFactory(new Callback<TableColumn<Person, Person>, TableCell<Person, Person>>() {
            @Override
            public TableCell<Person, Person> call(TableColumn<Person, Person> btnCol) {
                return new TableCell<Person, Person>() {
                    final ToggleButton btn = new ToggleButton("Show");
                    @Override
                    public void updateItem(final Person person, boolean empty) {
                        super.updateItem(person, empty);
                        if(person != null) {
                            if(btn.isSelected()) {
                                btn.setText("Hide");
                            } else {
                                btn.setText("Show");
                            }
                            setGraphic(btn);
                            btn.setOnAction(e -> {
                                if(btn.isSelected()) {
                                    passwordCol.setCellValueFactory(new PropertyValueFactory<Person, String>("password"));
                                } else {
                                    passwordCol.setCellValueFactory(new PropertyValueFactory<Person, String>("maskedPassword"));
                                }
                            });
                        }
                    }
                };
            }
        });

        table.setItems(data);
        table.getColumns().addAll(nameCol, passwordCol, btnCol);
        VBox vb = new VBox(table);
        stage.setScene(new Scene(vb));
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

这就是目前的情况。我实际上已经有了它,所以按钮显示了,但它们显示在每一行中,而不仅仅是 person != null 的行。当我确实让它们显示它们不起作用时。

http://i.imgur.com/gAndrGS.jpg

最佳答案

这是我的解决方法:

  1. 我认为屏蔽密码,或者是否应该屏蔽密码,不应该成为您的 Person 类的一部分。这不是数据的固有部分,而是数据 View 的一部分。
  2. 显示密码的 TableColumn 需要使用自定义单元工厂,它要么显示实际密码,要么显示屏蔽密码,具体取决于是否按下切换按钮。
  3. 您无法直接访问切换按钮,因为它们是虚拟化单元的一部分,因此您需要一些其他数据来跟踪显示哪些密码和隐藏哪些密码。一种方法(我认为最简单)就是使用包含显示密码的项目的 ObservableSet

现在您需要确保切换状态与可观察集匹配,因此您需要一个具有更新可观察集的切换按钮的监听器,以及一个具有更新切换按钮的可观察集的监听器。密码列的单元工厂还必须观察 ObservableSet 并相应地更新该列中的文本。

这是 SSCCE:

import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableSet;
import javafx.collections.SetChangeListener.Change;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.ToggleButton;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class ShowHidePasswordExample extends Application {

    private static final Random RNG = new Random();

    @Override
    public void start(Stage primaryStage) {

        TableView<User> userTable = new TableView<>();

        // standard column stuff:
        TableColumn<User, String> userNameCol = new TableColumn<>("User Name");
        userNameCol.setCellValueFactory(cellData -> cellData.getValue().userNameProperty());

        TableColumn<User, String> passwordCol = new TableColumn<>("Password");
        passwordCol.setCellValueFactory(cellData -> cellData.getValue().passwordProperty());

        // which passwords are shown:
        ObservableSet<User> usersWithShownPasswords = FXCollections.observableSet();

        // cell factory for password column. Cells must show either the
        // real or masked password, and may 
        // need to update if usersWithShownPasswords changes:

        passwordCol.setCellFactory(c -> {

            // plain old cell:
            TableCell<User, String> cell = new TableCell<>();

            // if the cell is reused for an item from a different row, update it:
            cell.indexProperty().addListener((obs, oldIndex, newIndex) -> 
                updateCell(usersWithShownPasswords, cell));

            // if the password changes, update:

            cell.itemProperty().addListener((obs, oldItem, newItem) -> 
                updateCell(usersWithShownPasswords, cell));

            // if the set of users with shown password changes, update the cell:
            usersWithShownPasswords.addListener((Change<? extends User> change) ->
                updateCell(usersWithShownPasswords, cell));

            return cell ;
        });

        // column with show/hide buttons:
        TableColumn<User, User> showHidePasswordCol = new TableColumn<>("Show/Hide password");

        // just use whole row (User) as data for cells in this column:
        showHidePasswordCol.setCellValueFactory(cellData -> new ReadOnlyObjectWrapper<>(cellData.getValue()));

        // cell factory for toggle buttons:
        showHidePasswordCol.setCellFactory(c -> new TableCell<User, User>() {

            // create toggle button once for cell:
            private final ToggleButton button = new ToggleButton();

            // anonymous constructor:
            {
                // update toggle button state if usersWithShownPasswords changes:
                usersWithShownPasswords.addListener((Change<? extends User> change) -> {
                    button.setSelected(usersWithShownPasswords.contains(getItem()));
                });

                // update usersWithShownPasswords if toggle selection changes:
                button.selectedProperty().addListener((obs, wasSelected, isNowSelected) -> {
                    if (isNowSelected) {
                        usersWithShownPasswords.add(getItem());
                    } else {
                        usersWithShownPasswords.remove(getItem());
                    }
                });

                // keep text "Show" or "Hide" appropriately:
                button.textProperty().bind(Bindings.when(button.selectedProperty()).then("Hide").otherwise("Show"));
                setAlignment(Pos.CENTER);
            }

            // Just update graphic as needed:

            @Override
            public void updateItem(User item, boolean empty) {
                super.updateItem(item, empty);
                if (empty) {
                    setGraphic(null);
                } else {
                    button.setSelected(usersWithShownPasswords.contains(item));
                    setGraphic(button);
                }
            }
        });

        userTable.getColumns().addAll(Arrays.asList(userNameCol, passwordCol, showHidePasswordCol));

        userTable.getItems().addAll(createData());

        Scene scene = new Scene(new BorderPane(userTable), 800, 600);
        primaryStage.setScene(scene);
        primaryStage.show();
    }


    private void updateCell(ObservableSet<User> usersWithShownPasswords,
            TableCell<User, String> cell) {
        int index = cell.getIndex();
        TableView<User> table = cell.getTableView();
        if (index < 0 || index >= table.getItems().size()) {
            cell.setText("");
        } else {
            User user = table.getItems().get(index);
            if (usersWithShownPasswords.contains(user)) {
                cell.setText(user.getPassword()) ;
            } else {
                cell.setText(mask(user.getPassword()));
            }
        }
    }

    private String mask(String text) {
        char[] chars = new char[text.length()];
        Arrays.fill(chars, '*');
        return new String(chars);
    }

    private List<User> createData() {
        return IntStream.rangeClosed(1, 100)
                .mapToObj(i -> new User("User "+i, randomPassword()))
                .collect(Collectors.toList());
    }

    private String randomPassword() {
        int pwSize = 6 + RNG.nextInt(5);
        char[] chars = new char[pwSize];
        for (int i = 0 ; i < chars.length; i++) {
            chars[i] = (char)('a'+RNG.nextInt(26));
        }
        return new String(chars);
    }

    public static class User {
        private final StringProperty userName = new SimpleStringProperty();
        private final StringProperty password = new SimpleStringProperty();

        public User(String userName, String password) {
            setUserName(userName);
            setPassword(password);
        }

        public final StringProperty userNameProperty() {
            return this.userName;
        }

        public final java.lang.String getUserName() {
            return this.userNameProperty().get();
        }

        public final void setUserName(final java.lang.String userName) {
            this.userNameProperty().set(userName);
        }

        public final StringProperty passwordProperty() {
            return this.password;
        }

        public final java.lang.String getPassword() {
            return this.passwordProperty().get();
        }

        public final void setPassword(final java.lang.String password) {
            this.passwordProperty().set(password);
        }


    }

    public static void main(String[] args) {
        launch(args);
    }
}

关于JavaFX 8 TableView 带有用于显示/隐藏密码的切换按钮,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32829737/

相关文章:

java - 使用 AffineTransform 将形状缩放/平移到给定的矩形

java - 指定通用文件路径以使用 jar 文件

java - 在 JavaFX 中包装内容

java - 如何在每个操作系统上运行 JavaFX 项目

java - 在java jsp中包含文件(svg)

java - 如何将 'next generation' java 数据对象样式与接口(interface)一起使用?

javafx - 如何告诉 IntelliJ 从 FXML Loader 获取 FXML Controller 以进行语法突出显示?

java - 安装JDK后,无法运行.jar

java - 强制使 JavaFX 属性失效

Raspberry pi 上的 JavaFx 集成