PreferencesFX自定义Setting的实现案例
PreferencesFX其实对常见类型的Setting有默认支持,比如字符串,数字,选择列表等, 甚至于也支持File/Directory类型的Setting, 允许我们使用FileChooser来选择和设定对应的Setting状态与展示, 但是,这几天想添加一个字体的Setting配置项, 发现默认的搞不定,但还想继续沿用PreferencesFX的基础设施,所以研究了下如何自定义PreferencesFX的Setting。
PreferencesFX其实提供了两种自定义Setting的扩展机制:
Setting.of(Node) Setting.of(description, Field, Property)
首先,我们的Font类型的自定义Setting在展示的时候, 预期的展示是这样的: 一个TextField作为字体类型, 大小和风格的选择结果展示, 一个Button, 当点击的时候,则打开一个字体选择对话框(我们使用ControlsFX的DialogSeletorDialog), 用户选择了相应字体之后, 则将所选择的字体信息格式化之后设置给TextField并更新对应的Property, 至于Setting的显示名称,则直接使用传入的description即可。
在这个前提下,我们首先得先定义一个SimpleControl, 下面是我们的实现:
import com.dlsc.formsfx.model.structure.StringField import com.dlsc.preferencesfx.formsfx.view.controls.SimpleControl import com.keevol.keenotes.desk.utils.FontStringConverter import javafx.geometry.Insets import javafx.scene.control.{Button, TextField} import javafx.scene.layout.{HBox, Priority, StackPane} import javafx.scene.text.Font import org.controlsfx.dialog.FontSelectorDialog /** * a custom simple control for font with foot chooser * * @author fq@keevol.com */ class SimpleFontControl extends SimpleControl[StringField, StackPane] { var textField: TextField = _ var fontChooseButton: Button = _ val fontStringConverter = new FontStringConverter() override def initializeParts(): Unit = { super.initializeParts() node = new StackPane() textField = new TextField() textField.setEditable(false) fontChooseButton = new Button("Choose Font") fontChooseButton.setOnAction(e => { val dialog = new FontSelectorDialog(Font.getDefault) val p = dialog.showAndWait() if (p.isPresent) { val font = p.get() println("font.toString: " + font.toString) textField.setText(fontStringConverter.toString(font)) } }) val hbox = new HBox(10) hbox.setPadding(new Insets(3)) hbox.getChildren.addAll(textField, fontChooseButton) HBox.setHgrow(textField, Priority.ALWAYS) node.getChildren.add(hbox) } override def layoutParts(): Unit = { } override def setupBindings(): Unit = { super.setupBindings() // without this, PreferencesFX will throw exception. if (field.valueProperty.get == "null" || field.valueProperty.get == null) field.valueProperty.set("") field.valueProperty().bindBidirectional(textField.textProperty()) } }
这几个override的方法原则上 layoutParts()
是必需的, 但我们把这个方法的一些逻辑直接合并到了initializeParts()方法中(即组件的初始化和layout放一起了)。
因为Font和String类型差异,我们将Font到String的格式化逻辑以及从String创建Font的逻辑抽象封装到了FontStringConverter(一个StringConverter实现):
package com.keevol.keenotes.desk.utils import javafx.scene.text.{Font, FontWeight} import javafx.util.StringConverter import org.apache.commons.lang3.StringUtils class FontStringConverter extends StringConverter[Font] { override def toString(font: Font): String = s"${font.getFamily}, ${font.getSize}, ${font.getStyle}" override def fromString(fontString: String): Font = { val fontFamily = StringUtils.substringBefore(fontString, ",") val fontSize = StringUtils.substringBetween(fontString, ", ", ", ") val fontStyle = StringUtils.substringAfterLast(fontString, ", ") val f = if (StringUtils.contains(fontStyle.toLowerCase, "bold")) { Font.font(fontFamily, FontWeight.BOLD, fontSize.toDouble) } else { Font.font(fontFamily, fontSize.toDouble) } f } }
有了这些之后, 我们就可以添加Font类型的自定义Setting到PreferencesFX了:
val fontProperty = new SimpleStringProperty("Serif") ... Setting.of("Font", Field.ofStringType(fontProperty).render(new SimpleFontControl()), fontProperty)
现在, 我们的主程序就可以基于这个Setting做初始化了:
def tile(channel: String, content: String, dt: Date = new Date()) = { val card = new KeeNoteCard card.title.setText(channel + s"@${DateFormatUtils.format(dt, "yyyy-MM-dd HH:mm:ss")}") card.content.setText(content) Bindings.bindBidirectional(settings.fontProperty, card.content.fontProperty(), new FontStringConverter()) card }
card.content是一个Label,所以它的fontProperty()就是Font类型的ObjectProperty, 因为与Settings中的fontProperty(SimpleStringProperty类型)类型不同,所以我们使用了Bindings.bindBidirectional配合FontStringConverter完成了设定与应用组件之间的状态绑定。
当然,这种方法不一定是最好或者最符合PreferencesFX设计哲学的方式, 但却是最符合我编码胃口的方式,起码SimpleFontControl和FontStringConverter把通用逻辑都封装的差不多了。
