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.
Related
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 ....
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.
What I'm trying to do is get the frame with the two buttons (sframe) centered inside of the notebook (master) frame. This works without issue on Python 2.4 but on Python 2.7 the frame is anchored to NW by default. I know if I rowconfigure() / columnconfigure() the master page frame the inner frame will center itself but this solution doesn't seem correct. Disabling propagation and changing row/column weights don't seem to help either. Is there anyway to just get that inner frame centered properly? Here is the test code I'm working with:
import Tkinter as tk, Tkinter
import Pmw
class SimpleApp(object):
def __init__(self, master, **kwargs):
title = kwargs.pop('title')
master.configure(bg='blue')
sframe = tk.Frame(master, relief=tk.RIDGE, bd=5, width=100,bg='green')
sframe.grid()
button = tk.Button(sframe, text = title)
button.grid(sticky = tk.W)
button = tk.Button(sframe, text = 'next')
button.grid(sticky = tk.E)
#sframe.propagate(0)
#master.rowconfigure(0, minsize = 300)
#master.columnconfigure(0, minsize = 300)
class Demo:
def __init__(self, parent):
# Create and pack the NoteBook.
notebook = Pmw.NoteBook(parent)
notebook.pack(fill = 'both', expand = 1, padx = 10, pady = 10)
# Add the "Appearance" page to the notebook.
page = notebook.add('Helpers')
app = SimpleApp(page, title= 'hello, world')
notebook.tab('Helpers').focus_set()
page = notebook.add('Appearance')
# Create the "Toolbar" contents of the page.
group = Pmw.Group(page, tag_text = 'Toolbar')
group.pack(fill = 'both', expand = 1, padx = 10, pady = 10)
b1 = Tkinter.Checkbutton(group.interior(), text = 'Show toolbar')
b1.grid(row = 0, column = 0)
b2 = Tkinter.Checkbutton(group.interior(), text = 'Toolbar tips')
b2.grid(row = 0, column = 1)
# Create the "Startup" contents of the page.
group = Pmw.Group(page, tag_text = 'Startup')
group.pack(fill = 'both', expand = 1, padx = 10, pady = 10)
home = Pmw.EntryField(group.interior(), labelpos = 'w',
label_text = 'Home page location:')
home.pack(fill = 'x', padx = 20, pady = 10)
page = notebook.add('Images')
notebook.setnaturalsize()
def basic():
root = tk.Tk()
#app = SimpleApp(root, title = 'Hello, world')
app = Demo(root)
root.mainloop()
basic()
Let me know if I can provide any additional information.
You need to configure the weight of row 0 and column 0 in the master:
master.grid_columnconfigure(0, weight=1)
master.grid_rowconfigure(0, weight=1)
You are placing that inner sframe in row 0 column 0 of master, and since that cell has no weight it shrinks up to the upper-left corner. Giving the row and column a weight of 1 makes the column and row fill the available space. Since you aren't using any sticky options for the sframe, it will stay centered in its cell rather than filling its cell.
from Tkinter import *
class Application (Frame):
def __init__(self, master):
Frame.__init__(self, master)
self.grid()
self.create_widgets()
def create_widgets(self):
Label(self, text = "Select the last book you read.").grid (row = 0, column = 0, sticky = W)
self.choice = StringVar()
Radiobutton (self,text = "Nausea by Jean-Paul Sartre",variable = self.choice,
value = "Wake up. This is a dream. This is all only a test of the emergency broadcasting system.",
command = self.update_text).grid (row = 2, column = 1, sticky = W)
Radiobutton (self,
text = "Infinite Jest by David Foster Wallace",
variable = self.choice,
value = "Because an adult borne without the volition to choose the thoughts that he thinks, is going to get hosed ;)",
command = self.update_text).grid (row = 3, column = 1, sticky = W)
Radiobutton (self,
text = "Cat's Cradle by Kurt Vonnegut",
variable = self.choice,
value = " \"Here we are, trapped in the amber of the moment. There is no why!\" ",
command = self.update_text.grid (row = 4, column = 1, sticky = W)
self.txt_display = Text (self, width = 40, height = 5, wrap = WORD)
self.txt_display.grid (row = 6, column = 0, sticky = W)
#There is only one choice value - self.choice. That can be "printed."
def update_text(self):
message = self.choice.get()
self.txt_display.delete (0.0, END)
self.txt_display.insert (0.0, message)
# The Main
root = Tk()
root.title ("The Book Critic One")
root.geometry ("400x400")
app = Application (root)
root.mainloop()
I keep getting a Syntax Error in the self.text_display_delete line which I can't seem to lose.
Any input would be greatly appreciated.
Take a look at the previous line - I only count one closing parenthesis, while you should have two:
Radiobutton (self,
text = "Cat's Cradle by Kurt Vonnegut",
variable = self.choice,
value = " \"Here we are, trapped in the amber of the moment. There is no why!\" ",
command = self.update_text.grid (row = 4, column = 1, sticky = W)) #<-- Missing that second paren
Usually if one line looks clean, the syntax error is on the previous line(s), and 99% of the time it's a missing paren.