在我的应用程序中,我有两个部分:数据列表和一个用于编辑该数据详细信息的部分。用户从ListView
或TableView
中选择一个项目,该项目的属性将显示在窗口右侧,并且可以在窗口右侧进行编辑。 下面的MCVE将对此进行演示。
我需要做的是在更改ListView
中的选择之前验证那些“编辑器”控件的值。
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
// Simple Interface
HBox root = new HBox(10);
root.setAlignment(Pos.CENTER);
root.setPadding(new Insets(10));
// Simple ListView
ListView<Person> listView = new ListView<>();
listView.getItems().setAll(
new Person("Jack", "j@email.com"),
new Person("Bill", "bill@email.com"),
new Person("Diane", "dd@email.com")
);
// TextFields to edit values
TextField txtName = new TextField();
TextField txtEmail = new TextField();
// Add controls to the root layout
root.getChildren().addAll(
listView,
new VBox() {{
getChildren().addAll(
new Label("Name:"),
txtName,
new Label("Email:"),
txtEmail);
}}
);
// Add listener to update bindings for the TextFields
listView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
// Unbind previously bound values
if (oldValue != null) {
// If name or email are missing, prevent the change
if (!validate(oldValue))
return; // *** This is where I need help as this obviously is not correct *** //
txtName.textProperty().unbindBidirectional(oldValue.nameProperty());
txtEmail.textProperty().unbindBidirectional(oldValue.emailProperty());
}
// Bind the new values
if (newValue != null) {
txtName.textProperty().bindBidirectional(newValue.nameProperty());
txtEmail.textProperty().bindBidirectional(newValue.emailProperty());
// Refresh the ListView to show changes
listView.refresh();
}
});
// Show the stage
primaryStage.setScene(new Scene(root));
primaryStage.setTitle("Sample");
primaryStage.show();
}
private boolean validate(Person oldValue) {
return !(oldValue.getName().trim().isEmpty() || oldValue.getName().trim().isEmpty());
}
}
class Person {
private StringProperty name = new SimpleStringProperty();
private StringProperty email = new SimpleStringProperty();
public Person(String name, String email) {
this.name.set(name);
this.email.set(email);
}
public String getName() {
return name.get();
}
public void setName(String name) {
this.name.set(name);
}
public StringProperty nameProperty() {
return name;
}
public String getEmail() {
return email.get();
}
public void setEmail(String email) {
this.email.set(email);
}
public StringProperty emailProperty() {
return email;
}
@Override
public String toString() {
return name.get();
}
}
ListView
中选择其他项目时,如何首先验证txtName
和txtEmail
是否包含有效值?
我一直遵循this Q&A来完全防止选择,但是如果值有效,我找不到找到允许选择更改的方法(同时也更新绑定)。
本质上,在此示例中,我要确保在移至新选择之前TextFields不为空。
答案 0 :(得分:1)
据我所知,您不能从ChangeListener内部使用此事件,因为它是一个侦听器,它会通知您有关已发生的更改。
但是,如果您不满意,则可以添加鼠标过滤器,侦听鼠标单击,验证此事件内所需的数据并使用它。
listView.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> {
if(!validate(listView.getSelectionModel().getSelectedItem())) event.consume();
});
我还相信您可以实现自己的MultipleSelectionModel,但这会花费更多时间
答案 1 :(得分:0)
当您的条件无效时,我认为重新选择旧项目将满足您的要求。这样,您就不必担心鼠标和键事件的过滤器。
我将侦听器实现更改为以下内容。它正在按预期方式在您的演示中运行。我唯一关心的是使用Platform.runLater。也许有人可以建议我如何不使用它。
// Add listener to update bindings for the TextFields
listView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
boolean proceed = true;
// Unbind previously bound values
if (oldValue != null) {
txtName.textProperty().unbindBidirectional(oldValue.nameProperty());
txtEmail.textProperty().unbindBidirectional(oldValue.emailProperty());
// If name or email are missing, prevent the change
if (validate(oldValue)) {
Platform.runLater(() -> listView.getSelectionModel().select(oldValue));
proceed = false;
}
}
// Bind the new values
if (newValue != null && proceed) {
txtName.textProperty().bindBidirectional(newValue.nameProperty());
txtEmail.textProperty().bindBidirectional(newValue.emailProperty());
// Refresh the ListView to show changes
listView.refresh();
}
});
更新:Arrggh ...我刚刚注意到我的答案与链接的答案几乎相似,并且您已经与@Kleopatra进行了讨论。但这会解决您的问题:)
@Kleopatra:根据您对refresh()的评论,您能否建议一种方法来在listView中更新详细信息。我被困在那一点。
答案 2 :(得分:0)
这不是对原始问题的答案,只是代码段太长而无法发表评论。
Re:refresh();
为什么不使用它:这是绝对的紧急api,如果无法通过任何方式使FX属性的自动魔术发挥作用,则可以使用粗糙的钩子强制进行更新。不能经常重复:不要不要-如果似乎需要,则您的数据设置有问题。事实总是如此(如99.9999999%)
如何避免:修正数据设置,确保按预期方式发送通知。一个常见的原因是列表中项目的属性已更改。默认情况下,observableList不侦听其项的内部更新,因此无法通知其自己的侦听器,您必须将其显式配置为侦听并触发更新事件-通过提供提取器来完成。
代码段:
ObservableList<Person> persons = FXCollections.observableArrayList(
p -> new Observable[]{p.nameProperty(), p.emailProperty()});
persons.setAll(new Person("Jack", "j@email.com"),
new Person("Bill", "bill@email.com"),
new Person("Diane", "dd@email.com"));
// Simple ListView
ListView<Person> listView = new ListView<>(persons);