Home > Blockchain >  Tkinter Switching between multiple frames with 2 buttons
Tkinter Switching between multiple frames with 2 buttons

Time:02-05

I am new to python and tk and trying to learn by creating a application. In this I want to have one window with 5 frames stacked on top of each other and two buttons called "Next" and "Back". When opening the window, frame_1 should be displayed and when I press "Next" frame_2 gets displayed. Pressing "Next" a second time raises frame_3 and so one. Do I press "Back" the previous frame should be displayed again.

What I got working so far is the change between two frames back and forth:

from tkinter import *

test = Tk()


def but_next_click():
    frame_2.tkraise()


def but_back_click():
    frame_1.tkraise()


test.geometry("300x300")
frame_1 = LabelFrame(test, text="frame_1", bg="blue4")
frame_2 = LabelFrame(test, text="frame_2", bg="yellow4")
frame_3 = LabelFrame(test, text="frame_3", bg="green4")
frame_4 = LabelFrame(test, text="frame_4", bg="red4")

but_next = Button(test, text="Next", width=5, height=1, pady=2,
                  command=but_next_click)
but_back = Button(test, text="Back", width=5, height=1, pady=2,
                  command=but_back_click)

frame_1.place(relx=0.5, rely=0.2, relwidth=0.9, relheight=0.9, anchor='n')
frame_2.place(relx=0.5, rely=0.2, relwidth=0.9, relheight=0.9, anchor='n')
frame_3.place(relx=0.5, rely=0.2, relwidth=0.9, relheight=0.9, anchor='n')
frame_4.place(relx=0.5, rely=0.2, relwidth=0.9, relheight=0.9, anchor='n')

but_next.place(relx=0.7, rely=0.05, anchor='n')
but_back.place(relx=0.3, rely=0.05, anchor='n')

frame_1.tkraise()

test.mainloop()

However now I am trying to kind of safe the "page_number". So what I tried to do, was to have a global variable called "pagenumber" (sry the underscore makes the text Italic :D) and store the return value of the but-next-click() or but-back-click() functions inside it. But at this point I cant get any further because the functions and variables dont work they way I imagined it (From working a bit with VBA). I think I am missing some basic understanding of how the functions and variables work with eachtother.

Could you please help me out?

from tkinter import *

page_number = 0

test = Tk()

test.geometry("300x300")
frame_1 = LabelFrame(test, text="frame_1", bg="blue4")
frame_2 = LabelFrame(test, text="frame_2", bg="yellow4")
frame_3 = LabelFrame(test, text="frame_3", bg="green4")
frame_4 = LabelFrame(test, text="frame_4", bg="red4")

but_next = Button(test, text="Next", width=5, height=1, pady=2,
                  command=lambda: but_next_click(page_number))
but_back = Button(test, text="Back", width=5, height=1, pady=2,
                  command=lambda: but_back_click(page_number))


def but_next_click(page):
    print(f'Button Next Start - Page is {page}')
    if page == 0:
        frame_1.tkraise()
        page = page   1
    if page == 1:
        frame_2.tkraise()
        page = page   1
    if page == 2:
        frame_3.tkraise()
        page = page   1
    print(f'Button Next END - Page is {page}')
    return page


def but_back_click(page):
    print(f'Button Back Start - Page is {page}')
    if page == 1:
        frame_1.tkraise()
        page = page - 1
    if page == 2:
        frame_2.tkraise()
        page = page - 1
    if page == 3:
        frame_3.tkraise()
        page = page - 1
    print(f'Button Back End - Page is {page}')
    return page


page_number = but_next_click(page_number) or but_back_click(page_number)
# thats how I would store the output of a function. But this also executes the functions.
# Which I dont want. I only want to store the page value and use with the next button click

frame_1.place(relx=0.5, rely=0.2, relwidth=0.9, relheight=0.9, anchor='n')
frame_2.place(relx=0.5, rely=0.2, relwidth=0.9, relheight=0.9, anchor='n')
frame_3.place(relx=0.5, rely=0.2, relwidth=0.9, relheight=0.9, anchor='n')
frame_4.place(relx=0.5, rely=0.2, relwidth=0.9, relheight=0.9, anchor='n')

but_next.place(relx=0.7, rely=0.05, anchor='n')
but_back.place(relx=0.3, rely=0.05, anchor='n')

frame_1.tkraise()

test.mainloop()

Thank you very much advance :)

I tried to understand the input and output of the function. What I dont understand is the Run output without clicking a button:

Button Next Start - Page is 0 Button Next END - Page is 3 (*1)

What I would expect is: The program checks which value page_number has and then using the first "if". But I dont understand, why it iterates through all the if´s leading to the output of 3 when I run the program. (*1)

But at least the value gets stored so that but_back_click can work with it. But somehow the output value then gets not stored.

Button Back Start - Page is 3 Button Back End - Page is 2 Button Back Start - Page is 3 Button Back End - Page is 2

This all confuses the hack out of my small brain :D Maybe you have some tip for me?

CodePudding user response:

I am going to suggest an alternative strategy. Instead of using a variable that keeps track of the current page number I would suggest creating a list containing each of your frames that you can iterate through, and storing it as well as the current top frame in a dictionary.

