My Tkinter button command only runs through the loop once - python

I am writing an program for my job where I am working with the Smartsheet API.
Here is my code:
import smartsheet
import logging
from tkinter import *
token = "API KEY HERE"
matt_workspace = 7813091611174788
ss_client = smartsheet.Smartsheet(token) #Matt's workspace
ss_client.errors_as_exceptions(True)
#for gethering workspaces
workspace_response = ss_client.Workspaces.list_workspaces(include_all=True)
all_workspaces = workspace_response.data
workspaces_name_and_id= []
for x in all_workspaces:
workspaces_name_and_id.append(([str(x.name)], x.id))
selected_workspaces = []
def raise_frame(frame):
frame.tkraise()
#TODO set up UI root
import tkinter as tk # python 3
from tkinter import font as tkfont # python 3
def set_selected_workspaces(listbox):
for x in listbox.curselection():
selected_workspaces.append(workspaces_name_and_id[x])
return selected_workspaces
class WorkspacesPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="Workspace Selection", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
wkdirections = tk.Label(self,
text = "1. Press the 'gather' button to gather all your available workspaces. \n 2. Select the workspaces with sheets you want to edit from the list. (Holding ctrl selects individual, holding shift selects en masse)")
wkdirections.pack(side=RIGHT, anchor=CENTER)
WorkspaceList = Listbox(self, selectmode=EXTENDED, width=100, height=50)
WorkspaceList.pack(side=LEFT)
for x in workspaces_name_and_id:
WorkspaceList.insert(END, (x))
button = tk.Button(self, text="Next", command=lambda: [controller.show_frame("FoldersPage"), set_selected_workspaces(WorkspaceList) ,print(selected_workspaces)], padx=20, pady=3)
button.place(relx=0.975, rely=0.975, anchor=SE)
I removed two classes from this code I included that are not necessary (too keep it less messy than it already is (including the main App/Tk app)).
My problem is with the set_selected_workplaces function in the "Next" button on my "Workspace Selection" page.
Right now it is populating my list box with workspaces and their respective ID's. Then when I click next, I want it to loop through the selected workspaces in the listbox, and add it to my list "selected_workspaces" for use later in my program.
The problem is that when I click this, it is only adding the first selection in my listbox, thus leading me to believe its only going through the first loop in my for loop.
Any suggestions? Please go easy on me, I'm decently new at coding and I'm sure its a mess. If you need any clarification or more insight, please let me know, I'm baffled.

I think you are returning too early. Instead of this
def set_selected_workspaces(listbox):
for x in listbox.curselection():
selected_workspaces.append(workspaces_name_and_id[x])
return selected_workspaces
try this
def set_selected_workspaces(listbox):
for x in listbox.curselection():
selected_workspaces.append(workspaces_name_and_id[x])
return selected_workspaces
Your loop will return on the first iteration, which I don't believe is what you want.

Related

python Tkinter grid method not working as should be for some reason

