1

I have been using the code found here:

How to limit number of decimal places to be displayed in Gtk CellRendererText

successfully for several years to format Treeview number columns. But when I insert columns using a loop, the columns display the data from the first column, rather than the data I would expect to get from the ListStore. Why is this? I've struggled with this for sometime and it probably is a really simple solution, but I am rather clueless!! Thank you very much. Here's a working example that shows my problem:

from gi.repository import Gtk, Gdk
class myClass:
    def __init__(self):
    # Setup
        self.iListstore = Gtk.ListStore(str, float, float, float, float)
        self.iListstore.append(['abc',209.8967,568.56432, 1, 2])
        self.iListstore.append(['def',2409.846,559.534, 3, 4])
        self.window = Gtk.Window()
        self.iTreeView = Gtk.TreeView(self.iListstore)
    # Column 0
        lblr= Gtk.CellRendererText()
        lcol = Gtk.TreeViewColumn('Row Label')
        self.iTreeView.append_column(lcol)
        lcol.pack_start(lblr, True)
        lcol.add_attribute(lblr, 'text',0)
    # Column 1
        cr = Gtk.CellRendererText(xalign=1)
        myCol = Gtk.TreeViewColumn('Col1')
        myCol.set_sort_column_id(1)
        self.iTreeView.append_column(myCol)
        myCol.pack_start(cr, True)
        myCol.add_attribute(cr, 'text',1)
        myCol.set_cell_data_func(cr,lambda column, cell, model, iter, unused:cell.set_property("text","{0:.2f}".format(round(model.get(iter,1)[0],2))))
    # Column 2
        myCol = Gtk.TreeViewColumn('Col2')
        myCol.set_sort_column_id(2)
        self.iTreeView.append_column(myCol)
        myCol.pack_start(cr, True)
        myCol.add_attribute(cr, 'text',2)
        myCol.set_cell_data_func(cr,lambda column, cell, model, iter, unused:cell.set_property("text","{0:.2f}".format(round(model.get(iter,2)[0],2))))
# The above works but the following does not. Col3 has the same value as Col4. Can someone tell me the reason a loop can not be used with the code?
        colNames=['Col3','Col4']
        for i in range(3,5):
            myCol = Gtk.TreeViewColumn(colNames[i-3]) # I realize this is a bit of a fudge 
            myCol.set_sort_column_id(i)
            self.iTreeView.append_column(myCol)
            myCol.pack_start(cr, True)
            myCol.add_attribute(cr, 'text',i)
            myCol.set_cell_data_func(cr,lambda column, cell, model, iter, unused:cell.set_property("text","{0:.2f}".format(round(model.get(iter,i)[0],2))))
    # Window
            self.window.add(self.iTreeView)
            self.window.show_all()

    def main(self):
        Gtk.main()

    p=myClass()
    p.main()
