change button text, using button name - python

I have an issue, I created a table of buttons using a loop, and I saved the button's names in a list, now I want to change the text of the buttons when one button is clicked.
I don't konw how.
this is the loop where i create the table
def grid(n):
i = n*n
for widget in LeftFrame.winfo_children():
widget.destroy()
for i in range(n):
for row in range(n):
for col in range(n):
button = Button(LeftFrame, text=' ', width=12, height=6, command=lambda: checker(button, i))
button.grid(row=row, column=col)
button_identities.append(button)
and this is the function on click of the button
def checker(buttons, i):
print(i)
global click, button_identities
print(button_identities)
bname = (button_identities[i])
print(bname)
if buttons["text"] == ' ' and click == True:
buttons["text"] = 'X'
click = False
# scorekeeper()
elif buttons['text'] == ' ' and click == False:
buttons['text'] = 'O'
click = True
# scorekeeper()
there is a method to check the text from the button name in the last function

Try this:
from functools import partial
def grid(n):
for widget in LeftFrame.winfo_children():
widget.destroy()
for i in range(n):
for row in range(n):
for col in range(n):
button = Button(LeftFrame, text=' ', width=12, height=6)
# Assign the button a command
command = partial(checker, button)
button.config(command=command)
button.grid(row=row, column=col)
button_identities.append(button)
def checker(button):
# The argument is a button
global click
# If the cell is empty
if button.cget("text") == " ":
# If Xs are you play
if click:
# Change the text of the button
button.config(text="X")
click = False
# If Os are to play
else:
# Change the text of the button
button.config(text="O")
click = True
I usually use <widget>.cget("<parameter>") to get the parameter out of a widget and <widget>.config(parameter=<new value>) to assign it a new value. Also you can use functools.partial to pass in arguments for the button command.

so this is my take on this issue:
from tkinter import Tk, Button
root = Tk()
class EditableButton(Button):
def __init__(self, parent, row, col):
Button.__init__(self, parent)
self.parent = parent
self.row = row
self.col = col
self.tracker = 0
self.name_list = ['apple', 'pear']
print(len(self.name_list))
self.button = Button(self.parent, text=self.name_list[self.tracker], command=self.change_text)
self.button.grid(row=self.row, column=self.col)
def change_text(self):
if self.tracker < len(self.name_list) - 1:
self.tracker += 1
else:
self.tracker = 0
self.button.config(text=self.name_list[self.tracker])
n = 3
for row in range(n):
for col in range(n):
button = EditableButton(root, row, col)
root.mainloop()
basically what happens is You make a class that is like a template for buttons and each class object created by the loop is independent, each of them has their own spot in memory, so whenever You press the button it only calls the function in its own class

Related

How to make a Button()s state continuous?

I have a for loop initiating buttons in a grid, and I want the buttons state to togglable, instead of turning off once the mouse button is no longer held. I also need that to know which button called the buttons' function, because I'm initializing a number of them in the same for loop, meaning they all call the same function once activated, anyone know how to help me?
EDIT:
minimal working example:
import tkinter as gui
def createWindow():
window = gui.Tk()
window.title("GoL")
icon = gui.PhotoImage(file='Not showing ya\'ll my files.png')
window.iconphoto(True, icon)
window.config(background="black")
label = gui.Label(window,\
text="Generation:",\
bg="black",\
fg="white",\
font=("Consolas",20))
label.pack()
return window
def newBoard(x = 10,y = 10):
window = createWindow()
for i in range(0, y):
for j in range(1, x+1):
button = gui.Button(window,bg="black",height=1,width=2,command=changeState)
button.place(x=23*(j-1),y=23*(i+2))
window.mainloop()
what I want is the function changeState to change the
You can achieve this by keeping a list of buttons (buttons), then using a lambda function to pass the row/column number to the button command function, which then allows you to change the properties of the selected button. In this case it toggles between black and white background colours.
def changeState(i, j):
global buttons
b = buttons[i][j]
b.config(bg = "white") if b.cget("bg") != "white" else b.config(bg = "black")
def newBoard(x = 10,y = 10):
global buttons
window = createWindow()
buttons = []
for i in range(0, y):
row = []
for j in range(1, x+1):
button = gui.Button(window,bg="black",height=1,width=2,command=lambda i=i, j=j-1: changeState(i,j))
row.append(button)
button.place(x=23*(j-1),y=23*(i+2))
buttons.append(row)
window.mainloop()

How to see which button is activated / determine right buttons are activated on python tkinter

