I'm trying to create a menu for a school project and wanted to make it so that it reads the files in a given directory and creates buttons on the screen based on what files are there. My main problem at the moment is that I can't get the Button to send the information i need.
I've tried to use the functions in a different way but this is the closest I can get to what I want.
class Menu(Frame):
def __init__(self, master=None):
#working out how big to make the window
global files_in_area
files_in_area = []
files_in_area = os.listdir(menu_folder_loc)
print files_in_area
a = len(files_in_area)
b = 3
rows = a / b
rows = rows + 1
window_height = rows * 56
root.geometry('%dx%d+%d+%d' % (450, window_height, 0, 0))
Frame.__init__(self, master)
self.master = master
self.init_menu()
def init_menu(self):
self.master.title("Menu")
self.pack(fill=BOTH, expand=1)
for i in range(0, len(files_in_area)):
#button placment
a = i
b = 3
row_no = a / b
row_no = row_no + 1
column_no = a % b
global file_name
file_name = str(files_in_area[i])
b1 = Button(self, text=file_name, bg= "red", height=3, width=20, command=self.client_print_file).grid(row=row_no, column=column_no, sticky=W)
def client_print_file(self):
print file_name
I've got the code to work when finding the files and putting them in a tkinter window, and when I click on the button, it should open the file (I'm just printing the file name at the moment to make sure it works). However it just prints the last file in the list.
Sorry if this question is a bit confusing, and thanks in advance.
This is not a problem about tkinter but about scopes and oop in python. I assume you got started with python by reading online stuff or got a fast introduction. My first advice is: do not use/copy from someone else's code something if you do not understand it. If you copy, try to dig in yourself and MAKE it your own code. One good way of reading up stuff is the very good online documentation of python.
Some exercises to get you started: 1) Research about python's scoping of variables.
2) More importantly: read up on classes, i.e. object oriented programming.
To the code: 1) Ditch the use of global alltogether. No need here. If you want to use the variable files_in_area in all methods of the class simply make it an attribute of the class, just like you did with master which you then go on to use in another method where it was not defined! Now to the variable filename: Again, ditch global. Then filename lives in the scope of the for loop which is exactly what you want here.
See it like this: if you had not used the variable filename at all but simply put text=str(files_in_area[i]) inside the Button-constructor as argument, you wouldnt have run into this problem here - at its core this really is a problem about thinking for yourself and not copying too much code from someone else - I know it because this was a big issue for me for long time.
The hardest thing here is how to pass a command depending on the i of the for loop to each button. You can find a lot here on stackoverflow. This again is about variables and their scope but also about their binding. In each loop pass, we want to generate a value of command depending on i. One way to use this is using the (at first mystical) lambda: command=lambda i=i: self.client_print_file(i). And define def client_print_file(self, i): ... with an additional argument i. The critical thing here is the binding. What would not work is command=lambda i: self.client_print_file(i).
(Another way instead of using lambda would be using partial from functools...)
Assignment: read up on lambda and what it is about and about name binding and variables in python :)
Related
I am making a rock paper scissors program and I need to change whose turn it is when they click a button, but I do not want to use the global keyword because the program is inside of a function.
Here is an example of what I am trying to do without using the global keyword:
from tkinter import *
root = Tk()
var = 1
def buttonClick():
global var
var += 1
print(var)
button = Button(root, text="button", command=buttonClick).pack()
root.mainloop()
I have tried to write command=(var += 1) but that did not work.
If the whole script is inside a function (including the buttonClick() function) then use the nonlocal keyword:
def buttonClick():
nonlocal var
var += 1
print(var)
If the function is not nested, the only way is to create a global variable and the global keyword in both functions.
No, you indeed can't. It would be possible to change the contents of a global varif it were a list, for example. And then you could write your command as a lambda expression with no full function body.
But this is not the best design at all.
Tkinter event model couples nicely with Python object model - in a way that instead of just dropping your UI components at the toplevel (everything global), coordinated by sparse functions, you can contain everything UI related in a class - even if it will ever have just one instance - that way your program can access the var as "self.var" and the command as "self.button_click" with little danger of things messing up were they should not.
It is just that most documentation and tutorial you find out will have OOP examples of inheriting tkinter objects themselves, and adding your elements on top of the existing classes. I am strongly opposed to that approach: tkinter classes are complex enough, with hundreds of methods and attributes -wereas even a sophisticated program will only need a few dozens of internal states for you to worry about.
Best thing is association: everything you will ever care to access should eb a member of your class. In the start of your program, you instantiate your class, that will create the UI elements and keep references to them:
import tkinter as tk # avoid wildcard imports: it is hard to track what is available on the global namespace
class App:
def __init__(self):
self.root = tk.Tk()
self.var = 1
# keep a refernce to the button (not actually needed, but you might)
self.button = tk.Button(self.root, text="button", command=self.buttonClick)
self.button.pack()
def buttonClick(self):
# the button command is bound to a class instance, so
# we get "self" as the object which has the "var" we want to change
self.var += 1
print(self.var)
def run(self):
self.root.mainloop()
if __name__ == "__main__": # <- guard condition which allows claases and functions defined here to be imported by larger programs
app = App()
app.run()
Yes, you can. Here's a hacky way of doing it that illustrates that it can be done, although it's certainly not a recommended way of doing such things. Disclaimer: I got the idea from an answer to a related question.
from tkinter import *
root = Tk()
var = 1
button = Button(root, text="button",
command=lambda: (globals().update(var=var+1), print(var)))
button.pack()
root.mainloop()
Ï'm trying to make a simple code that can automatically perform a sequence of smaller tasks on the click of a button. There's some information that currently needs to be punched into several word and excel sheets and webpages at work; it's time consuming and boring. My idea has been to create a tkinter window that allows the user to enter the necessary information, which is subsequently stored in a .csv file. I made it work without the use of classes, but when researching Tkinter, I realised that it's good practise to use classes. Problem is, I can't make it work when I put the entry widget into a class. This code works, but doesn't apply class(es):
def newprojectinput():
task_number_s = task_number_e.get()
#code is written to .csv in this method
my_window = Tk()
generate_b = Button(my_window, text="Make new project", command=newprojectinput)
generate_b.grid(row=12,column=1)
task_number_e = Entry(my_window)
task_number_e.grid(row=0,column=1)
my_window.mainloop()
This doesn't work:
def newprojectinput():
task_number_s = task_number_e.get()
#code is written to .csv in this method
class Toplevel_new_project:
def __init__(self, top=None):
self.Frame1 = tk.Frame(top)
self.task_number_e = tk.Entry(self.Frame1)
self.generate_b = tk.Button(self.Frame1, command=newprojectinput)
I would appreciate it if someone could take the time to provide optimal code and maybe briefly explain why the previous code doesn't work.
I'm having what seems like a basic problem but I'm not sure how to fix it.
I'm making a program which lets the user draw lines over a canvas. I previously had the entire code in one file but since I'm going to add many new tools it seemed a sensible decision to isolate all functions into separate files.
However doing this has resulted in problems. Apologies that there's a lot of code to press through. Here's my code:
Root program: root.py (The one I'm actually running)
#Import TKINTER toolset:
from tkinter import *
import starting_variables
#Starting variables:
starting_variables.start_vars()
#Tool width control:
global tool_width
tool_width = Scale(control_panel,from_=1,to=32)
canvas.bind("<Button-1>",line_start_xy)
control_panel.pack(side=LEFT,fill=Y)
tool_width.pack()
wrkspace.pack()
canvas.pack()
#Runs window:
window.mainloop()
This file defines all the variables needed from the start: (starting_variables.py)
from tkinter import *
def start_vars():
#Starting variables:
line_startx = 0
line_starty = 0
line_endx = 0
line_endy = 0
mouse_x = 0
mouse_y = 0
#Main window:
window = Tk()
#Workspace and Canvas:
wrkspace = Frame(window, bg="blue",width=640,height=480)
canvas = Canvas(wrkspace,bg="white",width=640,height=480)
control_panel = Frame(wrkspace,bg="white",width=32,relief=SUNKEN,bd=5)
For some reason, when I run the root program is tells me that in line 10 of root.py control_panel has not been defined, but I ran the function which defines it. What am I doing wrong here?
You need to understand scopes in python. Everything you did in the start_vars function is useless to the rest of the program. The control_panel only exists in the body of that function. When the function is done, you have NO control_panel
Although this is bad programming to begin with, you could try doing something like this:
def start_vars():
#Starting variables:
line_startx = 0
line_starty = 0
line_endx = 0
line_endy = 0
mouse_x = 0
mouse_y = 0
#Main window:
window = Tk()
#Workspace and Canvas:
wrkspace = Frame(window, bg="blue",width=640,height=480)
canvas = Canvas(wrkspace,bg="white",width=640,height=480)
control_panel = Frame(wrkspace,bg="white",width=32,relief=SUNKEN,bd=5)
return locals() # this gives a dictionary containing the local names... you don't have acces to them otherwise.
and then, in your root.py do this at some point
my_vars = starting_variables.start_vars()
Then you can get your the control_panel in root.py like this
control_panel = my_vars['control_panel']
and ONLY AFTER THIS you can use the control_panel variable.
Additionally you can do hacks like this
my_vars = starting_variables.start_vars()
globals().update(my_vars)
But this is a bad advice, and you program is structured badly.
[EDIT]
Since you seem a little confused, here's how I would have done this thing:
class Application:
# simple variables (ints, strings, etc) can be initiated here
line_startx = 0
line_starty = 0
...
# you should have some arguments here, so as not to hard-code everything, so you can reuse this application class. For example, the widths, heights, colors maybe
def __init__(self):
# In the init, put things that take a little more customization, that need parameterization, or that take longer to execute
self.window = TK()
self.workspace = Frame(self.window, bg="blue",width=640,height=480)
self.canvas = Canvas(self.wrkspace, bg="white", width=640, height=480)
self.control_panel = Frame(self.wrkspace, bg="white", width=32, relief=SUNKEN, bd=5)
This can be in your starting_variables.py module
Then, in your root.py, you can put
from tkinter import *
import starting_variables
#Starting variables:
app = starting_variables.Application() # here's where you'd pass the heights, widths, and the rest of the parameters.
#Tool width control:
tool_width = Scale(app.control_panel,from_=1,to=32)
app.canvas.bind("<Button-1>",app.line_start_xy)
app.control_panel.pack(side=LEFT,fill=Y)
tool_width.pack()
app.wrkspace.pack()
app.canvas.pack()
#Runs window:
app.window.mainloop()
I'm not sure if you're familiar with object-orientation, but this is just the way I'd do it. Your way would totally work in C, but Python just doesn't have global variables as you expected them to be.... everything's an attribute on a module, an object...something.
Why I think the way I wrote the code is better, is because touching functions like locals, globals, or touching module attributes from other modules is considered bad in Python because it's unexpected. People expect functions to return values, object to hold attributes and methods, classes to describe objects, etc... Sorry If I'm boring you with too much theory man, just hope the explanations help :)
So I'm writing this program, to draw disks using turtle. I'm doing a tkinter interface, using buttons etc. but, I don't seem to be able to execute a function inside a class through the buttons. It prompts me with this classic python error, "turtleInput() missing 1 required positional argument: 'numPressed'"
I've tried it a million times, a million ways, I just can't see the problem, maybe one of you can. I'm gonna provide you with the function inside the class and the the button ( in code of course ) hopefully you can help me out. feel free to ask questions if you don't quite understand what I am saying.
def turtleInput(self, numPressed):
self.length = int(numPressed)
self.lstColor = ["maroon","brown","red","orange","yellow",
"green","lightgreen","purple","blue",
"lightblue"]
for i in range(0,self.length):
self.shrink = 220
self.shrinkLst = []
while self.shrink > 0:
self.shrink = self.shrink-20
self.shrinkLst.append(self.shrink)
self.diskCol = self.lstColor[i]
self.turtleDisks(self.diskCol,self.shrinkLst[i])
now the code for the button
num2= Button(root, text="2", width=3)
num2["command"]= lambda: Disk.turtleInput(2)
num2.grid(row=1, column=0, sticky=W, padx=3)
keep in mind that I imported tkinter, turtle and everything else works fine, that is the only problem.
You need to be referencing a Disk object, not the Disk class when referencing a function.
The solution looks like this:
# create instance of Disk
disk = Disk()
...
num2["command"]= lambda: disk.turtleInput(2)
I have a kind of tricky problem here. Basically what I want to do is have a tamaguchi-like program, where people get to choose what the tamaguchi does and it either increases in size or decreases. This works great for there's only one tamaguchi! But it should be possible to create and manage several tamaguchis simultaneously. I run into big problems when I try this though. Basically this is how I've been thinking so far:
class Tamaguchi(object):
def __init__(self,name,size=1):
self.name=name
self.size=size
def increasesize(self):
self.size+=1
GoodBadChange.config(text="Good job. You increased in size!")
def decreasesize(self):
self.size-=1
GoodBadChange.config(text="Bad job. You decreased in size!")
def main():
print(name)
root = tkinter.Tk()
global root
root.title("Tamaguchi-spelet")
root.geometry("900x900")
getLists()
getPhotos()
Picture = tkinter.Label(root, image=normal)
Picture.pack()
global Picture
ScoreBoard = tkinter.Label(root, text="Score " + str(tamaguchin.size), font=('Helvetica', 15))
ScoreBoard.pack()
global ScoreBoard
GoodBadChange = tkinter.Label(root, text="", font=('Helvetica', 12))
GoodBadChange.pack()
global GoodBadChange
LatestAction = tkinter.Label(root, text="", font=('Helvetica', 12))
LatestAction.pack()
global LatestAction
app = Application(root)
app.pack()
updateDisplay()
LatestAction.config(text="Hello and welcome to the tamaguchi game!\nThe tamaguchi starts the day with the size "+str(tamaguchin.size)+"!\nThe tamaguchi starts the day off with the last three actions of "+lista[0]+"-"+lista[1]+"-"+lista[2]+"\n")
root.mainloop()
tamaguchi_list=[]
amount=int(input("Number of tamaguchis in your farm: "))
for i in range(amount):
name = input("What do you want to name your tamaguchis?: ")
tamaguchi_name = Tamaguchi(name)
tamaguchi_list.append(tamaguchi_name)
for tamaguchis in tamaguchi_list:
main()
for tamaguchis in tamaguchi_list:
print(name,"reached a size of ",name.size,"!")
Sorry it's a bit long, I've still shortened off the parts that aren't relevant. But I was thinking, we create a list with all the tamaguchis in them, and then we just run the main function for each tamaguchi in the tamaguchi-list. That way, for example "erik" gets one score, and "mary" another, and this should then be written in the end. However, this does not seem to work, as you can see I write "print(name)" in the beginning of the main function just to see that it actually goes through all the names in the list, but in fact it just prints the same name over and over again. I have no idea why it doesn't go through all names.
Another problem is the fact I have written in the main function stuff like str(tamaguchin.size) when I want to show the size of the tamaguchi, but this was because when I only had one tamaguchi I just created it in the beginning and I could just refer to that in the rest of the program (tamaguchin=Tamaguchi('SomeName') is what I used to have!) Can this be solved?
Thanks a lot for any help, I'm really stuck with this.
edit: Perhaps it's unclear since I don't show all of the code. I thought it might just be too long, but perhaps it's better to understand what I mean! I uploaded it here!
This chapter describes a game in which the player causes a creature to "increase in size" due to an event in the game: http://inventwithpython.com/pygame/chapter6.html
Read through the game design and similar to increasing the size you can decrease the size of the creature.
In order to manage multiple creatures read this chapter: http://inventwithpython.com/pygame/chapter8.html
I need you to be more specific in which lines of your code your error is occurring to properly answer this question. That being said, however, I would recommend using a more efficient structure to manage your tamaguchins.
What I understood you describing was 'tamaguchi does something' then 'tamaguchi grows or shrinks'.
Try something like this using a dictionary:
tamaguchins = {"ralph":10, "george":5, "chester":2}
def size_modify(tamaguchin, plus_minus, tamaguchins):
tamaguchins[tamaguchin] += plus_minus
return tamaguchins
Call it like this:
tamaguchins = size_modify("ralph", -1, tamaguchins)
Or like this:
tamaguchins = size_modify("george", 1, tamaguchins)
Or however you like, just remember that in this case you must always precede the calling of the function with 'tamaguchins =' and your third argument must always be tamaguchins.
Cheers