JavaFX 绑定(bind)到嵌套列

标签 java generics javafx-2 guava

我目前正在尝试编写我的第一个 JavaFX 应用程序,它将从套接字解码的消息写入 JavaFX 表中。该表需要是动态的,如果消息包含表中当前不存在的数据,则应添加新列。一些字段嵌套在包装类中,我试图使用嵌套列来表示这些嵌套,如下所示:http://docs.oracle.com/javafx/2/ui_controls/table-view.htm#CJABHBEH

我的问题是我似乎无法获得正确的数据来绑定(bind)到嵌套列。当我尝试为我的 CustomType 绑定(bind)到 PropertyValueFactory("one") 时,我从 MessageType1 获取可选值。我尝试了几种不同的方法,您将在下面的示例中看到:

import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Stage;

import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;


public class SampleTable extends Application {
  private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
  private final ObservableList<Message> msgList = FXCollections.observableArrayList();

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

  @Override
  public void start(Stage primaryStage) throws Exception {
    final TableView tableView = new TableView<>(msgList);
    tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);


    final TableColumn<Message, MessageType> msgTypeCol = new TableColumn<Message, MessageType>();
    msgTypeCol.setText("Message Type");
    msgTypeCol.setCellValueFactory(new PropertyValueFactory<Message, MessageType>("messageType"));

    final TableColumn<MessageType1, Optional<Integer>> intCol1 = new TableColumn<MessageType1, Optional<Integer>>();
    intCol1.setText("Int1");
    intCol1.setCellValueFactory(new PropertyValueFactory<MessageType1, Optional<Integer>>("one"));

    final TableColumn<MessageType1, Optional<Integer>> intCol2 = new TableColumn<MessageType1, Optional<Integer>>();
    intCol2.setText("Int2");
    intCol2.setCellValueFactory(new PropertyValueFactory<MessageType1, Optional<Integer>>("two"));

    final TableColumn<MessageType1, Optional<Integer>> intCol3 = new TableColumn<MessageType1, Optional<Integer>>();
    intCol3.setText("Int3");
    intCol3.setCellValueFactory(new PropertyValueFactory<MessageType1, Optional<Integer>>("three"));

    final TableColumn<MessageType2, List<String>> strCol = new TableColumn<MessageType2, List<String>>();
    strCol.setText("String List");
    strCol.setCellValueFactory(new PropertyValueFactory<MessageType2, List<String>>("list"));

   //final TableColumn<MessageType3, Optional<CustomType>> customTypeCol = new TableColumn<MessageType3, Optional<CustomType>>();
    final TableColumn customTypeCol = new TableColumn<>();
    customTypeCol.setText("Custom Type");

//    final TableColumn<CustomType, Boolean> customTypeCol1 = new TableColumn<CustomType, Boolean>();
//    customTypeCol1.setText("CT1");
//    customTypeCol1.setCellValueFactory(new PropertyValueFactory<CustomType, Boolean>("one"));
//    final TableColumn<CustomType, Boolean> customTypeCol2 = new TableColumn<CustomType, Boolean>();
//    customTypeCol2.setText("CT2");
//    customTypeCol2.setCellValueFactory(new PropertyValueFactory<CustomType, Boolean>("customTwo"));
//    final TableColumn<CustomType, Integer> customTypeCol3 = new TableColumn<CustomType, Integer>();
//    customTypeCol3.setText("CT3");
//    customTypeCol3.setCellValueFactory(new PropertyValueFactory<CustomType,Integer>("customThree"));
  final TableColumn<MessageType3, Boolean> customTypeCol1 = new TableColumn<MessageType3, Boolean>();
  customTypeCol1.setText("CT1");
  customTypeCol1.setCellValueFactory(new PropertyValueFactory<MessageType3, Boolean>("one"));
  final TableColumn<MessageType3, Boolean> customTypeCol2 = new TableColumn<MessageType3, Boolean>();
  customTypeCol2.setText("CT2");
  customTypeCol2.setCellValueFactory(new PropertyValueFactory<MessageType3, Boolean>("customTwo"));
  final TableColumn<MessageType3, Integer> customTypeCol3 = new TableColumn<MessageType3, Integer>();
  customTypeCol3.setText("CT3");
  customTypeCol3.setCellValueFactory(new PropertyValueFactory<MessageType3,Integer>("customThree"));