I'm making a game that finding seven right buttons(It has a fixed answer) among 12 buttons by using tkinter. There are a total of 12 buttons, and only if all seven of them are clicked will win the game.
I'm curious about how to make the window to see which button is activated or inactivated, and how to make a function to determine whether one wins or loses. (when seven specific buttons are activated, it wins.)
++
game1=tkinter.Tk()
game1.title("Test")
game1.geometry("600x450")
button1 = Button(game1, text=' 1 ', fg='black', bg='red',
height=3, width=10)
button1.place(x=0,y=300)
button2 = Button(game1, text=' 2 ', fg='black', bg='red',
height=3, width=10)
button2.place(x=100,y=300)
game1.mainloop()
This is the first time using tkinter on python so actually I stopped after writing this really basic code.
The player can choose seven buttons until player itself clicks the "finish" button. (after click that button, player cannot modify anything.)
At first, I thought if I declare "num" and +=1 on that when the right buttons are clicked, but this trial failed because the player can choose whether one activates or inactivates until until player itself clicks the "finish" button. So I thought that this code needs the way to check the final statements of the buttons.
Is there any ways to use something like "if" statements on tkinter? (if players choose right seven buttons, so it is found that they're activated --> then player wins.)
EDIT - I've updated the code:
import tkinter as tk
import tkinter.ttk as ttk
class Application(tk.Tk):
def __init__(self, *args, **kwargs):
from random import sample
tk.Tk.__init__(self, *args, **kwargs)
self.title("Buttons")
self.resizable(width=False, height=False)
row_count = 3
column_count = 4
self.winning_buttons_count = 7
assert 0 < self.winning_buttons_count <= row_count * column_count
winning_buttons_numbers = sample(range(0, row_count * column_count), k=self.winning_buttons_count)
self.selected_buttons_count = 0
self.selected_button_color = "gray"
self.correct_button_color = "green"
self.incorrect_button_color = "red"
self.missed_button_color = "orange"
self.default_button_color = tk.Button().cget("bg")
def on_press(button):
color = button.cget("bg")
if color == self.default_button_color and self.selected_buttons_count < self.winning_buttons_count:
button.configure(bg=self.selected_button_color)
button.configure(relief=tk.SUNKEN)
self.selected_buttons_count += 1
elif color == self.selected_button_color:
button.configure(bg=self.default_button_color)
button.configure(relief=tk.RAISED)
self.selected_buttons_count -= 1
def check_win():
selected_winning_count = 0
for button in self.buttons:
is_selected = button.cget("bg") == self.selected_button_color and \
button.cget("relief") == tk.SUNKEN
is_winning = button.number in winning_buttons_numbers
if is_selected:
if is_winning:
button.configure(bg=self.correct_button_color)
selected_winning_count += 1
else:
button.configure(bg=self.incorrect_button_color)
else:
if is_winning:
button.configure(bg=self.missed_button_color)
if selected_winning_count == self.winning_buttons_count:
self.finish_button.configure(text="You won!")
else:
self.finish_button.configure(text="Incorrect.")
self.buttons = []
for row in range(row_count):
for column in range(column_count):
button = tk.Button(self, text=" " * 8)
button.grid(row=row, column=column)
button.number = (row * column_count) + column
button.configure(command=lambda b=button: on_press(b))
self.buttons.append(button)
vertical_line = ttk.Separator(self, orient=tk.VERTICAL)
vertical_line.grid(row=0, column=column_count+1, rowspan=row_count, sticky="ns", padx=(8, 8))
self.finish_button = tk.Button(self, text="Did I win?", command=check_win)
self.finish_button.grid(row=row_count//2, column=column_count+2)
def main():
application = Application()
application.mainloop()
return 0
if __name__ == "__main__":
import sys
sys.exit(main())

Tkinter Pressing Buttons to produce a list

I'm trying to make a system where buttons are generated from a list, then as each one is pressed that button disappears and updates a value in a list it has created of equal length in the order the buttons were pressed i.e. [1,3,2,4] would mean that the first button was pressed first, then the 3rd, 2nd etc.
I've currently have it at the stage where the buttons will disappear but I can't yet have it increment its order or produce a list.
def buttonSelected(k, buttonNames, order, itemButton, buttonOrder):
order = order + 1
itemButton[k].destroy()
buttonOrder[k] = order
orderText = ["first, second, third"]
if order <= 2:
lbl.configure(text="Which button do you want to click first?")
else:
lbl.configure(text="Which button do you want to click %sth?" % order)
return order
def chooseOrder(buttonNames, order, buttonOrder):
orderText = ["first, second, third"]
itemButton = []
for i in range(len(buttonNames)):
print i
itemButton.append('')
itemButton[i] = tkinter.Button(window, text=buttonNames[i], command=lambda c=i: buttonSelected(c, buttonNames, order, itemButton, buttonOrder), font=("Helvetica", 10))
itemButton[i].pack()
if order <= 2:
lbl.configure(text="Which button do you want to click first?")
else:
lbl.configure(text="Which button do you want to click %sth?" % order)
buttonOrder = []
for i in range(len(buttonNames)):
buttonOrder.append(0)
chooseOrder(buttonNames, 0, buttonOrder)
My order doesn't iterate because I haven't returned it and I don't know how to return a list either.
This is my complete code:
# import and rename the 'tkinter' module for < Python 3.3
import Tkinter as tkinter
import os
class Checkbar(tkinter.Frame):
def __init__(self, parent=None, picks=[], side=tkinter.TOP, anchor=tkinter.W):
tkinter.Frame.__init__(self, parent)
self.vars = []
for pick in picks:
var = tkinter.IntVar()
chk = tkinter.Checkbutton(self, text=pick, variable=var, font=("Helvetica", 10))
chk.pack(side=side, anchor=anchor, expand=tkinter.YES)
self.vars.append(var)
def state(self):
return map((lambda var: var.get()), self.vars)
def moduleSelected(k, wantedModules, order, itemButton, moduleOrder):
order = order + 1
#for j in range(len(itemButton)):
itemButton[k].destroy()
moduleOrder[k] = order
#chooseOrder(wantedModules, order)
orderText = ["first, second, third"]
if order <= 2:
lbl.configure(text="Which item do you want appearing first?")
else:
lbl.configure(text="Which item do you want appearing %sth?" % order)
if order == len(moduleOrder):
moduleOrder1Finished = 1
return order
def chooseOrder(wantedModules, order, moduleOrder):
orderText = ["first, second, third"]
itemButton = []
for i in range(len(wantedModules)):
print i
itemButton.append('')
itemButton[i] = tkinter.Button(window, text=wantedModules[i], command=lambda c=i: moduleSelected(c, wantedModules, order, itemButton, moduleOrder), font=("Helvetica", 10))
itemButton[i].pack()
print wantedModules
if order <= 2:
lbl.configure(text="Which button do you want appearing first?")
else:
lbl.configure(text="Which button do you want appearing %sth?" % order)
def chooseOrders(moduleList, moduleSelectColumn, order):
wantedModules = []
moduleOrder = []
for i in range(len(moduleList)):
if moduleList[i] == 1:
wantedModules.append(moduleSelectColumn[i])
moduleOrder.append(0)
chooseOrder(wantedModules, order, moduleOrder)
def modulesSelected(modules1, moduleSelectColumn1, modules2, moduleSelectColumn2, moduleSelectButton):
moduleList1 = modules1.state()
moduleList2 = modules2.state()
modules1.destroy()
modules2.destroy()
moduleSelectButton.destroy()
moduleOrder1 = chooseOrders(moduleList1, moduleSelectColumn1, 0)
moduleOrder2 = chooseOrders(moduleList2, moduleSelectColumn2, 0)
def produceSurvey():
moduleSelectColumn = []
for i in range(4):
moduleSelectColumn.append([])
moduleSelectColumn[1] = ['cheese', 'wine', 'bread', 'cereal']
moduleSelectButton = tkinter.Button(window, text="Finish Selecting", bg="#ffffff", font=("Helvetica", 16), command=lambda: modulesSelected(modules1, moduleSelectColumn[1], modules2,moduleSelectColumn[2], moduleSelectButton))
moduleSelectButton.pack(side=tkinter.TOP)
modules1 = Checkbar(window, moduleSelectColumn[1])
modules2 = Checkbar(window, moduleSelectColumn[2])
modules1.pack(side=tkinter.LEFT)
modules2.pack(side=tkinter.RIGHT)
lbl.configure(text="Select which buttons you want to Select?")
window.geometry("720x720")
def analSurvey():
print "analysing survey!"
def optionButton(option, option1, option2):
option1.destroy()
option2.destroy()
if option == 1:
produceSurvey()
elif option == 2:
analSurvey()
def callback_and_hide(button):
callback()
button.destroy()
def callback():
lbl.configure(text="What would you like to do?")
option1 = tkinter.Button(window, text="Help JameswDemps on stackexchange", bg="#ffffff", font=("Helvetica", 16), command=lambda: optionButton(1, option1, option2))
option1.pack()
option2 = tkinter.Button(window, text="go to a blank page", bg="#ffffff", font=("Helvetica", 16), command=lambda: optionButton(2, option1, option2))
option2.pack()
# create a new window
window = tkinter.Tk()
# set window title
window.title("Button Presser")
# Gets the requested values of the height and widht.
windowWidth = window.winfo_reqwidth()
windowHeight = window.winfo_reqheight()
# Gets both half the screen width/height and window width/height
positionRight = int(window.winfo_screenwidth()/2 - windowWidth/2)
positionDown = int(window.winfo_screenheight()/2 - windowHeight/2)
# Positions the window in the center of the page.
window.geometry("+{}+{}".format(positionRight-250, positionDown-150))
window.geometry("720x480")
# set the window background to hex code ...
window.configure(background="#ffffff")
# create a label widget call 'lbl'
lbl = tkinter.Label(window, text="Welcome to the Button presser!", bg="#ffffff", font=("Helvetica", 24))
# creat a text entry widget called 'ent'
#ent = tkinter.Entry(window)
# create a button widget called btn
startBtn = tkinter.Button(window, text="Start", command=lambda: callback_and_hide(startBtn), font=("Helvetica", 16))
# pack (add) the widget into the window
lbl.pack()
#ent.pack()
startBtn.pack()
# draw the window, and start the 'application'
window.mainloop()
RESULT = []
def moduleSelected(k, wantedModules, order, itemButton, moduleOrder):
order = order + 1
#for j in range(len(itemButton)):
itemButton[k].destroy()
moduleOrder[k] = order
print k
RESULT.append(k+1)
print RESULT # -> This will print the output you want.
#chooseOrder(wantedModules, order)
orderText = ["first, second, third"]
if order <= 2:
lbl.configure(text="Which item do you want appearing first?")
else:
lbl.configure(text="Which item do you want appearing %sth?" % order)
if order == len(moduleOrder):
moduleOrder1Finished = 1
return order
I have added a RESULT list to store the order of buttons in which they are clicked.

Tkinter how to catch this particular key event

Problem Description
I have an application in Tkinter that uses a Listbox that displays search results. When I press command + down arrow key, I am putting the focus from the search field to the first item in the Listbox. This is exactly how I want the behaviour but instead for just the down arrow.
However, I am already binding the down arrow to this Listbox by self.bind("<Down>", self.moveDown). I can not understand why command + down works while simply down (to which I literally bind'ed it to) does not. Specifically the result of pressing the down arrow is the following
While pressing command + down gives the intended result:
How can I let down behave just like command + down, and what is the reason why command is required at all?
Code snippets
def matches(fieldValue, acListEntry):
pattern = re.compile(re.escape(fieldValue) + '.*', re.IGNORECASE)
return re.match(pattern, acListEntry)
root = Tk()
img = ImageTk.PhotoImage(Image.open('imgs/giphy.gif'))
panel = Label(root, image=img)
panel.grid(row=1, column=0)
entry = AutocompleteEntry(autocompleteList, panel, root, matchesFunction=matches)
entry.grid(row=0, column=0)
root.mainloop()
With AutocompleteEntry being:
class AutocompleteEntry(Tkinter.Entry):
def __init__(self, autocompleteList, df, panel, rdi, *args, **kwargs):
self.df = df
self.product_row_lookup = {key:value for value, key in enumerate(autocompleteList)}
temp = df.columns.insert(0, 'Product_omschrijving')
temp = temp.insert(1, 'grams')
self.result_list = pd.DataFrame(columns=temp)
self.panel = panel
self.rdi = rdi
# self.bind('<Down>', self.handle_keyrelease)
# Listbox length
if 'listboxLength' in kwargs:
self.listboxLength = kwargs['listboxLength']
del kwargs['listboxLength']
else:
self.listboxLength = 8
# Custom matches function
if 'matchesFunction' in kwargs:
self.matchesFunction = kwargs['matchesFunction']
del kwargs['matchesFunction']
else:
def matches(fieldValue, acListEntry):
pattern = re.compile('.*' + re.escape(fieldValue) + '.*', re.IGNORECASE)
return re.match(pattern, acListEntry)
self.matchesFunction = matches
Entry.__init__(self, *args, **kwargs)
self.focus()
self.autocompleteList = autocompleteList
self.var = self["textvariable"]
if self.var == '':
self.var = self["textvariable"] = StringVar()
self.var.trace('w', self.changed)
self.bind("<Right>", self.selection)
self.bind("<Up>", self.moveUp)
self.bind("<Down>", self.moveDown)
self.bind("<Return>", self.selection)
self.listboxUp = False
self._digits = re.compile('\d')
def changed(self, name, index, mode):
if self.var.get() == '':
if self.listboxUp:
self.listbox.destroy()
self.listboxUp = False
else:
words = self.comparison()
if words:
if not self.listboxUp:
self.listbox = Listbox(width=self["width"], height=self.listboxLength)
self.listbox.bind("<Button-1>", self.selection)
self.listbox.bind("<Right>", self.selection)
self.listbox.bind("<Down>", self.moveDown)
self.listbox.bind("<Tab>", self.selection)
self.listbox.place(x=self.winfo_x(), y=self.winfo_y() + self.winfo_height())
self.listboxUp = True
self.listbox.delete(0, END)
for w in words:
self.listbox.insert(END, w)
else:
if self.listboxUp:
self.listbox.destroy()
self.listboxUp = False
else:
string = self.get()
if '.' in string:
write_to_file(self, string)
def contains_digits(self, d):
return bool(self._digits.search(d))
def selection(self, event):
if self.listboxUp:
string = self.listbox.get(ACTIVE)
self.var.set(string + ' ')
self.listbox.destroy()
self.listboxUp = False
self.icursor(END)
def moveDown(self, event):
self.focus()
if self.listboxUp:
if self.listbox.curselection() == ():
index = '0'
print "ok"
else:
index = self.listbox.curselection()[0]
print "blah"
if index != END:
self.listbox.selection_clear(first=index)
print "noo"
if index != '0':
index = str(int(index) + 1)
self.listbox.see(index) # Scroll!
self.listbox.selection_set(first=index)
self.listbox.activate(index)
else:
print "not up"
def comparison(self):
return [w for w in self.autocompleteList if self.matchesFunction(self.var.get(), w)]
Both command+down and down should produce the same output excepted that down also types question mark  onto the entry which made the last letter typed is the question mark box.
This is because pressing command, your computer checks the option menu to see if there's a shortcut with that key, if there isn't any, it will not do anything. While tkinter registered the down button as being pressed, so the event was triggered.
In contrast, With out pressing command, the Entry first displays the value of "down", which there isn't any, then executes the event binding, what you can do is, in the event, remove the last letter of the Entry. You can do so by self.delete(len(self.get())-1) in your event. Or add a return 'break' at the end of your event to prevent it from being typed.
Unfortunately, it's really hard to understand your real problem because you've posted too much unrelated code and not enough related code. It seems to me that what you're trying to accomplish is to let the user press the down or up arrow while an entry has focus, and have that cause the selection in a listbox to move down or up. Further, it seems that part of the problem is that you're seeing characters in the entry widget that you do not want to see when you press down or up.
If that is the problem, the solution is fairly simple. All you need to do is have your binding return the string "break" to prevent the default binding from being processed. It is the default binding that is inserting the character.
Here is an example. Run the example, and press up and down to move the selection of the listbox. I've left out all of the code related to autocomplete so you can focus on how the event binding works.
import Tkinter as tk
class Example(object):
def __init__(self):
self.root = tk.Tk()
self.entry = tk.Entry(self.root)
self.listbox = tk.Listbox(self.root, exportselection=False)
for i in range(30):
self.listbox.insert("end", "Item #%s" % i)
self.entry.pack(side="top", fill="x")
self.listbox.pack(side="top", fill="both", expand=True)
self.entry.bind("<Down>", self.handle_updown)
self.entry.bind("<Up>", self.handle_updown)
def start(self):
self.root.mainloop()
def handle_updown(self, event):
delta = -1 if event.keysym == "Up" else 1
curselection = self.listbox.curselection()
if len(curselection) == 0:
index = 0
else:
index = max(int(curselection[0]) + delta, 0)
self.listbox.selection_clear(0, "end")
self.listbox.selection_set(index, index)
return "break"
if __name__ == "__main__":
Example().start()
For a fairly thorough explanation of what happens when an event is triggered, see this answer: https://stackoverflow.com/a/11542200/7432
Again, leaving aside the autocomplete requirement, I came up with a solution that uses that standard available commands and events for Listbox and Scrollbar. <<ListboxSelect>> lets you capture changes in selection from any of the lists and align the others. In addition, the Scrollbar and Listbox callbacks are directed to a routing function that passes things on to all of the listboxes.
# updownmultilistbox.py
# 7/24/2020
#
# incorporates vsb to propagate scrolling across lists
#
import tkinter as tk
class Example(object):
def __init__(self):
self.root = tk.Tk()
self.listOfListboxes = []
# self.active_lb = None
self.vsb = tk.Scrollbar(orient='vertical', command=self.OnVsb)
self.vsb.pack(side='right', fill='y')
self.lb1 = tk.Listbox(self.root, exportselection=0,
selectmode= tk.SINGLE, yscrollcommand=self.vsb_set)
self.lb2 = tk.Listbox(self.root, exportselection=0,
selectmode=tk.SINGLE, yscrollcommand=self.vsb_set)
self.lb3 = tk.Listbox(self.root, exportselection=0,
selectmode=tk.SINGLE, yscrollcommand=self.vsb_set)
self.listOfListboxes.append(self.lb1)
self.listOfListboxes.append(self.lb2)
self.listOfListboxes.append(self.lb3)
for i in range(30):
self.lb1.insert("end", "lb1 Item #%s" % i)
self.lb2.insert("end", "lb2 Item #%s" % i)
self.lb3.insert("end", "lb3 Item #%s" % i)
self.lb1.pack(side="left", fill="both", expand=True)
self.lb2.pack(side="left", fill="both", expand=True)
self.lb3.pack(side="left", fill="both", expand=True)
for lb in self.listOfListboxes:
lb.bind('<<ListboxSelect>>', self.handle_select)
for lb in self.listOfListboxes:
lb.selection_set(0)
lb.activate(0)
self.listOfListboxes[0].focus_force()
def start(self):
self.root.title('updownmultilistbox')
self.root.mainloop()
def OnVsb(self, *args):
for lb in self.listOfListboxes:
lb.yview(*args)
def vsb_set(self, *args):
print ('vsb_set args: ', *args)
self.vsb.set(*args)
for lb in self.listOfListboxes:
lb.yview_moveto(args[0])
def handle_select(self, event):
# set evey list to the same selection
print ('select handler: ', event, event.widget.curselection())
# self.active_lb = event.widget
for lb in self.listOfListboxes:
if lb != event.widget:
lb.selection_clear(0, 'end') # have to avoid this for the current widget
lb.selection_set(event.widget.curselection())
lb.activate(event.widget.curselection())
if __name__ == "__main__":
Example().start()

How to make ttk.Treeview's rows editable?

Is there any way to use ttk Treeview with editable rows?
I mean it should work more like a table. For example on double click on the item make the #0 column 'editable'.
If this isn't possible, any way to allow mouse selecting on the item would be just fine. I haven't found any mention of this in tkdocs or other documents.
After long research I haven't found such feature so I guess there's any. Tk is very simple interface, which allows programmer to build 'high-level' features from the basics. So my desired behaviour this way.
def onDoubleClick(self, event):
''' Executed, when a row is double-clicked. Opens
read-only EntryPopup above the item's column, so it is possible
to select text '''
# close previous popups
# self.destroyPopups()
# what row and column was clicked on
rowid = self._tree.identify_row(event.y)
column = self._tree.identify_column(event.x)
# get column position info
x,y,width,height = self._tree.bbox(rowid, column)
# y-axis offset
# pady = height // 2
pady = 0
# place Entry popup properly
text = self._tree.item(rowid, 'text')
self.entryPopup = EntryPopup(self._tree, rowid, text)
self.entryPopup.place( x=0, y=y+pady, anchor=W, relwidth=1)
This is method within a class which composes ttk.Treeview as self._tree
And EntryPopup is then very simple sub-class of Entry:
class EntryPopup(Entry):
def __init__(self, parent, iid, text, **kw):
''' If relwidth is set, then width is ignored '''
super().__init__(parent, **kw)
self.tv = parent
self.iid = iid
self.insert(0, text)
# self['state'] = 'readonly'
# self['readonlybackground'] = 'white'
# self['selectbackground'] = '#1BA1E2'
self['exportselection'] = False
self.focus_force()
self.bind("<Return>", self.on_return)
self.bind("<Control-a>", self.select_all)
self.bind("<Escape>", lambda *ignore: self.destroy())
def on_return(self, event):
self.tv.item(self.iid, text=self.get())
self.destroy()
def select_all(self, *ignore):
''' Set selection on the whole text '''
self.selection_range(0, 'end')
# returns 'break' to interrupt default key-bindings
return 'break'
You could also pop up a tool window with the editable fields listed with Entries to update the values. This example has a treeview with three columns, and does not use subclasses.
Bind your double click to this:
def OnDoubleClick(self, treeView):
# First check if a blank space was selected
entryIndex = treeView.focus()
if '' == entryIndex: return
# Set up window
win = Toplevel()
win.title("Edit Entry")
win.attributes("-toolwindow", True)
####
# Set up the window's other attributes and geometry
####
# Grab the entry's values
for child in treeView.get_children():
if child == entryIndex:
values = treeView.item(child)["values"]
break
col1Lbl = Label(win, text = "Value 1: ")
col1Ent = Entry(win)
col1Ent.insert(0, values[0]) # Default is column 1's current value
col1Lbl.grid(row = 0, column = 0)
col1Ent.grid(row = 0, column = 1)
col2Lbl = Label(win, text = "Value 2: ")
col2Ent = Entry(win)
col2Ent.insert(0, values[1]) # Default is column 2's current value
col2Lbl.grid(row = 0, column = 2)
col2Ent.grid(row = 0, column = 3)
col3Lbl = Label(win, text = "Value 3: ")
col3Ent = Entry(win)
col3Ent.insert(0, values[2]) # Default is column 3's current value
col3Lbl.grid(row = 0, column = 4)
col3Ent.grid(row = 0, column = 5)
def UpdateThenDestroy():
if ConfirmEntry(treeView, col1Ent.get(), col2Ent.get(), col3Ent.get()):
win.destroy()
okButt = Button(win, text = "Ok")
okButt.bind("<Button-1>", lambda e: UpdateThenDestroy())
okButt.grid(row = 1, column = 4)
canButt = Button(win, text = "Cancel")
canButt.bind("<Button-1>", lambda c: win.destroy())
canButt.grid(row = 1, column = 5)
Then confirm the changes:
def ConfirmEntry(self, treeView, entry1, entry2, entry3):
####
# Whatever validation you need
####
# Grab the current index in the tree
currInd = treeView.index(treeView.focus())
# Remove it from the tree
DeleteCurrentEntry(treeView)
# Put it back in with the upated values
treeView.insert('', currInd, values = (entry1, entry2, entry3))
return True
Here's how to delete an entry:
def DeleteCurrentEntry(self, treeView):
curr = treeView.focus()
if '' == curr: return
treeView.delete(curr)
I have tried #dakov solution but it did not work for me since my treeView has multiple columns and for few more reasons. I made some changes that enhanced it so here is my version
class Tableview(ttk.Treeview):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
tv.bind("<Double-1>", lambda event: self.onDoubleClick(event))
def onDoubleClick(self, event):
''' Executed, when a row is double-clicked. Opens
read-only EntryPopup above the item's column, so it is possible
to select text '''
# close previous popups
try: # in case there was no previous popup
self.entryPopup.destroy()
except AttributeError:
pass
# what row and column was clicked on
rowid = self.identify_row(event.y)
column = self.identify_column(event.x)
# handle exception when header is double click
if not rowid:
return
# get column position info
x,y,width,height = self.bbox(rowid, column)
# y-axis offset
pady = height // 2
# place Entry popup properly
text = self.item(rowid, 'values')[int(column[1:])-1]
self.entryPopup = EntryPopup(self, rowid, int(column[1:])-1, text)
self.entryPopup.place(x=x, y=y+pady, width=width, height=height, anchor='w')
The EntryPopup class
class EntryPopup(ttk.Entry):
def __init__(self, parent, iid, column, text, **kw):
ttk.Style().configure('pad.TEntry', padding='1 1 1 1')
super().__init__(parent, style='pad.TEntry', **kw)
self.tv = parent
self.iid = iid
self.column = column
self.insert(0, text)
# self['state'] = 'readonly'
# self['readonlybackground'] = 'white'
# self['selectbackground'] = '#1BA1E2'
self['exportselection'] = False
self.focus_force()
self.select_all()
self.bind("<Return>", self.on_return)
self.bind("<Control-a>", self.select_all)
self.bind("<Escape>", lambda *ignore: self.destroy())
def on_return(self, event):
rowid = self.tv.focus()
vals = self.tv.item(rowid, 'values')
vals = list(vals)
vals[self.column] = self.get()
self.tv.item(rowid, values=vals)
self.destroy()
def select_all(self, *ignore):
''' Set selection on the whole text '''
self.selection_range(0, 'end')
# returns 'break' to interrupt default key-bindings
return 'break'
from tkinter import ttk
from tkinter import *
root = Tk()
columns = ("Items", "Values")
Treeview = ttk.Treeview(root, height=18, show="headings", columns=columns) #
Treeview.column("Items", width=200, anchor='center')
Treeview.column("Values", width=200, anchor='center')
Treeview.heading("Items", text="Items")
Treeview.heading("Values", text="Values")
Treeview.pack(side=LEFT, fill=BOTH)
name = ['Item1', 'Item2', 'Item3']
ipcode = ['10', '25', '163']
for i in range(min(len(name), len(ipcode))):
Treeview.insert('', i, values=(name[i], ipcode[i]))
def treeview_sort_column(tv, col, reverse):
l = [(tv.set(k, col), k) for k in tv.get_children('')]
l.sort(reverse=reverse)
for index, (val, k) in enumerate(l):
tv.move(k, '', index)
tv.heading(col, command=lambda: treeview_sort_column(tv, col, not reverse))
def set_cell_value(event):
for item in Treeview.selection():
item_text = Treeview.item(item, "values")
column = Treeview.identify_column(event.x)
row = Treeview.identify_row(event.y)
cn = int(str(column).replace('#', ''))
rn = int(str(row).replace('I', ''))
entryedit = Text(root, width=10 + (cn - 1) * 16, height=1)
entryedit.place(x=16 + (cn - 1) * 130, y=6 + rn * 20)
def saveedit():
Treeview.set(item, column=column, value=entryedit.get(0.0, "end"))
entryedit.destroy()
okb.destroy()
okb = ttk.Button(root, text='OK', width=4, command=saveedit)
okb.place(x=90 + (cn - 1) * 242, y=2 + rn * 20)
def newrow():
name.append('to be named')
ipcode.append('value')
Treeview.insert('', len(name) - 1, values=(name[len(name) - 1], ipcode[len(name) - 1]))
Treeview.update()
newb.place(x=120, y=(len(name) - 1) * 20 + 45)
newb.update()
Treeview.bind('<Double-1>', set_cell_value)
newb = ttk.Button(root, text='new item', width=20, command=newrow)
newb.place(x=120, y=(len(name) - 1) * 20 + 45)
for col in columns:
Treeview.heading(col, text=col, command=lambda _col=col: treeview_sort_column(Treeview, _col, False))
root.mainloop()
After so much research while doing my project got this code, it helped me a lot.
Double click on the element you want to edit, make the required change and click 'OK' button
I think this is what exactly you wanted
#python #tkinter #treeview #editablerow
New row
Editable row
This is just for creating a tree for the specified path that is set in the constructor. you can bind your event to your item on that tree. The event function is left in a way that the item could be used in many ways. In this case, it will show the name of the item when double clicked on it. Hope this helps somebody.
import ttk
from Tkinter import*
import os*
class Tree(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
path = "/home/...."
self.initUI(path)
def initUI(self, path):
self.parent.title("Tree")
self.tree = ttk.Treeview(self.parent)
self.tree.bind("<Double-1>", self.itemEvent)
yScr = ttk.Scrollbar(self.tree, orient = "vertical", command = self.tree.yview)
xScr = ttk.Scrollbar(self.tree, orient = "horizontal", command = self.tree.xview)
self.tree.configure(yscroll = yScr.set, xScroll = xScr.set)
self.tree.heading("#0", text = "My Tree", anchor = 'w')
yScr.pack(side = RIGHT, fill = Y)
pathy = os.path.abspath(path)
rootNode = self.tree.insert('', 'end', text = pathy, open = True)
self.createTree(rootNode, pathy)
self.tree.pack(side = LEFT, fill = BOTH, expand = 1, padx = 2, pady = 2)
self.pack(fill= BOTH, expand = 1)
def createTree(self, parent, path)
for p in os.listdir(path)
pathy = os.path.join(path, p)
isdir = os.path.isdir(pathy)
oid = self.tree.insert(parent, 'end' text = p, open = False)
if isdir:
self.createTree(oid, pathy)
def itemEvent(self, event):
item = self.tree.selection()[0] # now you got the item on that tree
print "you clicked on", self.tree.item(item,"text")
def main():
root = Tk.Tk()
app = Tree(root)
root.mainloop()
if __name__ == '__main__'
main()
You should not do this manually
there are ready to use pack that have this Feature and many more such as
tkintertable
it have some insane features
there is also pygubu-editable-treeview
if you are intrested in pygubu,
as for the the reason you shouldnt code your own ,
in order to do a good treeview you will need to build more Feature that make your gui easier to use
however such Feature takes hundred lines of code to create.(takes a long time to get right)
unless you are making a custom TREE-View-widget,it doesnot worth the effort.
I don't know about making the row editable, but to capture clicking on a row, you use the <<TreeviewSelect>> virtual event. This gets bound to a routine with the bind() method, then you use the selection() method to get the ids of the items selected.
These are snippets from an existing program, but show the basic sequence of calls:
# in Treeview setup routine
self.tview.tree.bind("<<TreeviewSelect>>", self.TableItemClick)
# in TableItemClick()
selitems = self.tview.tree.selection()
if selitems:
selitem = selitems[0]
text = self.tview.tree.item(selitem, "text") # get value in col #0

Categories

Resources