制作我自己的按钮 Class

Making My Own Button Class

我正在使用 TornadoFX 框架和 Kotlin 创建一个简单的桌面应用程序,让用户单击一个按钮,select 一个 USFM 文件,然后将该 USFM 文件的内容吐到一个文本区域。我已经掌握了基础知识,但我想通过分离按钮的逻辑来提升我的代码。目前,我的唯一视图定义了单击按钮时要采取的操作的逻辑。代码如下所示:

import tornadofx.*
import javafx.scene.layout.BorderPane
import javafx.stage.FileChooser
import javafx.scene.control.Button
import javafx.scene.control.Label
import javafx.scene.control.TextArea
import java.io.File

class AppView : View() {
    override val root: BorderPane by fxml()

    val chooseFile: Button by fxid<Button>()
    val fileName: Label by fxid<Label>()
    val textArea: TextArea by fxid<TextArea>()
    lateinit var fileChosen: File
    init {
        title = "USFM Viewer"
        root.lookup(".button").setOnMouseClicked {
            var fileFilter = arrayOf(FileChooser.ExtensionFilter("USFM files", "*.usfm"))
            fileChosen = chooseFile("Select a USFM File", fileFilter, FileChooserMode.Single)[0]
            fileName.text = fileChosen.name
            val fileParser = USFMFileParser(fileChosen)
            val printout = fileParser.readFile(fileChosen)
            textArea.text = printout.joinToString()
        }

    }
}

我最初的计划是创建我自己的按钮 class,该按钮继承自按钮 class,并使用自己的方法覆盖或初始化处理点击事件。目标是简单地在我的视图文件中声明按钮,而不必为其定义额外的逻辑。我似乎在努力完成这项工作。

这是我的自定义按钮原型 class:

import javafx.scene.control.Button
import javafx.stage.FileChooser
import tornadofx.*
import java.io.File

class ChooseFileButton: Button(){
    val fileFilter = arrayOf(FileChooser.ExtensionFilter("USFM File", "*.usfm"))
    val files: List<File>
        get() = chooseFile("Select a USFM File", fileFilter, FileChooserMode.Single)
    init {
        action { println(this.files[0].name) }
    }
}

此外,这是我的 FXML 文件,用于提供有关我舞台中元素的更多上下文:

<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.layout.BorderPane?>

<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.layout.FlowPane?>
<BorderPane id="appview"
            xmlns="http://javafx.com/javafx/8.0.40"
            xmlns:fx="http://javafx.com/fxml/1"
            prefHeight="635"
            prefWidth="500">
    <top>
        <FlowPane>
            <Button fx:id="chooseFile" id="chooseFile" text="Choose File..." />
            <Label fx:id="fileName" id="fileName" text="No File Chosen"/>
        </FlowPane>
    </top>
    <center>
        <ScrollPane>
            <TextArea fx:id="textArea" id="textArea"></TextArea>
        </ScrollPane>
    </center>
</BorderPane>

有没有人有关于如何将按钮逻辑与视图分开的提示、建议、链接等?这可能吗?

无需为此创建 Button 子类。只需在 ButtonBase 上创建一个扩展函数,按照您想要的方式配置按钮,然后为每个按钮调用一次该函数。您还应该考虑踢出 FXML 以使用类型安全的构建器,这将使这更加方便,更不用说类型安全了。

这是此类函数的示例,它允许您指定解析器、过滤器并将文件加载到目标字符串中 属性。还有一个默认的解析器,它只按原样加载文件。

fun ButtonBase.loadFile(target: Property<String>,
                        parser: (File) -> String = { Files.readAllBytes(it.toPath()).toString(StandardCharsets.UTF_8) },
                        filters: Array<FileChooser.ExtensionFilter>) {
    action {
        chooseFile(text, filters, FileChooserMode.Single).firstOrNull()?.let {
            target.value = parser.invoke(it)
        }
    }
}

这是一个完整的类型安全构建器示例,使用函数:

class FileChooserTest : View() {
    val fileContent = SimpleStringProperty()

    override val root = borderpane {
        center {
            textarea(fileContent)
        }
        bottom {
            buttonbar {
                button("Select a USFM File") {
                    loadFile(fileContent, filters = arrayOf(FileChooser.ExtensionFilter("USFM File", "*.usfm")))
                }
            }
        }
    }
}

如果你真的想坚持使用 FXML(也许你喜欢只输入东西 :),你可以这样称呼它:

class AppView : View("USFM Viewer") {
    override val root: BorderPane by fxml()

    val chooseFile: Button by fxid<Button>()
    val textArea: TextArea by fxid<TextArea>()

    init {
        chooseFile.loadFile(textArea.textProperty(), filters = arrayOf(FileChooser.ExtensionFilter("USFM File", "*.usfm")))
    }
}

loadFile 功能可以按您喜欢的任何方式扩展,例如通过返回所选文件、为文件选择器对话框分配单独的标题等。

另一个提示:在您的示例代码中,您使用 by fxml() 委托定位了按钮,但随后您又执行了另一个 lookup 来再次定位按钮。您应该重新使用您已经找到的按钮。