JavaFX: Cell & Cell Factory

文章目录

    • Cell
    • Cell Factories

官方文档相关链接

Cell


The Cell API is used for virtualized controls such as ListView, TreeView, and TableView.A Cell is a Labeled Control, and is used to render a single"row" inside a ListView, TreeView or TableView. Cells are also used for each individual ‘cell’ inside a TableView (i.e. each row/column intersection). Seethe JavaDoc for each control separately for more detail.

Cell API 是用来进行虚拟控制的,例如在ListView中,以及TreeView中,还有TableView中的行列中作为一个类似单元格的表示存在,可以是TableView中的一个TableColumn(表格中的一行)中某一行(前面这三个都是JavaFX中的一种界面元素集合的表示)。

Every Cell is associated with a single data item (represented by the item property). The Cell is responsible for rendering that item and, where appropriate, for editing the item. An item within a Cell may be represented by text or some other control such as a CheckBox, ChoiceBox or any other Node such as a HBox, GridPane, or even a Rectangle.

每个 Cell 对应于一个 data item(用这个item 的 property,例如一个你定义的类中的属性,来表示),而 Cell 的工作就是对你定义的这一系列的属性进行渲染,渲染出来的结果可能是一些用来对这些属性进行修改的组件,例如一些 Button,也可能是一些 CheckBox,当然甚至可以是新的一张 Pane,或者是一些图形。

Because TreeView, ListView, TableView and other such controls can potentially be used for displaying incredibly large amounts of data, it is not feasible to create an actual Cell for every single item in the control.We represent extremely large data sets using only very few Cells. Each Cellis “recycled”, or reused. This is what we mean when we say that these controls are virtualized.

此外,你需要注意,这些 Cell 都是可以重复使用的,即 recycled,reused,例如在一些ScrollPane中,事实上你只需要使用很少量的 Cell ,例如在当前窗口中能够显示的最大数量的行数,你就可以表示这个滚动页面所包含的全部数据了。这就是为什么说这些控件都是虚拟的(即真实定义的 Cell 的数量远少于数据项的量)

Since Cell is a Control, it is essentially a “model”. Its Skin is responsible for defining the look and layout, while the Behavior is responsible for handling all input events and using that information to modify the Control state. Also, the Cell is styled from CSS just like any other Control.However, it is not necessary to implement a Skin for most uses of a Cell.This is because a cell factory can be set - this is detailed more shortly.

显然,Cell 作为控件的一种,在 MVC 中是属于 model

  • Skiin:定义控件的外观和布局
  • Behaviour:可以处理各种输入事件,并且根据获得的信息修改控件的状态

并且 Cell 也和其他的控件一样,利用 CSS 进行风格化,但是一般都是对 Cell Factory 对整体进行风格上的设定,后面我们会介绍相关的一些概念。

Because by far the most common use case for cells is to show text to a user,this use case is specially optimized for within Cell. This is done by Cell extending from Labeled. This means that subclasses of Cell need only set the text property, rather than create a separate Label and set that within the Cell. However, for situations where something more than just plain text is called for, it is possible to place any Node in the Cell graphic property. Despite the term, a graphic can be any Node, and will be fully interactive.For example, a ListCell might be configured with a Button as itsgraphic. The Button text could then be bound to the cells item property. In this way, whenever the item in the Cell changes, the Button text is automatically updated.

不过,之前 Cell 通常是提供给用户来实现文本的显示的,而这种使用方式是非常高效并且实用的,通过让 Cell 扩展Label的功能,然后 Cell 只需要设置响应的文本即可,而不需要在 Cell 中重新创建一个单独的 Label 控件来实现这个功能。而控件的相关内容,例如如果是文本的话,可以赋值为这一行的对象的某个属性,这时,只要对象的属性发生变化,或者对象本身发生变化,文本内容也会随之发生变化(有点类似js的绑定)

但是有时候我们需要实现更加复杂的功能,这时可以把例如,Node之类的作为 Cell 的 graph,例如下面重新定义一个包含3个Button的Cell,用于音乐的播放,添加到播放列表和下载功能:

