Tkinter - Python 3 - Adding an Increment Counter into a class window? - python

class AppetiserClass():
root = Tk()
root.title("Appetiser Page")
root.geometry("1920x1080")
meal1 = 0
def plus1():
global meal1
meal1 = meal1 + 1
DisplayButton["text"]=str(meal1)
return
def neg1():
global meal1
meal1 = meal1 + 1
DisplayButton["text"]=str(meal1)
return
app = Frame(root)
app.grid()
Label(app, text = "", width = 75, height = 20).grid(row = 1, column = 0, sticky = N)
DisplayButton = Button(app, text = meal1)
DisplayButton.grid(column = 1, row = 2, sticky = W)
DisplayButton.config(height = 10, width = 10 )
Plus1Button = Button(app, text = "+1", command=plus1, bg="green")
Plus1Button.grid(column = 2, row = 2, sticky = W)
Plus1Button.config(height = 10, width = 10 )
Neg1Button = Button(app, text = "-1", command=neg1, bg="green")
Neg1Button.grid(column = 3, row = 2, sticky = W)
Neg1Button.config(height = 10, width = 10 )
root.mainloop()
The problem I am having is that I have set a value to my global variable (meal1, being 0) but when I press the +1, or -1 button, a value is not being displayed on the "DislpayButton" and I am receiving this message:
"NameError: global name 'DisplayButton' is not defined "
"DisplayButton", is a button i have placed, to display a value onto. Nothing more, but I am receiving this error message.
If i remove the classes, and just run this code, with the single window, The code works fine.
Any help would be much appreciated!

If your indentation is correct, the problem isn't that DisplayButton and meal1 are global, it's that they're class-level and you're not accessing it that way, which means you should use self keyword to access it. (It doesn't have to be "self" - the first argument of any function in a class always defines the variable through which you can access other members in the same class - but it's Python style to use "self.") Add self as an argument to all your functions in that class, like so:
def neg1(self):
And then access meal1 and DisplayButton through self:
self.meal1 += 1
and:
self.DisplayButton["text"] = str(meal1)
I've re-written your class so that all the important stuff within the class can be accessed by everything else using self:
from tkinter import *
class AppetiserClass:
meal1 = 0
root = Tk()
app = Frame(self.root)
def __init__(self):
self.root.title("Appetiser Page")
self.root.geometry("1920x1080")
self.app.grid()
Label(self.app, text = "", width = 75, height = 20).grid(row = 1, column = 0, sticky = N)
self.DisplayButton = Button(self.app, text = self.meal1)
self.DisplayButton.grid(column = 1, row = 2, sticky = W)
self.DisplayButton.config(height = 10, width = 10 )
self.Plus1Button = Button(self.app, text = "+1", command=self.plus1, bg="green")
self.Plus1Button.grid(column = 2, row = 2, sticky = W)
self.Plus1Button.config(height = 10, width = 10 )
self.Neg1Button = Button(self.app, text = "-1", command=self.neg1, bg="green")
self.Neg1Button.grid(column = 3, row = 2, sticky = W)
self.Neg1Button.config(height = 10, width = 10 )
self.root.mainloop()
def plus1(self):
self.meal1 += 1
self.DisplayButton["text"]=str(self.meal1)
def neg1(self):
self.meal1 -= 1
self.DisplayButton["text"]=str(self.meal1)
if __name__ == "__main__":
AppetiserClass()
I changed a decent amount. First off, you had a lot of code written outside any particular method, which is something I prefer to keep inside the class methods except for class variable definitions (meal1 = 0 and etc.). It's fairly arbitrary - anything defined within a method as self.whatever has the same accessibility as stuff defined at the class scope. I've also made it so that you can keep reference to your buttons with self.ButtonName. Lastly, I've made it so that the window is instantiated only if you're running the file and not importing your code into a different file.

Related

how to switch from a frame to another frame

