JavaFX的TableView相关问题

前言

最近在着手一个学生管理系统的编写,涉及到TableView的使用,这前前后后的也有了些经验和想法想要记录和分享一下(事实上我正在想要用html网页代替界面),更多的是学习之用。

先看看TableView中有些什么

在IDEA中,按住Ctrl然后点中TableView关键字会自动跟进到它定义的地方,我们可以先看看这里面到底都有些什么东西。

你会比较先的看到它的两个构造函数:

// 第一个构造函数
public TableView() {
    this(FXCollections.<S>observableArrayList());
}

// 第二个构造函数
public TableView(ObservableList<S> items) {
    getStyleClass().setAll(DEFAULT_STYLE_CLASS);
    setAccessibleRole(AccessibleRole.TABLE_VIEW);

    // we quite happily accept items to be null here
    setItems(items);

    // install default selection and focus models
    // it's unlikely this will be changed by many users.
    setSelectionModel(new TableViewArrayListSelectionModel<S>(this));
    setFocusModel(new TableViewFocusModel<S>(this));

    // we watch the columns list, such that when it changes we can update
    // the leaf columns and visible leaf columns lists (which are read-only).
    getColumns().addListener(weakColumnsObserver);

    // watch for changes to the sort order list - and when it changes run
    // the sort method.
    getSortOrder().addListener((ListChangeListener<TableColumn<S, ?>>) c -> {
        doSort(TableUtil.SortEventType.SORT_ORDER_CHANGE, c);
    });

    // We're watching for changes to the content width such
    // that the resize policy can be run if necessary. This comes from
    // TreeViewSkin.
    getProperties().addListener(new MapChangeListener<Object, Object>() {
        @Override
        public void onChanged(Change<? extends Object, ? extends Object> c) {
            if (c.wasAdded() && SET_CONTENT_WIDTH.equals(c.getKey())) {
                if (c.getValueAdded() instanceof Number) {
                    setContentWidth((Double) c.getValueAdded());
                }
                getProperties().remove(SET_CONTENT_WIDTH);
            }
        }
    });

    isInited = true;
}

可以大致的看一下,不过最重要的是清楚了一点:TableView内部是维护了一个类型为FXCollections.< S >observableArrayList的集合。其中< S >代表用户自己定义的类型。

也可以看到如何给Table添加监听者:

getProperties().addListener(new MapChangeListener<Object, Object>() {
    @Override
    public void onChanged(Change<? extends Object, ? extends Object> c) {
        if (c.wasAdded() && SET_CONTENT_WIDTH.equals(c.getKey())) {
            if (c.getValueAdded() instanceof Number) {
                setContentWidth((Double) c.getValueAdded());
            }
            getProperties().remove(SET_CONTENT_WIDTH);
        }
    }
});

总之你会看到许多非常有意思的东西,这里就不细说了,有兴趣的可以去自己读一下,对于理解TableView控件有着非常好的帮助,你能顾更加理解它运行的原理还有机制。

实际的运用

我们就来看看实际的运用吧,官方给出了非常详细的文档,有幸找到了把它翻译成较好版本中文的网站,直接给链接,里面就有一些简单的应用:

简单的应用:http://www.javafxchina.net/blog/2015/04/doc03_tableview/
官方的文档:http://docs.oracle.com/javafx/2/ui_controls/table-view.htm

TableView列的两种数据形式:

一种是维护类的TableColumn<Person,String>类型,列的每一个数据都是一个类(这里是一个Person类),而String类型对应列名。映射需要这样设置:

col.setCellValueFactory(
new PropertyValueFactory<Person, String>("firstName"));    // firstName对应列名

另一种是维护Map的TableColumn<Map,String>类型,列的每一个数据都是Map。设置映射时需要这样:

col.setCellValueFactory(new MapValueFactory(colName));        // colName对应字符类型列名```

表格可编辑:

可以向官方文档中那样,也可以先增加一个TextFieldTableCell,然后再添加响应函数:

// 设置CellFactory,填充一个TextField进列
col.setCellFactory(TextFieldTableCell.<Map>forTableColumn());
// 设置编辑响应的函数
col.setOnEditCommit(new EventHandler<TableColumn.CellEditEvent<Map, String>>() {
    @Override public void handle(TableColumn.CellEditEvent<Map, String> t) {
    System.out.println("检测到改变"); 
    // 这里修改维护的对应的设置进TableView的ObservableList集合
    }
});

添加行删除行也是同样的操作,可以直接修改TableView维护的集合来完成。