i am trying to get my listbox to move to the bottom of the gui but no matter how high of a row value i give it it wont budge. you can see my listbox in the Creat_Gui method in my code. im not sure why this is happpening it cant be the button because the button is in row 1 so im not sure whats causing this.
i tried using sticky='s' that didnt work i tried changing the rows multiple times didnt work. i tried using the root.rowconfigure(100,weight=1) this worked kind of but messes with thte grid which is annoying
import tkinter as tk
class Manager:
def __init__(self):
self.root=tk.Tk()
self.root.title('password_manager')
self.root.geometry('500x600')
self.create_GUI()
self.storage = {}
self.root.mainloop()
def create(self):
pass
def open_page(self):
print('openpage')
def add_new_button(self):
pass
def add_new(self):
self.app_title=tk.Label(text='test')
self.app_title.grid(row=2,column=50)
self.application_name=tk.Entry(self.root,width=20,font=('arial',18))
self.username=tk.Entry(self.root,width=20,font=('arial',18))
self.password=tk.Entry(self.root,width=20,font=('arial',18))
self.application_name.grid(row=2, column=1)
self.username.grid(row=3, column=2)
self.password.grid(row=4, column=3)
self.app_name_label = tk.Label(self.root, text='Application Name:')
self.username_label = tk.Label(self.root, text='Username:')
self.password_label = tk.Label(self.root, text='Password:')
self.app_name_label.grid(row=2, column=0)
self.username_label.grid(row=3, column=1)
self.password_label.grid(row=4, column=2)
self.password.bind('<Return>',self.hide)
def hide(self,thing):
#store user info to pass onto dictionary and hide textboxes
username=self.username.get()
password=self.password.get()
app_name=self.application_name.get()
self.application_name.grid_forget()
self.username.grid_forget()
self.password.grid_forget()
self.add_to_memory(username,password,app_name)
def add_to_memory(self,username,password,app_name):
#store username password and application name in dictionary
if app_name in self.storage.keys():
return
else:
self.storage[app_name]=(username,password)
print(self.storage)
def create_GUI(self):
#create initial interface
#self.root.columnconfigure(100, weight=1)
#self.root.rowconfigure(100, weight=1)
self.listbox=tk.Listbox(self.root,width=100)
self.listbox.grid(row=200,column=0)
self.button=tk.Button(self.root,text='add new',font=('arial',18),command=self.add_new)
self.button.grid(row=1,column=0)
Manager()
If you want to use grid, and you want a widget to be at the bottom of it's parent, you need to designate some row above that widget to take all of the extra space, forcing the widget to the bottom. Here's one example:
def create_GUI(self):
self.listbox=tk.Listbox(self.root,width=100)
self.expando_frame = tk.Frame(self.root)
self.button=tk.Button(self.root,text='add new',font=('arial',18),command=self.add_new)
self.root.grid_rowconfigure(2, weight=1)
self.button.grid(row=1,column=0)
self.expando_frame.grid(row=2, sticky="nsew")
self.listbox.grid(row=3,column=0)
With that, the listbox will be at the bottom, with extra space above it. If you want to add more widgets, you can add them to self.expando_frame rather than self.root and they will appear above the listbox.
Using frames in this way is a valuable technique. You could, for example, use three frames in root: one for the top row of buttons, one for the listbox on the bottom, and one for everything in the middle. You could then use pack on these frames and save a line of code (by virtue of not having to configure the rows). You can then use grid for widgets inside the middle frame.

How do I keep label text side by side with label value in tkinter

I am trying to keep the label text value: "This is the subtotal" next to subtotal value. Meaning:
If I were to click on the "Calulate Subtotal" Button the text "This is the subtotal" should be to the right and the actual subtotal should be to the left. Currently, If I were to click on the "Calulate Subtotal" Button the text "this is the subtotal" disappears. Can someone steer me in the right direction?
try:
import Tkinter as tk
except:
import tkinter as tk
class GetInterfaceValues():
def __init__(self):
self.root = tk.Tk()
self.root.geometry('500x200')
self.button = tk.Button(self.root,
text='Calculate Subtotal',
command=self.getSubtotals)
self.button.pack()
self.firstLabel = tk.Label(self.root, text="This is the subtotal:")
self.firstLabel.pack()
self.root.mainloop()
def getSubtotals(self):
self.firstLabel["text"] = 55*10
app = GetInterfaceValues()
You can simply change your getSubtotals method to retain the current text of firstLabel as the following:
def getSubtotals(self):
self.firstLabel["text"] = self.firstLabel["text"] + str(55 * 10)
Couple of suggestions:
You might want to create another widget to show subtotal value other than firstLabel.
You might want to restructure your class so that you only initialize the attributes in the init method
Please check the indentations and code formatting when asking questions to make it easier for others to inspect your code

How to display text beside cursor in tkinter Listbox