I'd like to switch from the rating_frame to the summary_frame. How would I do this? Would I destroy the rating_frame? I want to go onto the rating_frame by clicking the 'Show' button.
I have a search frame that is staying there. I only want the rating frame to change.
I have not yet made a start on the summary_frame as I don't know how to change from the rating_frame or where I would write it. Could you give me a good foundation?
Here is my Wireframe:
Here is my code:
from tkinter import *
class Movie:
def __init__(self, movie):
self.movie = movie
self.ratings = "No Rating"
class MovieRaterGUI:
def __init__(self, parent):
self.counter = 0
self.index = 0
#variable set up
self.v = StringVar()
self.v.set("No Rating")
#frames used so you can easily switch between rating frame and summary frame - keeping the search frame
rating_frame = Frame(root)
search_frame = Frame(root)
summary_frame = Frame(root)
rating_frame.pack(side="top", expand=True)
search_frame.pack(side="bottom", expand=True)
summary_frame.pack(side="top", expand = True)
#rating frame
#list of ratings for movies
self.movies = [
Movie("The Hobbit"),
Movie("Coraline"),
Movie("Love, Rosie")]
#used to display the ratings
self.ratings = ["No Rating", "Forget it", "2", "3", "4", "Must See"]
#labels
self.movie_label = Label(rating_frame, text = "Please Rate:", borderwidth = 10)
self.current_movie = Label(rating_frame, text = self.movies[self.counter].movie, borderwidth = 10)
self.rating_label = Label(rating_frame, text = "Your rating:", borderwidth = 10)
self.movie_label.grid(row = 0, column = 0, sticky = W)
self.current_movie.grid(row = 0, column = 1, sticky = W)
self.rating_label.grid(row = 1, column = 0, sticky = W)
#making radio buttons
self.radiobutton = []
self.num_choices = self.ratings
for i in range(len(self.ratings)):
self.option = Radiobutton(rating_frame, variable = self.v, value = self.ratings[i], text = self.ratings[i], borderwidth = 10, command = self.update_rating)
self.radiobutton.append(self.option)
self.option.grid(row = i+1, column = 1, sticky = W)
next_btn = Button(rating_frame, text = "Next", borderwidth = 10, command = self.next_movie)
previous_btn = Button(rating_frame, text = "Previous", borderwidth = 10, command = self.previous_movie)
next_btn.grid(row = 7, column = 1, sticky = W)
previous_btn.grid(row = 7, column = 0, sticky = W)
#search frame
self.search_label = Label(search_frame, text = "Search for movies with a rating of:", borderwidth = 10)
self.search_label.grid(row=0, column=0, columnspan=len(self.num_choices))
for i in range(len(self.num_choices)):
option = Radiobutton(search_frame, variable = self.v, value = i, text = self.num_choices[i])
option.grid(row = 1, column = i, sticky = W)
show_btn = Button(search_frame, text = "Show", borderwidth = 10, command = self.summary_frame)
show_btn.grid(row = 3, column = 0, columnspan = len(self.num_choices))
def next_movie(self):
self.counter +=1
self.current_movie.configure(text = self.movies[self.counter].movie)
#used so each radio button the user chooses will be saved
for i in range(len(self.radiobutton)):
self.radiobutton[i].configure(variable = self.v, text = self.ratings[i], value = self.ratings[i])
#the default movie rating is no rating for every movie
self.v.set("No Rating")
def previous_movie(self):
self.counter -=1
self.current_movie.configure(text = self.movies[self.counter].movie)
#the default movie rating is no rating for every movie
self.v.set("No Rating")
def update_rating(self):
self.movies[self.counter].ratings = self.v.get()
for element in self.movies:
print(element.ratings)
print()
print('*'*20)
print()
if __name__ == "__main__":
root = Tk()
root.title("Movie Ratings")
radiobuttons = MovieRaterGUI(root)
root.mainloop()
If you use destroy then you destroy also data which you have in this frame - and you couldn't use them. You would have to get data from frame before you destroy it.
But it may be better to remove frame without destroying (pack_forget, grid_forget) and then you have access to data in frame and you can always display this frame again.
With grid it can be simpler to put new element in the same place.
Where put this code?
Usually programs have buttons << Previous,Next >> or << Details, Summary >> to change frames/steps - but it seems you forgot these buttons.
Eventually you can use Notebook to have frames in tabs.
Applications for smartphones usually can slide frames (using fingers) but desktop programs rather don't use this method. And tkinter doesn't have special methods for this. It would need much more code with events or drag'&'drop. Buttons are much simpler to create and simpler to use by users.

Python skipped one of my function

I am using a tkinter frame to call another tkinter frame. So from frame one i will click a button and it will check if there is a file at C:\ and if the file is not there it should call the Chrome_gui function which is another tkinter frame at "def p2(self)". When the Chrome_gui is called it will create the test file and the self.p2 will be called again to check if the file is there. But it will become a never ending loop as the function self.Chrome_guiis not called. And when i remove self.p2, the function self.Chrome_gui can be called. So can anyone tell me why it is skipping the self.Chrome_gui function?
def __init__(self):
tk.Tk.__init__(self)
tk.Tk.title(self,"qwerty")
self.b1 = tk.Button(self, text="P2", command = self.p2)
self.b1.grid(row = 3, column = 1, sticky = 'EWNS' )
def p2 (self):
self.values()
print ('printdwo')
my_file1 = Path("C:\test.pdf")
if my_file1.is_file():
print ("File Found")
else:
print ('not found')
self.Chrome_gui()
self.p2()
def Chrome_gui(self):
self.chrome = tk.Tk()
self.chrome.title('Date')
self.label = tk.Label(self.chrome, text="", width=20)
self.label.grid(row = 1, column = 1)
self.c1 = tk.Button(self.chrome, text="Yes", command = self.yes)
self.c1.grid(row = 2, column = 1, sticky = W+E)#side = LEFT)
global e
e = ""
self.c2 = tk.Button(self.chrome, text = "No" , command = self.no)
self.c2.grid(row = 3, column = 1, sticky = W+E)#side = LEFT)
Your code is looping because your condition if my_file1.is_file(): is always false so it's always calling self.p2() in the else part.
When you're defining a string and you want to put a '\', you have to put '\'. In your case you have '\t' so it will replace it by a tabulation. Replace it by Path("C:\\test.pdf")

