Reputation: 25
Description
I want to make a custom "Table Widget"
for Tkinter and so far it's almost as I want.
The problem I have is that I want the table to consist of "Labels"
, I have only found that you have to use "StringVar()'s"
in order to update the labels.
I dont really want 1000 variables for 1000 labels so I have tried to solve this issue for a week now. The only solution I've found is to use "Dicts"
but I dont think this is a good way to do it? I might be wrong but it feels like it has to be a way to do something like this:
myLabel = Label(root, text='myText')
myLabel.pack()
myLabel.setText('new text')
The current widget code looks like this (This is my first custom widget so if this is horribly wrong or wrong in any way, please tell me)
Source
"""
Description:
A TABLE WIDGET Extension for Tkinter
"""
try:
#Python 3.X
import tkinter #this is quite annoyng, without it (tkinter) wont be available....
from tkinter import * # even with this
from tkinter import ttk
from tkinter.ttk import *
except ImportError:
#python 2.X
import tkinter
from Tkinter import *
from Tkinter import ttk
from Tkinter.ttk import *
class ETable(tkinter.Frame):
def __init__(self, parent, *args, **kwargs):
tkinter.Frame.__init__(self, parent)
self.init_table(parent, *args, **kwargs)
def init_table(self, parent, *args, **kwargs):
row_settings = {
'anchor': CENTER,
'background': '#C5FFFF',
'bitmap': None,
'borderwidth': 5,
'compound': None,
'cursor': None,
'font': None,
'foreground': '#000000',
'image': None,
'relief': FLAT,
'rows': 1,
'state': NORMAL,
'text': None,
'textvariable': None,
'underline': -1,
'width': 20,
}
for kwarg in kwargs:
if kwarg in row_settings:
row_settings[kwarg] = kwargs[kwarg]
self.table_rows = self.init_row(parent, *args, **row_settings)
def init_row(self, parent, *args, **kwargs):
text_list = kwargs.pop('text')
row_list = []
cols = []
rows = kwargs.pop('rows')
for row in range(rows):
for col in range(len(text_list)):
tempLabel = Label(parent, text = text_list[col], *args, **kwargs)
tempLabel.grid(row=row, column=col)
cols.append(tempLabel)
row_list.append(cols)
return row_list
Goal
I want to be able to do something like this
table.getRow[0][0].setText('new text') #This would change col 1 in row 1 to "new text"
Bonus Question
(please tell me if I should make a new question for this)
As I said, I want to make a "Table Widget"
for Tkinter but I also want to add behaviours to the table, IE when a user clicks on a row, I want the row to expand "Downwards"
much like a "Dropdown Menu"
but it will only close when it looses focus, the goal here is to add "Entry or Text boxes"
in order to edit the columns.
The Question here is, is this possible with Tkinter?
Upvotes: 0
Views: 2549
Reputation: 55469
As Bryan said, you don't need to use a StringVar as a Label's textvariable
option to change its text. It can be more convenient, though, especially if you want to be able to read the current text string from the Label. FWIW, you can read the options of a Tkinter widget by calling its .config()
method with no argument, which returns a dictionary of all current option values. But I must admit that's a bit messy if you only want to inspect a single value.
If you do want to use StringVars you don't need to keep a separate list or dict of them: you can attach a StringVar as an attribute to the Label object. Just be careful that you don't clobber any of the Label's existing attributes. :)
I've modified your code a little bit. It was quite good, although you did have a little bug in the init_row
method with your cols
list. Your code just kept adding entries to that list and then copying it to the row_list
. So the one cols
list ends up containing every Label from every row, and the row_list
ends up with multiple references to that one cols
list.
One thing I need to mention: please avoid doing "star" imports. When you do
from tkinter import *
it puts 135 Tkinter names into your namespace; in Python 2 you get 175 names. This creates needless clutter in the namespace and it can cause name collisions: if you accidentally name one of your variables with one of the imported names that can lead to mysterious bugs. It's even worse when you do star imports with multiple modules since they can stomp over each others' names. Also, star imports make the code harder to read since you have to remember which names are defined locally and which are imported.
There are a couple of other things I changed in your code.
The way you updated row_settings
with kwargs
is fine, but it's a little more compact and more efficient to do it with a generator expression and the dict.update
method.
I pass the number of columns explicitly, rather than calculating it from the length of the text list. And I save both the number of columns and rows as instance attributes of the class, since they may come in handy.
I've added a __getitem__
method to your class to make it easier to access the rows of Labels.
I add a set_text
method to each Label as its created to make it simple to change its text. The syntax isn't identical to what you have in the question, but I think you'll like it. :)
I've added some code to create a main Tkinter window to test your ETable
class and call the set_text
method; I've also put a Button in that window.The Button callback first prints out the config
dictionary of a Label and then it updates its text.
For future reference, it's a Good Idea to post a MCVE, especially with Tkinter questions, so that people who want to answer can run and test your code without having to write a bunch of stuff first.
This code has been tested on Python 2.6.6 and Python 3.6.0a0.
"""
Description:
A TABLE WIDGET Extension for Tkinter
Written by Estrobeda, with modifications by PM 2Ring
2016.05.30
"""
from __future__ import print_function
try:
# Python2
import Tkinter as tk
except ImportError:
# Python3
import tkinter as tk
class ETable(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent)
self.init_table(parent, *args, **kwargs)
def init_table(self, parent, *args, **kwargs):
row_settings = {
'anchor': 'center',
'background': '#C5FFFF',
'bitmap': None,
'borderwidth': 5,
'cols': 1,
'compound': None,
'cursor': None,
'font': None,
'foreground': '#000000',
'image': None,
'relief': 'flat',
'rows': 1,
'state': 'normal',
'texts': None,
'textvariable': None,
'underline': -1,
'width': 20,
}
# Update row_settings with valid entries in kwargs
row_settings.update((k, v) for k, v in kwargs.items() if k in row_settings)
self.rows = row_settings.pop('rows')
self.cols = row_settings.pop('cols')
self.table_rows = self.init_rows(parent, *args, **row_settings)
def init_rows(self, parent, *args, **kwargs):
texts = iter(kwargs.pop('texts'))
row_list = []
for row in range(self.rows):
col_list = []
for col in range(self.cols):
tempLabel = tk.Label(parent, text=next(texts), *args, **kwargs)
#Bind a function to the label to change its text
tempLabel.set_text = lambda s, w=tempLabel: w.config(text=s)
tempLabel.grid(row=row, column=col)
col_list.append(tempLabel)
row_list.append(col_list)
return row_list
def __getitem__(self, row):
return self.table_rows[row]
# Test
root = tk.Tk()
root.title("LabelTable Demo")
texts = list('abcdefghijkl')
table = ETable(root, rows=3, cols=4, texts=texts, relief='ridge', width=10)
# Change the text of a label
table[0][1].set_text('new text')
def change_text():
widget = table[2][3]
# Display the widget's current configuration
cfg = widget.config()
for k in sorted(cfg):
print(k, '=', cfg[k])
# Change the text
widget.set_text('hello')
b = tk.Button(root, text='Change', command=change_text)
b.grid(row=4, column=0)
root.mainloop()
Upvotes: 0
Reputation: 385970
I have only found that you have to use "StringVar()'s" in order to update the labels.
This is not a true statement. You can update the attributes of any widget with the config
/ configure
method. Starting with your first example, it would look like this:
myLabel = Label(root, text='myText')
myLabel.pack()
myLabel.configure(text='new text')
And, in your final example:
table.getRow[0][0].configure(text='new text')
As for the bonus question, yes, it's possible.You can bind any code you want to a label just as you can any other widget, and in addition to bindings on keys and mouse buttons, you can bind on gaining and losing focus.
Upvotes: 2