Kotlin 与 JavaFX - ListView css 类也适用于空行

标签 kotlin listview javafx

我正在使用 JavaFX 开发一个 Kotlin 应用程序,并且我已经制作了“自定义 ListView”的包装器。操作大致如下:

ListView 显示应用程序启动时加载的客户端列表。在众多选项中,用户有两个相关的选项:启用和禁用所选客户端。禁用的客户端通过外部文件中的特殊 css 样式突出显示。

我遇到的问题是,当我禁用客户端时,CSS 样式不仅适用于该客户端,而且还适用于额外的空行之一,并且当我按下按钮重新启用相同的客户,我必须做两次。 (一次取消空行的样式,一次取消正确行的样式)。

该应用程序非常大,并从数据库中获取每个客户端的数据,但我简化了代码以在此处显示它。

测试应用程序.kt:

class TestApplication : Application() {

    override fun start(stage: Stage) {

        val fxmlLoader = FXMLLoader(TestApplication::class.java.getResource("test-view.fxml"))
        val scene = Scene(fxmlLoader.load())
        stage.title = "Test application"
        stage.scene = scene
        scene.stylesheets.add(this::class.java.getResource("custom.css").toExternalForm())

        stage.show()
    }
}

fun main() {
    Application.launch(TestApplication::class.java)
}

文件 TestController.kt:


class TestController: Initializable {

    private lateinit var objListViewWrapper: ListViewWrapper

    // This MutableList is sent to a model (not included here) to update the customers database table:
    private var listCustomers: MutableList<MutableMap<String, String>> = getCustomers()

    @FXML
    lateinit var lvCustomers: ListView<CustomerData>

    override fun initialize(p0: URL?, p1: ResourceBundle?) {
        objListViewWrapper = ListViewWrapper(lvCustomers, "id", "text")

        generateListView()
    }

    private fun generateListView(){
        objListViewWrapper.clearListView()
        objListViewWrapper.populateListView(listCustomers)
    }

    private fun updateListViewContent(){
        listCustomers.forEach { customer ->

            val id = customer.getValue("id")
            val active = customer.getValue("active").toInt()

            lvCustomers.items.forEach { lvCustomer ->
                if(lvCustomer.getId() == id){
                    lvCustomer.setActive(active)
                }
            }

        }

        objListViewWrapper.clearSelection()

    }

    private fun getCustomers(): MutableList<MutableMap<String, String>>{
        var li: MutableList<MutableMap<String, String>> = mutableListOf()

        val listNames = listOf(
            "John Doe", "Jan Jansen",
            "Osman Whitfield", "Jane Smith",
            "Joe Bloggs", "Michalina Cottrell"
        )

        listNames.forEachIndexed { index, name ->
            val mp: MutableMap<String, String> = mutableMapOf()

            mp["id"] = (1 + index).toString()
            mp["text"] = name.uppercase()
            mp["active"] = "1"

            li.add(mp)
        }

        return li
    }

    @FXML
    fun disableCustomer(){
        val selectedItem = objListViewWrapper.getSelectedItem()

        if(selectedItem == null){
            println("Error: You must select a customer.")
            return
        }

        val idCustomer = selectedItem.getId()

        listCustomers.forEach { mutableMap ->
            if(idCustomer == mutableMap.getValue("id")){
                mutableMap["active"] = "0"
            }
        }

        updateListViewContent()
    }

    @FXML
    fun enableCustomer(){
        val selectedItem = objListViewWrapper.getSelectedItem()

        if(selectedItem == null){
            println("Error: You must select a customer.")
            return
        }

        val idCustomer = selectedItem.getId()

        listCustomers.forEach { mutableMap ->
            if(idCustomer == mutableMap.getValue("id")){
                mutableMap["active"] = "1"
            }
        }

        updateListViewContent()
    }
    
}


文件ListViewWrapper.kt:


class CustomerData(id: String, text: String, active: Int) {

    var id: SimpleStringProperty = SimpleStringProperty(id)
    var text: SimpleStringProperty = SimpleStringProperty(text)
    var active: SimpleIntegerProperty = SimpleIntegerProperty(active)

    fun getId(): String {
        return this.id.get()
    }

    fun setId(id: String) {
        this.id.set(id)
    }

    fun idProperty(): SimpleStringProperty {
        return id
    }

    fun getText(): String {
        return this.text.get()
    }

    fun setText(text: String) {
        this.text.set(text)
    }

    fun textProperty(): SimpleStringProperty {
        return text
    }

    fun getActive(): Int {
        return this.active.get()
    }

    fun setActive(active: Int) {
        this.active.set(active)
    }

    fun activeProperty(): SimpleIntegerProperty {
        return active
    }

}


class ListViewWrapper(listView: ListView<CustomerData>, idField: String = "id", textField: String = "text") {

    private var listView = listView
    private var idField = idField
    private var textField = textField

    private var obsList: ObservableList<CustomerData> = FXCollections.observableArrayList()
    private val cssDisabledRow = "row-disabled"

