The absolute beginners guide - Part IX - for gnu-smalltalk/gtk (using GtkTreeView as a table)

Today I want to show, how to use the GtkTreeView as a table.

This could be the killer-app (an emergency-case address-database for our local police-station) where we end with today:

2379000.png

As usual, the instance variables:

| window container menubar scroll list |

The window, the container, where we put all the elements in and the menubar and eventually the list.

We have our usual initialize method as:

initialize [
    
    self createWindow;
         createContainer;
         createMenubar;
         createList.

    container packStart: menubar expand: false fill: true padding: 3;
              packStart: scroll expand: true fill: true padding: 3.
    window add: container.
]

And now the first interesting part, the creation of the table/list. (The second interesting part will be the callback-method for selection...) This is somehow a bit like the creation of the multicolumn ComboBox. We use the same model-type, the GTK.GtkListStore and we have to provide a CellRenderer for each column we want to display etc.

But first, we create a scroll-area, because our list could be greater than the size of the window and we would like to scroll into each edge of the table, with:

scroll := GTK.GtkScrolledWindow new: nil vadjustment: nil.
scroll setPolicy: GTK.Gtk gtkPolicyAutomatic vscrollbarPolicy: GTK.Gtk gtkPolicyAlways.

First, we created an instance of the ScrolledWindow and then, we set the policy, when the scrollbars should be painted. Always, automatically (if the table will be greater than the current size of the scrollarea), or never. You could provide the following variables:

  • GTK.Gtk gtkPolicyNever
  • GTK.Gtk gtkPolicyAutomatic
  • GTK.Gtk gtkPolicyAlways

Than we should create the TreeView, connect a callback-method for the signal 'changed' to it and pack it into the scrollarea:

list := GTK.GtkTreeView new.
list getSelection connectSignal: 'changed' to: self selector: #listChanged.
scroll add: list.

Now, for each column of the table, we have to provide the CellRenderer, create a new TreeViewColumn, set the title for the column and pack it into the table. For each column, the source-code looks like:

rend := GTK.GtkCellRendererText new.
col := GTK.GtkTreeViewColumn new.
col setTitle: 'Column X'.
col packStart: rend expand: true.
col addAttribute: rend attribute: 'text' column: (i -1).
list insertColumn: col position: -1

Then the model for the list should be created (like in the post about the ComboBox, but in this case, we want to have 4 columns, one integer and 3 string-types), set to the view and filled with data.

The fill-up of the modell is exactly the same as in the ComboBox example, but with some additional columns.

In the code example below, I used for the header of the table an array with names, over which I did an iteration to fill the CellRenderer and the same for the model-data (in this case an array of arrays). Just to shorten the resulting source-code.

createList [
    | listModel iterator col rend header data |

    scroll := GTK.GtkScrolledWindow new: nil vadjustment: nil.
    scroll setPolicy: GTK.Gtk gtkPolicyAutomatic vscrollbarPolicy: GTK.Gtk gtkPolicyAlways.

    list := GTK.GtkTreeView new.
    list getSelection connectSignal: 'changed' to: self selector: #listChanged.
    scroll add: list.

    header := #('No.' 'First' 'Last' 'eMail').
    header keysAndValuesDo: [ :i :val |
        rend := GTK.GtkCellRendererText new.
        col := GTK.GtkTreeViewColumn new.
        col setTitle: val;
            packStart: rend expand: true;
            addAttribute: rend attribute: 'text' column: (i -1).
        list insertColumn: col position: -1
    ].

    listModel := GTK.GtkListStore new: 4
        varargs: {
            GTK.GValue gTypeInt.
            GTK.GValue gTypeString.
            GTK.GValue gTypeString.
            GTK.GValue gTypeString
        }.
    list setModel: listModel.

    data := #(
        #('Bat' 'Man' 'betty@example.com')
        #('Super' 'Man' 'manny@example.com')
        #('The' 'Flash' 'flashy@example.com')
        #('The' 'Hulk' 'hulky@example.com')
        #('Spider' 'Man' 'spidy@example.com')
        #('Wonder' 'Woman' 'wow@example.com')
        #('Dare' 'Devil' 'daemon@example.com')
        #('Cat' 'Woman' 'meow@example.com')
    ).

    data keysAndValuesDo: [:i :v |
        iterator := GTK.GtkTreeIter new.
        listModel append: iterator;
        setOop: iterator column: 0 value: i.
        v keysAndValuesDo: [:index :string |
            listModel setOop: iterator column: index value: string.
        ]
    ]
]

Now, let's have a look into the connected callback-method for the change of the table-selection:

listChanged [
    | rowContent iterator model result |
    
    'listChanged' printNl.

    model := list getModel.
    iterator := GTK.GtkTreeIter type new.
    (list getSelection getSelected: nil iter: iterator)
        ifTrue: [
            result := model getOop: iterator column: 0.
            ('clicked at No.: ', result printString) printNl.

            "Just to get the whole data"
            rowContent := Array
                with: (model getOop: iterator column: 1)
                with: (model getOop: iterator column: 2)
                with: (model getOop: iterator column: 3).
            ('complete Data: ', rowContent printString) printNl
        ].
]

Because I haven't saved the model into an instance-variable (which could be done of course), I have to get the data from the table-component, which is called list in this example. This is achieved with getModel ;-)

Than, we have to get the data of the selected row through it's iterator. (Remember, for filling the data into the model, each row is inserted with a separate iterator, and this iterator is it, we get now back from the selection.)

And through this iterator, we have access to the complete data of the selected row.

That's it again!

Happy coding!
Joachim.

User login