Two tk windows instead of one

I am new to tkinter and I would like to create a tkinter interface with "entries" , a canvas with a picture , and a button. I expected only one tk window but there is a second small empty tk window when I run my code. I guess it is because of the class I use but I'm not sure..
Here is my code :
from tkinter import*
import tkinter as tk
import time
class Application(Tk):
def __init__(self,transfo,nb_itération):
Tk.__init__(self)
self.transfo = transfo
self.nb_itération = nb_itération
def affichage_graphique(self):
self.matrix_hex = extraction_rgb(tk.PhotoImage(file='obama_128.gif'))
self.width , self.height = len(self.matrix_hex[0]) , len(self.matrix_hex)
self.WIDTH, self.HEIGHT = 8*self.width+6, 8*self.height+6
self.cnv = Canvas(self, width=self.WIDTH, height=self.HEIGHT, bg='white',highlightthickness=0)
self.cnv.grid(row = 2 , columnspan = 3)
self.txt1 = Label(self, text = 'Transformation :').grid(row = 0 , sticky = E)
self.txt2 = Label(self, text = 'Nombre d\'itérations : ').grid(row = 1 , sticky = E)
self.entr1 = Entry(self)
self.entr2 = Entry(self)
self.entr1.grid(row = 0, column = 1, sticky = W)
self.entr2.grid(row = 1, column = 1, sticky = W)
Button(self, text="Quitter", command=self.destroy).grid(row = 0 , column = 2)
if self.transfo == photomaton or self.transfo == boulanger:
print("valeur par défaut mauvaise")
self.cnv.delete(ALL)
self.img01 = self.PhotoImage(width=self.width*4, height=self.height*4)
self.img = self.PhotoImage(width=self.width*4, height=self.height*4)
self.matrix_hex4 = [[x for x in range(self.width*4)] for y in range(self.height*4)]
self.matrix_4 = [[x for x in range(self.width*4)] for y in range(self.height*4)]
self.img.put(data=self.matrix_4 , to=(0,0))
self.cnv.create_image(0, 0, image=self.img, anchor=tk.NW)
self.img01.put(data=self.matrix_hex4, to=(0,0))
self.cnv.create_image(self.WIDTH, 0, image=self.img01, anchor=tk.NE)
self.cnv.bind('<Button-1>',self.click_handler)
else:
self.img = tk.PhotoImage(width=self.width*4, height=self.height*4)
self.img.put(data=self.matrix_4 , to=(0,0))
self.cnv.create_image(0, 0, image=self.img, anchor=tk.NW)
self.entr1.insert(0,"photomaton")
self.entr2.insert(0,"1")
self.entr2.bind("<Return>",Programme().Enter)
class Programme(Application):
def __init__(self,transfo = 0, nb_itération = 0 ,orbites =[],période = []):
self.choice = 0
self.orbites = orbites
self.période = période
self.transfo = transfo
self.nb_itération = nb_itération
Application.__init__(self, self.transfo , self.nb_itération)
def Enter(self,event):
self.choix_transfo = self.entr1.get()
self.nb_itération = self.entr2.get()
return self.choix_transfo , self.nb_itération
def start(self):
self.affichage_graphique()
prog = Programme()
prog.start()
prog.mainloop()
The problem seems to be on this line:
self.entr2.bind("<Return>",Programme().Enter)
This line has multiple problems:
it will create a second Program() instance (assuming that this is a typo), thus creating a second Tk window (because Program extends Tk)
it will do so immediately, not only when the button is clicked, and bind the method of that new instance to the button
Instead, you probably want to use
self.entr2.bind("<Return>", self.Enter)
to call the Enter method of the current Program instance, or
self.entr2.bind("<Return>", lambda: Program().Enter)
if you actually want to create a second Program window when the button is clicked; however, in this case you will also have to call the gui method in order to initialize the widgets.
Note that there are a few other problems in your code, as discussed in comments, but I assume that those don't exist in your actual code, otherwise you would not get that behaviour.
You are creating a new window in two places. First, with this:
prog = Programme()
The second is here:
self.entr2.bind("<Return>",Programme().Enter)
Why? The above code is functionally identical to this:
p = Programme()
self.entr2.bind("<Return>", p.Enter)
The normal way to call functions defined in your program class is to use the existing reference. Since your code is being run in a method belonging to the Programme class, you can do this:
self.entr2.bind("<Return>", self.Enter)

Multi-page window cannot highlight Entry text with callback

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 is this code working I took from a textbook for GUI Python?

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 :)

Categories

Resources