    init {

        listView.setCellFactory { lv ->
            object : ListCell<CustomerData?>() {

                override fun updateItem(obj: CustomerData?, empty: Boolean) {
                    super.updateItem(obj, empty)

                    if (empty || obj == null) {
                        text = null
                        style = null
                        graphic = null

                    } else if(obj.getActive() == 0) {
                        text = obj.getText()

                        if(!styleClass.contains(cssDisabledRow)){
                            styleClass.add(cssDisabledRow)
                        }

                    } else {
                        text = obj.getText()

                        if(styleClass.contains(cssDisabledRow)){
                            styleClass.remove(cssDisabledRow)
                        }
                    }
                }

            }
        }
    }

    fun clearListView(){
        obsList.clear()
    }

    fun clearSelection(){
        listView.selectionModel.clearSelection()
    }

    fun populateListView(li: MutableList<MutableMap<String, String>>){
        if(li.isEmpty()){
            println("The list of items is empty.")
            return
        }

        li.forEach {
            val id = it.getValue(idField)
            val text = it.getValue(textField)
            val active = it.getValue("active").toInt()

            obsList.add(CustomerData(id, text, active))

        }

        listView.items.setAll(obsList)

    }

    fun getSelectedItem(): CustomerData? {
        return listView.selectionModel.selectedItem
    }

}

文件 test-view.fxml:


<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>

<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="350.0" prefWidth="400.0" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.example.testproject.TestController">
   <padding>
      <Insets bottom="4.0" left="4.0" right="4.0" top="4.0" />
   </padding>
   <children>
      <ListView fx:id="lvCustomers" layoutX="48.0" layoutY="42.0" prefHeight="200.0" prefWidth="200.0" AnchorPane.bottomAnchor="36.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
      <HBox alignment="CENTER" layoutX="14.0" layoutY="250.0" prefHeight="32.0" spacing="4.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0">
         <children>
            <Button fx:id="btnDisable" mnemonicParsing="false" onAction="#disableCustomer" prefWidth="100.0" text="Disable" />
            <Button fx:id="btnEnable" mnemonicParsing="false" onAction="#enableCustomer" prefWidth="100.0" text="Enable" />
         </children>
      </HBox>
   </children>
</AnchorPane>

文件custom.css:


.list-cell:filled:hover {
    -fx-background-color: #0093ff;
    -fx-text-fill: white;
}

.row-disabled{
    -fx-background-color: #c2c2c2 !important;
    -fx-text-fill: red;
}

.list-cell:filled:selected:focused, .list-cell:filled:selected {
    -fx-background-color: linear-gradient(#328BDB 0%, #207BCF 25%, #1973C9 75%, #0A65BF 100%);
    -fx-text-fill: white;
}

截图:

disabled rows in dark gray so that they are more noticeable here

编辑:

感谢jewelsea的评论我已经意识到如何使用 ListView。 ListView 不需要重新生成,它只需要更新“active”属性。我已经编辑了代码并进行了修改以使其正常工作。我知道代码很长,对于那些试图找到问题的人来说可能很麻烦,但我认为这会消除新手的任何疑虑。

最佳答案

我将回答我的问题并借此机会澄清我是如何解决它的:

正如我所说,问题在于我正在重新生成包含客户数据的“ObservableList”,而不是仅仅更新“active”属性。

在代码的前面,所做的是使用generateListView()方法从ListView中删除项目并在每次禁用或启用客户端时再次重新填充它。为了解决这个问题,我创建了 updateListViewContent() 方法。如您所见,此方法仅更新“active”属性,并采用 listCustomers 内分配的值。

private fun updateListViewContent(){
    listCustomers.forEach { customer ->

        val id = customer.getValue("id")
        val active = customer.getValue("active").toInt()

        lvCustomers.items.forEach { lvCustomer ->
            if(lvCustomer.getId() == id){
                lvCustomer.setActive(active)
            }
        }

    }

    objListViewWrapper.clearSelection()
}

listCustomers 是包含每个客户数据的映射的列表,lvCustomers 是包含 CustomerData 类型的项目的 ListView。如果已禁用或启用的客户端 ID 与 ListView 中存在的 ID 之一匹配,则更新 active 属性。

可以通过单击按钮直接更改事件属性。此示例实际上并不需要 listCustomers。这是我在原始代码中用来存储额外数据的变量,这些数据不属于此ListView,并且以“逐步”方式呈现的表单中收集。

关于Kotlin 与 JavaFX - ListView css 类也适用于空行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70715790/

相关文章:

java - 在 Android 上使用 SQLDelight JVM 驱动程序

android - 选择选项卡更改 ListView 项目重复

android - 将 Android ListView "categories"贴到顶部

java - 单击 JavaFX ListView 中的任意位置将返回索引 -1 并崩溃

java - 为什么这个 CSS 属性在 JavaFX CSS 引用指南中没有详细说明?

android - Retrofit2 GET批注动态网址

android - 如果仅在Kotlin中首次执行主要 Activity 时才出现对话框,该怎么办?

安卓 11 : Save a file that can other apps can access

Android:通过 SimpleAdapter 在 imageview 中显示图像

xml - 即时动态构建 JavaFX UI