Tkinter widget after method - python

I haven't found a thread to answer my question, so:
My GUI consists of 3 separate "windows" (Frames?) as classes: TextInput, TextOutput and Statistic. For performance reasons I only ran the Statistic.mainloop() but the other classes show up, too.
What I want my TextInput class to do is
iterate through a list with strings and insert them in a textwidget "self.ref_text".
class TextInput(tk.Frame):
LARGE_FONT = ("Arial Bold ", 18)
SMALL_FONT = ("Arial", 16)
BGC = '#CDCDC1'
FG = ['#000000', '#f44242']
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
# print('415 TextInput instance: ', type(self))
self.controller = controller
self.reference = [] # some strings in here
self.createText()
def createText(self):
self.ref_text = tk.Text(self, height=30, width=100, font=TextInput.SMALL_FONT)
self.ref_text.config(background=TextInput.BGC)
self.ref_text.grid(column=0, columnspan=4, row=1, padx=5, sticky="W")
def display_ref(self, line):
print('line: ', line)
self.ref_text.insert('end', line)
def read_ref(self):
for line in self.reference:
self.ref_text.insert('end', line)
self.ref_text.after(1500, self.read_ref)
the after() method inserts all strings of "self.reference" instead of the intended FOR Loop. Also, the whole TextInput app seems to tilt (to much recursion?)
In another version I tried to call
self.ref_text.after(1500, self.display_ref, line)
which again puts all the text in the widget after 1500 ms.
What am I doing wrong?
Is it a problem that I only run
Statistik.mainloop()
at the bottom instead of TextInput.mainloop().
Thanks for your help
as for the minimal example:
import tkinter as tk
class Interface(tk.Tk):
def __init__(self, name, page, *kwargs):
tk.Tk.__init__(self, name, *kwargs)
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
container.master.title(name)
self.frames = {}
self.windows = {}
self.windows[name] = page
self.window = page(container, self)
self.frames[name] = self.window
self.window.grid(row=0, column=0, sticky='nsew')
self.show_window(name)
def show_window(self, cont):
window = self.frames[cont]
window.tkraise()
class TextInput(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.reference = ['a is a sentence', 'b follows a', 'c closes the session']
self.createText()
self.read = True
self.read_ref()
def stop_read(self):
self.read = False
def createText(self):
self.ref_text = tk.Text(self, height=30, width=80,)
self.ref_text.grid(column=0, row=1, columnspan=3, padx=5, sticky="W")
def display_ref(self, line):
print('line: ', line)
self.ref_text.insert('end', line)
def read_ref(self):
'''
the goal is to loop through self.reference line by line
with a 1500 ms pause inbetween
'''
for line in self.reference:
if self.read:
self.ref_text.insert('end', line + '\n')
self.ref_text.after(1500, self.read_ref)
else:
return
class Statistik(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
'''does some other stuff'''
textinput_instance = Interface('TextInput', TextInput)
statistik_instance = Interface('Statistik', Statistik)
statistik_instance.mainloop()

If your goal is to loop through the list, displaying each line at 1500ms intervals, the easiest way is to have a function that does one iteration and then repeats itself using after
Something like this, perhaps:
def read_ref(self, lines):
# remove one item from the list
line = lines.pop(0)
# insert it
self.ref_text.insert("end", line + "\n")
# run again in 1500ms if there's still more work to do
if lines:
self.after(1500, self.read_ref, lines)
Then, call this function exactly once to start the process:
self.read_ref(self, self.reference)
If you want to be able to stop it, you can check for a flag in your function:
def read_ref(self):
...
if self.reference and not self.stop:
self.after(1500, self.read_ref)
The above code slowly removes items from self.reference. If you don't want that to happen, pass a copy of self.reference when you start so that the function will remove items from a copy of the original data.
self.read_ref(self, self.reference[:])

Related

How to forget a child frame?

My question seems easy but after trying all methods, which should work, I could not forget or delete child frame.
Program is based on this ex: Switch between two frames in tkinter
My code:
import tkinter as tk
from tkinter import ttk
class myProgram(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.title(self, "myProgram")
framesForAllWindows = tk.Frame(self)
framesForAllWindows.pack(side="top")
framesForAllWindows.grid_rowconfigure(0)
framesForAllWindows.grid_columnconfigure(0)
self.frames = dict()
for pages in (checkPage, PageOne):
frame = pages(framesForAllWindows, self)
self.frames[pages] = frame
frame.grid(row=0, column=0, sticky="nsew")
print(framesForAllWindows.winfo_children())
self.show_frame(checkPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class checkPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.f = tk.Frame()
self.f.pack(side="top")
self.controller = controller
self.enterAppid = ttk.Label(text='Please enter your something: ', font=('Courier', 20))
self.enterAppid.pack(padx=50, pady=100)
self.appidVar = tk.StringVar()
self.appidEnter = ttk.Entry(width=60, textvariable=self.appidVar)
self.appidEnter.pack()
self.checkAppid = ttk.Button(text='check', command = self.checkInfo)
self.checkAppid.pack(pady=50, ipady=2)
def checkInfo(self):
self.appid = self.appidVar.get()
if(self.appid == "good"):
self.controller.show_frame(PageOne)
#self.f.pack_forget() doesn`t work
else:
print('Unknown Error')
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="You are on page One")
label.pack(pady=1,padx=10)
app = myProgram()
app.state('zoomed')
app.mainloop()
The goal is to forget all checkPage frame and move on to PageOne. When I execute the code that I currently have "You are on page one" appears in addition to all widgets from checkPage which I don`t want. Any ideas where I am making mistake?
You didn't specify your parent for the Label, Entry and Button. If you change these 3 lines it should work.
So like this:
self.enterAppid = ttk.Label(self.f, text='Please enter your something: ', font=('Courier', 20))
self.appidEnter = ttk.Entry(self.f, width=60, textvariable=self.appidVar)
self.checkAppid = ttk.Button(self.f, text='check', command = self.checkInfo)

Why does changing a variable from a lambda not work?

I have been working on Tkinter and am finding trouble passing values between different frames, so I followed this tutorial here, using the "shared data" solution provided by Bryan Oakley and adding it to my own code.
Except I cannot set the value in the "shared data" dictionary as a command on a button.
A few comments in the code below outline the problem. If I just try to change the variable during the init of my choice page, it changes normally. But putting it in a lambda means that the dictionary variable won't change at all. And trying to use a def for the button command has its own complications.
import tkinter as tk
import tkinter.ttk as ttk
# from tkinter import messagebox
TITLE_FONT = ("Segoe UI Light", 22)
SUBTITLE_FONT = ("Segoe UI Light", 12)
window_size = [300, 200]
resistors = []
choice = "default"
class RegApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.iconbitmap(self, default="test.ico")
tk.Tk.wm_title(self, "Test")
self.shared_data = {
"choice": tk.StringVar(),
}
container = tk.Frame(self, width=window_size[0], height=window_size[1])
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in panels:
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="NSEW")
self.show_frame(WelcomePage)
def show_frame(self, container):
frame = self.frames[container]
frame.tkraise()
class WelcomePage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
title_label = ttk.Label(self, text="Welcome", font=TITLE_FONT)
subtitle_label = ttk.Label(self, text="Let's run some numbers.", font=SUBTITLE_FONT)
start_button = ttk.Button(self, text="Begin", width=24, command=lambda: controller.show_frame(ChoicePage))
title_label.pack(pady=(40, 5))
subtitle_label.pack(pady=(0, 10))
start_button.pack()
class ChoicePage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.controller.shared_data["choice"].set("test2") # Here, the variable is set fine
title_label = ttk.Label(self, text="Is your resistor network \nin series or parallel?", font=SUBTITLE_FONT,
justify=tk.CENTER)
series_button = ttk.Button(self, text="Series", width=24,
command=lambda: [self.controller.shared_data["choice"].set("series"), controller.show_frame(ValuePage)])
# But when I use it in a lambda, the variable doesn't even seem to set at all. It switches to the next page and has the value ""
parallel_button = ttk.Button(self, text="Parallel", width=24,
command=lambda: controller.show_frame(ValuePage))
title_label.pack()
series_button.pack()
parallel_button.pack()
# TODO Make the user select between 'series' and 'parallel'
class ValuePage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
title_label = ttk.Label(self, text=self.controller.shared_data["choice"].get(), font=SUBTITLE_FONT,
justify=tk.CENTER)
title_label.pack()
panels = [WelcomePage, ChoicePage, ValuePage]
app = RegApp()
app.resizable(False, False)
app.geometry('{}x{}'.format(window_size[0], window_size[1]))
app.mainloop()
well passing data between frames isn't too hard. there are 2 ways I like to do it.
Method 1:
setup a frame to be something like this....
class ThirdName(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
now just say something like:
self.data = "this is some data"
now when you're in another frame you can call it like this:
print(ThirdName.data)
>>> "this is some data"
Second way is just to send it somewhere like this:
value_1 = 'Richard'
bobby_def(value_1, 42, 'this is some text')
...
def bobby_def(name, number, text)
print(text)
or...
return(name, number, text)
Bobby will get the data :)
ok.... second point... moving between frames can be done with something like this:
self.button_to_go_to_home_page = tk.Button(self, text='Third\nPage', font=Roboto_Normal_Font,
command=lambda: self.controller.show_frame(ThirdName),
height=2, width=12, bd = 0, activeforeground=active_fg, activebackground=active_bg, highlightbackground=border_colour,
foreground=bg_text_colour, background=background_deselected)
self.button_to_go_to_home_page.place(x=20, y=280)
**set up a frame with stuff like this:
class SecondName(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
Yes, the self.controller method is one I use too and it's nice to have lots of your data together in one frame.
Why are you using 'controller' instead of 'self.controller'? It's kind of confusing that you assign 'self.controller' at the start of the constructor and then you use 'controller' instead. Maybe that variable shadowing is causing your problem.

Tkinter class method and accessing class members

In the attempt to make an editor with composite tkinter widgets I have stumbled upon an error or a bug?
The methods defined in the MyTextWidget class : fl() and fh() are to set the font size of the text widget belonging to the same class.
My understanding is that this should work, but when I have three instances of the same MyTextWidget class on a canvas using create_window() method, upon pressing the fl button and fh button, the text size in all three textwidgets changes simulataneously. I first tested it with one widget, when everything was working to my satisfaction, I added two more instances of the same class, but now it is not working as I expected it.
If this helps, the version is Python 2.7 and Tkinter version is Revision: 81008, debian linux.
Your help is appreciated, especially if you can guide me to a book or document that helps with the relevant information. Kindly enlighten.
import Tkinter as tk
import tkFont
class MyTextWidget(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent)
self.text = tk.Text(self, *args, **kwargs)
self.tbar = tk.Frame(self, *args, **kwargs)
self.tbar.pack(side=tk.TOP, padx=2, pady=2, fill="x", expand=True)
self.fl = tk.Button(self.tbar)
self.fl.pack(side=tk.LEFT, padx=2, pady=2)
self.fh = tk.Button(self.tbar, *args, **kwargs)
self.fh.pack(side=tk.LEFT, padx=2, pady=2)
def fl():
print "fl called"
self.text.configure(font=tkFont.Font(family="mytsmc", size=7), spacing1=2,spacing2=22,spacing3=2)
def fh():
print "fh called"
self.text.configure(font=tkFont.Font(family="mytsmc", size=9), spacing1=2,spacing2=22,spacing3=2)
self.fl.config(text="fl", width=1, command=fl)
self.fh.config(text="fh", width=1, command=fh)
self.vsb = tk.Scrollbar(self, orient="vertical", command=self.text.yview)
self.text.configure(yscrollcommand=self.vsb.set,
font=tkFont.Font(family="mytsmc", size=8),
spacing1=2,spacing2=32,spacing3=2)
self.vsb.pack(side="right", fill="y")
# self.text.pack(side="left", fill="both", expand=True)
self.text.pack(side="left", fill="both", expand=False)
self.insert = self.text.insert
self.delete = self.text.delete
self.mark_set = self.text.mark_set
self.get = self.text.get
self.index = self.text.index
self.search = self.text.search
class myEditor(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.w = tk.Canvas(self, width=1320,
height=740,
borderwidth=1,
background='white',
relief='raised')
self.w.pack(anchor='center')
#One
self.scrolled_text1 = MyTextWidget(self)
self.firstwidget = self.w.create_window(10, 10,
anchor=tk.NW,
width=400,
height=400,
window=self.scrolled_text1)
with open("/home/username/datafiles/1.txt", "r") as f:
self.scrolled_text1.insert("1.0", f.read())
#Two
self.scrolled_text2 = MyTextWidget(self)
self.firstwidget = self.w.create_window(420, 10,
anchor=tk.NW,
width=400,
height=400,
window=self.scrolled_text2)
with open("/home/username/datafiles/2.txt", "r") as f:
self.scrolled_text2.insert("1.0", f.read())
#Three
self.scrolled_text3 = MyTextWidget(self)
self.firstwidget = self.w.create_window(830, 10,
anchor=tk.NW,
width=400,
height=400,
window=self.scrolled_text3)
with open("/home/username/datafiles/3.txt", "r") as f:
self.scrolled_text3.insert("1.0", f.read())
def switchtob(event=None):
self.scrolled_text1.text.focus()
print "switched to b"
def switchton(event=None):
self.scrolled_text2.text.focus()
print "switched to n"
def switchtom(event=None):
self.scrolled_text3.text.focus()
print "switched to m"
root.bind('<Control-b>',switchtob)
root.bind('<Control-n>',switchton)
root.bind('<Control-m>',switchtom)
root = tk.Tk()
myEditor(root).pack(side="top", fill="both", expand=True)
def exit(event=None):
quit()
root.bind('<Control-q>',exit)
root.mainloop()
The simple fix is to create the fonts once, save a reference to them, and then use them instead of instantiating new fonts every time you click the button.
class MyTextWidget(tk.Frame):
def __init__(self, parent, *args, **kwargs):
...
self.font1 = tkFont.Font(family="mytsmc", size=7)
self.font2 = tkFont.Font(family="mytsmc", size=9)
...
def fl():
self.text.configure(font=self.font1, spacing1=2,spacing2=22,spacing3=2)
def fh():
self.text.configure(font=self.font2, spacing1=2,spacing2=22,spacing3=2)

Python tkinter text modified callback

In python 2.7, I am trying to get a callback every time something is changed in the Tkinter Text widget.
The program uses multiple frames based on code found here: Switch between two frames in tkinter
The callback part is taken from the following example: http://code.activestate.com/recipes/464635-call-a-callback-when-a-tkintertext-is-modified/
Both codes work fine separately, but combining those two is difficult for me.
Here is my attempt with as bare bones code as possible.
import Tkinter as tk
class Texter(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.pack()
self.frames = {}
for F in (ConnectPage, EditorPage):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
page_name = EditorPage.__name__
self.frames[page_name] = frame
self.show_frame(ConnectPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
def get_page(self, page_name):
return self.frames[page_name]
class ConnectPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
button1 = tk.Button(self, text="SecondPage",
command=lambda: controller.show_frame(EditorPage))
button1.grid(row=2, column=3, padx=15)
class EditorPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.text = tk.Text(self, height=25, width=80)
self.text.grid(column=0, row=0, sticky="nw")
button2 = tk.Button(self, text="FirstPage",
command=lambda: controller.show_frame(ConnectPage))
button2.grid(row=2, column=3, padx=15)
self.clearModifiedFlag()
self.bind_all('<<Modified>>', self._beenModified)
def _beenModified(self, event=None):
if self._resetting_modified_flag: return
self.clearModifiedFlag()
print("Hello!")
#self.beenModified(event)
def clearModifiedFlag(self):
self._resetting_modified_flag = True
try:
self.tk.call(self._w, 'edit', 'modified', 0)
finally:
self._resetting_modified_flag = False
if __name__ == '__main__':
gui = Texter()
gui.mainloop()
I tried taking only the necessary parts from the callback example.
The code does do a callback (if self.tk.call(self._w, 'edit', 'modified', 0) line is commented out) when the text is modified, but resetting the modified flag does not work, so only the first modification is registered.
At the moment I get the following error:
line 67, in clearModifiedFlag
self.tk.call(self._w, 'edit', 'modified', 0)
_tkinter.TclError: bad option "edit": must be cget or configure
In the callback example code "edit" works fine.
Edit: This is the working code
import Tkinter as tk
class Texter(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.pack()
self.frames = {}
for F in (ConnectPage, EditorPage):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
page_name = EditorPage.__name__
self.frames[page_name] = frame
self.show_frame(ConnectPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
def get_page(self, page_name):
return self.frames[page_name]
class ConnectPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
button1 = tk.Button(self, text="SecondPage",
command=lambda: controller.show_frame(EditorPage))
button1.grid(row=2, column=3, padx=15)
class EditorPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.text = CustomText(self, height=25, width=80)
self.text.grid(column=0, row=0, sticky="nw")
self.text.bind("<<TextModified>>", self.onModification)
button2 = tk.Button(self, text="FirstPage",
command=lambda: controller.show_frame(ConnectPage))
button2.grid(row=2, column=3, padx=15)
def onModification(self, event):
print("Yellow!")
class CustomText(tk.Text):
def __init__(self, *args, **kwargs):
"""A text widget that report on internal widget commands"""
tk.Text.__init__(self, *args, **kwargs)
# create a proxy for the underlying widget
self._orig = self._w + "_orig"
self.tk.call("rename", self._w, self._orig)
self.tk.createcommand(self._w, self._proxy)
def _proxy(self, command, *args):
cmd = (self._orig, command) + args
result = self.tk.call(cmd)
if command in ("insert", "delete", "replace"):
self.event_generate("<<TextModified>>")
return result
if __name__ == '__main__':
gui = Texter()
gui.mainloop()
I suggest a simpler approach. You can set up a proxy for the widget, and within that proxy you can detect whenever anything was inserted or deleted. You can use that information to generate a virtual event, which can be bound to like any other event.
Let's start by creating a custom text widget class, which you will use like any other text widget:
import Tkinter as tk
class CustomText(tk.Text):
def __init__(self, *args, **kwargs):
"""A text widget that report on internal widget commands"""
tk.Text.__init__(self, *args, **kwargs)
# create a proxy for the underlying widget
self._orig = self._w + "_orig"
self.tk.call("rename", self._w, self._orig)
self.tk.createcommand(self._w, self._proxy)
def _proxy(self, command, *args):
cmd = (self._orig, command) + args
result = self.tk.call(cmd)
if command in ("insert", "delete", "replace"):
self.event_generate("<<TextModified>>")
return result
The proxy in this example does three things:
First it calls the actual widget command, passing in all of the arguments it received.
Next it generates an event for every insert and every delete
Then it then generates a virtual event
And finally it returns the results of the actual widget command
You can use this widget exactly like any other Text widget, with the added benefit that you can bind to <<TextModified>>.
For example, if you wanted to display the number of characters in the text widget you could do something like this:
root = tk.Tk()
label = tk.Label(root, anchor="w")
text = CustomText(root, width=40, height=4)
label.pack(side="bottom", fill="x")
text.pack(side="top", fill="both", expand=True)
def onModification(event):
chars = len(event.widget.get("1.0", "end-1c"))
label.configure(text="%s chars" % chars)
text.bind("<<TextModified>>", onModification)
root.mainloop()
I integrated the above <<TextModified>> example in my code and it
worked quite well, except that it was interfering with some
edit_modified() commands.
Fortunately, the tkinter Text window has a poorly documented feature
which is as good and is fully compatible with the edit_modified() get
or set commands: the predefined <<Modified>> tag. You don't even have
to create it, it works out-of-the-box.
Here are the relevant parts of my code:
The "self" prefixes were removed, some adjustments may be needed
Put that in your Text gadget code:
title = set_title(fname, numbr)
text.bind("<<Modified>>", lambda dummy: save_indicator(title))
Make sure these functions are visible:
def set_title(fname, numbr):
"Creates a window title showing the save indicator,"
"the file name and a window number"
fname = strip_path(fname)
if not fname:
fname = "(New Document)"
return "+ {} - Window no.{}".format(fname, numbr)
def strip_path(fname):
return os.path.split(fname)[-1]
def save_indicator(title, event=None):
"Update the window title"
titre = toggle_star(title)
text.winfo_toplevel().title(title)
def toggle_star(title):
"Change the first character of the title"
chr='+'; chr0='x'
if text.edit_modified():
title = chr0 + title[1:]
else:
title = chr + title[1:]
return title
Here is a complete working example with the predefined <<Modified>> tag:
def toggle_star(title):
"Change the color of the star in the title bar"
chr='+'; chr0='x'
if text.edit_modified():
title = chr0 + title[1:]
else:
title = chr + title[1:]
return title
def set_title(fname, winno):
"Put save indicator, file name and window number in the title"
if not fname:
fname = "(New Document)"
return "+ {} - Window no.{}".format(fname, winno)
def mockSave(title, event=None):
title = toggle_star(title)
root.winfo_toplevel().title(title)
text.edit_modified(0)
def read_ed_mod():
print("text.edit_modified()=", text.edit_modified())
def onModification(title, event=None):
title = toggle_star(title)
root.winfo_toplevel().title(title)
from tkinter import *
fname = 'blabla.txt'
winno = 1 ;
root = Tk()
label = Label(root, anchor="w")
text = Text(root, width=40, height=4)
label.pack(side="bottom", fill="x")
text.pack(side="top", fill="both", expand=True)
Button(root, text='Mock Save', command= lambda: mockSave(title)).pack(side=LEFT)
Button(root, text='Read ed_mod', command= lambda: read_ed_mod()).pack(side=RIGHT)
text.bind('<<Modified>>', lambda event: onModification(title))
title = set_title(fname, winno)
root.winfo_toplevel().title(title)
text.edit_modified(0)
root.mainloop()

tkinter - Going Back and Forth Between Frames Using Buttons

I need functions, preferably one function, that can go back and forth between pages when the next and back buttons are pressed. I imagine this could be done by assigning boolean variables to the back and next buttons (not sure if this can be done) to figure out if you're going foward or back down an ordered list of all the pages. The index of the currently raised frame will need to be known. The indexes could be used to figure out the next page and then it would be raised. If the current index is 0 or the last index (in this case 2) and you press back or next respectively, then you would go to a homepage class frame, in this case BlankPage.
import tkinter as tk
from tkinter import ttk
class Program(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.iconbitmap(self, default = "")
tk.Tk.wm_title(self, "")
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (Add, BlankPage):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row = 0, column = 0, sticky = "nsew")
self.show_frame(Add)
def show_frame(self,cont):
frame = self.frames[cont]
frame.tkraise()
class Add(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
innerFrame = tk.Frame(self)
innerFrame.place(relx=.5, rely=.5, anchor="c", relwidth=1.0, relheight=1.0)
innerFrame.grid_rowconfigure(1, weight=1)
innerFrame.grid_columnconfigure(0, weight=1)
name = tk.Label(innerFrame, text = "User")
name.grid(row=0, sticky="NE")
pagename = tk.Label(innerFrame, text = "Label")
pagename.grid(row=0, sticky="N")
next = ttk.Button(innerFrame, text = "Next", command = self.changePage)
next.grid(row=2, sticky="E")
back = ttk.Button(innerFrame, text = "Back", command = self.changePage)
back.grid(row=2, sticky="W")
###########################################################################################################
self.pageThree = tk.Frame(innerFrame)
self.pageThree.grid(row=1)
self.pageThree.grid_rowconfigure(0, weight=1)
self.pageThree.grid_columnconfigure(0, weight=1)
pagename = tk.Label(self.pageThree, text = "Page 3")
pagename.grid(row=0, sticky="N")
###########################################################################################################
self.pageTwo = tk.Frame(innerFrame)
self.pageTwo.grid(row=1)
self.pageTwo.grid_rowconfigure(0, weight=1)
self.pageTwo.grid_columnconfigure(0, weight=1)
pagename = tk.Label(self.pageTwo, text = "Page 2")
pagename.grid(row=0, sticky="N")
###########################################################################################################
self.pageOne = tk.Frame(innerFrame)
self.pageOne.grid(row=1)
self.pageOne.grid_rowconfigure(0, weight=1)
self.pageOne.grid_columnconfigure(0, weight=1)
pagename = tk.Label(self.pageOne, text = "Page 1")
pagename.grid(row=0, sticky="N")
###########################################################################################################
def changePage(self,buttonBool):
pages = [self.pageOne,self.pageTwo,self.pageThree]
#find current raised page and set to variable 'current'
position = pages.index(current)
if (postion==0 and buttonBool==False) or (postion==len(pages)-1 and buttonBool==True):
show_frame(BlankPage)
elif buttonBool==True:
pages[position+1].tkraise()
else:
pages[position-1].tkraise()
class BlankPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
app = Program()
app.state('zoomed')
app.mainloop()
The changePage function is my attempt at this, how would I complete it?
You are very close to having it all working, after some looking myself I couldn't find any (not overly-complicated) way to figure out the top most Frame so it would probably be best to just keep a record of the current position:
def __init__(self, parent, controller):
...
self.position = 0 #the index of the pages list
And to get buttonBool to be passed to changePage you can something from here (Tlapička gives the best solution in my eyes since lambda expressions make the lines of code way too long)
def __init__(self, parent, controller):
...
# button commands don't have an event but sometimes you use these callbacks for both .bind and buttons
# so having event=None makes it work for both.
def go_next(event=None):
self.changePage(True)
next = ttk.Button(innerFrame, text = "Next", command = go_next)
next.grid(row=2, sticky="E")
def go_back(event=None):
self.changePage(False)
back = ttk.Button(innerFrame, text = "Back", command = go_back)
back.grid(row=2, sticky="W")
...
With these two (and implementing self.position into changePage) you can accomplish what you originally asked, everything below this is the code reviewer in me talking.
Although using a boolean would work, this strategy of dealing with extra arguments to callbacks lets you pass any argument into changePage so it would probably simplify the conditionals in changePage if it got the change in pages (so 1 or -1):
def go_next(event=None):
self.changePage(1)
next = ttk.Button(innerFrame, text = "Next", command = go_next)
next.grid(row=2, sticky="E")
def go_back(event=None):
self.changePage(-1)
back = ttk.Button(innerFrame, text = "Back", command = go_back)
back.grid(row=2, sticky="W")
#this is for the last suggestion
self.nextButton = next
self.backButton = back
...
then changePage could look like this although I'm not sure what would happen to self.position if you changed to an invalid page:
def changePage(self,change):
pages = [self.pageOne,self.pageTwo,self.pageThree]
new_position = self.position + change
if (new_postion < 0) or (new_postion <= len(pages)):
show_frame(BlankPage)
#not sure how you would handle the new position here
else:
pages[new_position].tkraise()
self.position = new_position
Even better, if you keep a reference to the next and back buttons you can config them to indicate that it is the end/beginning:
def changePage(self,change):
pages = [self.pageOne,self.pageTwo,self.pageThree]
new_position = self.position + change
if (0 <= new_postion < len(pages)):
pages[new_position].tkraise()
self.position = new_position
else:
show_frame(BlankPage)
if new_position+1 >= len(pages):
self.nextButton.config(text="End") #, state=tk.DISABLED)
else:
self.nextButton.config(text="Next") #, state=tk.NORMAL)
if new_position-1 < 0:
self.backButton.config(text="First") #, state=tk.DISABLED)
else:
self.backButton.config(text="Back") #, state=tk.NORMAL)
that way you would know when you reached the end even if there isn't indication from the contents. (or you could disable the buttons to prevent going past)

Categories