// 通过扩展TableCell,重新定义一个用来装填按钮的Cell类
public class PlayButtonCell<S, T> extends TableCell<S, T> {
		// 包含一个HBox,即 Horizontal Box,可以理解为一个水平的容器,相对的还有一个VBox,是Vertical Box,垂直容器,通常用来作为Button的容器
		private final HBox HBox_Buttons;
		// 三个执行不同功能的Button
		private final Button Button_Play;
		private final Button Button_Add;
		private final Button Button_Download;
		public PlayButtonCell() {
			// 创建三个按钮对象
			this.Button_Play = new Button("播放");
			this.Button_Add = new Button("+");
			this.Button_Download = new Button("↓");
			// ...
			// 这里可以添加按钮点击事件的handler
			// ...
			// 创建水平容器
			this.HBox_Buttons = new HBox();
			// 往容器中添加按钮
			this.HBox_Buttons.getChildren().add(this.Button_Play);
			this.HBox_Buttons.getChildren().add(this.Button_Add);
			this.HBox_Buttons.getChildren().add(this.Button_Download);
			// 设置这个Cell的Graphic为包含上述控件的HBox
			setGraphic(HBox_Buttons);
		}
		// 重写Cell的updateItem函数
		@Override
		protected void updateItem(T item, boolean empty) {
			// System.out.println("empty:"+empty);
			// 调用TableCell的updateItem函数
			super.updateItem(item, empty);
			// 判断Table中的这一行是否有对应的Music对象,没有则不进行渲染
			if (empty) {
				setText(null);
				setGraphic(null);
				}
			// 否则,将渲染对象设置为上面创建的HBox
			else {
				setGraphic(HBox_Buttons);
			}
		}
}

Cell Factories


