I'm still working on a code that moves around two planets and shows the gravitational force between them. I tried to make it easier to use by just showing two buttons that allow to select the planet you want to move. Then you can click in the canvas and the selected planet will move where you've clicked.
The programm works, but I'd like to know if there is a better way to write it then using the chngB and chngO functions with global statements.
I still can't believe that in Python you are forced to use a function without parameters when assigning it to the command parameter of a button.
Basically I'd like to know if it's just possible to write something like command = (a=1)
(I know that this doesn't work, but you get the idea.)
Also, there probably just is another way of doing it than having to use a variable to know which planet is selected (which button has been pressed last).
I use Python 3.
from tkinter import *
import math
x, y = 135, 135
a = 0
def gravitation (obj1,obj2):#showing gravitational force between planets
a, b, c, d = can.coords (obj1)
e, f, g, h = can.coords (obj2)
dist = math.sqrt ((((a+c)/2)-((e+g)/2))**2+(((b+d)/2)-((f+h)/2))**2)
if dist != 0:
grav = 6.67384/dist
else:
grav = "Infinite"
str(grav)
return grav
def chngB ():#Telling Blue planet is selected
global a
a = 1
def chngO ():#Telling Orange planet is selected
global a
a = 0
def updt ():#Updating gravitation label
lbl.configure (text = gravitation(oval1, oval2))
def moveBlue (event):#Placing blue planet where mouse click on canv
coo = [event.x-15, event.y-15, event.x+15, event.y+15]
can.coords(oval1, *coo)
updt()
def moveOrange (event):#Placing orange planet where mouse click on canv
coo = [event.x-15, event.y-15, event.x+15, event.y+15]
can.coords(oval2, *coo)
updt()
def choice (event):#Function binded to can, move the selected planet (blue = 1, prange = 0)
if a == 0:
moveOrange(event)
else :
moveBlue(event)
##########MAIN############
wind = Tk() # Window and canvas
wind.title ("Move Da Ball")
can = Canvas (wind, width = 300, height = 300, bg = "light blue")
can.grid(row=0, column=0, sticky =W, padx = 5, pady = 5, rowspan =3)
can.bind ("<Button-1>", choice)
Button(wind, text = 'Quit', command=wind.destroy).grid(row=2, column=1, sticky =W, padx = 5, pady = 5)
oval1 = can.create_oval(x,y,x+30,y+30,width=2,fill='blue') #Planet 1 moving etc
buttonBlue = Button(wind, text = 'Blue Planet', command = chngB)
buttonBlue.grid(row=1, column=1, sticky =W, padx = 5, pady = 5)
oval2 = can.create_oval(x+50,y+50,x+80,y+80,width=2,fill='orange') #Planet 2 moving etc
buttonOrange = Button(wind, text = 'Orange Planet', command = chngO)
buttonOrange.grid(row=0, column=1, sticky =W, padx = 5, pady = 5)
lbl = Label(wind, bg = 'white')#label
lbl.grid(row=4, column=1, sticky =W, padx = 5, pady = 5, columnspan = 3)
gravitation (oval1, oval2)
wind.mainloop()
Normally, if you want a button to toggle a variable between one of N values, you would use a set of radiobuttons. When you use a radiobutton you can associate it with a variable so that whenever you click the button, the variable is automatically selected.
For example:
planet = IntVar()
planet.set(0)
buttonBlue = Radiobutton(wind, text="Blue Planet", variable=planet, value=1)
buttonOrange = Radiobutton(wind, text="Orange Planet", variable=planet, value=0)
...
def choice (event):#Function binded to can, move the selected planet (blue = 1, prange = 0)
if planet.get() == 0:
moveOrange(event)
else :
moveBlue(event)
If you really want to use a regular button, you can do that with a single callback rather than two, and then use lambda or functools.partial to pass in the new value.
For example:
buttonBlue = Button(wind, text = 'Blue Planet', command = lambda: change(1))
buttonOrange = Button(wind, text = 'Blue Planet', command = lambda: change(0))
def change(newValue):
global a
a = newValue
Unfortunately, you cannot have a lambda expression with a statement (assignment) in it.
But you can easily have just one setter function which returns a closure that you can pass to your Button constructor. Here's an example of creating and using closures:
a = 0
def set_a (v):
def closure ():
global a
a = v
return closure
command = set_a(42)
command()
print a # prints 42
command = set_a(17)
command()
print a # prints 17
Related
These are few lines from my actual code - I am aware this is not the best way of writing a code, but as I am new and getting familiarize with Tkinter (py2) consider this as my scratch work.
I am listing a question and multiple options. When the user selects an option, a SUBMIT button is created and when clicks on SUBMIT button it will accordingly change the color of Option to green or red. If green then another NEXT button will be available to clean and move to next question.
The issue that I am facing is if a user selects option A but then without clicking the SUBMIT button selects another option the submit button multiplies. I want to destroy the unwanted buttons or even do not want to create multiple SUBMIT buttons.
Please do help in achieving the same.
import Tkinter
from Tkinter import *
import yaml
import random
grey = "#808080"
offwhite = "#e3e3e3"
filepath = "chapter-2.yaml"
tk = Tkinter.Tk()
tk.title("iCodet Learnings")
tk.geometry("800x600")
x = ''
tk.config(background=offwhite)
tk.resizable(0,0)
q_count = 0
def yaml_loader(filepath):
with open (filepath, "r") as fileread:
data = yaml.load(fileread)
return data
def cleaner(hint):
global rbutton
global q_count
global quest_label
global radio1
global button_game
quest_label.destroy()
radio1.destroy()
# destroys the radio buttons
for b in rbutton:
b.destroy()
# destroys the SUBMIT button
button_game.destroy()
# go to ext question
if hint == 'next':
q_count += 1
game_loop()
# This is display the first element from the yaml i.e the question
def display_question(questions, qc):
global quest_label
q = questions.keys()[qc]
a = questions[q]
v = a.keys()
quest_label = Label(tk, text = q, font = ("Consolas", 16), width = 500, justify = "center", wraplength = 400)
quest_label.pack(pady = (50,0))
return v
# This is for selecting the radio buttons
def selected():
global radio_default, button_next,radio1, val
global x, data,q_count, vali, rbutton, select_val
x = radio_default.get()
select_val = rbutton[x]
if q_count <= len(data):
q = data.keys()[q_count]
a = data[q] #second dictionary
v = a.keys() #second dictionary keys
# True or False from Yaml
val = a[v[x]][0]
press_button(val)
else:
print ("Mid way")
# This will list all the options under question
def display_answer(ans):
global radio1, rbutton
global x, q_count
global radio_default
radio_default = IntVar()
rbutton = []
rad_select = []
val_count = 0
for i in ans:
radio1 = Radiobutton(tk, text = i, font = ("times", 14, "bold"), value = val_count, variable = radio_default, command = selected, background = 'NavajoWhite3')
rbutton.append(radio1)
val_count += 1
radio1.pack(pady = (30,0))
radio_default.set(-1)
# This displays the SUBMIT buuton
def press_button(val):
global button_game
# true
if val:
button_game = Button(tk, text = 'SUBMIT', font = ("default", 15, "bold"), bg='orange', fg = 'white', border=2, height = 2, width = 8, command = lambda: cleaner('next'))
button_game.pack(pady = (30,0))
# false
elif not val:
print "Do nothing"
button_game = Button(tk, text = 'SUBMIT', font = ("default", 15, "bold"), bg='orange', fg = 'white', border=2, height = 2, width = 8, command = lambda: cleaner('stay'))
button_game.pack(pady = (30,0))
return True
def game_loop():
global q_count
global x, data
global quest_label, button_game
action = True
data = yaml_loader(filepath)
if q_count <= len(data)-1:
l_ans = display_question(data, q_count)
display_answer(l_ans)
else:
txt_label = Label(tk, text = "CONGRATULATIONS ON COMPLETING CHAPTER", font = ("Comicsans", 24, "bold"), background = offwhite, wraplength = 700)
txt_label.pack(pady = (100,0))
button_end = Button(tk, text = 'THANK YOU !', font = ("default", 15, "bold"), bg='saddle brown', fg = 'white', border=2, height = 3, width = 10, command = tk.destroy)
button_end.pack(pady = (50,0))
game_loop()
tk.mainloop()
chapter-1.yaml
> "What’s the complete name of Sachin Tendulkar ?":
> "Sachin Ramya Tendulkar":
> - False
> "Sachin Ramesh Tendulkar":
> - True
> "Sachin Tendehar":
> - False
> " Sachin 10dulkar":
> - False
> "Hint":
> - "biscuit & cookies"
As things are, each time press_button() is run, a new Button object is generated, and placed in the button_game variable. This does not remove or hide the previous button, which still exists in the packed UI.
A simple solution that would save the machine some work is to initialize the button only once, earlier in the code, but omit placing/displaying/packing it until that block within press_button() is run.
I was able to achieve what I was looking for with the help of config.
I created the SUBMIT button once at the beginning and then instead of calling the whole function again and again; I just replaced press_button with button_game.config(command = lambda: right(chapter, num_ques, topic, val))
Now I should write this code using class in python.
I am helping with a small project where we want to add and take items away from a store. The code is below:
from tkinter import *
import tkinter
####################
# Variables
eggs = 0
milk = 0
butter = 0
lemon = 0
guiSize = "800x1280"
def newItemGUI():
main.withdraw()
def addEgg():
global eggs
eggs += 1
updateLabels()
def menu():
global eggs
update(eggs)
itemWindow.destroy()
main.deiconify()
def updateLabels():
eggLabel = Label(itemWindow, text = eggs)
eggLabel.grid(row = 3,column = 0)
itemWindow = Toplevel()
itemWindow.geometry(guiSize)
itemWindow.title("Add a new item")
itemWindow.configure(background = "#a1dbcd")
heading = Label(itemWindow, text="Add new items", font=("Arial",20),background = "#a1dbcd")
heading.grid(row=0, columnspan = 3)
egg = PhotoImage(file ="images/egg.gif")
eggButton = Button(itemWindow, image = egg, command = addEgg)
eggButton.grid(row = 2, column = 0,padx = 10, pady = 10)
eggLabel = Label(itemWindow, text = eggs).grid(row = 3,column = 0)
back = Button(itemWindow, text = "Main Menu", command = menu, width = 15)
back.grid(row = 4, column = 0, padx = 20, pady = 20)
def update(eggs):
items.delete("1.0",END)
items.insert(END,"Eggs \t:")
items.insert(END,eggs)
items.insert(END,"\n")
main=tkinter.Tk()
main.geometry(guiSize)
bgColour = "#DDA0DD"
main.configure(background = bgColour)
button1 = Button(main, text="Input new products", width = 20, height = 5, command = newItemGUI)
button1.grid(column = 1, row =2, padx = 20, pady = 20)
label2 = Label(main,text = "Items in the fridge :", font =("Arial,20"), background = bgColour)
label2.grid(row=4, columnspan = 2)
items = Text(main, width = 60, height = 10)
items.insert(END,"Eggs \t:")
items.insert(END,eggs)
items.insert(END,"\n")
items.grid(row=5, columnspan = 4)
main.mainloop()
When you click on the input new products button, this takes you to a new screen. On the screen should be a photo of an egg with a count underneath. For some reason the image of the egg is not showing and the button will not click.
If I change the eggButton from an image to:
eggButton = Button(itemWindow, text = "egg", command = addEgg)
this seems to allow me to click and the variable and it increases. Any idea as to what/where we have gone wrong? I know the path is correct as I can get the button to display a picture of an egg in a Label.
The problem is because the PhotoImage is being stored in a variable named egg which is local to the newItemGUI() function, so it (and associated image object) are being deleted when the function returns. This is a fairly common problem, so your question is likely a duplicate of another (and may get closed).
This PhotoImage documentation mentions avoiding this potential issue the way shown below in the Note near the end.
Regardless, to prevent that from happening, you can store the value somewhere else such as in an attribute of the Toplevel window. I would also recommend changing its name to something more descriptive like egg_image.
Here are changes to your code showing what how it could be done:
itemWindow.egg_image = PhotoImage(file="images/egg.gif")
eggButton = Button(itemWindow, image=itemWindow.egg_image, command = addEgg)
Recently I've changed the layout of my program to include a multi-page window similar to what is in the provided example.
In the original, two-window configuration I had a binding set on each window to highlight all of the text in the Entry widget, based on a condition (no condition present in the example). This was fine.
Upon upgrading to a multi-page window, I tried to combine the callback to highlight text by passing the relevant widget and calling widget.select_range(0, END) as it is done in the example. Now I can't seem to highlight any text on mouse-click.
In addition to this, I've also tested my example code with having a separate callback for each Entry; even this would not highlight the text in the Entry upon clicking on it.
Could this have something to do with lifting frames & where the focus lies? As a test I've added a similar callback for "submitting" the Entry value, and this is working fine. At this point I'm confused as to why this wouldn't work. Any help is greatly appreciated.
UPDATE:
I forgot that to solve the highlighting problem, I've needed to include a return "break" line in the callback that is used to highlight the text.
Now, with this included, I have some very strange behavior with the Entry widgets. I can't click on them unless they have been focused using the tab key.
Is there any way to work around this problem?
Here is the example code I have been playing with (with the updated return statement):
from Tkinter import *
class Window():
def __init__(self, root):
self.root = root
self.s1 = StringVar()
self.s1.set("")
self.s2 = StringVar()
self.s2.set("")
# Frame 1
self.f1 = Frame(root, width = 50, height = 25)
self.f1.grid(column = 0, row = 1, columnspan = 2)
self.page1 = Label(self.f1, text = "This is the first page's entry: ")
self.page1.grid(column = 0, row = 0, sticky = W)
self.page1.grid_columnconfigure(index = 0, minsize = 90)
self.val1 = Label(self.f1, text = self.s1.get(), textvariable = self.s1)
self.val1.grid(column = 1, row = 0, sticky = E)
self.l1 = Label(self.f1, text = "Frame 1 Label")
self.l1.grid(column = 0, row = 1, sticky = W)
self.e1 = Entry(self.f1, width = 25)
self.e1.grid(column = 1, row = 1, sticky = E)
self.e1.bind("<Button-1>", lambda event: self.event(self.e1))
self.e1.bind("<Return>", lambda event: self.submit(self.e1, self.s1))
# Frame 2
self.f2 = Frame(root, width = 50, height = 25)
self.f2.grid(column = 0, row = 1, columnspan = 2)
self.page2 = Label(self.f2, text = "This is the 2nd page's entry: ")
self.page2.grid(column = 0, row = 0, sticky = W)
self.page2.grid_columnconfigure(index = 0, minsize = 90)
self.val2 = Label(self.f2, text = self.s2.get(), textvariable = self.s2)
self.val2.grid(column = 1, row = 0, sticky = E)
self.l2 = Label(self.f2, text = "Frame 2 Label")
self.l2.grid(column = 0, row = 1, sticky = W)
self.e2 = Entry(self.f2, width = 25)
self.e2.grid(column = 1, row = 1, sticky = E)
self.e2.bind("<Button-1>", lambda event: self.event(self.e2))
self.e2.bind("<Return>", lambda event: self.submit(self.e2, self.s2))
self.b1 = Button(root, width = 15, text = "Page 1", command = lambda: self.page(1), relief = SUNKEN)
self.b1.grid(column = 0, row = 0, sticky = E)
# Buttons
self.b2 = Button(root, width = 15, text = "Page 2", command = lambda: self.page(2))
self.b2.grid(column = 1, row = 0, sticky = W)
# Start with Frame 1 lifted
self.f1.lift()
def page(self, val):
self.b1.config(relief = RAISED)
self.b2.config(relief = RAISED)
if val == 1:
self.f1.lift()
self.b1.config(relief = SUNKEN)
else:
self.f2.lift()
self.b2.config(relief = SUNKEN)
def event(self, widget):
widget.select_range(0, END)
return "break"
def submit(self, widget, target):
target.set(widget.get())
root = Tk()
w = Window(root)
root.mainloop()
Well, this has been a productive question. If anyone in the future is doing something similar to this and needs a reference for how to solve the problem:
I was able to work around the problem by forcing the Entry widgets into focus every time I switch frames, and using the return "break" statement that I mention in the question's update.
This isn't ideal, as every time a page is changed you automatically focus on the Entry widget, but once the widget is in focus it's behavior is exactly what I would expect so this isn't of great concern. In my program, if you are changing pages it is quite likely you will use the Entry widget anyway (it is a search entry).
Here's the final changes required to make the code work correctly:
# .... some code ....
self.f1.lift()
self.e1.focus_force()
def page(self, val):
self.b1.config(relief = RAISED)
self.b2.config(relief = RAISED)
if val == 1:
self.f1.lift()
self.b1.config(relief = SUNKEN)
self.e1.focus_force()
else:
self.f2.lift()
self.b2.config(relief = SUNKEN)
self.e2.focus_force()
def event(self, widget):
widget.select_range(0, END)
return "break"
# .... more code ....
Why isn't this working. This is straight from the text book. I'm getting an Attribute error saying self._area does not exist.
from Tkinter import *
import math
class CircleArea(Frame):
def __init__(self):
"""Sets up a window and widgets."""
Frame.__init__(self)
self.master.title("Circle Area")
self.grid()
#Label and field for radius
self._radiusLabel = Label(self, text = "Radius")
self._radiusLabel.grid(row = 0, column = 0)
self._radiusVar = DoubleVar()
self._radiusEntry = Entry(self, textvariable = self._radiusVar)
self._radiusEntry.grid(row = 0, column = 1)
#Label and field for the area
self._areaLabel = Label(self, text = "Area")
self._areaLabel.grid(row = 1, column = 0)
self._areaVar = DoubleVar()
self._areaEntry = Entry(self, textvariable = self._areaVar)
self._areaEntry.grid(row = 1, column = 1)
# The command button
self._button = Button(self, text = "Compute", command = self._area)
self._button.grid(row = 2, column = 0, columnspan = 2)
def _area(self):
"""Event handler for button."""
radius = self._radiusVar.get()
area = radius ** 2 * math.pi
self._areaVar.set(area)
def main():
CircleArea(). mainloop()
run = CircleArea()
run.main()
Is it because the _area method is declared after it is called? That doesn't make sense why it wouldn't work using a down up programming technique. I'm really new to GUI just started learning. First chapter on GUI for class.
edit*: I'm expecting a window to pop up and have one Entry field for input for the radius of the circle. With a label Radius. And an output entry field for the results of the area of the circle based on the radius. and a compute button at the bottom which computes it.
And I just wanted to get used to typing the different commands and such. I haven't even been in the lecture for this yet. I was just seeing what this code would do and what it would look like. I typed it all out by hand if that makes you feel better.:P Instead of copy and pasting.
The problem is that your indenting is wrong. _area and main are defined within __init__, which you don't want. Correct indenting is below (you don't need a main function).
from Tkinter import *
import math
class CircleArea(Frame):
def __init__(self):
"""Sets up a window and widgets."""
Frame.__init__(self)
self.master.title("Circle Area")
self.grid()
#Label and field for radius
self._radiusLabel = Label(self, text = "Radius")
self._radiusLabel.grid(row = 0, column = 0)
self._radiusVar = DoubleVar()
self._radiusEntry = Entry(self, textvariable = self._radiusVar)
self._radiusEntry.grid(row = 0, column = 1)
#Label and field for the area
self._areaLabel = Label(self, text = "Area")
self._areaLabel.grid(row = 1, column = 0)
self._areaVar = DoubleVar()
self._areaEntry = Entry(self, textvariable = self._areaVar)
self._areaEntry.grid(row = 1, column = 1)
# The command button
self._button = Button(self, text = "Compute", command = self._area)
self._button.grid(row = 2, column = 0, columnspan = 2)
def _area(self):
"""Event handler for button."""
radius = self._radiusVar.get()
area = radius ** 2 * math.pi
self._areaVar.set(area)
run = CircleArea()
run.mainloop()
Actually I think you miss an argument in your main method,you define a class CircleArea , but in python you know that, each method defined in class must have an default argument named 'self',so just try this
def main(self):
CircleArea(). mainloop()
I think it will work as you wish :)
I have create a frame but do not know how to draw the geometry on the sub-frame.
Here is the code of my current window:
class App:
def __init__(self, master):
frame = Frame(master)
frame.grid()
self.Quit = Button(frame, text = "QUIT", command = frame.quit)
self.Quit.grid(row = 0, column = 48, sticky = N)
self.adpt = Button(frame, text = "Add Point", command = self.adpt)
self.adpt.grid(row = 0, column = 49, sticky = N)
self.adln = Button(frame, text = "Add Line", command = self.adln)
self.adln.grid(row = 0, column = 50, sticky = N)
self.adpg = Button(frame, text = "Add Polygon", command = self.adpg)
self.adpg.grid(row = 0, column = 51, stick = N)
iframe = Frame(frame, bd = 2, relief = RAISED, width=1000, height=500)
iframe.grid(row = 1, columnspan = 100, sticky = N)
def adpt(self):
pass
def adln(self):
pass
def adpg(self):
pass
I need to create each kind of geometry by clicking the corresponding button, and then draw it on the sub-frame, but I do not know how to use event to draw geometry in the sub-frame (iframe). For example, to draw point, click the button "Add point". Then Click on the sub-frame to generate a point. Double click the sub-frame to save the points to a point list.
The first problem is how to draw the point on the sub-frame by click on it.
The second problem is how to make the sub-frame handle double click and click separately. When I double click a widget, it first go through the click event, and then the double click event.
I have create classes to draw geometry with canvas. The class of point, line, polygon can draw geometry with canvas.
Here is the example codes for point class:
class Point:
def __init__(self,x, y):
self.x = x
self.y = y
def __str__(self):
return " (" + str(self.x) + "," + str(self.y) + ")"
def draw(self,canvas):
canvas.create_line(self.x-10,self.y,self.x+10,self.y)
canvas.create_line(self.x,self.y-10,self.x,self.y+10)
If you are selecting the type of geometry with the buttons, on their event handlers you can set the class that is going to be used. Then you can use the coordinates of the event information to draw the item on the canvas.
self.adln = Button(frame, text = "Add Line", command=self.adln)
self.adpt = Button(frame, text = "Add Point", command=self.adpt)
self.canvas.bind("<Button-1>", self.click)
#...
def adln(self):
self.geometry = "Line"
def adpt(self):
self.geometry = "Points"
#...
def click(self, event):
if self.start is None:
self.start = (event.x, event.y)
else:
self.draw_geometry()
self.start = None
def draw_geometry(self):
if self.geometry == "Points":
p1 = Point(*self.start)
p2 = Point(event.x, event.y)
p1.draw(self.canvas)
p2.draw(self.canvas)
elif self.geometry == "Line":
line = Line(event.x, event.y, *self.start)
line.draw(self.canvas)
Note the number of arguments of the constructors and the existance of the draw method may not correspond with what you have, since this is just an example. It is a bit unpythonic, but it's the simplest way I have come out.
On the other hand, since the event <Button-1> is always triggered on a double click, I suggest you to use another button for a separate action, like <Button-2> or <Button-3>.