    customTypeCol.getColumns().addAll(customTypeCol1, customTypeCol2, customTypeCol3);


//    customTypeCol.setCellValueFactory(new PropertyValueFactory<MessageType3, Optional<CustomType>>("customType"));
//    customTypeCol.setCellFactory(
//        new Callback<TableColumn<MessageType3, Optional<CustomType>>, TableCell<MessageType3, Optional<CustomType>>>() {
//      @Override
//      public TableCell<MessageType3, Optional<CustomType>> call(
//          final TableColumn<MessageType3, Optional<CustomType>> param) {
//        return new TableCell<MessageType3, Optional<CustomType>>() {
//          @Override
//          protected void updateItem(Optional<CustomType> customType, boolean empty) {
//            super.updateItem(customType, empty);
//
//            if(!empty) {
//              //Tried this but didn't work either
////              final TableColumn<CustomType, Boolean> customTypeCol1 = new TableColumn<CustomType, Boolean>();
////              customTypeCol1.setText("CT1");
////              customTypeCol1.setCellValueFactory(new PropertyValueFactory<CustomType, Boolean>("one"));
////              final TableColumn<CustomType, Boolean> customTypeCol2 = new TableColumn<CustomType, Boolean>();
////              customTypeCol2.setText("CT2");
////              customTypeCol2.setCellValueFactory(new PropertyValueFactory<CustomType, Boolean>("customTwo"));
////              final TableColumn<CustomType, Integer> customTypeCol3 = new TableColumn<CustomType, Integer>();
////              customTypeCol3.setText("CT3");
////              customTypeCol3.setCellValueFactory(new PropertyValueFactory<CustomType,Integer>("customThree"));
//              //
//              final TableColumn<MessageType3, Boolean> customTypeCol1 = new TableColumn<MessageType3, Boolean>();
//              customTypeCol1.setText("CT1");
//              customTypeCol1.setCellValueFactory(new PropertyValueFactory<MessageType3, Boolean>("one"));
//              final TableColumn<MessageType3, Boolean> customTypeCol2 = new TableColumn<MessageType3, Boolean>();
//              customTypeCol2.setText("CT2");
//              customTypeCol2.setCellValueFactory(new PropertyValueFactory<MessageType3, Boolean>("customTwo"));
//              final TableColumn<MessageType3, Integer> customTypeCol3 = new TableColumn<MessageType3, Integer>();
//              customTypeCol3.setText("CT3");
//              customTypeCol3.setCellValueFactory(new PropertyValueFactory<MessageType3,Integer>("customThree"));
//
//              param.getColumns().addAll(customTypeCol1, customTypeCol2, customTypeCol3);
//            }
//          }
//        };
//      }
//    });
    tableView.getColumns().addAll(msgTypeCol, intCol1, intCol2, intCol3, strCol, customTypeCol);

    final Group root = new Group();
    primaryStage.setScene(new Scene(root));
    root.getChildren().add(tableView);

    //Simulate incoming DUMMY Messages
    executor.scheduleAtFixedRate(new Runnable() {
      private int i = 0;

      @Override
      public void run() {
        switch(i) {
        case 0:
          msgList.add(new MessageType1());
          i++;
          break;
        case 1:
          msgList.add(new MessageType2());
          i++;
          break;
          //System.exit(0);
        case 2:
          msgList.add(new MessageType3());
          i = 0;
          break;
        }
      }
    }, 0, 1, TimeUnit.SECONDS);

