tkinter callback method not defined global name not defined - python

I would like to know why the methods SavePVParms Close and ApplyGraph are not defined when I call them through the buttons. I know that if I put them inside __init__ they will work and my problem is solved but I don't understand why it wouldn't work the way it is. I tried to look for this question and found this Python Class: Global/Local variable name not defined and this Python says global name not defined but forgive me if I can't understand the answer there. Could you help me understand why?
Background: This class is a top level window that popups when I press a button in the root window (here i am just showing you guys the toplevel window directly). It is supposed to pass a dictionary of all the entries when I save it (not yet implemented). **Additionally (but not required to answer if you don't want to) is this an adequate style of coding in OOP? (less than 1 week learning tkinter and moving from precedural python to OOP python).
import Tkinter as tk
import ttk
class PVDialog(tk.Toplevel):
def SavePVParms(self):
print "saved"
self.destroy()
def Close(self):
self.destroy()
def ApplyGraph(self):
print 'applied'
def __init__(self, parent):
tk.Toplevel.__init__(self, parent)
print parent
self.title('PV Parameters Configuration')
self.geometry('800x480')
self.resizable(width=False, height=False)
self.columnconfigure(0, weight=1)
self.columnconfigure(1, weight=8)
self.rowconfigure(0, weight=1)
self.rowconfigure(1, weight=2)
# ====== FRAMES =======
lb1Frame = tk.LabelFrame(self, text='Electric Properties')
lb1Frame.grid(row=0, column=0, sticky='ewns', pady=(15, 2), padx=(30,10))
AdvFrame = tk.LabelFrame(self, text='Thermal Properties')
AdvFrame.grid(row=1, column=0, sticky='ewns', pady=5, padx=(30,10))
pcurveFrame = tk.LabelFrame(self, text='PV Curves')
pcurveFrame.grid(row=0, column=1, sticky='ewns', padx=(0,30), pady=(15,5),rowspan=2)
ctrlFrame = tk.Frame(self)
ctrlFrame.grid(row=2, column=0, columnspan=2, sticky='ens', padx=20, pady=(2, 20))
# ======== PARAMETER INPUT DATA FRAME ============= #
labelName = tk.Label(lb1Frame, text='Name', anchor='w')
labelCellType = tk.Label(lb1Frame, text='Cell Type', anchor='w')
labelPower = tk.Label(lb1Frame, text='Rated Power [W]', anchor='w')
labelOV = tk.Label(lb1Frame, text='Open Voltage [V]', anchor='w')
labelSCC = tk.Label(lb1Frame, text='Short-circuit Current [A]', anchor='w')
labelMPC = tk.Label(lb1Frame, text='Maximum Point Current [I]', anchor='w')
labelMPV = tk.Label(lb1Frame, text="Maximum point Voltage [V]", anchor='w')
labelSeries = tk.Label(lb1Frame, text='Cells in series', anchor='w')
labelPallel = tk.Label(lb1Frame, text='Cells in parallel', anchor='w')
labelNOCT = tk.Label(AdvFrame, text='NOCT', anchor='w')
labelThC = tk.Label(AdvFrame, text='Current T factor [%/K]', anchor='w')
labelThV = tk.Label(AdvFrame, text='Voltage T factor [%/K]', anchor='w')
labelThP = tk.Label(AdvFrame, text='Power T factor [%/K]', anchor='w')
labelName.grid(row=0, sticky='ew', padx=10, pady=2)
labelCellType.grid(row=1, sticky='ew', padx=10, pady=2)
labelPower.grid(row=2, sticky='ew', padx=10, pady=2)
labelOV.grid(row=3, sticky='ew', padx=10, pady=2)
labelSCC.grid(row=4, sticky='ew', padx=10, pady=2)
labelMPC.grid(row=5, sticky='ew', padx=10, pady=2)
labelMPV.grid(row=6, sticky='ew', padx=10, pady=2)
labelSeries.grid(row=7, sticky='ew', padx=10, pady=2)
labelPallel.grid(row=8, sticky='ew', padx=10, pady=2)
labelNOCT.grid(row=9 , sticky='ew', padx=10, pady=2)
labelThC.grid(row=10, sticky='ew', padx=10, pady=2)
labelThV.grid(row=11, sticky='ew', padx=10, pady=2)
labelThP.grid(row=12, sticky='ew', padx=10, pady=2)
entryName = ttk.Entry(lb1Frame, show='Default')
entryCellType = ttk.Combobox(lb1Frame)
entryPower = ttk.Entry(lb1Frame)
entryOV = ttk.Entry(lb1Frame)
entrySCC = ttk.Entry(lb1Frame)
entryMPC = ttk.Entry(lb1Frame)
entryMPV = ttk.Entry(lb1Frame)
entrySeries = ttk.Entry(lb1Frame)
entryPallel = ttk.Entry(lb1Frame)
entryNOCT = ttk.Entry(AdvFrame, width=23)
entryThC = ttk.Entry(AdvFrame, width=23)
entryThV = ttk.Entry(AdvFrame, width=23)
entryThP = ttk.Entry(AdvFrame, width=23)
entryName.grid(row=0, column=1, sticky='ew')
entryCellType.grid(row=1, column=1, sticky='ew')
entryPower.grid(row=2, column=1, sticky='ew')
entryOV.grid(row=3, column=1, sticky='ew')
entrySCC.grid(row=4, column=1, sticky='ew')
entryMPC.grid(row=5, column=1, sticky='ew')
entryMPV.grid(row=6, column=1, sticky='ew')
entrySeries.grid(row=7, column=1, sticky='ew')
entryPallel.grid(row=8, column=1, sticky='ew')
entryNOCT.grid(row=9, column=1, sticky='w', padx=(26,0))
entryThC.grid(row=10, column=1, sticky='w', padx=(26,0))
entryThV.grid(row=11, column=1, sticky='w', padx=(26,0))
entryThP.grid(row=12, column=1, sticky='w', padx=(26,0))
# ==== BUTTON COMANDS ======
saveBttn = ttk.Button(ctrlFrame, text='Save', command=SavePVParms)
closBttn = ttk.Button(ctrlFrame, text='Cancel', command=Close)
applyBttn = ttk.Button(ctrlFrame, text='Apply', command=ApplyGraph)
saveBttn.grid()
closBttn.grid(row=0, column=1, padx=10)
applyBttn.grid(row=0, column=2, padx=(0, 10))
# ======== RUNNIG THE CLIENT ============
root = tk.Tk()
dialog = PVDialog(root)
root.mainloop()
Update: As I was making about to submit this question, I remembered that __init__ is the first thing that is read and executed in the Class, so that's why the are defined if I put them in __init__. I still don't understand why python can't call them if they are outside __init__ doesn't python read the whole class? how can I call then self.method that is outside of __init__ then?. Thanks!

SavePVParms, Close, ApplyGraph methods are belong to your class, so you need to use self. to tell interpreter that, you want to use methods from your class.
saveBttn = ttk.Button(..., command=self.SavePVParms)
For coding standarts, you should check PEP 8 but as long as you are consistent with your style, it shouldn't matter that much.

Related

Problem with positioning widgets right next to each other with tkinter

So, I have this piece of code:
import tkinter as tk
root = tk.Tk()
root.geometry("400x300")
root.columnconfigure(0, weight=1, minsize=75)
root.rowconfigure(0, weight=1, minsize=50)
subjects = ["None"]
frm_new_grade = tk.Frame()
lbl_new_grade = tk.Label(text="New Grade", font="System")
lbl_add_subject = tk.Label(frm_new_grade, text="Subject:", font="System")
lbl_add_subject.grid(row=0, column=0, sticky="E")
subject_value = tk.StringVar(frm_new_grade)
subject_value.set("Select an Option")
question_menu = tk.OptionMenu(frm_new_grade, subject_value, *subjects)
question_menu_menu = root.nametowidget(question_menu.menuname)
question_menu_menu.config(font="System")
question_menu.config(font="System")
question_menu.grid(row=0, column=1, sticky="W")
btn_new_subject = tk.Button(frm_new_grade, text="+", font="System", width=2)
btn_new_subject.grid(row=0, column=2, padx=5, sticky="W")
lbl_add_grade = tk.Label(frm_new_grade, text="Grade:", font="System")
lbl_add_grade.grid(row=1, column=0, sticky="E")
ent_add_grade = tk.Entry(frm_new_grade, font="System", width=18)
ent_add_grade.grid(row=1, column=1, columnspan=2, sticky="W")
btn_add_grade = tk.Button(frm_new_grade, text="Add Grade", font="System")
btn_add_grade.grid(row=2, column=1, sticky="W", pady=5)
frm_new_grade.grid()
root.mainloop()
My problem is that I want btn_new_subject to be right next to question_menu at all times, like when question_menu has "Select an Option", but not like when I select "None"
I tried to fix this using columnspan on ent_add_grade (like you see on my code), I thought that this with the sticky properties of btn_new_subject would make it so that they are right next to each other, but instead I get btn_new_subject placed like if it had no sticky property.
I'm quite new to programming and python so any feedback will be appreciated.
You can just set sticky="EW" in question_menu.grid(...) so that question_menu will fill the space horizontally in the cell.
Result:
There is typo error missing for lbl_new_grade.grid()
In line 12, add root for lbl_new_grade(frm_new_grade,
Set all row=x, column=0. The x denote 1, 2 and 3. While row=2
column=1. And for btn_add_grade.grid(row=3, column=1
If you like layout for root.geometry("235x150")
If you like w/out glitch for layout. Then set index to 1
root.columnconfigure(1,
It is up to you to decide.
Snippet for readability:
import tkinter as tk
root = tk.Tk()
root.geometry("235x150")
root.columnconfigure(0, weight=1, minsize=75)
root.rowconfigure(3, weight=1, minsize=50)
subjects = ["None"]
frm_new_grade = tk.Frame()
lbl_new_grade = tk.Label(frm_new_grade, text="New Grade", bg='red', font="System")
lbl_new_grade.grid(row=0, column=1)
lbl_add_subject = tk.Label(frm_new_grade, text="Subject:", font="System")
lbl_add_subject.grid(row=1, column=0, sticky="WE", pady=10)
subject_value = tk.StringVar(frm_new_grade)
subject_value.set("Select an Option")
question_menu = tk.OptionMenu(frm_new_grade, subject_value, *subjects)
question_menu_menu = root.nametowidget(question_menu.menuname)
question_menu_menu.config(font="System")
question_menu.config(font="System")
question_menu.grid(row=1, column=1, sticky="WE")
btn_new_subject = tk.Button(frm_new_grade, text="+", font="System", width=2)
btn_new_subject.grid(row=1, column=2, padx=5, sticky="WE")
lbl_add_grade = tk.Label(frm_new_grade, text="Grade:", font="System")
lbl_add_grade.grid(row=2, column=0, sticky="W")
ent_add_grade = tk.Entry(frm_new_grade, font="System", width=18)
ent_add_grade.grid(row=2, column=1, columnspan=2, sticky="W")
btn_add_grade = tk.Button(frm_new_grade, text="Add Grade", font="System")
btn_add_grade.grid(row=3, column=1, sticky="W", pady=5)
frm_new_grade.grid()
root.mainloop()
Screenshot:

Using a Tkinter Widget from Lower Down in the Program

So I'm trying to make this tkinter GUI in which you can add or search for a customer (the customer is stored in a SQlite database). Problem is, I can't see a way of making the def for when the add customer button is pressed, for as far as I know, I would need to use the names of the variables from lower down in the program where the widgets are created. I could switch the order of the functions around, but then the widgets would be calling functions that have not already been created. Can someone help fix this probem? Thanks in advance.
Here's the code:
from tkinter import *
app = Tk()
app.minsize(400,420)
app.title("Gatecode Manager")
app.resizable(False, False)
def add():
first = createWindow().firstAdd.get()
last = createWindow().firstAdd.get()
phone = createWindow().numberAdd.get()
def createWindow():
Label(text="Welcome to Gatecode Manager", font=("bold", 17)).grid(row=0, columnspan=2, pady=30, sticky=N)
Label(text="Search for a Customer").grid(row=1, column=0, padx=30, pady=10)
Label(text="First Name:").grid(row=2, column=0, padx=30, pady=10)
firstSearch = Entry().grid(row=3, column=0, padx=30)
Label(text="Last Name:").grid(row=4, column=0, padx=30, pady=10)
lastSearch = Entry().grid(row=5, column=0, padx=30)
Label(text="Add Customer to System").grid(row=1, column=1, padx=30, pady=10)
Label(text="First Name:").grid(row=2, column=1, padx=30, pady=10)
firstAdd = Entry().grid(row=3, column=1, padx=30)
Label(text="Last Name:").grid(row=4, column=1, padx=30, pady=10)
lastAdd = Entry().grid(row=5, column=1, padx=30)
Label(text="Phone Number:").grid(row=6, column=1, padx=30, pady=10)
numberAdd = Entry().grid(row=7, column=1, padx=30)
add = Button(text="Add", command="null").grid(padx=30, pady=10, column=1)
app.mainloop()
createWindow()
I find it difficult to understand exactly how you want this program to function. Is the add button supposed to call the function add() which then calls createWindow()?
I'm guessing a little here but what about:
1) Don't create the GUI in a function. A function will keep the variables in the scope of the functioin.
2) Use StringVar() to access entries. Since you don't save the identity of your entries it becomes impossible to interrogate them.
Example:
from tkinter import *
app = Tk()
app.minsize(400,420)
app.title("Gatecode Manager")
app.resizable(False, False)
def add():
first = firstAddVar.get()
last = firstAddVar.get()
phone = numberAddVar.get()
Label(text="Welcome to Gatecode Manager", font=("bold", 17)).grid(row=0,
columnspan=2, pady=30, sticky=N)
Label(text="Search for a Customer").grid(row=1, column=0, padx=30, pady=10)
Label(text="First Name:").grid(row=2, column=0, padx=30, pady=10)
firstSearchVar = StringVar()
firstSearch = Entry(textvar=firstSearchVar).grid(row=3, column=0, padx=30)
Label(text="Last Name:").grid(row=4, column=0, padx=30, pady=10)
lastSearchVar = StringVar()
lastSearch = Entry(textvar=lastSearchVar).grid(row=5, column=0, padx=30)
Label(text="Add Customer to System").grid(row=1, column=1, padx=30, pady=10)
Label(text="First Name:").grid(row=2, column=1, padx=30, pady=10)
firstAddVar = StringVar()
firstAdd = Entry(textvariable=firstAddVar).grid(row=3, column=1, padx=30)
Label(text="Last Name:").grid(row=4, column=1, padx=30, pady=10)
lastAddVar = StringVar()
lastAdd = Entry(textvariable=lastAddVar).grid(row=5, column=1, padx=30)
Label(text="Phone Number:").grid(row=6, column=1, padx=30, pady=10)
numberAddVar = StringVar()
numberAdd = Entry(textvariable=numberAddVar).grid(row=7, column=1, padx=30)
add = Button(text="Add", command=add).grid(padx=30, pady=10, column=1)
app.mainloop()
When your GUIs become more complex you should study OOP.

How to add more than one feature into the same cell using the grid method, Python Tkinter

There might be question like this, but I can't find it.
I want to have more than one entry or label etc. in the same cell without them overlapping. I hope you know what I mean.
Any ideas?
Put as many items as you want in a frame, and then put the frame in the grid cell.
Example
import tkinter as tk
root = tk.Tk()
# some random widgets, for illustrative purposes
l0 = tk.Label(root, text="Cell 0,0", borderwidth=1, relief="solid")
l1 = tk.Label(root, text="Cell 0,1", borderwidth=1, relief="solid")
l2 = tk.Label(root, text="Cell 1,0", borderwidth=1, relief="solid")
l3 = tk.Label(root, text="Cell 1,1", borderwidth=1, relief="solid")
l4 = tk.Label(root, text="Cell 1,2", borderwidth=1, relief="solid")
# create a frame for one of the cells, and put
# a label and entry widget in it
f1 = tk.Frame(root, borderwidth=1, relief="solid")
l5 = tk.Label(f1, text="Cell 0,2")
e1 = tk.Entry(f1)
# put the label and entry in the frame:
l5.pack(side="top", fill="both", expand=True)
e1.pack(side="top", fill="x")
# put the widgets in the root
l0.grid(row=0, column=0, padx=2, pady=2, sticky="nsew")
l1.grid(row=0, column=1, padx=2, pady=2, sticky="nsew")
f1.grid(row=0, column=2, padx=2, pady=2, sticky="nsew")
l2.grid(row=1, column=0, padx=2, pady=2, sticky="nsew")
l3.grid(row=1, column=1, padx=2, pady=2, sticky="nsew")
l4.grid(row=1, column=2, padx=2, pady=2, sticky="nsew")
root.mainloop()

tkinter ListBox and Label position

I am little bit comfused with grid system in tkinter Python. Can anyone show how to make it in right way?! ListBox and Label items positions are not in the places where I expexted to see them.
CODE:
self.third_label = Label(self, text="TEXT")
self.third_label.grid(row=2, column=0, columnspan=4, padx=10, pady=10, sticky=W)
self.fourth_label = Label(self, text="LONG TEXT")
self.fourth_label.grid(row=2, column=1, columnspan=4, padx=10, pady=10, sticky=W)
self.fifth_label = Label(self, text="SOME TEXT")
self.fifth_label.grid(row=2, column=2, columnspan=6, padx=10, pady=10, sticky=W)
self.sixth_label = Label(self, text="BIG TEXT")
self.sixth_label.grid(row=2, column=3, columnspan=4, padx=10, pady=10, sticky=W)
self.first_listbox = Listbox(self, width=40, selectmode=EXTENDED)
self.first_listbox.grid(row=3, column=0, columnspan=4, padx=10, pady=10, sticky=W)
self.second_listbox = Listbox(self, width=40, selectmode=EXTENDED)
self.second_listbox.grid(row=3, column=2, columnspan=4, padx=10, pady=10, sticky=W)
self.third_listbox = Listbox(self, width=40, selectmode=EXTENDED)
self.third_listbox.grid(row=3, column=4, columnspan=4, padx=10, pady=10, sticky=W)
self.fourth_listbox = Listbox(self, width=40, selectmode=EXTENDED)
self.fourth_listbox.grid(row=3, column=6, columnspan=4, padx=10, pady=10, sticky=W)
What I have right now:
Just Example:
The grid system works fine. The problem is your columnspans, which don't make much sense. You're gridding the widgets into certain column positions then giving them a columnspan that is beyond the range of where the next widget is to be gridded so on and so forth.
Small example:
import string
import tkinter as tk
root = tk.Tk()
for i in range(3):
tk.Label(root, text=string.ascii_letters).grid(row=0, column=i)
tk.Listbox(root, width=40).grid(row=1, column=i)
root.mainloop()
Edit from comments (for listbox size):
To get the number of lines in a listbox you can use the .size() method.
Image:

Tkinter update state (disable): need for redraw/refresh?

I cannot get the state of a (tk.)Label and (tk.)Checkbutton to visually turn to disable and normal depending on the value of an OptionMenu.
The command binding on the OptionMenu below seems fine, but my use of configure(state=...), in updateState() has no visible effect. Should I force some "refresh" or "repaint" (if yes how in the partial code below?) or am I missing something else?
import Tkinter
from Tkinter import Frame, LabelFrame, Label, Entry, Button, StringVar, OptionMenu, Checkbutton, IntVar, DISABLED, NORMAL
class GeneratorDialog:
# The UI to configure the settings by the user
def __init__(self, root, ctrl):
self.__root = root
self.__ctrl = ctrl
def updateState(self, value, label, entry):
if(value.get()==CONST.FORMAT_PDF): # test works, as the dialog below show in alternance as expected
tkMessageBox.showinfo('Info', message="enabling checkbox")
label.configure(state=NORMAL)
entry.configure(state=NORMAL)
else:
tkMessageBox.showinfo('Info', message="disabling the checkbox")
label.configure(state=DISABLED)
entry.configure(state=DISABLED)
#self.__root.update_idletasks() # how to force "redraw" with grid() manager?
def show(self):
self.__root.title(CONST.APP_NAME)
mainFrame = Frame(self.__root)
mainFrame.grid(sticky='ew')
outputFrame = LabelFrame(mainFrame, text='Output Settings')
outputFrame.grid(column=0, row=1, padx=5, pady=5, sticky='ew')
keepGeneratedScribusFilesLabel = Label(outputFrame, text='Keep Scribus Files:', width=15, anchor='e')
keepGeneratedScribusFilesLabel.grid(column=4, row=2, padx=5, pady=5, sticky='e')
keepGeneratedScribusFilesCheckbox = Checkbutton(outputFrame, variable=self.__ctrl.getKeepGeneratedScribusFilesCheckboxVariable(), anchor='w')
keepGeneratedScribusFilesCheckbox.grid(column=5, row=2, padx=5, pady=5, sticky='w')
mergeOutputLabel = Label(outputFrame, text='Merge in Single File:', width=15, anchor='w')
mergeOutputLabel.grid(column=0, row=2, padx=5, pady=5, sticky='w')
mergeOutputCheckbox = Checkbutton(outputFrame, variable=self.__ctrl.getMergeOutputCheckboxVariable())
mergeOutputCheckbox.grid(column=1, row=2, padx=5, pady=5, sticky='w')
outputFormatLabel = Label(outputFrame, text='Output Format:', anchor='e')
outputFormatLabel.grid(column=2, row=2, padx=5, pady=5, sticky='e')
outputFormatListBox = OptionMenu(outputFrame, self.__ctrl.getSelectedOutputFormat(), *self.__ctrl.getOutputFormatList(),
command=lambda l=keepGeneratedScribusFilesLabel, c=keepGeneratedScribusFilesCheckbox, v=self.__ctrl.getSelectedOutputFormat(): self.updateState(v, l, c))
outputFormatListBox.grid(column=3, row=2, padx=5, pady=5, sticky='w')
# Buttons to Cancel or to Run the Generator with the given Settings
helpButton = Button(buttonFrame, text='Help', width=10, command=self.__ctrl.helpButtonHandler)
helpButton.grid(column=0, row=0, padx=5, pady=5, sticky='e')
cancelButton = Button(buttonFrame, text='Cancel', width=10, command=self.__ctrl.buttonCancelHandler)
cancelButton.grid(column=1, row=0, padx=5, pady=5, sticky='e')
generateButton = Button(buttonFrame, text='Generate', width=10, command=self.__ctrl.buttonOkHandler)
generateButton.grid(column=2, row=0, padx=5, pady=5, sticky='e')
# Finally show the Generator Dialog
mainFrame.grid()
self.__root.grid()
self.__root.mainloop()
full code is at https://github.com/berteh/ScribusGenerator/blob/93a12de4be28054344533ad8fc424e37180668de/ScribusGenerator.py#L278
solved it by using class variables (self.X) instead of lambda local variables.
still don't know why the former code (with local variables in the lambda) would not update the status of the Tk elements (maybe it created copies thereof?)... but I found a workaround.
thanks all for your help

Categories