PYTHON TKINTER set focus to next row instead of next widget - python

I have frames in a grid with widgets in them. These frames get destroyed and new ones are created. I want to move between these with keyboard.
The Problem: every time there is a new frame it gets treated by focus next as the last one.
The Question: how to focus by row instead.
def create_a_line(i):
p=1 #sometimes there are more widgets in a row, but here just take 1
bframe = Frame(root)
bframe.grid(row=i, column=0)
m = Button(bframe, text=h[i], , takefocus = 1)
m.bind('<Right>', lambda event, i=i: focus_next(i, p))
m.bind('<Double-1>', lambda event, i=i: double_click_text(i))
m.grid(row=0, column=p)
def double_click_text(i)
for widget in root.grid_slaves(row=i):
widget.destroy()
#change h[i] i.e. text of the button to something else
create_a_line(i) #with new text but same row
def focus_next(i, p): #this is what im struggling with
event.widget.tk_focusNext().focus() #this is how it is right now
#this is how i would like it to be, but no idea how:
# if in row=i other widgets with column >p: sometimes there will be more than one widget in a row
# event.widget.tk_focusNext().focus()
# else:
# set_focus(first widget in grid row=i+1)
for i in range(0, 5) #the end number is based on number of lines f.e. 5
create_a_line(i)

For lack of a better solution this is how I solved it based on comments:
labellsall=[]
h = []
def ad_to_labelsall(i, p , z):
ag = ["#"+str(i)+"i#", p, z] #made i more complicated so I dont find random numbers
labelsall.append(ag)
def del_from_labelsall(i):
agi = ("#"+str(i)+"i#")
ngip = [e for e, s in enumerate(labelsall) if agi in s]
for index in sorted(ngip, reverse=True):
del labelsall[index]
def create_a_line(i):
p=1
bframe = Frame(root)
bframe.grid(row=i, column=0)
m = Button(bframe, text=h[i], , takefocus = 1)
m.bind('<Right>', lambda event, i=i: focus_next(event, i))
m.bind('<Double-1>', lambda event, i=i: double_click_text(i))
m.grid(row=0, column=p)
ad_to_labelsall(i, p , z=m)
def double_click_text(i)
del_from_labelsall(i)
for widget in root.grid_slaves(row=i):
widget.destroy()
#some other code change h[i] i.e. text of the button to something else
create_a_line(i) #with new text but same row
def focus_next(event, i):
grid_info = event.widget.grid_info()
p = grid_info["column"]
agi = ("#"+str(i)+"i#")
ngip = [e for e, s in enumerate(labelsall) if agi in s]
for index in sorted(ngip):
the_row = labelsall[index]
if int(the_row[1]) > p: #check if other widget is in the row before jumping to next row
the_row[2].focus_set() #if other widget found focus it
return("break")
else:
pass
i_next = i+1
try:
agi_next = ("#"+str(i_next)+"i#")
ngip_next = [e for e, s in enumerate(labelsall) if agi_next in s]
labelsall[ngip_next[0]][2].focus_set()
except:
agi_next = ("#"+str(i_next+1)+"i#") #sometimes there are rows with no widgets, so just try the next one
ngip_next = [e for e, s in enumerate(labelsall) if agi_next in s]
labelsall[ngip_next[0]][2].focus_set()
return("break")

Related

Turning StringVar value to list