增加列,删除列

这就不仅仅要删除集合中的数据,还要从表格里面的Columns集合中删除相应的数据才可以,或许你还会在删除和增加中加入一定的判断来保证操作的正确性:

table.getColumns().add(tempCol);        // 列表中显示新增的列
table.getColumns().remove(index);          // 删除index位置的列

监听列的变化

你大可以选择向源文件中的那样,通过getProperties().addListener来完成监听,同样也可以添加进一个ListChangeListener:

// 给table设置监听器监听列的变化
table.getColumns().addListener(new ListChangeListener() {
    @Override
    public void onChanged(Change c) {
        c.next();                   // 接受变化,否则报错

        // 处理列拖动后的事件
        if (c.wasRemoved()) {
            // 定义一个保存了现在列排序的集合
            List<TableColumn<ObservableList<Map>, String>> newList =
                    new ArrayList<>(table.getColumns());
            // 定义一个保存了原来列排序的集合
            List<TableColumn<ObservableList<Map>, String>> oldList =
                    new ArrayList<>(c.getList());
            // 相关操作
        }   // end if:拖动事件处理完毕
    }
});
TableView 表 TableColumn列
构建一个表主要有TableView,TableColumn,ObservableList和Bean
添加列
table.getColumns().addAll(firstNameCol, lastNameCol, emailCol); 
ObservableList里面存放的是数据
添加数据
table.setItems(ObservableList)
observableList里面一般是存放的Bean
列与Bean之间建立联系,从而获取值。
列与Bean之间建立联系, 通过cell值工厂建立与Bean的联系。
RoomIdCol.setCellValueFactory(new PropertyValueFactory<Person, String>("RoomIdCol"))
它这里并不需要知道你是传了什么Bean,它只需要通过“RoomIdCol”反射成getRoomIdCol()方法去Bean里面获得值,所以Bean属性定义的名字不需要与它相同,只需要有getRoomIdCol()方法。
firstNameCol.setCellValueFactory
    (new Callback<TableColumn.CellDataFeatures<Person, String>, 
     ObservableValue<String>>() {  
         @Override  
         public ObservableValue<String> call(CellDataFeatures<Person, String> arg0) {          
             // return new  
             // 	SimpleStringProperty(arg0.getValue(),"sd",arg0.getValue().getFirstName());  
             // //bean, bean的名称,值  
             return new SimpleStringProperty(arg0.getValue().getFirstName());   
             // 这样你可以不建立值与对象的映射关系。  
         }  
});
arg0.getValue()等于这里的person。若是你observableList.add(list),则这arg0.getValue()等于list。
SimpleStringProperty(arg0.getValue(),"sd",arg0.getValue().getFirstName()); 
这里的意思既是arg0.getValue()既是你observableList.add的值,“sd”为bean取得名字,arg0.getValue().getFirstName()既是你该列想要获得的值。如果是list则arg0.getValue().get(j)则为该列的每行赋值了。
cell里面不仅只存放文字,还可以存放其它Node:
firstNameCol.setCellFactory(new Callback<TableColumn<Person, String>, TableCell<Person, String>>() {  
    @Override  
    public TableCell<Person, String> call( // 单元格内容  
    TableColumn<Person, String> arg0) {  
       return new TableCell<Person, String>() {                @Override  
         protected void updateItem(final String str,boolean arg1) {        
              super.updateItem(str, arg1);  
         if (arg1) {                            setText(null);  
        setGraphic(null);  
          else {                                 setText(str);  
                  setGraphic(new CheckBox());  
        }  
          }  
       }  
}); 
和TreeCell使用一样,可以对cell里面弄重新构造。
lastNameCol.setCellFactory(TextFieldTableCell.forTableColumn());
有一些默认的构造,就不需要自己去new TableCell了
//TableColumn设置sort的3个方法 
firstNameCol.setSortNode(new Text("a")); // 默认是表头上的小图标三角形,可以改变 
firstNameCol.setSortable(true); // 设置可排序 
firstNameCol.setSortType(SortType.DESCENDING);//设置升降序 
若要在一个column中包含多个column,则可以调用TableColumn的getColumns().setAll(TableColumn…);
        firstNameColumn = new TableColumn<Person, String>("First");  
        firstNameColumn.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));  
//        firstNameColumn.setCellFactory(TextFieldCellFactory.<Person>forTableColumn());  
  
        lastNameColumn = new TableColumn<Person, String>("Last");  
        lastNameColumn.setCellValueFactory(new PropertyValueFactory<Person, String>("lastName"));  
