I am writing a program which analyses a CSV file containing my credit card monthly statement. I am trying to create a gui with tkinter in which I have a row of label headings followed by rows which consist of 3 more labels and then a combobox in the 4th column which has a number of categories. I have written a class which defines each row of the table. I want to pass a value to the instance of the class which specifies the initial value for the combobox. This is different for each instance or row. Or at least should be....
My problem is that in passing in different numbers for each row/instance I always get all rows having the value specified for the first created instance.
My classes are as follows:
class CatFormApp:
def __init__(self,parent):
self.parent = parent
self.datelabel = Tkinter.Label(root, text='Date',font ='Verdana 12 bold',borderwidth=5).grid(row=0,column=0)
self.transactionlabel = Tkinter.Label(root, text='Transactions',font ='Verdana 12 bold',borderwidth=5).grid(row=0,column=1)
self.paidoutlabel = Tkinter.Label(root, text='Paid Out',font ='Verdana 12 bold',borderwidth=5).grid(row=0,column=2)
self.catlabel = Tkinter.Label(root, text='Category',font ='Verdana 12 bold',borderwidth=5).grid(row=0,column=3)
class RowFormat:
def __init__(self,parent,rownumbval,defaultval,dateval,transactionval,paidoutval,CatTupleVals,bkg):
self.parent = parent
self.datevallabel = Tkinter.Label(root, text=dateval, borderwidth=5).grid(row=rownumbval,column=0)
self.transactionvallabel = Tkinter.Label(root, text=transactionval, borderwidth=5, background = bkg).grid(row=rownumbval,column=1)
self.paidoutvallabel = Tkinter.Label(root, text=paidoutval, borderwidth=5).grid(row=rownumbval,column=2)
self.combo(rownumbval,defaultval,CatTupleVals)
def combo(self,rownumb,default,CatTuple):
self.box_value = '1'
self.box = ttk.Combobox(self.parent,textvariable = self.box_value)
self.box['values']=CatTuple
self.box.current(default)
self.box.grid(row=rownumb,column=3)
The relevant bit of my main code is:
root = Tkinter.Tk()
app = CatFormApp(root)
row = ['']*(len(CreditData['Transactions'])+1)
for r in range(1,len(CreditData['Transactions'])):
if CreditData['Paid out'][r-1] != '':
if noMatch[r-1] == True:
print 1
row[r] = RowFormat(root,r,1,CreditData['Date'][r-1],CreditData['Transactions'][r-1],CreditData['Paid out'][r-1][1:],tuple(CatHeadings[:-1]),bkg = 'red')
else:
print 2
row[r] = RowFormat(root,r,2,CreditData['Date'][r-1],CreditData['Transactions'][r-1],CreditData['Paid out'][r-1][1:],tuple(CatHeadings[:-1]),bkg = 'white smoke')
root.mainloop()
In the above example all comboboxes have the value associated with 1. Whereas I should get some with a 1 and some with a 2 depending on the if statement. I'm puzzled because I do something very similar for the other label values passed and these all come out differently as expected.
If someone could explain where I'm going wrong I'd be really grateful
This code is wrong:
self.box_value = '1'
self.box = ttk.Combobox(...,textvariable = self.box_value)
The value for the textvariable attribute must be an instance of the tkinter class StringVar (or one of the other special tkinter variables).
Use it like this:
self.box_value = Tkinter.StringVar()
self.box = ttk.Combobox(self.parent,textvariable = self.box_value)
To set values, use:
self.box['values'] = ('A', 'B', 'C')
To select the first item, use:
self.box.current(0)
Related
I am remaking a GUI calculator app in Tkinter, in order to learn about classes, methods, attributes, and also to shorten my original code. In order to shorten the code, I made a frame class that generates frames, entries, labels and dropdown menus, so I don't have to create them individually. Everything went well until I got to the dropdown menu part. When the user selects a different option from the Filters - dropdown menu like V, or B or L etc. the value in frame 1 -> entry[1] doesn't update. The method that updates the value in that entry is called add(self) and it's a part of calculator class.
Here is the simple version
import numpy as np
import tkinter as tk
window = tk.Tk()
window.geometry("920x500")
window.resizable(0,0)
window.title('Exposure Time Calculator')
class Calculator:
def __init__(self, window):
self.create_test_frame1()
self.create_test_frame2()
self.add(None)
def create_test_frame1(self):
labelvalues=['val 1','val 2']
entryvalues=['203','1333']
self.frame_1 = frame('Test Frame 1',labelvalues,entryvalues,6, 2, 0, 0, "no",0,0,0,0,0,30,40)
def create_test_frame2(self):
labelvalues = ['val 3','val 4']
entryvalues = ['10','24.5']
option_menu_values = ['B','V','R','I','Luminance','Hydrogen 3nm']
self.frame_2 = frame('Frame 2', labelvalues, entryvalues, 14, 2, 0, 2,
"option_menu1_yes", option_menu_values,'Filters',
0,0,0,
5,20)
def add(self, e):
qe = self.frame_1.entry[1]
bandOption = self.frame_2.clicked.get()
if bandOption == "B":
qe.delete(0,tk.END)
qe.insert(0,22)
elif bandOption == "V":
qe.delete(0,tk.END)
qe.insert(0,33)
class frame:
# Creates a frame class for automatic frame generation
# with entries, labels and/or option menus
# 1. name : frame name
# 2. label_default_values: name of labels
# 3. entry_default_values: default values in entries
# 4. entry_width: the entries dimensions
# 5. I: number of labels and entries
# 6. grid_row: frame grid row placement
# 7. grid_column: frame grid column placement
# 8. option_menu: true or false if user wants a option list or not
# 9. option_list_values: list for option menu
# 10. option_label: name for option menu label
# 11. ipax, ipady: padding
# 12. comand: comand for option list
def __init__(self, name, label_default_values, entry_default_values, entry_width, I, grid_row, grid_column,
option_menu1, option_list_values, option_label,
option_menu2, option2_list_values,option_label2,
ipad_x, ipad_y
):
self.name = name
self.label_default_values = label_default_values
self.entry_default_values = entry_default_values
self.I = I
self.grid_row = grid_row
self.grid_column = grid_column
self.dropMenu_options = option_list_values
self.label = option_label
self.entry_width = entry_width
self.dropMenu_options2 = option2_list_values
self.option_label2 = option_label2
self.ipad_x = ipad_x
self.ipad_y = ipad_y
frame = tk.LabelFrame(window, text = name, highlightbackground='grey', highlightthickness=1)
frame.grid(row=self.grid_row, column=self.grid_column, padx=5, pady=5, ipadx=ipad_x, ipady=ipad_y)
if option_menu1 == "option_menu1_yes":
self.clicked = tk.StringVar()
self.clicked.set(self.dropMenu_options[0])
self.drop = tk.OptionMenu(frame, self.clicked, *self.dropMenu_options, command = self.add)
self.drop.grid(row=5, column=1, sticky="ew")
label = tk.Label(frame, text = option_label, highlightbackground='grey', highlightthickness=1)
label.grid(row = 5, column = 0, sticky = "w")
if option_menu2 == "option_menu2_yes":
self.clicked2 = tk.StringVar()
self.clicked2.set(self.dropMenu_options2[0])
self.drop2 = tk.OptionMenu(frame, self.clicked2, *self.dropMenu_options2)
self.drop2.grid(row=6, column=1, sticky="ew")
label = tk.Label(frame, text = option_label2, highlightbackground='grey', highlightthickness=1)
label.grid(row = 6, column = 0, sticky = "w")
self.entry ={}
for i in range(0, self.I):
label = tk.Label(frame, text = self.label_default_values[i], justify = "left")
label.grid(row=i, column=0, sticky = "w")
self.entry[i] = tk.Entry(frame, textvariable = float(self.entry_default_values[i]), width=self.entry_width)
self.entry[i].grid(row=i, column=1, sticky = "e")
self.entry[i].delete(0, tk.END)
self.entry[i].insert(0, self.entry_default_values[i])
c=Calculator(window)
window.mainloop()
The method add is in the Calculator class, so instead of self.add you need to call add on the calculator. Since the frame doesn't know what the calculator is, you need to pass it in when constructing the frame.
Something like the following, where the calculator instance is passed as the first option:
self.frame_1 = frame(self, 'Test Frame 1', ...)
Next, you need to define your class to accept and save the reference to the calculator and then use it in the command of the OptionMenu:
class frame:
def __init__(self, calculator, name, ...):
self.calculator = calculator
...
self.drop = tk.OptionMenu(..., command = self.calculator.add)
Also, you define add like this:
def add(self, e):
I assume that means you think the second parameter is an event object. It is not. It is the value that was picked from the optionmenu.
Arguably, a better way to define this would be to actually use this new value if provided, and fall back to calling get if a value isn't provided. Also, you can reduce the wall of if statements into a single dictionary lookup to make the code shorter and more robust.
def add(self, new_value=None):
qe = self.frame_1.entry[1]
bandOption = self.frame_2.clicked.get() if new_value is None else new_value
band = {"B": 22, "V": 33}
qe.delete(0, "end")
qe.insert(0, band[bandOption])
This solution is 2/3 the size of your original, and more flexible and easier to maintain.
There are 2 problems:
The first one is that you mentioned and to fix it:
rename def add(self) to def add(self, e) and rename add() to add(None). Then change lambda event: self.add to self.add
The second one is:
AttributeError: 'frame' object has no attribute 'frame_camera'
but is not question related
It works if I define add(event) outside classes.
def add(event):
qe = c.frame_1.entry[1]
bandOption = c.frame_2.clicked.get()
if bandOption == "B":
qe.delete(0,tk.END)
qe.insert(0,22)
elif bandOption == "V":
qe.delete(0,tk.END)
qe.insert(0,33)
And this in the frame class:
self.drop = tk.OptionMenu(frame, self.clicked, *self.dropMenu_options, command = lambda event:add(event))
I'm trying to create a hangman game which produces a random amount of entries. Problem is, I can't get the unique value of each entry.
This is what I have tried so far:
from tkinter import *
import random
root = Tk()
root.geometry('500x500')
color = 'blue'
a1 = Canvas(root, height = 500, width = 500, bg = color)
a1.pack()
#Variable Declaration
var = StringVar()
x = ''
words = ['penguin','wolves','skyscraper','dinosaur','time travel']
lists = {}
def Enter():
for i in lists:print(lists[i])
x = 'entry'
a=150
b=350
for i in range(len(random.choice(words))):
lists[f'entry{i}'] = Entry(textvariable = StringVar(),width =3, font = 'tahoma',justify=CENTER).place(x=a,y=b)
a+=30
b1 = Button(root, text = "Enter",command=Enter)
b1.place(x=225,y=400)
root.mainloop()
The first thing you must do is separate the creation of the widget from the layout of the widget. In python, x().y() returns the value of y(), so Entry(...).place(...) returns the value of place(...) and place(...) always returns None.
The second thing I would do is not use f'entry{i}' for the index into your lists. It will work, but it's more cumbersome than it needs to be.
for i in range(len(random.choice(words))):
lists[i] = Entry(textvariable = StringVar(),width =3, font = 'tahoma',justify=CENTER)
lists[i].place(x=a,y=b)
a+=30
The above lets you easily reference any entry widget with something like lists[0], lists[1], etc.
Finally, in your Enter method (which should be named enter according to PEP8 guidelines), you can directly loop over the entries without using the index at all:
def Enter():
for index, entry in lists.items():
value = entry.get()
print(value)
In your case I would advice the make 1 input field for text and split the letter later.
A string in python can be accessed like a list, you have len(string) and string[3] gives the 4 letter ect.
I'm new in Python and I'm currently trying to use tkinter as first GUI. I was used to making it without classes. And it is my first time to use import tkinter as tk instead of import *
import tkinter as tk
def update():
pass
#Game.statsFrame #doesn't work Game.statsFrame.stat1_amountLabel too
#Game.stat1_amountLabel #doesnt work < want to use update_idletasks() or
#just type new cofnig...
#just errors like: "Game' has no attribute 'statsFrame" etc #Game
class character:
name = ""
experience = 0
level = 0
gold = 0
stat1 = 0
stat2 = 0
stat3 = 0
stat4 = 0
stat5 = 0
avaiblePoints = 0
def add_stat1(self):
if self.avaiblePoints >= 1:
self.stat1 += 1
self.avaiblePoints -= 1
update()
else:
pass
def add_stat2(self):
if self.avaiblePoints >= 1:
self.stat2 += 1
self.avaiblePoints -= 1
update()
[...]
myChar = character()
myChar.avaiblePoints = 3
class Game:
def __init__(self, parent):
self.myParent = parent
self.myGame = tk.Frame(parent)
self.myGame.grid()
self.statsFrame = tk.Frame(self.myGame).grid()
self.stat1Label = tk.Label(self.statsFrame)
self.stat1Label.config(text="Strength:")
self.stat1Label.grid(column=1, row=1)
self.stat1_amountLabel = tk.Label(self.statsFrame)
self.stat1_amountLabel.config(text=myChar.stat1)
self.stat1_amountLabel.grid(column=2, row=1)
self.add_stat1Button = tk.Button(self.statsFrame)
self.add_stat1Button.config(text="+", command=myChar.add_stat1)
self.add_stat1Button.grid(column=3, row=1)
root = tk.Tk()
myapp = Game(root)
root.mainloop()
But I can't get to (for example) stat1Label and change text inside it and after it use update_idletasks(). It's like it doesnt exist. Errors shows that Game has not atributtes like stat1Label etc.
I want to use it becouse I have read that __init__ method is better and I want to swtich between pages. I have no idea, when I wasn't using class in tkinter some things (like this) was easier and had no problems. I'm very confused guys.
It's excellent that you're using import tkinter as tk instead of the dreaded "star" import, and that you're trying to organize your code with classes. It can be a little confusing at first, but it makes your code more modular, which helps enormously, especially when the GUI gets large.
There are a few problems with your code. The most important one is this line:
self.statsFrame = tk.Frame(self.myGame).grid()
The .grid method (and .pack and .place) all return None. So that line saves None to self.statsFrame, not the Frame widget. So when you later try to do stuff with self.statsFrame it won't do what you expect.
Another problem is that the text attribute of your self.stat1_amountLabel doesn't track the value of myChar.stat1, so when you change the value of myChar.stat1 you need to explicitly update the Label with the new value. Alternatively, you could use the textvariable attribute with an IntVar to hold the character's stat. See the entry for textvariable in the Label config docs for info.
Your character class has a whole bunch of attributes like name, experience etc as class attributes. That's not a good idea because class attributes are shared by all instances of a class. But you probably want each character instance to have their own instance attributes. So you should give character an __init__ method where you set those attributes. OTOH, it's ok to use class attributes for default values that get overridden by instance attributes.
Anyway, here's a repaired version of your code with a Button that updates the Strength stat. I've put the stats in a list, rather than having a bunch of separate named stats that would have to be managed separately. And I've given Game a make_stat method so you can easily add rows for the other stats.
import tkinter as tk
class Character:
def __init__(self, availablePoints=0):
self.name = ""
self.experience = 0
self.level = 0
self.gold = 0
self.stats = [0] * 5
self.availablePoints = availablePoints
def add_stat(self, idx):
if self.availablePoints >= 1:
self.stats[idx] += 1
self.availablePoints -= 1
class Game:
def __init__(self, parent):
self.myParent = parent
self.myGame = tk.Frame(parent)
self.myGame.grid()
self.statsFrame = tk.Frame(self.myGame)
self.statsFrame.grid()
self.make_stat("Strength:", 0, 0)
def make_stat(self, text, idx, row):
label = tk.Label(self.statsFrame, text=text)
label.grid(column=1, row=row)
amount = tk.Label(self.statsFrame, text=myChar.stats[idx])
amount.grid(column=2, row=row)
def update():
myChar.add_stat(idx)
amount["text"] = myChar.stats[idx]
button = tk.Button(self.statsFrame, text="+", command=update)
button.grid(column=3, row=row)
myChar = Character(3)
root = tk.Tk()
myapp = Game(root)
root.mainloop()
This code is still not ideal, but it's an improvement. ;) For example, it would be good to give Game a method for creating new characters, rather than creating them in the global context. You could store them in a dict attribute of Game, using the character's name as the key.
Here's a new version that works on separate named stat attributes. As I said in the comments, doing it this way is more complicated (and less efficient) than using a list to hold the stats.
import tkinter as tk
class Character:
def __init__(self, availablePoints):
self.name = ""
self.experience = 0
self.level = 0
self.gold = 0
self.stat1 = 0
self.stat2 = 0
self.stat3 = 0
self.stat4 = 0
self.stat5 = 0
self.availablePoints = availablePoints
class Game:
def __init__(self, parent):
self.myParent = parent
self.myGame = tk.Frame(parent)
self.myGame.grid()
self.statsFrame = tk.Frame(self.myGame)
self.statsFrame.grid()
self.make_stat("Strength:", "stat1", 1, 1)
def make_stat(self, text, stat, column, row):
label = tk.Label(self.statsFrame, text=text)
label.grid(column=column, row=row)
amount = tk.Label(self.statsFrame, text=getattr(myChar, stat))
amount.grid(column=(column+1), row=row)
def update():
if myChar.availablePoints >= 1:
v = getattr(myChar, stat) + 1
setattr(myChar, stat, v)
myChar.availablePoints -= 1
amount["text"] = v
button = tk.Button(self.statsFrame, text="+", command=update)
button.grid(column=(column+2), row=row)
myChar = Character(5)
root = tk.Tk()
myapp = Game(root)
root.mainloop()
im using tkinter with class abd im having trouble with adding a product
class Add_Page():
def __init__(self, child):
self.child = child
child.title = "Ajouter"
self.l1=Label(child,text="Ajouter produit :",bg="blue").grid(row=0,columnspan=2)
self.l2=Label(child,text="Matricule").grid(row=1,column=0)
self.vlrm = StringVar()
self.en2 = Entry(child, textvariable=self.vlrm, width=30).grid(row=1,column=1)
self.l3=Label(child,text="Nom").grid(row=2,column=0)
self.vlrn = StringVar()
self.en3 = Entry(child, textvariable=self.vlrn, width=30).grid(row=2,column=1)
self.l4=Label(child,text="Prix").grid(row=3,column=0)
self.vlrp = IntVar()
self.en4 = Entry(child, textvariable=self.vlrp, width=30).grid(row=3,column=1)
self.b2=Button(child,text="Valider",command=self.add_p).grid(row=4,columnspan=2)
#Add product function
def add_p(self):
print(self.vlrm.get())
print(self.vlrp.get())
the results are anempty chaine and 0
i dont seem to find the problem especially that i used the get method in users class and its working just fine
Heelp
You don't need create a variable to entry, only make this for radiobutton or checkbutton. And you can change your create of tkinter object, like that
change this
self.l1=Label(child,text="Ajouter produit :",bg="blue").grid(row=0,columnspan=2)
for this
self.l1 = Label(child, text = "Ajouter produit :", bg = "blue")
self.l1.grid(row = 0, columnspan = 2) # remove all variables StringVar() and IntVar()
if you need make some future change using .config or .get() you don't can make that in the first example. You can continue using variable, but i don't recommend that, if you make this change .get() will work now.
I maked a easy way to draw in tkinter, you can use or make change, is for python 2
from Tkinter import *
class Draw_tk():
Row, Column, List = 0, 0, []
def __init__(self, child):
self.child = child
child.title = "Ajouter"
def labelAndEntry(self, text): # def to create a entry and a label
self.l = Label(self.child, text = text) # create label
self.l.grid(row = Draw_tk.Row, column = Draw_tk.Column) # place label
Draw_tk.Column += 1 # add 1 in Column to place the entry
self.e = Entry(self.child, width = 30) # create entry
self.e.grid(row = Draw_tk.Row, column = Draw_tk.Column) # place entry
Draw_tk.List.append(self.e) # add the entry in a list
Draw_tk.Row, Draw_tk.Column = Draw_tk.Row + 1, 0
def label(self, text):
self.l = Label(self.child, text = text, bg = "blue") # def to create a simple label
self.l.grid(row = Draw_tk.Row, columnspan=2) # place the label
Draw_tk.Row += 1
def button(self, text, var): # create a simple button
self.b = Button(self.child, text = text, command = var) # create button
self.b.grid(row = Draw_tk.Row, column = Draw_tk.Column) # place the button
def valid():
for item in Draw_tk.List: # run a variable in your values list
print item.get() # get the value and print
root = Tk()
controller = Draw_tk(root) # create object Draw_tk
controller.label('Ajouter produit')
controller.labelAndEntry('Matricule')
controller.labelAndEntry('Nom')
controller.labelAndEntry('Prix')
controller.button('Valider', valid)
root.mainloop()
I am very new to Tkinter ( I find it very difficult to learn). I have a python script working based on user input. I would like to wrap a GUI around it and eventually put it on web. In any case for user input I would like to get this from the GUI with a combination of Entry widgets and some buttons. First thing is I was reading and some people mentioned to use a class so I have the following. I have a few questions
I would like to check to see if indeed the users entered a value before he hits the GO button. How do I do this?
I would like the value entered to be made accessible by the rest of the program in the main body. How do I do this?
Thanks,
from Tkinter import *
class MainWindow():
def get_fc(self):
a = self.fc_gui.get()
return a
def __init__(self, master):
self.master = master
self.master.title('TEST')
self.fc_gui = DoubleVar(self.master, value = 500.00)
self.fclabel1 = Label(self.master, text = 'Please Enter a value', fg = 'black', bg = 'yellow')
self.fclabel1.grid(row = 0, column = 0)
self.fcedit1 = Entry(self.master, textvariable = self.fc_gui, bd = 5 )
self.fcedit1.grid(row = 1, column = 0)
fcbutton1 = Button(self.master, text='GO', command = self.get_fc)
fcbutton1.grid(row = 1, column = 1)
master = Tk()
MainWindow(master)
master.mainloop()
It doesn't make sense to return to a Button. The Button can't do anything with the value. Instead, save the value as an instance variable.
You don't have a mainloop().
You can't really check if the user entered a value before they hit "Go" - at the start of the program, of course they haven't entered anything yet. If you needed to track the contents of this field, there are ways to do that, but it's not necessary for a simple validation. Just check the value when they hit the button.
from Tkinter import *
class MainWindow():
def get_fc(self):
a = self.fc_gui.get()
if a: # this block will execute if a has content
self.a = a # save it for future use
def __init__(self, master):
self.master = master
self.master.title('TEST')
self.fc_gui = DoubleVar(self.master, value = 500.00)
self.fclabel1 = Label(self.master, text='Please Enter a value',
fg = 'black', bg = 'yellow')
self.fclabel1.grid(row = 0, column = 0)
self.fcedit1 = Entry(self.master, textvariable = self.fc_gui, bd = 5 )
self.fcedit1.grid(row = 1, column = 0)
fcbutton1 = Button(self.master, text='GO', command = self.get_fc)
fcbutton1.grid(row = 1, column = 1)
master = Tk()
MainWindow(master)
master.mainloop() # don't forget mainloop()