I have been working on a Tkinter project. I have set the listbox as StringVar and I would like to call a list of items inside the right listbox by setting a function 'process'.
enter image description here
After I press the button process in the gui, I am supposed to return a list ['A'] in the above case.
However, it return ['(', "'", 'A', "'", ')']
If I just print app.list_var2.get() it return ('A',)
import tkinter as tk
import pandas as pd
app=tk.Tk()
app.geometry('640x480')
app.resizable(width=True, height=True)
app.title('Simulator')
# list variables
app.list_var1 = tk.StringVar()
app.list_var2 = tk.StringVar()
app.list_var1.set(value=['A','B','C'])
app.list_var2.set(value=[])
# label frame
app.label_frame= tk.Frame(app)
app.label1 = tk.Label(app.label_frame,text='PDC',justify='left')
app.label1.pack(ipadx=10,ipady=10,side='left', anchor='w')
app.label2 = tk.Label(app.label_frame,text='SDC')
app.label2.pack(ipadx=20,ipady=10, side='left',anchor='e')
# main frame
app.main_frame = tk.Frame(app)
app.listbox1 = tk.Listbox(app.main_frame, listvariable=app.list_var1, selectmode='single')
app.listbox2 = tk.Listbox(app.main_frame, listvariable=app.list_var2, selectmode='single')
def init_default_values():
app.list_var1.set(value=['A','B','C'])
app.list_var2.set(value=[])
def move_to_right(only_one_item=False):
if app.listbox1.curselection() == ():
return
# get tuple of selected indices
if only_one_item:
selection = (app.listbox1.curselection()[0],)
else:
selection = app.listbox1.curselection()
# left all/selected values
left_value_list = [line.strip(' \'') for line in app.list_var1.get()[1:-1].split(',')]
left_selected_list = [left_value_list[index] for index in selection]
# values from right side
right_value_list = [line.strip(' \'') for line in app.list_var2.get()[1:-1].split(',')]
# merge w/o duplicates
result_list = sorted(list(set(right_value_list + left_selected_list)))
for x in left_value_list:
if x in result_list:
left_value_list.remove(x)
while("" in left_value_list) :
left_value_list.remove("")
while("" in result_list) :
result_list.remove("")
app.list_var2.set(value=result_list)
app.list_var1.set(value=left_value_list)
def move_to_left(only_one_item=False):
if app.listbox2.curselection() == ():
return
# get tuple of selected indices
if only_one_item:
selection = (app.listbox2.curselection()[0],)
else:
selection = app.listbox2.curselection()
# right all/selected values
right_value_list = [line.strip(' \'') for line in app.list_var2.get()[1:-1].split(',')]
right_selected_list = [right_value_list[index] for index in selection]
# values from left side
left_value_list = [line.strip(' \'') for line in app.list_var1.get()[1:-1].split(',')]
# merge w/o duplicates
result_list = sorted(list(set(left_value_list + right_selected_list)))
for x in right_value_list:
if x in result_list:
right_value_list.remove(x)
while("" in right_value_list) :
right_value_list.remove("")
while("" in result_list) :
result_list.remove("")
app.list_var1.set(value=result_list)
app.list_var2.set(value=right_value_list)
def reset():
init_default_values()
def process():
#if len(app.list_var2.get()) == 1:
lst=app.list_var2.get()
print(len(lst))
lst = str(app.list_var2.get()).replace(',','')
lst=list(lst)
print(type(lst))
print(len(lst))
print(lst)
# little button frame
app.button_frame = tk.Frame(app.main_frame)
app.one_to_right_button = tk.Button(app.button_frame, text='>', command=lambda: move_to_right(True))
app.one_to_left_button = tk.Button(app.button_frame, text='<', command=lambda: move_to_left(True))
app.reset_button = tk.Button(app.button_frame,text='Reset',command=reset)
app.process = tk.Button(app.button_frame,text='Process',command=process)
# packing
app.one_to_right_button.pack()
app.one_to_left_button.pack()
app.reset_button.pack()
app.process.pack()
app.listbox1.pack(side='left', anchor='w')
app.button_frame.pack(side='left')
app.listbox2.pack(side='right', anchor='e')
app.label_frame.pack()
app.main_frame.pack(padx=20,pady=20)
# insert default values
init_default_values()
app.mainloop()
Since app.list_var2 is a StringVar, so app.list_var2.get() will always return a string.
Better use app.listbox2.get(0, "end") to get all the items (a tuple) in the listbox:
def process():
lst = list(app.listbox2.get(0, 'end'))
print(lst, type(lst))
Note that you can use eval() to convert the string returned by app.list_var2.get() to tuple but it is not recommended:
def process():
lst = list(eval(app.list_var2.get()))
print(lst, type(lst))

can't get Variable value of groups of ttk.CheckButtons

