8

I am currently using a function to display a pandas dataframe in a spreadsheet style format. I would like to be able to add some functionality to format individual cells of the treeview based on their content e.g. if they contain substring 'X' or if their value is higher than Y.

The update function currently implemented is as follows:

   def updateTree(self, dataframe):
    '''
    Updates the treeview with the data in the dataframe
    parameter
    '''
    #Remove any nan values which may have appeared in the dataframe parameter
    df = dataframe.replace(np.nan,'', regex=True)

    #Currently displayed data
    self.treesubsetdata = dataframe

    #Remove existing items
    for item in self.tree.get_children(): self.tree.delete(item)
    #Recreate from scratch the columns based on the passed dataframe
    self.tree.config(columns= [])
    self.tree.config(columns= list(dataframe.columns))

    #Ensure all columns are considered strings and write column headers
    for col in dataframe.columns:
        self.tree.heading(col,text=str(col))

    #Get number of rows and columns in the imported script
    self.rows,self.cols = dataframe.shape

    #Populate data in the treeview
    for row in dataframe.itertuples():
        self.tree.insert('', 'end',values = tuple(row[1:]))

    #Minimise first column
    self.tree.column('#0',width=0)
    self.tree.update()

Can anyone confirm that you can in fact edit an individual cell in a treview?

If yes are there any ideas as to how this could be implemented?

Ivan Kolesnikov
  • 1,787
  • 1
  • 29
  • 45
user3535074
  • 1,268
  • 8
  • 26
  • 48
  • I don't think that's possible. Is there any reason you don't want to make a simple grid of Label or Entry widgets instead? – Novel Mar 15 '17 at 18:33
  • I use a custom class to to create, display, add scrollbars, update and perform complex sort and filters using bound keys on the displayed data. I'm not sure I can rework the framework at this point. If it isn't possible I guess I'll have to reconsider the functionality. – user3535074 Mar 15 '17 at 22:49

2 Answers2

1

It's not possible to set the style of individual cells in Treeview; only entire rows can use the tag attribute.

If you just want a table of values then I'd recommend just using ttk.Label widgets, which you can format in a huge number of ways. For example:

import Tkinter as tk
import ttk
import pandas as pd
from random import randrange

PADDING = dict(padx=3, pady=3)
class GridView(tk.Frame):
    def __init__(self, master=None, **kwargs):
        tk.Frame.__init__(self, master, **kwargs)
        self.labels = []
        style = ttk.Style()
        style.configure("red.TLabel", background='red')
        style.configure("green.TLabel", background='green')
        style.configure("header.TLabel", font = '-weight bold')

    def set(self, df):
        self.clear()
        for col, name in enumerate(df.columns):
            lbl = ttk.Label(self, text=name, style='header.TLabel')
            lbl.grid(row=0, column=col, **PADDING)
            self.labels.append(lbl)

        for row, values in enumerate(df.itertuples(), 1):
            for col, value in enumerate(values[1:]):
                lbl = ttk.Label(self, text=value, style=self.get_style(value))
                lbl.grid(row=row, column=col, **PADDING)
                self.labels.append(lbl)

    @staticmethod
    def get_style(value):
        if value > 70:
            return "red.TLabel"
        elif value < 30:
            return "green.TLabel"
        else:
            return None

    def clear(self):
        for lbl in self.labels:
            lbl.grid_forget()
        self.labels = []

class GUI(tk.Frame):
    def __init__(self, master=None, **kwargs):
        tk.Frame.__init__(self, master, **kwargs)
        self.table = GridView(self)
        self.table.pack()
        btn = ttk.Button(self, text="populate", command=self.populate)
        btn.pack()
        btn = ttk.Button(self, text="clear", command=self.table.clear)
        btn.pack()

    def populate(self):
        self.table.set(new_rand_df())

def main():
    root = tk.Tk()
    win = GUI(root)
    win.pack()
    root.mainloop()

def new_rand_df():
    width = 5
    height = 5
    return pd.DataFrame([[randrange(100) for _ in range(width)] for _ in range(height)], columns = list('abcdefghijklmnopqrstuvwxyz'[:width]))

if __name__ == '__main__':
    main()
Novel
  • 13,406
  • 2
  • 25
  • 41
  • It seems it's impossible to do what I wanted in the traditional ttk.Treeview so I accept Jonathan's answer for proposing an alternative solution in detail. – user3535074 Mar 23 '17 at 11:13
1

this is possible using a custom cellrenderer in Gtk3, e.g.:

import gi
gi.require_version( 'Gtk', '3.0' )
from gi.repository import Gtk, GObject


class My_CellRendererText( Gtk.CellRendererText ):
    def __init__( self ):
        super().__init__()

    def do_render( self, cr, widget, background_area, cell_area, flags ):
        cell_text = self.props.text
        self.props.underline = ( "X" in cell_text )
        self.props.weight = 700 if ( cell_text.isdigit() and int( cell_text ) > 3 ) else 400
        return Gtk.CellRendererText.do_render( self, cr, widget, background_area, cell_area, flags )

GObject.type_register( My_CellRendererText )
  • Is this possible to use with the ttk Treeview? Or would I need to build the GUI from scratch using Gtk? – user3535074 Mar 19 '17 at 11:39
  • yes this cellrenderer requires a Gtk.Treeview. i don't know if it can also be made to work with ttk, but i think not. –  Mar 19 '17 at 18:16