bekwam courses

TornadoFX Tableview Styling

July 24, 2018

Left unstyled the default TableView from TornadoFX is pretty dull. It has a monotone color scheme, and can often be improperly sized for the data it holds. With the user experience being one of the most important parts of any application, allowing the TableView to remain so unfriendly is unacceptable. In this demo, I will cover ways to style a TableView in TornadoFX.

In TornadoFX, CSS can be applied directly to a node using style{}. While this is an option, I use a stylesheet because it makes code easier to read and update. I also cover certain non-CSS aspects of TableView that are still stylistically important.

Unstyled TableView

Screenshot of an unstyled TornadoFX TableView

Styled TableView

Screenshot of final Styled Table

Inside of the table an ObservableList of Students is displayed. The code used to create this view as well as the Student class are shown below.


class MyApp : App(MyView::class,MyStyles::class){
   override fun createPrimaryScene(view: UIComponent) = Scene(view.root, 1000.0,500.0)
}
class MyView : View() {
    val studentList = listOf<Student>(
            Student("Robert", 19, 3.5, 2020),
            Student("Carl", 47, 3.67, 2022),
            Student("James", 20, 2.00, 2023),
            Student("Henry", 24,3.46,2030),
            Student("Larry", 17,3.33,2020)
    ).observable()

    override val root = vbox {
        label("Student List Table:")
        tableview(studentList) {
            column("Name", Student::nameProperty)
            column("Age", Student::ageProperty)
            column("GPA", Student::gpaProperty)
            column("Graduation Year", Student::gradProperty)

            columnResizePolicy = TableView.CONSTRAINED_RESIZE_POLICY
            
           vboxConstraints {
                vGrow = Priority.ALWAYS
            }
        }
        
        addClass(MyStyles.table)
    }

}

class Student(
        val name : String,
        val age : Int,
        val gpa : Double,
        val graduationYear : Int
){
        val nameProperty = SimpleStringProperty(name)
        val ageProperty = SimpleIntegerProperty(age)
        val gpaProperty = SimpleDoubleProperty(gpa)
        val gradProperty = SimpleIntegerProperty(graduationYear)
}
	

Stylesheets

Below is the Stylesheet used to style my TableView:

	
	class MyStyles : Stylesheet() {
    val cellBorderColor = rgb(168,214,205)
    val oddCellColor = rgb(105,132,173)
    val highlightColor = rgb(244,244,244)
    val textColor = rgb(89,89,89)
    val evenCellColor = rgb(168,199,214)
    
	companion object {
        val table by cssclass()
    }

    init {
        label{
            backgroundColor += Color.LIGHTGRAY
            font = Font.font("Times New Roman")
            fontSize = 24.px
        }
        table {
            tableView {
                tableCell {
                    borderColor += box(textColor)
                }
                tableRowCell {
                    and (odd){
                        backgroundColor += oddCellColor
                        and(hover){
                            backgroundColor += highlightColor
                        }
                    }
                    and (even){
                        and(hover){
                            backgroundColor += highlightColor
                        }
                        backgroundColor+=evenCellColor
                    }
                }
                tableColumn {

                    label {
                        backgroundColor += oddCellColor
                    }
                }
                fixedCellSize = 36.px
                fontSize = 32.px
                        font = Font.font("Times New Roman")


            }
            backgroundColor += Color.LIGHTGRAY

        }

    }
}
	
	

At the very top of my stylesheet class I start by creating vals that hold the colors I will be using to style my table. I do this in case I ever need to change the color scheme of my code. Storing the color in a val means that I would only have to change the val to change every instance of that color in my program. It is not necessary, but could potentially save a lot of time.

The companion object is where I create my CSS classes, in this case table. My program is a pretty basic single view, so I could skip this step and create a stylesheet with one default style, but I choose not to. In any app where multiple different styles are needed this step would be required. After creating the class I add it to the TableView node with this line of code: addClass(MyStyles.table).

The Init {} Block

Inside of the Init {} block is where I add the CSS. There are three parts of the TableView that I add CSS to, the tableCell, the tableRowCell, and the tableColumn.

tableCell:

TableCell

tableCell

Adding CSS to the tableCell applies styles to every individual cell in the table. In my example I define my border color inside of the tableCell{} block to give each cell a defined border. TableCell is also a good place to change the font, fontSize, and textFill because doing so would create a consistent style throughout the table.

tableRowCell:

TableRowCell

TableRowCell

The rows of a TableView are cells that contain one cell for every column of the table. Styles added in the tableRowCell block apply styles to the group of cells contained inside of this outer cell. In my example, I use psuedo-classes odd and even to alternate the background color of each row. Inside of each of the psuedo classes I also add the psuedo-class hover to change the background color of a row when moused over. This is shown in the image below.

Highlighted Row

Highlighted Row

tableColumn:

tableColumn

tableColumn

Adding CSS to tableColumn changes the header of a tableView. Each column contains a label located at the top. To make changes to this label, add a label class inside of the tableColumn class.

Non-CSS TableView Styling

Some of the cosmetic features of my TableView do not come from CSS Styling. for example, one of the most glaring differences between my styled tableview and the default is the extra whitespace provided by the default TableView. To eliminate this extra column I added the columnResizePolicy in the Tableview node. This is not part of the CSS styling, but is still important for the appearence of the tableview.

	
	tableview(studentList) {
            column("Name", Student::nameProperty)
            column("Age", Student::ageProperty)
            column("GPA", Student::gpaProperty)
            column("Graduation Year", Student::gradProperty)

            columnResizePolicy = TableView.CONSTRAINED_RESIZE_POLICY
			
            prefWidth = 1000.0
            prefHeight = 500.0
       
           vboxConstraints {
                vGrow = Priority.ALWAYS
            }
        }
	
	

The Tableview node is also where I give my Tableview permission to take up all the available space (this is done inside the vboxConstraints{}). To limit the space the Tableview is allowed to use a prefWidth and prefHeight can be set. I left an example of this in my code above, however, vboxConstraints{} take priority, and those two lines of code do nothing where they are now.


By Rob Walker

Software Engineer at Bekwam, Inc