I have this few lines of code which print the selected content in the Listbox when double clicked but i want to display text beside the cursor like double click the selected content to print to prompt the user to take such action before it can be printed.
I search the listbox documentation and found the is an attribute cursor which you set the cursor type to display when the widget has focus but didn't find something like cursor-text to do that.Is the a way i can work around to achieve that, your suggestions are welcomed.
from tkinter import *
def test(event=None):
print("woow test works")
print(l.get(ACTIVE))
root = Tk()
l = Listbox(root, cursor="tcross")
l.pack()
l.insert(END, ("today"),("tomorrow"))
l.bind("<Double-Button-1>", test)
root.mainloop()
I don't quite understand your English, but you can see this page for an example of using tooltips, which make text appear on mouse hover.
Yes, it's possible to add specific info or comments to listbox lines using Hovertip from idlelib.tooltip. In the example below, Mark Lutz's customizable scrolled listbox works normally, except that right-clicking on any line opens a tip with info/comments for that line.
Ref. : Programming Python 4th ed. Example 9-9
from tkinter import *
from idlelib.tooltip import Hovertip
class ScrolledList(Frame):
def __init__(self, options, parent=None):
Frame.__init__(self, parent)
self.pack(expand=YES, fill=BOTH)
botfrm = Frame(parent)
botfrm.pack(side=BOTTOM)
Label(botfrm,
text="Select a line and right-click for info").pack()
self.makeWidgets(options)
self.myTip = None
def handleList(self, event):
if self.myTip:
self.myTip.__del__()
label = self.lstbx.get(ACTIVE)
self.runCommand(label)
def overflyLine(self, event):
if self.myTip:
self.myTip.__del__()
self.myTip = Hovertip(self.lstbx,f"Comments for {self.lstbx.get(ACTIVE)}")
self.myTip.showtip()
def makeWidgets(self, options):
sbar = Scrollbar(self)
list = Listbox(self, relief=SUNKEN, bg='misty rose')
sbar.config(command=list.yview)
list.config(yscrollcommand=sbar.set)
sbar.pack(side=RIGHT, fill=Y)
list.pack(side=LEFT, expand=YES, fill=BOTH)
for label in options:
list.insert(END, label)
self.lstbx = list
list.bind('<Button-3>', self.overflyLine)
list.bind('<Double-1>', self.handleList)
list.bind('<Return>', self.handleList)
def runCommand(self, selection):
print('You selected:', selection)
if __name__ == '__main__':
options = (('Lumberjack-%s' % x) for x in range(20))
scr = ScrolledList(options).mainloop()

tkinter, "refresh" the mainwindow