    primaryStage.show();
  }

  public enum MessageType { TYPE1, TYPE2, TYPE3 };
  public interface Message {
    MessageType getMessageType();
  }
  public static final class CustomType {
    private final boolean one = true;
    private final boolean customTwo = true;
    private final int customThree = 3;

    public Boolean getOne() { return one; }
    public Boolean getCustomTwo() { return customTwo; }
    public Integer getCustomThree() { return customThree; }
  }

  public static final class MessageType1 implements Message {
    private final List<Optional<Integer>> intList =
        ImmutableList.<Optional<Integer>>of(Optional.of(1),
            Optional.of(2),
            Optional.<Integer>absent());

    @Override
    public MessageType getMessageType() { return MessageType.TYPE1; }
    public Optional<Integer> getOne() { return intList.get(0); }
    public Optional<Integer> getTwo() { return intList.get(1); }
    public Optional<Integer> getThree() { return intList.get(2); }
  }

  public static final class MessageType2 implements Message {
    List<String> stringList = ImmutableList.of("one", "two", "three");

    @Override
    public MessageType getMessageType() { return MessageType.TYPE2; }
    public List<String> getList() { return stringList; }
  }

  public static final class MessageType3 implements Message {
    private final Optional<CustomType> obj = Optional.of(new CustomType());

    @Override
    public MessageType getMessageType() { return MessageType.TYPE3; }
    public Optional<CustomType> getCustomType() { return obj; }
  }
}

我想问题归结为如何将我的 CustomType 数据绑定(bind)到嵌套列?我尝试的一切似乎都失败了。我的直觉告诉我,这与在插入之前将泛型放在我的 TableColumns 上有关,但我没有看到其他选择。关于如何编写像本例这样的混合数据表,是否有更好的建议?也许我正在以完全错误的方式解决这个问题?

谢谢!

注意:我编写此代码是为了尝试表明我试图完成的任务绝不是“生产”级别。

最佳答案

该问题与嵌套列无关。您正在使用 TableView 作为原始(非参数化)类型。不建议在 Java 中使用原始类型,因为编译器无法检查和确保类型对话以及与所使用的参数类型相关的其他内容。IMO 当使用原始类型时,如果在运行时出现对话错误,JavaFX 会忽略它通过返回 null 来作为无操作操作(具体实现可以从源代码中看到)。更具体地说,每个

new PropertyValueFactory<MessageType3, Boolean>("field")

似乎正在通过当前添加项(MessageType 1,2,3)的反射来查找“getField()”或“fieldProperty()” getter ,而不管列的参数化类型。这就是“PropertyValueFactory(“one”) for my CustomType I am gettingOptions from MessageType1”的原因,因为两个类具有相同的命名 getter。

解决方法可能是:
1) 将 getter 名称更改为表中所示的类之间唯一。
2)使用instanceof。代码

customTypeCol1.setCellValueFactory(
                 new PropertyValueFactory<MessageType3, Boolean>("one"));

或多或少相当于:

customTypeCol1.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<MessageType3, Boolean>, ObservableValue<Boolean>>() {
    @Override
    public ObservableValue<Boolean> call(TableColumn.CellDataFeatures<MessageType3, Boolean> p) {
        return new SimpleBooleanProperty(p.getValue().getOne());
    }
});

此处列参数化类型不会被忽略,因此在第一次尝试将 MessageType1 项添加到表中时运行此结果会导致 ClassCastException,因为这里 p.getValue() 将返回 MessageType1。使用 instanceof 将是一种解决方法,

customTypeCol1.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<MessageType3, Boolean>, ObservableValue<Boolean>>() {
    @Override
    public ObservableValue<Boolean> call(TableColumn.CellDataFeatures<MessageType3, Boolean> p) {
        if (p.getValue() instanceof MessageType3) {
            return new SimpleBooleanProperty(p.getValue().getOne());
        } else {
            return null;
        }
    }
});

<小时/> 说了这么多之后,我更喜欢的真正解决方案是定义一个新的数据模型类,该类由 MessageTypes 1、2 和 3 部分的字段组成。构建此数据模型并作为参数化的源 TableView 。这将有助于其他同事轻松理解代码、进行改进和维护。

What is a raw type and why shouldn't we use it? 的良好引用

关于JavaFX 绑定(bind)到嵌套列,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22890132/

相关文章:

java - 使用反射的通用类类型父级

c# - 将 List<T> 转换为 object[]

java - JavaFX 中的多线程会挂起 UI

Java:如何避免 display() 和 write(pdf) 之间的代码重复

Java 2d Array 还是 2d ArrayList?

java - 有没有一种方法可以通用地测量物体的大小或长度?

java - 将参数从 JNLP 传递到 JavaFX2

java - 多次高效地使用 Prepared Statement

c# - 求值表达式 通用 C# 求值条件 多态性

java - 如何在javafx中的同一窗口中打开新页面