//        lastNameColumn.setCellFactory(TextFieldCellFactory.<Person>forTableColumn());  
  
        nameColumn = new TableColumn<Person, String>("Name");  
        nameColumn.getColumns().setAll(firstNameColumn, lastNameColumn); 
table的单元之间有明显的横线分割,可以通过css去掉。
去掉行横线
.table-view .table-row-cell {  
    -fx-background-insets: 0;  
} 
若想同时去掉没有数据的竖线
.table-row-cell:empty .table-cell {  
    -fx-border-width: 0px;  
}  
若想对行进行操作,可以通过setRowFactory。如下面对行的双击进行操作
tableView.setRowFactory(new Callback<TableView<T>, TableRow<T>>() {  
            @Override  
            public TableRow<T> call(TableView<T> param) {  
                return new TableRowControl();  
            }  
        });  
class TableRowControl extends TableRow<T> {  
  
        public TableRowControl() {  
            super();  
            this.setOnMouseClicked(new EventHandler<MouseEvent>() {  
                @Override  
                public void handle(MouseEvent event) {  
                    if (event.getButton().equals(MouseButton.PRIMARY)  
                            && event.getClickCount() == 2  
                            && TableRowControl.this.getIndex() < tableView.getItems().size()) {  
                          //doSomething  
                    }  
                }  
            });  
        }  
    }  
往table中插入数据,table中的数据显示,是根据你的itemlist来的,list里面的数据排什么序,那table也就排什么序。若添加一条新数据,直接往list里面加。而list又提供按位置加,那么table显示就是按位置加了。
tableView.getItems().add(selectedRow, newRecord);  
newRecord一个新的对象,没赋值。
自定义TableCell一般都是重写updateItem方法。如果有需要在编辑做操作,可以重写startEdit,cancelEdit
@Override  
      public void startEdit() {      
          if (!this.getTableRow().isVisible()) {  
              return;  
          }  
          super.startEdit();  
  
          if (checkBox == null) {  
              createCheckBox();  
          }  
          setText(null);  
          setGraphic(checkBox);  
      }  
  
      @Override  
      public void cancelEdit() {  
          super.cancelEdit();  
          setText(getItem().toString());  
          setGraphic(null);  
      }  
可以看到,一旦点击编辑状态,则改变Cell里面的内容。一离开编辑就换成原本cell里面的内容。这样就可以显示的时候就是字符串,而编辑的时候就可以弄一个控件,如日历。
获取选中的TableColumn
table.getSelectionModel().getSelectedCells().get(0).getTableColumn() 
table自带方法可以过滤column,也就是只显示哪些column
table.setTableMenuButtonVisible(true);  
设置为true后,会出现一个加号的column,它可以对column进行过滤
table默认是只能选着一行的,如果想选着多行,设置SelectionMode,此时可以对选中的多个进行监听。
ListChangeListener<Person> indicesListener = new   ListChangeListener<Person>() {  
           @Override public void onChanged(Change<? extends Person> c) {  
               while (c.next()) {  
                  
                   selectionUpdated(c.getAddedSubList(), c.getRemoved());  
               }  
           }  
       };  
       tableView.getSelectionModel().getSelectedItems().addListener(indicesListener);  
       tableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);  
tableView.getSelectionModel()得到的是个抽象类SelectionModel,它有二个子类MultipleSelectionModel, SingleSelectionModel。它们主要处理选择事件,可以看它们的方法:
getSelectedIndex()   
getSelectedItem()   
selectedIndexProperty()   
selectedItemProperty()
获取选中的item和索引。一个是获取其值,另一个是获取封装属性,用于bind变化。
select(int index)   
select(T obj)   
selectFirst()   
selectLast()   
...  
clearSelection()   
clearSelection(int index) 
这些方法都是操作选中。
setSelectionMode(SelectionMode.MULTIPLE);  
selectIndices(int index, int... indices)   
selectRange(int start, int end)   
MultipleSelectionModel则提供多选功能,并且提供多选的一些方法。
select(int row, TableColumn<S,?> column)   
selectAboveCell()   
selectBelowCell()  
selectLeftCell()   
selectRightCell()    
setCellSelectionEnabled(boolean value)  
TableView.TableViewSelectionModel是继承了MultipleSelectionModel,主要针对table的选中事件提供了一些方法。
上一篇:JavaFX(第二篇)Stage和Modality.WINDOW_MODAL


下一篇:JSON在Java中的使用