Then when either the back or forward button is pressed it can iterate through the list containing the frames and check if each one is the top frame. When it finds the current top frame it can then determine which frame to raise next based on it's position in the list. This avoids needing an if statement for each and every frame and it will work equally for any number of frames you end up needing to create.

It also has the benefit of not needing to pass anything to the button callbacks, or the use of a lambda in the buttons command parameter.

For example:

from tkinter import *

test = Tk()

test.geometry("300x300")
frame_1 = LabelFrame(test, text="frame_1", bg="blue4")
frame_2 = LabelFrame(test, text="frame_2", bg="yellow4")
frame_3 = LabelFrame(test, text="frame_3", bg="green4")
frame_4 = LabelFrame(test, text="frame_4", bg="red4")

frame_info = {
    "top": frame_1,  # start with frame_1 on top
    "frames": [frame_1, frame_2, frame_3, frame_4]
}


def get_frame_index():
    """Iterate list of frames to find which one is on top."""
    for i, frame in enumerate(frame_info["frames"]):
        if frame == frame_info["top"]:
            return i

def but_next_click():
    """Determine next frame based on it's position in the list."""
    frames = frame_info["frames"]
    index = get_frame_index()
    if index == len(frames) - 1:
        next_frame = frames[0]
    else:
        next_frame = frames[index 1]
    next_frame.tkraise()    # raise the next frame
    frame_info["top"] = next_frame   # assign the next frame to the "top" frame in dictionary.

def but_back_click():
    frames = frame_info["frames"]
    index = get_frame_index()
    if index == 0:
        next_frame = frames[-1]
    else:
        next_frame = frames[index-1]
    next_frame.tkraise()
    frame_info["top"] = next_frame


but_next = Button(test, text="Next", width=5, height=1, pady=2,
                  command=but_next_click)
but_back = Button(test, text="Back", width=5, height=1, pady=2,
                  command=but_back_click)
frame_1.place(relx=0.5, rely=0.2, relwidth=0.9, relheight=0.9, anchor='n')
frame_2.place(relx=0.5, rely=0.2, relwidth=0.9, relheight=0.9, anchor='n')
frame_3.place(relx=0.5, rely=0.2, relwidth=0.9, relheight=0.9, anchor='n')
frame_4.place(relx=0.5, rely=0.2, relwidth=0.9, relheight=0.9, anchor='n')

but_next.place(relx=0.7, rely=0.05, anchor='n')
but_back.place(relx=0.3, rely=0.05, anchor='n')

frame_1.tkraise()

test.mainloop()

CodePudding user response:

Just in case other newbies having a similiar problem, the following also works. But to clearify this answer: I just post it to get feedback from the experts, wether this is working but just a crutch or actually a usable solution.

So what I found is, that I had to add "global" to both functions. This way but_back_click() and but_next_click() can read and write it.

def but_next_click():
    global page_number
    print(f'Button Next Start - Page is {page_number}')
    if page_number <= 2:       #this just prevents overcounting, when the button is clicked too many times
        if page_number == 0:
            frame_2.tkraise()
        elif page_number == 1:
            frame_3.tkraise()
        elif page_number == 2:
            frame_4.tkraise()
        page_number = page_number   1
    print(f'Button Next END - Page is {page_number}')
    return

CodePudding user response:

Different way, work with classes

Create Ui object and do all the stuff in this object. Create screens and add them to a list. Later you can change the current index position. A manage method tracks the current index and pack the frame on window.

Maybe it will help you.

import tkinter as tk 
from tkinter import ttk 

class Ui:
def __init__(self):        
    self.frame_obj_array=[]
    self.create_screens(5)
    self.activescreen_index=0
    self.last_activescreen_index=1
    root.after(12,self.update)
    
def create_screens(self,n):
    for i in range(n):
        frame=tk.Frame(root)
        btn_back=ttk.Button(frame,text='back',command=self.back)
        btn_next=ttk.Button(frame,text='next',command=self.next)
        lbl=tk.Label(frame,text=f'Frame: {str(i)}!',width=100,font=('Railway',20))
        btn_back.pack(side='left',anchor='sw',pady=40,padx=40)
        btn_next.pack(side='right',anchor='se',pady=40,padx=40)
        lbl.pack(side='top')
        self.frame_obj_array.append(frame)

def back(self):
    if not self.activescreen_index <= 0:
        self.activescreen_index-=1

def next(self):
    if self.activescreen_index < len(self.frame_obj_array)-1:
        self.activescreen_index =1

def manage_screens(self):
    if self.activescreen_index != self.last_activescreen_index:
        self.frame_obj_array[self.last_activescreen_index].pack_forget()
        self.frame_obj_array[self.activescreen_index].pack(expand=True,fill='both')
        self.last_activescreen_index=self.activescreen_index
   
def update(self):
    self.manage_screens()
    root.after(12,self.update)

if __name__ == 'main':
    root=tk.Tk()
    root.geometry('%dx%d %d %d' % (900,600, 0, 0))
    app=Ui()
    root.mainloop()
  • Related