I'm creating 3 groups of ttk.CheckButtons, using a class clsGrpCheckButton.
The problem is that I can access only the status of the last group created, and not for the previous group.
When clicking on one of the checkButtons of any group, I'm expecting to get the list of checkbuttons check in the group (by using method chkGrpGetValue with is the command parameter of each checkbutton)
However, whatever which button is clicked, the method only and always returns the status of the last group
Here is the code to recreate the issue, and in attachment a picture that shows the problems
Thks for your help.
Rgds
import tkinter as tk
from tkinter import ttk
import pandas as pd
class clsGrpCheckButton(ttk.Checkbutton):
def __init__(self,pContainer, pLstVal,pCommand,pInitValue=True):
self.grpChk=[0]*len(pLstVal)
self.grpKey=[0]*len(pLstVal)
self.grpLstVal = [0]*len(pLstVal)
self.grpVariable= [0]*len(pLstVal)
self.grpActiveKeys = [0]
for l,t in enumerate(pLstVal):
#l : index of the list of tuples
self.grpKey[l] = t[0]
self.grpLstVal[l] = t[1]
self.grpVariable[l] = tk.StringVar()
self.grpChk[l] = ttk.Checkbutton(pContainer, text=self.grpLstVal[l],
state='active' ,
onvalue= self.grpKey[l],
offvalue= '',
variable = self.grpVariable[l],
command=pCommand)
#get default value
if pInitValue :
self.grpVariable[l].set(self.grpKey[l])
self.grpActiveKeys.append(self.grpKey[l])
#get the index in the list of checkboxes
# depending on the key
def chkGrpGetIdx(self, pKey):
i=0
while i <len(self.grpKey):
if self.grpKey[i]==pKey :
return i
i=len(self.grpKey)
else:
i+=1
def chkGrpSetValue(self, pKey, pValue):
#need find correct index first
i=self.chkGrpGetIdx(pKey)
self.grpVariable[i] = pValue
#return the list of keys of the group
def chkGrpKeyLst(self):
return self.grpKey
#return the checkox element of the group of checkox
def chkGrpGetChkObj(self,pKey):
i=self.chkGrpGetIdx(pKey)
return self.grpChk[i]
#action when check/uncheck
#at list one element should be active
def chkGrpGetValue(self):
i=0
r=len(self.grpVariable)
self.grpActiveKeys.clear()
while i < len(self.grpVariable):
if self.grpVariable[i].get() =='':
r-=1
else:
self.grpActiveKeys.append(self.grpKey[i])
i+=1
if r==0:
self.grpVariable[0].set(self.grpKey[0])
self.grpActiveKeys.append(self.grpKey[0])
print(self.grpActiveKeys)
#to avoid accessing to class attribute directly
def chkGetCheckedValues(self):
return self.grpActiveKeys
class clsWindows(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
la = [1,1,1,1,1,2,2,2,2,2,2,2,3,3,3,3]
lb= [10,11,12,14,15,20,21,22,23,24,25,26,30,31,32,33]
lc=['d10','d11','d12','d14','d15','d20','d21','d22','d23','d24','d25','d26','d30','d31','d32','d33']
df = pd.DataFrame(
{'DIVISION': la,
'DEPT_CODE': lb,
'DEPT_NAME': lc
})
lW = list(zip(df['DIVISION'].astype(str) , df['DEPT_CODE'].astype(str)))
lpt = list(zip(df['DEPT_CODE'].astype(str) , df['DEPT_NAME'].astype(str)))
curHead = ""
r=0
c=-1
for head, DPT in lW:
if not curHead==head:
curHead = head
c+=1
r=0
dq=df.query('DIVISION=='+head)
lpt = list(zip(dq['DEPT_CODE'].astype(str) , dq['DEPT_NAME'].astype(str)))
t=ttk.Labelframe(self,text=head)
t.grid(column=c, row=0, sticky='nw')
self.checkGrpDept= clsGrpCheckButton(t,lpt,lambda:self.checkGrpDept.chkGrpGetValue(),True)
self.checkGrpDept.chkGrpGetChkObj(DPT).grid(column=c, row=r, sticky='nw')
t.rowconfigure(r, weight=1)
t.columnconfigure(c, weight=1)
r+=1
def wQuit(self):
self.destroy()
app = clsWindows()
app.mainloop()
Example of issue
finally, I came up with a solution by using partial when assigning the command to each checkbutton.
Here is the full code if someone faces a similar issue
import tkinter as tk
from tkinter import ttk
import pandas as pd
from functools import partial
class clsGrpCheckButton():
def __init__(self,pContainer, pLstVal,pCommand,pInitValue=True):
self.grpChk=[0]*len(pLstVal)
self.grpKey=[0]*len(pLstVal)
self.grpLstVal = [0]*len(pLstVal)
self.grpVariable= [0]*len(pLstVal)
self.grpActiveKeys = [0]
for l,t in enumerate(pLstVal):
#l : index of the list of tuples
self.grpKey[l] = t[0]
self.grpLstVal[l] = t[1]
self.grpVariable[l] = tk.StringVar()
self.grpChk[l] = ttk.Checkbutton(pContainer, text=self.grpLstVal[l],
state='active' ,
onvalue= self.grpKey[l],
offvalue= '',
variable = self.grpVariable[l],
command=partial(pCommand,self))
#get default value
if pInitValue :
self.grpVariable[l].set(self.grpKey[l])
self.grpActiveKeys.append(self.grpKey[l])
#get the index in the list of checkboxes
# depending on the key
def chkGrpGetIdx(self, pKey):
i=0
while i <len(self.grpKey):
if self.grpKey[i]==pKey :
return i
i=len(self.grpKey)
else:
i+=1
def chkGrpSetValue(self, pKey, pValue):
#need find correct index first
i=self.chkGrpGetIdx(pKey)
self.grpVariable[i] = pValue
#return the list of keys of the group
def chkGrpKeyLst(self):
return self.grpKey
#return the checkox element of the group of checkox
def chkGrpGetChkObj(self,pKey):
i=self.chkGrpGetIdx(pKey)
return self.grpChk[i]
#action when check/uncheck
#at list one element should be active
def chkGrpGetValue(self):
i=0
r=len(self.grpVariable)
self.grpActiveKeys.clear()
while i < len(self.grpVariable):
if self.grpVariable[i].get() =='':
r-=1
else:
self.grpActiveKeys.append(self.grpKey[i])
i+=1
if r==0:
self.grpVariable[0].set(self.grpKey[0])
self.grpActiveKeys.append(self.grpKey[0])
print(self.grpActiveKeys)
#to avoid accessing to class attribute directly
def chkGetCheckedValues(self):
return self.grpActiveKeys
class clsWindows(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
la = [1,1,1,1,1,2,2,2,2,2,2,2,3,3,3,3]
lb= [10,11,12,14,15,20,21,22,23,24,25,26,30,31,32,33]
lc=['d10','d11','d12','d14','d15','d20','d21','d22','d23','d24','d25','d26','d30','d31','d32','d33']
df = pd.DataFrame(
{'DIVISION': la,
'DEPT_CODE': lb,
'DEPT_NAME': lc
})
lW = list(zip(df['DIVISION'].astype(str) , df['DEPT_CODE'].astype(str)))
lpt = list(zip(df['DEPT_CODE'].astype(str) , df['DEPT_NAME'].astype(str)))
curHead = ""
r=0
c=-1
for head, DPT in lW:
if not curHead==head:
curHead = head
c+=1
r=0
dq=df.query('DIVISION=='+head)
lpt = list(zip(dq['DEPT_CODE'].astype(str) , dq['DEPT_NAME'].astype(str)))
t=ttk.Labelframe(self,text=head)
t.grid(column=c, row=0, sticky='nw')
checkGrpDept= clsGrpCheckButton(t,lpt,clsGrpCheckButton.chkGrpGetValue,True)
checkGrpDept.chkGrpGetChkObj(DPT).grid(column=c, row=r, sticky='nw')
t.rowconfigure(r, weight=1)
t.columnconfigure(c, weight=1)
r+=1
def wQuit(self):
self.destroy()
app = clsWindows()
app.mainloop()

Problems assigning a value from a randomly populated array to a tkinter button

Hello I am trying to create a game where you click on a button and it tells you if it is treasure or a bandit. It if is treasure then you get points if it is a bandit you loose your coins.
I have created the following code but cannot find a way of finding out if I have managed to set the buttons to the values stored in the array.
How do you use the data in the array and when a button is clicked it tells you what the value is.
Am I doing the array correctly any help would be appreciated I am going round in circles and cannot think straight now.
from tkinter import *
import tkinter.messagebox
import random
#count=0
root = Tk()
TreasureMap = Frame(root)
TreasureMap.grid()
root.title("TreasureMap")
text_box = Entry(TreasureMap, justify=RIGHT)
text_box.grid(row = 0, column = 0, columnspan = 8,)
text_box.insert(0, "0")
amount_to_add='B'
my_array = [[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0]]
my_array[random.randrange(len(my_array))].append(amount_to_add)
my_array[random.randrange(len(my_array))].append(amount_to_add)
my_array[random.randrange(len(my_array))].append(amount_to_add)
my_array[random.randrange(len(my_array))].append(amount_to_add)
Treasure='T'
my_array[random.randrange(len(my_array))].append(Treasure)
my_array[random.randrange(len(my_array))].append(Treasure)
my_array[random.randrange(len(my_array))].append(Treasure)
my_array[random.randrange(len(my_array))].append(Treasure)
my_array[random.randrange(len(my_array))].append(Treasure)
my_array[random.randrange(len(my_array))].append(Treasure)
my_array[random.randrange(len(my_array))].append(Treasure)
my_array[random.randrange(len(my_array))].append(Treasure)
my_array[random.randrange(len(my_array))].append(Treasure)
my_array[random.randrange(len(my_array))].append(Treasure)
print(my_array)
def changelabel(): `
tkinter.messagebox.showinfo('Hellow this button works') ``
print('hello this button works') ` ``
print(my_array) `` ``
return
i = 0
bttn = []
for j in range(1,9):`
for k in range(8): ``
bttn.append(Button(TreasureMap, text = ' ', value=random.shuffle
(my_array),command=changelabel))
bttn[i].grid(row = j, column = k)
i += 1 ``
TreasureMap.mainloop()
This may be what you're wanting to do.
Borrowing info from this answer, I put a callback in the Button command to pass the row and column numbers.
Also, 'B' and 'T' are being appended to each array of 0's, instead of getting assigned to locations within the array, which is what I think you want.
I made a few other changes, including fixing the for loops to start from 0 (the first row), etc.
# previous code
amount_to_add='B'
my_array = [[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0]]
# ... all code up to here unchanged ...
for _ in range(4):
my_array[random.randrange(len(my_array))][random.randrange(len(my_array))] = amount_to_add
Treasure='T'
for _ in range(4):
my_array[random.randrange(len(my_array))][random.randrange(len(my_array))] = Treasure
# print my_array # uncomment to check array values before shuffle
random.shuffle(my_array)
# print my_array # and after shuffle to confirm button is getting the correct values
def changelabel(j,k):
tkinter.messagebox.showinfo('Button %d,%d' % (j,k), 'Button %d,%d contains a %s' % (j,k, my_array[j][k]))
print('hello this button works')
# return # not needed
i = 0
bttn = []
for j in range(0,8):
for k in range(8):
bttn.append(Button(TreasureMap, text = ' ', command= lambda row = j, column = k: changelabel(row, column)))
bttn[i].grid(row = j, column = k)
i += 1
Feel free to comment or ask questions if this doesn't do what you were asking for. Otherwise, hope it helps.

PyGTK custom ComboBox behavior

I'm trying to create a custom ComboBox that behaves like the one in here: http://chir.ag/projects/name-that-color/
I've got two problems right now:
I can't seem to find a way to have a scrollbar on the side; the gtk.rc_parse_string function should do that, since the ComboBox widget has a "appears-as-list" style property, but my custom widget seems unaffected for some reason.
When you select a color from my widget, then click the ComboBox again, instead of showing the selected item and its neighbours, the scrolled window starts from the top, for no apparent reason.
This is the code, you can pretty much ignore the __load_name_palette method. You need the /usr/share/X11/rgb.txt file to run this code, it looks like this: http://pastebin.com/raw.php?i=dkemmEdr
import gtk
import gobject
from os.path import exists
def window_delete_event(*args):
return False
def window_destroy(*args):
gtk.main_quit()
class ColorName(gtk.ComboBox):
colors = []
def __init__(self, name_palette_path, wrap_width=1):
gtk.ComboBox.__init__(self)
liststore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING,
gobject.TYPE_STRING)
name_palette = self.__load_name_palette(name_palette_path)
for c in name_palette:
r, g, b, name = c
if ((r + g + b) / 3.) < 128.:
fg = '#DDDDDD'
else:
fg = '#222222'
bg = "#%02X%02X%02X" % (r, g, b)
liststore.append((name, bg, fg))
self.set_model(liststore)
label = gtk.CellRendererText()
self.pack_start(label, True)
self.set_attributes(label, background=1, foreground=2, text=0)
self.set_wrap_width(wrap_width)
if len(name_palette) > 0:
self.set_active(0)
self.show_all()
def __load_name_palette(self, name_palette_path):
if exists(name_palette_path):
try:
f = open(name_palette_path,'r')
self.colors = []
palette = set()
for l in f:
foo = l.rstrip().split(None,3)
try:
rgb = [int(x) for x in foo[:3]]
name, = foo[3:]
except:
continue
k = ':'.join(foo[:3])
if k not in palette:
palette.add(k)
self.colors.append(rgb + [name])
f.close()
return self.colors
except IOError as (errno, strerror):
print "error: failed to open {0}: {1}".format(name_palette_path, strerror)
return []
else:
return []
if __name__ == '__main__':
win = gtk.Window()
#colname = ColorName('./ntc.txt')
colname = ColorName('/usr/share/X11/rgb.txt')
gtk.rc_parse_string("""style "mystyle" { GtkComboBox::appears-as-list = 1 }
class "GtkComboBox" style "mystyle" """)
print 'appears-as-list:', colname.style_get_property('appears-as-list')
model = gtk.ListStore(gobject.TYPE_STRING)
hbox = gtk.HBox()
win.add(hbox)
hbox.pack_start(colname)
win.connect('delete-event', window_delete_event)
win.connect('destroy', window_destroy)
win.show_all()
gtk.main()
The problem was the self.show_all() line. Also, you can't have a list AND a wrap_width != 1

Passing extra arguments with Tkinter Bindings

I'm trying to bind an extra argument opt to the following radiobuttons below. I'm trying to make it so that when WSRB_UD is triggered I can know which radiobutton triggered it. How do I go about this?
Snippet:
self.WS.SW.SearchFrame = []
self.WS.SW.SearchRB = []
self.WS.RBvar = Tkinter.IntVar()
i = 0
while i < 6 :
Frame = Tkinter.Frame(self.WS.SW.OptFrame, width=125, height=22, bd=1,
bg=self.WSbg)
Frame.grid(column=0, row=4 + i)
Frame.grid_propagate(0)
self.WS.SW.SearchFrame.append(Frame)
RB = Tkinter.Radiobutton(self.WS.SW.SearchFrame[i], value=i,
variable=self.WS.RBvar, indicatoron=0, font=self.WSfo,
fg=self.WSfg, activeforeground=self.WSfg, bg=self.WSbg, activebackground=self.WSbg,
selectcolor=self.WSbg, bd=self.WSbw)
RB.grid()
RB.bind( "<Enter>", self.WSRB_UD, i)
print i
self.WS.SW.SearchRB.append(RB)
i = i + 1
self.QuickLinkList= []
self.WS_timer_count = 0
def WSRB_UD(self, event, opt):
print self.WS.RBvar.get()
You can use lambda to define an anonymous partial function:
RB.bind( "<Enter>", lambda event: self.WSRB_UD(event, i) )
You could also use functools.partial if you don't like the lambda syntax.

Categories

Resources