The default representation of the Cell item is up to the various virtualized container’s skins to render. For example, the ListView by default will convert the item to a String and call Labeled.setText(java.lang.String) with this value. If you want to specialize the Cell used for the ListView (for example), then you must provide an implementation of the cellFactory callback function defined on the ListView. Similar API exists on most controls that use Cells (for example, TreeView, TableView, TableColumn and ListView.

Cell的默认形式是根据虚拟容器的外观来进行渲染的,如果你希望自定义在TableView或者是ListView还是其他的数据表现方式中的Cell,必须实现其中的cellFactory回调函数(callback)

The cell factory is called by the platform whenever it determines that a newcell needs to be created. For example, perhaps your ListView has 10 million items. Creating all 10 million cells would be prohibitively expensive. So instead the ListView skin implementation might only create just enough cells to fit the visual space. If the ListView is resized to be larger, the systemwill determine that it needs to create some additional cells. In this case it will call the cellFactory callback function (if one is provided) to create the Cell implementation that should be used. If no cell factory is provided,the built-in default implementation will be used.

当一个新的 cell 需要被创建的时候,cell factory 就会被平台调用,而这种创建事实上是动态的,也就是说,并不会在一开始就创建对应数量的 cell,正如我们前面有所提到的,只会创建窗口所能够显示的最多的数量的 cell,而当发生例如 resize 的时候,才会根据情况创建更多的 cell,这样子是比较合理的,保证了更好的运行效率,而不是在软件启动的时候,用户必须长时间等待大量的 cell 的渲染。此时,需要创建新的 cellcellFactory 就会被调用,而如果没有定义,则会调用默认的 cell factory。简单来说,你可以理解为:Cell Factory 顾名思义,就是一个生产 Cell 的工厂,它明白该如何渲染一个 Cell,而这种渲染 Cell 的行为是可以由用户定义的,通过定义一个callFactory回调函数,使得我们可以自定义 Cell 的样式。

The implementation of the cell factory is then responsible not just for creating a Cell instance, but also configuring that Cell such that it reacts to changes in its state. For example, if I were to create a custom Cell which formatted Numbers such that they would appear as currency types, I might do so like this:

不过,cell factory 并不是仅仅负责创建 cell 的单例(instance),还负责进行相关的部署,使得这些 cell 可以根据对应的 item 的变化改变自身的状态,例如创建一个可以显示货币类型的数字格式的cell

 public class MoneyFormatCell extends ListCell<Number> {

     public MoneyFormatCell() {    }
       
     @Override protected void updateItem(Number item, boolean empty) {
         // calling super here is very important - don't skip this!
         super.updateItem(item, empty);
           
         // format the number as if it were a monetary value using the 
         // formatting relevant to the current locale. This would format
         // 43.68 as "$43.68", and -23.67 as "-$23.67"
         setText(item == null ? "" : NumberFormat.getCurrencyInstance().format(item));

         // change the text fill based on whether it is positive (green)
         // or negative (red). If the cell is selected, the text will 
         // always be white (so that it can be read against the blue 
         // background), and if the value is zero, we'll make it black.
         if (item != null) {
             double value = item.doubleValue();
             setTextFill(isSelected() ? Color.WHITE :
                 value == 0 ? Color.BLACK :
                 value < 0 ? Color.RED : Color.GREEN);
         }
     }
 }

This class could then be used inside a ListView as such:

ListView 中的应用如下:

ObservableList<Number> money = ...;
final ListView<Number> listView = new ListView<Number>(money);
listView.setCellFactory(new Callback<ListView<Number>, ListCell<Number>>() {
	@Override public ListCell<Number> call(ListView<Number> list) {
		return new MoneyFormatCell();
	}
});

In this example an anonymous inner class is created, that simply returns instances of MoneyFormatCell whenever it is called. The MoneyFormatCell class extends ListCell, overriding the updateItem method. This methodis called whenever the item in the cell changes, for example when the user scrolls the ListView or the content of the underlying data model changes(and the cell is reused to represent some different item in the ListView).Because of this, there is no need to manage bindings - simply react to the change in items when this method occurs. In the example above, whenever the item changes, we update the cell text property, and also modify the text fill to ensure that we get the correct visuals. In addition, if the cell is “empty”(meaning it is used to fill out space in the ListView but doesn’t have any data associated with it), then we just use the empty String.
Note that there are additional methods prefixed with ‘update’ that may be of interest, so be sure to read the API documentation for Cell, and sub classes of Cell, closely.

注意,上面定义的这个回调函数,会在 item 的状态发生改变的时候被调用,这使得在 ListView 中相关的数据始终和对象中的属性保持一致,官方给出的例子中,当被用户用鼠标选择显示的数字,或者是数字的值发生变化符合某一个条件,这个 updateItem() 函数就会被调用,使得 cell 的状态得到相应的更新,并且需要注意,这个 cell 是被重用的,所以并不需要考虑任何的绑定(binding)。而当对象为空的时候(比如说数量不足,视觉范围内的列表无法填满),此时只需要使用空串即可。
此外还有一些函数是update开头的,需要注意区分

Of course, we can also use the binding API rather than overriding the ‘update’ methods. Shown below is a very trivial example of how this could be achieved.

当然也可以使用绑定来实现:

public class BoundLabelCell extends ListCell<String> {
	public BoundLabelCell() {
		textProperty().bind(itemProperty());
	}
}

另外给出本人写的音乐播放器重写回调函数的例子,结合前面给出的自定义Cell 的代码一起:

// 通过扩展TableCell,重新定义一个用来装填按钮的Cell类
public class PlayButtonCell<S, T> extends TableCell<S, T> {
		// 包含一个HBox,即 Horizontal Box,可以理解为一个水平的容器,相对的还有一个VBox,是Vertical Box,垂直容器,通常用来作为Button的容器
		private final HBox HBox_Buttons;
		// 三个执行不同功能的Button
		private final Button Button_Play;
		private final Button Button_Add;
		private final Button Button_Download;
		public PlayButtonCell() {
			// 创建三个按钮对象
			this.Button_Play = new Button("播放");
			this.Button_Add = new Button("+");
			this.Button_Download = new Button("↓");
			// ...
			// 这里可以添加按钮点击事件的handler
			// ...
			// 创建水平容器
			this.HBox_Buttons = new HBox();
			// 往容器中添加按钮
			this.HBox_Buttons.getChildren().add(this.Button_Play);
			this.HBox_Buttons.getChildren().add(this.Button_Add);
			this.HBox_Buttons.getChildren().add(this.Button_Download);
			// 设置这个Cell的Graphic为包含上述控件的HBox
			setGraphic(HBox_Buttons);
		}
		// 重写Cell的updateItem函数
		@Override
		protected void updateItem(T item, boolean empty) {
			// System.out.println("empty:"+empty);
			// 调用TableCell的updateItem函数
			super.updateItem(item, empty);
			// 判断Table中的这一行是否有对应的Music对象,没有则不进行渲染
			if (empty) {
				setText(null);
				setGraphic(null);
				}
			// 否则,将渲染对象设置为上面创建的HBox
			else {
				setGraphic(HBox_Buttons);
			}
		}
}

void initialize(){
		// 重写Callback函数
		TableColumn_Button.setCellFactory(
					new Callback<TableColumn<Music, Boolean>, TableCell<Music, Boolean>>(){
			public TableCell<Music, Boolean> call(TableColumn<Music, Boolean> param){
				// 使得这个函数调用的时候返回我们定义的Cell
				return new PlayButtonCell<Music, Boolean>();
			}
		});
}

你可能感兴趣的:(JavaFX: Cell & Cell Factory)