John
  • 178
  • 10
  • Possible duplicate of [Python Lambda in a loop](https://stackoverflow.com/questions/19837486/python-lambda-in-a-loop) – liberforce Oct 15 '18 at 11:55
  • Slightly better: duplicate of https://stackoverflow.com/questions/7546285/creating-lambda-inside-a-loop – liberforce Oct 15 '18 at 11:59
  • Libeforce Thank you for the solution. This has really helped me understand my error. – John Oct 17 '18 at 16:52

2 Answers2

0

I really tried to get the lambda function going using Libeforce's links (thanks again!) but couldn't resolve it. According to What do (lambda) function closures capture?,"The trick is to remember that loops do not create new scope", so the next column is never changed. I broke the function out and code now works:

from gi.repository import Gtk, Gdk
import  inspect

class myClass:
    def __init__(self):
    # Setup
        self.iListstore = Gtk.ListStore(str, float, float, float, float)
        self.iListstore.append(['abc',209.8967,568.56432, 1, 2])
        self.iListstore.append(['def',2409.846,559.534, 3, 4])
        self.window = Gtk.Window()
        self.iTreeView = Gtk.TreeView(self.iListstore)
        self.window.add(self.iTreeView) # moved to avoid an error of adding TreeView twice to Window
    # Column 0
        lblr= Gtk.CellRendererText()
        lcol = Gtk.TreeViewColumn('Row Label')
        self.iTreeView.append_column(lcol)
        lcol.pack_start(lblr, True)
        lcol.add_attribute(lblr, 'text',0)
    # Column 1
        cr = Gtk.CellRendererText(xalign=1)
        myCol = Gtk.TreeViewColumn('Col1')
        myCol.set_sort_column_id(1)
        self.iTreeView.append_column(myCol)
        myCol.pack_start(cr, True)
        myCol.add_attribute(cr, 'text',1)
        myCol.set_cell_data_func(cr,lambda column, cell, model, iter, unused:cell.set_property("text","{0:.2f}".format(round(model.get(iter,1)[0],2))))
    # Column 2
        myCol = Gtk.TreeViewColumn('Col2')
        myCol.set_sort_column_id(2)
        self.iTreeView.append_column(myCol)
        myCol.pack_start(cr, True)
        myCol.add_attribute(cr, 'text',2)
        myCol.set_cell_data_func(cr,lambda column, cell, model, iter, unused:cell.set_property("text","{0:.2f}".format(round(model.get(iter,2)[0],2))))
# The above works and the following now works. Could not figure out how to use lambda properly so broke the function out into roundCell
        colNames=['Col3','Col4']
        for i in range(3,5):
            myCol = Gtk.TreeViewColumn(colNames[i-3])
            myCol.set_sort_column_id(i)
            self.iTreeView.append_column(myCol)
            myCol.pack_start(cr, True)
            myCol.add_attribute(cr, 'text',i)
            myCol.set_cell_data_func(cr, self.roundCell,i)
    # Window
            self.window.show_all()

    def roundCell(self, col, myCell, mdl, itr,i):
        # col =Columnn, myCell = Cell, mdl = model, itr = inter, i = column number
        # We don't use column, but it is provided by the function
        myCell.set_property("text","{0:.2f}".format(round(mdl.get(itr,i)[0],2)))

    def main(self):
        Gtk.main()

p=myClass()
p.main()
John
  • 178
  • 10
0

Just indeed the set_cell_data_func is a pivotal point for formatting text in a treeviewcolumn cell. I simplified the above answer, all is done in one go via a for loop. For the real formatting you can use the new f-string feature.

import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Gdk

class myClass:
    def __init__(self):
    # Setup
        self.window = Gtk.Window()
        iListstore = Gtk.ListStore(str, float, float, float, float)
        iListstore.append(['abc',209.8967,568.56432, 1, 2])
        iListstore.append(['def',2409.846,559.534, 3, 4])
        self.iTreeView = Gtk.TreeView(model=iListstore)
        self.window.add(self.iTreeView)
        treeview_columns = ['Row Label', 'Col1', 'Col2', 'Col3', 'Col4']
        for col_num, name in enumerate(treeview_columns):
            # align text in column cells of row (0.0 left, 0.5 center, 1.0 right)
            rendererText = Gtk.CellRendererText(xalign=1.0, editable=False)
            column = Gtk.TreeViewColumn(name ,rendererText, text=col_num)
            column.set_cell_data_func(rendererText, self.celldatafunction, func_data=col_num)
            # center the column titles in first row
            column.set_alignment(0.5)
            # make all the column reorderable, resizable and sortable
            column.set_sort_column_id(col_num)
            column.set_reorderable(True)
            column.set_resizable(True)
            self.iTreeView.append_column(column)
    # Window
        self.window.connect("destroy", Gtk.main_quit)
        self.window.show_all()

    def celldatafunction(self, col, cell, mdl, itr, i):
    # col = Columnn, cell = Cell, mdl = model, itr = iter, i = column number
    # column is provided by the function, but not used
        value = mdl.get(itr,i)[0]
        if type(value) is not str:
            cell.set_property('text',f'{value+0.005:.2f}')
        path = mdl.get_path(itr)
        row = path[0]
        colors = ['white', 'lightskyblue']
    # set alternating backgrounds
        cell.set_property('cell-background', colors[row % 2])

    def main(self):
        Gtk.main()

p = myClass()
p.main()
Erich Kuester
  • 460
  • 4
  • 11