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 ....
Related
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.
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)
I am creating a simple GUI program to manage priorities. I am having troubles with adding items to the listbox. I tried to create an instance of Priority class by passing two attributes to the constructor and then use g.listBox.insert(END, item), but it seems it doesn't work like that. I am getting an error:
/usr/bin/python3.5 /home/cali/PycharmProjects/priorities/priorities.py
Exception in Tkinter callback Traceback (most recent call last):
File "/usr/lib/python3.5/tkinter/init.py", line 1553, in call
return self.func(*args) File "/home/cali/PycharmProjects/priorities/priorities.py", line 52, in
addItem
item = Priority(subject = g.textBox.get("1.0", 'end-1c'), priority = g.textBox.get("1.0", 'end-1c')) AttributeError: 'GuiPart' object has no attribute 'textBox'
Process finished with exit code 0
Here is what I have done:
# priorities.py
# GUI program to manage priorities
from tkinter import *
class Priority:
def __init__(self, subject, priority):
self.subject = subject
self.priority = priority
def subject(self):
return self.subject
def priority(self):
return self.priority
class GuiPart:
def __init__(self):
self.root = self.createWindow()
def createWindow(self):
root = Tk()
root.resizable(width = False, height = False)
root.title("Priorities")
return root
def createWidgets(self):
Button(self.root,
text = "Add",
command = self.addItem).grid(row = 2, column = 0, sticky = W + E)
Button(self.root,
text="Remove",
command = self.removeItem).grid(row = 2, column = 1, sticky = W + E)
Button(self.root,
text="Edit",
command = self.editItem).grid(row = 2, column = 2, sticky = W + E)
listBox = Listbox(width = 30).grid(row = 1, sticky = W + E, columnspan = 3)
textBox = Text(height=10, width=30).grid(row = 3, columnspan = 3, sticky = W + E + N + S)
def addItem(self):
item = Priority(subject = g.textBox.get("1.0", 'end-1c'), priority = g.textBox.get("1.0", 'end-1c'))
g.listBox.insert(END, item)
def removeItem(self):
pass
def editItem(self):
pass
class Client:
pass
if __name__ == "__main__":
g = GuiPart()
g.createWidgets()
g.root.mainloop()
I'm using Python 3.5.
So if I understood your aim, you are trying to describe a priority by allowing the user to type, within the text zone widget, its information which consists in its subject and order; after that, the user can click on the "Add" button to insert the priority information into your list box.
There are lot of things to consider around your code. If I go to fix and comment them one by one, I believe my answer will be long while I feel lazy today.
I think my program below is easy to understand (ask a clarification otherwise). I did not find specifications inherent to how the propriety information is typed in the text zone. So my program below works under the assumption the user types the priority subject on the first line of the text area, and then uses a new line to type the priority order. The click on "Add" button will lead to the insertion of these 2 data on the same line of the text box widget as shown below:
Here is an MCVE:
import tkinter as tk
class ProioritiesManager(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.master = master
self.master.resizable(width = False, height = False)
self.master.title("Priorities")
self.create_buttons()
self.create_listbox()
self.create_priorities_description_zone()
def create_buttons(self):
add_item = tk.Button(self.master, text='Add', command=self.add_item)
add_item.grid(row=2, column=0, sticky=tk.W+tk.E)
remove_item = tk.Button(self.master, text='Remove', command=self.remove_item)
remove_item.grid(row=2, column=1, sticky=tk.W+tk.E)
edit_item = tk.Button(self.master, text='Edit', command=self.edit_item)
edit_item.grid(row=2, column=2, sticky=tk.W+tk.E)
def create_listbox(self):
self.item_alternatives = tk.Listbox(self.master, width=30)
self.item_alternatives.grid(row=1, sticky=tk.W+tk.E, columnspan=3)
def create_priorities_description_zone(self):
self.priority_text_zone = tk.Text(self.master, height=10, width=30)
self.priority_text_zone.grid(row=3, columnspan=3, sticky=tk.W+tk.E+tk.N+tk.S)
def get_priority_subject(self):
return self.priority_text_zone.get('1.0', '1.0 lineend')
def get_priority_order(self):
return self.priority_text_zone.get('2.0', '2.0 lineend')
def add_item(self):
self.item_alternatives.insert(tk.END, self.get_priority_subject()+' '+ self.get_priority_order())
def remove_item(self):
pass
def edit_item(self):
pass
if __name__ == "__main__":
root = tk.Tk()
ProioritiesManager(root)
root.mainloop()
If you want to give a good UX to your GUI then it would be nice if you add a button to allow the user to clear the content of the text area so that he can type in a new priority:
For this purpose, you can add a rest button to create_buttons() function by adding these 2 lines of code:
clear_text_area = tk.Button(self.master, text='Reset', command=self.reset_priority_text_zone)
clear_text_area.grid(row=4, column=2)
The callback reset_priority_text_zone() is defined this way:
def reset_priority_text_zone(self):
self.priority_text_zone.delete('1.0', tk.END)
These Lines are causing Error :
listBox = Listbox(width = 30).grid(row = 1, sticky = W + E, columnspan = 3)
textBox = Text(height=10, width=30).grid(row = 3, columnspan = 3, sticky = W + E + N + S)
Do it like this:
self.listBox = Listbox(self.root,width = 30)
self.listBox.grid(row = 1, sticky = W + E, columnspan = 3)
self.textBox = Text(self.root,height=10, width=30)
self.textBox.grid(row = 3, columnspan = 3, sticky = W + E + N + S)
Actually you are not creating listBox and textBox objects instead grid is returning to listBox and textBox
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.