Though I think that the solution might be similar to this one: tkinter, display a value from subwindow in mainwindow , I still decided to ask this question since I have trouble to figure it out on my own.
I have the list "fields" with which I am creating any given number of rows, with two labes inside of them. After opening a subwindow, I want to be able to manipulate that list (in my example simply just append at the moment) and after clicking on a button (here "ADD"), I want the mainwindow to update, so that it shows the rows of the manipulated list. It works fine for the most part but I dont what is the best way to update the mainwindow in this example.
The only solution I was able to come up with, is to destroy the mainwindow and recreate it but I have the feeling that this might not be the best solution. Is there a better one?
import tkinter as tk
a=0
fields=[("a",1),("c",2),("e",3)]
class clsApp(object):
def __init__(self):
self.root=tk.Tk()
self.root.title("MainWindow")
##Labels##
self.rootLabel=tk.Label(self.root, text="WindowAppExperiment", padx=100)
self.aLabel=tk.Label(self.root, text=a, padx=100)
##Buttons##
self.BtExit=tk.Button(self.root, text="Quit", fg="red", command=self.root.quit)
###self.BtNewWindow=tk.Button(self.root, text ="Edit", command=lambda:self.clsNewWindow(self.root, self.aLabel).run())
self.BtNewField=tk.Button(self.root, text ="New Field", padx=30, command=lambda:self.clsNewFields(self.root).run())
def grid (self):
self.rootLabel.pack()
self.aLabel.pack()
self.fckPackFields()
self.BtNewField.pack()
self.BtExit.pack(side="left")
###self.BtNewWindow.pack(side="right")
def fckPackFields(self):
if fields:
for field in fields:
##create labels##
row=tk.Frame(self.root)
nameLabel=tk.Label(row, text =field[0], width=20, anchor="w")
valueLabel=tk.Label(row, text =field[1], width=5)
##pack labels##
row.pack(side="top", fill="x", padx=5, pady=5)
nameLabel.pack(side="left")
valueLabel.pack(side="right", expand=True, fill="x")
def run(self):
self.grid()
self.root.mainloop()
self.root.destroy()
class clsNewFields(object):
def __init__(self, Parent):
self.parent=Parent
##Window##
self.top=tk.Toplevel()
self.top.title("Add Fields")
##Labels##
self.enterNameLabel=tk.Label(self.top, text ="Enter fieldname", padx=10)
self.enterValueLabel=tk.Label(self.top, text ="Enter value", padx=10)
##Entryfields##
self.EntryName=tk.Entry(self.top)
self.EntryValue=tk.Entry(self.top)
##Buttons##
self.BtADD=tk.Button(self.top, text ="ADD", command=lambda:self.fckAddField(self.EntryName, self.EntryValue))
self.BtClose=tk.Button(self.top, text ="Close", command=self.top. quit)
def grid(self):
self.enterNameLabel.pack()
self.enterValueLabel.pack()
self.EntryName.pack()
self.EntryValue.pack()
self.BtADD.pack()
self.BtClose.pack()
def fckAddField(self, Name, Value):
self.name=Name.get()
self.value=Value.get()
global fields
fields.append((self.name, self.value))
print(fields)
self.parent.update
def run(self):
self.grid()
self.top.mainloop()
self.top.destroy()
clsApp().run()
Welcome to StackOverflow.
First of all - do you really want to declare the clsNewFields inside your clsApp ? Yes, the Fields should be used inside App, but i do not see a need for using class-in-class-declaration.
Second - you are packing the Fields in def fckPackFields(self):. This is not automatically called when you update it.
You are not calling update function by using self.parent.update.
You are using global variable for fields, what does not really suit your needs. Why not having a list inside your App-class like:
def __init__(self):
self.__fields=[]
def __set_fields(self, value):
self.__fields=value
def __get_fields(self):
return self.__fields
Fields = property(__get_fields, __set_fields)
def __loadUI(self, event=None):
# This function should be called at the end of __init__
self.fieldFrame=tk.Frame(self.root)
self.fieldFrame.pack(side="top")
def fckPackFields(self):
#First clean area
[...]
#Then add fields
for field in self.__fields:
# create the row, etc.
# !!! but do it inside self.fieldFrame !!!
[...]
I would prefer using grid instead of pack over here, because there I think it is easier to place a frame at a certain position, then you could just destroy self.fieldFrame and recreate it at the same position for placing the fields in it.
UPDATE:
Just checked your code again. With some simple tricks your can tweak your GUI to do what you want:
def __init__(self):
self.fieldFrame=None #this line has been added
#completely reworked this function
def grid(self):
self.rootLabel.grid(row=1, column=0, columnspan=2, sticky=tk.NW+tk.SE)
self.fckPackFields()
self.BtNewField.grid(row=3, column=0, sticky=tk.NW+tk.SE)
self.BtExit.grid(row=3, column=1, sticky=tk.NW+tk.SE)
#Only one line changed / one added
def fckPackFields(self):
self.__cleanFields() #added this line, function beyond
if fields:
for field in fields:
##create labels##
row=tk.Frame(self.fieldFrame) #add the row to the fieldFrame
[...]
#In here we destroy and recreate fieldFrame as needed
def __cleanFields(self):
if self.fieldFrame:
self.fieldFrame.destroy()
##FieldFrame##
self.fieldFrame=tk.Frame(self.root)
self.fieldFrame.grid(row=2, column=0, columnspan=2)
In clsNewFields:
def fckAddField(self, Name, Value):
[...]
self.parent.fckPackFields() # instead of self.parent.update
EDIT:
Have a look at these two questions:
Class inside a Class
Benefit of nested Classes
I did not mean to point out that nested classes are to be avoided in general but I do want to focus you into the thought of "is there a real necessity or benefit of it for my use-case".

how to stack two widgets and switch between them?

I want to script two widgets which would be stacked and I would switch from one to another with a key. Each widget would be a Frame containing several Labels. I have the following code so far (only one Label per Frame):
import Tkinter as tk
import ttk
import datetime
def main():
# initialize root
root = tk.Tk()
# initialize widgets
dash = Dashboard(root)
notepad = Notepad(root)
# set key actions
root.bind('<F11>', root.lift)
root.bind('<F1>', dash.raiseme)
root.bind('<F2>', notepad.raiseme)
root.mainloop()
class Dashboard(ttk.Frame):
def __init__(self, parent):
ttk.Frame.__init__(self, parent) # voodoo
self.dashframe = tk.Frame(parent)
self.labone = tk.Label(self.dashframe, text="lab1", fg='black', bg='blue')
self.labone.grid(row=0, column=0)
def raiseme(self, event):
print "raiseme dash"
self.labone.configure(text=datetime.datetime.now())
self.dashframe.lift()
class Notepad(ttk.Frame):
def __init__(self, parent):
ttk.Frame.__init__(self, parent) # also voodoo
self.noteframe = tk.Frame(parent)
self.laboneone = tk.Label(self.noteframe, text="lab11", fg='white', bg='red')
self.laboneone.grid(row=0, column=0)
def raiseme(self, event):
print "raiseme notepad"
self.laboneone.configure(text=datetime.datetime.now())
self.noteframe.lift()
if __name__ == '__main__':
main()
Pressing F1 and F2 reach the correct routines but the only thing I get is the main window, empty. There are no errors displayed so I guess that the code runs fine (just not the way O would like to :)).
Can I achieve the switch using the skeleton above?
There are at least two big problems with your code.
First, you're creating all these new frames, but not placing them anywhere, so they will never show up anywhere. If you have a main window with nothing placed on it, of course you will just "get the main window, empty". You need to call pack or some other layout method on any widget to get it to show up on its parent. In this case, it sounds like you want to put them both in the exact same place, so grid or place is probably what you want.
Second, your Dashboard and Notepad classes are themselves Frames, but they don't do any Frame-ish stuff; instead, they each create another, sibling Frame and attach a label to that sibling. So, even if you packed the Dashboard and Notepad, they're just empty frame widgets, so that wouldn't do any good.
If you fix both of those, I think your code does what you want:
class Dashboard(ttk.Frame):
def __init__(self, parent):
ttk.Frame.__init__(self, parent) # voodoo
self.labone = tk.Label(self, text="lab1", fg='black', bg='blue')
self.labone.grid(row=0, column=0)
self.grid(row=0, column=0)
def raiseme(self, event):
print "raiseme dash"
self.labone.configure(text=datetime.datetime.now())
self.lift()
class Notepad(ttk.Frame):
def __init__(self, parent):
ttk.Frame.__init__(self, parent) # also voodoo
self.laboneone = tk.Label(self, text="lab11", fg='white', bg='red')
self.laboneone.grid(row=0, column=0)
self.grid(row=0, column=0)
def raiseme(self, event):
print "raiseme notepad"
self.laboneone.configure(text=datetime.datetime.now())
self.lift()
However, you might also want to set a fixed size for everything; otherwise you could end up lifting the red widget and, e.g., only covering 96% of the blue one because the current time is a bit narrower than the previous oneā€¦
The code you linked to for inspiration attempted to do this:
newFrame = tkinter.Frame(root).grid()
newFrame_name = tkinter.Label(newFrame, text="This is another frame").grid()
That won't work, because grid returns None, not the widget. But at least it calls grid; yours doesn't even do that.

Categories

Resources