使用 Tkinter 中的菜单栏在页面之间切换

Posted

技术标签:

【中文标题】使用 Tkinter 中的菜单栏在页面之间切换【英文标题】:Switch from page to page with Menubar in Tkinter 【发布时间】:2020-06-25 15:38:56 【问题描述】:

我的 tkinter IHM 有一些问题: 我希望能够通过始终保持原位的菜单栏从一帧切换到另一帧。 我尝试了 Bryan Oakley 的方法。可以使用简单的小部件页面,但不能使用涉及更复杂小部件的页面。

在我的示例中:ZoomConstructorPage 始终可见,不会被其他页面隐藏。 此外,我不知道如何将 Zoom_constructor_Frame 小部件包含在 PanedWindow 中。

我希望在打开应用程序时显示 StartPage,然后在单击“Charger un jeu de données”时切换到 PageTwo,或者在单击“Définir un zoom”时在 PanedWindow 中显示 ZoomConstructorPage

我是 tkinter 的新手,非常感谢任何帮助。

这是我的主要代码:`

import tools
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox
from tkinter import filedialog
    
    
###############################################################################        


class MainWindow(tools.FullscreenWindow):
""" Fenêtre principale """
    def __init__(self, title, *args, **kwargs):
        super().__init__(*args, **kwargs)
        
        self.wm_title(title)
        # self.iconbitmap('drone.ico')
        
        container = ttk.Frame(self)
        container.grid()
        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)
                        
        # Affiche la Barre de Menu      
        '''MenuBar(self).grid()
        self.columnconfigure(0, weight=1)'''
        
###############################################################################
        # Barre de Menu principal
        menu_bar = tk.Menu(self)
        menu_bar.add_command(label='Traiter nouveau fichier', 
                             command=self.explorer_fichier)
        
        
        menu_bar.add_command(label='Charger un jeu de données', 
                             command=self.charger_data)
        
        
        menu_bar.add_command(label='Définir un zoom', 
                             command=self.definir_zoom)
        
        
        # Sous menu liste des enregistrements
        submenu1 = tk.Menu(menu_bar, tearoff=0)
        menu_bar.add_cascade(label='Enregistrements', menu=submenu1)
        submenu1.add_command(label='Liste complète')
        
                
        # Quitter l'application
        menu_bar.add_command(label='Quitter', 
                             command=self.close_application)
                       
        
        # Affiche le Menu 
        self.config(menu=menu_bar)
###############################################################################        
        
        # Déroule les différentes pages        
        self.frames =     
        for F in (StartPage, ZoomConstructorPage, PageTwo):
            frame = F(container, self)
            self.frames[F] = frame
            frame.grid(row=0, column=0, sticky="nsew")            

        self.show_frame(StartPage)


    def show_frame(self, cont):
        frame = self.frames[cont]
        frame.tkraise()
               
        
    def inscrire(self):
        messagebox.showinfo("Info", "Wait a minute")
        
        
    def explorer_fichier(self):
        file_path = filedialog.askopenfilename()
        print(file_path)

        
    def close_application(self):    
        reponse = messagebox.askyesnocancel("Quit", 
                  "êtes vous sûr de vouloir quitter l'application ?")
        if reponse:
          self.destroy()
          
          
    def charger_data(self):
        self.show_frame(PageTwo)
    
    
    def definir_zoom(self):        
        self.show_frame(ZoomConstructorPage)
        
    
    
          
class Zoom_constructor_Frame(ttk.Frame):
    """ Frame et controller des 2 Scales qui permettent de définir une section
    zoommée"""
    def __init__(self, parent, xmin, xmax, *args, **kwargs):
        super().__init__(*args, **kwargs)
        
        self.parent = parent
        
        label = ttk.Label(text='Choisir le début et la fin de la section à zoomer')
        self.start_slider = tools.Slider(parent, self, 'start', xmin, xmin, xmax, 
                                   name='Début du zoom')
        self.end_slider = tools.Slider(parent, self, 'end', xmax, xmin, xmax,
                                 name='Fin du zoom')
        
        label.grid(column=0, row=0, pady=5)
        self.start_slider.grid(column=0, row=1, pady=8)
        self.end_slider.grid(column=0, row=2, pady=8)

        

    def slider_command(self, command_parameter):
        """ commande les deux sliders """
        # Récupère les valeurs des 2 sliders
        self.get_sliders_value()
        
        print('\nentrée', command_parameter, self.new_start_value, self.new_end_value)
        
        if command_parameter == 'start':            
            self.border_start_value()
            
        if command_parameter == 'end':
            self.border_end_value()
        
        # Récupére les valeurs éventuellement actualisées
        self.get_sliders_value()
        
    
    def get_sliders_value(self):
        """ Récupère les valeurs actualisées des 2 sliders """
        self.new_start_value = self.start_slider.value.get()
        self.new_end_value = self.end_slider.value.get()
        
    
    def border_start_value(self):
        """ Limite la valeur de start_value à end_value - 1 """
        if self.new_start_value > self.new_end_value:
            self.start_slider.value.set(self.new_end_value - 1)
           
            
    def border_end_value(self):
        """Limite la valeur de end_value à start_value + 1 """        
        if self.new_start_value > self.new_end_value:
            self.end_slider.value.set(self.new_start_value + 1)

###############################################################################            
#   *****   PAGES   *****
class StartPage(tk.Frame):
    def __init__(self, parent, controller):
        super().__init__(parent)
        label = tk.Label(self, text="Start Page  This is a Start Page")
        label.pack(pady=10, padx=10)        


class ZoomConstructorPage(tk.Frame):
    def __init__(self, parent, controller):
        super().__init__(parent)
        
        # Valeurs à définir avec la data
        self.start_value = 0
        self.end_value = 5850
        
        label = tk.Label(self, text="This is Zoom ConstructPage")
        label.grid(column=0, row=0, pady=8)
        
        
        # Affiche la fenêtre de gauche              
        my_paned_window_1 = tk.PanedWindow()
        my_paned_window_1.grid(column=1, row=1)
        my_paned_window_2 = tk.PanedWindow(my_paned_window_1, orient=tk.VERTICAL)
        my_paned_window_1.add(my_paned_window_2)
        
        self.left_frame = Zoom_constructor_Frame(my_paned_window_2,
                                          self.start_value, 
                                          self.end_value,
                                          height=400, width=500,
                                          relief='groove', borderwidth=4)
        
        my_paned_window_2.add(self.left_frame)
        bottom_pane_text = tk.Text(my_paned_window_2, height=30, width=60)
        my_paned_window_2.add(bottom_pane_text)
        right_pane_text = tk.Text(my_paned_window_1, height=30, width=3)
        my_paned_window_1.add(right_pane_text)
                
            

class PageTwo(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        label = tk.Label(self, text="Page Two")
        label.pack(pady=10, padx=10)

        button1 = tk.Button(self, text="Back to Home",
                        command=lambda: controller.show_frame(StartPage))
        button1.pack()
        button2 = tk.Button(self, text="Page One",
                        command=lambda: controller.show_frame(ZoomConstructorPage))
        button2.pack()
###############################################################################

        
if __name__ == '__main__':
    app = MainWindow(title='Instrument analyser')
    app.mainloop()

还有一些来自工具模块的类:

class FullscreenWindow(tk.Tk):
    """ Une Window qui se met directement en plein écran (wm_state = 'zoomed')
    et que l'on peut réduire par touche "escape", rezoomer par F11 """
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        #self.parent = parent
        self.initUI()
        

    def initUI(self):       
        #self.pack(fill="both", expand=True, side="top")
        self.grid()
        self.bind("<F11>", self.fullscreen_toggle)
        self.bind("<Escape>", self.fullscreen_cancel)
        self.fullscreen_toggle()
        

    def fullscreen_toggle(self, event="none"):
        self.wm_state('zoomed')
        self.focus_set()
        #self.parent.wm_attributes("-topmost", 1)
        

    def fullscreen_cancel(self, event="none"):        
        self.reduceWindow()
        self.wm_state('normal')
        #self.parent.wm_attributes("-topmost", 0)
        

    def reduceWindow(self):
        """ Récupére largeur et hauteur d'écran. Réduit la taille de la 
        fenêtre à 70% """
        sw = self.winfo_screenwidth()
        sh = self.winfo_screenheight()
        
        w = sw*0.7
        h = sh*0.7
        
        x = (sw-w)/2
        y = (sh-h)/2
        
        self.geometry("%dx%d+%d+%d" % (w, h, x, y))


class Slider(tk.Frame):
    """ Associe un Entry et un Scale, dont les valeurs sont liées à un 
    IntVar unique : value. Les modifications de value sont pris en compte en
    temps réel et retournées au widget controller par la fonction slider_command """
    def __init__(self, parent, controller, command_parameter, 
                 start, xmin, xmax, 
                 orientation='horizontal', name='Slider', *args, **kwargs):
        super().__init__(*args, **kwargs)
        
        if start < xmin or start > xmax:
            raise ValueError("Start is out of bounds")
            
        self.parent = parent
        self.controller = controller
        self.command_parameter = command_parameter
        self.start = start
        self.min = xmin
        self.max = xmax
        self.orientation = orientation
        self.name = name
        self.value = tk.IntVar()
        
        # Calcul de l'intervalle entre 2 ticks pour le Scale 
        nb_data = self.max - self.min
        nb_interval = 5
        tickinterval = tick_interval(nb_data, nb_interval)
        
        # Création des deux widgets
        self.entry = ttk.Entry(self, width= 10, textvariable=self.value)
        
        self.slider = tk.Scale(self, 
                          from_=self.min, 
                          to=self.max, 
                          orient=self.orientation,
                          resolution=1, 
                          tickinterval=tickinterval, 
                          length=300, 
                          variable=self.value,
                          label=self.name,
                          command = self.slider_command)
        
        # Initialisation de la valeur de départ               
        self.slider.set(self.start)        
        
        # Affichage des widgets
        self.entry.grid(column=0, row=0, sticky='e', padx=10)        
        self.slider.grid(column=0, row=1, columnspan=1, padx=8)       
        #self.entry.pack()
        #self.slider.pack()
        
    def slider_command(self, value):
        self.controller.slider_command(self.command_parameter)




        
def tick_interval(x, nb_ticks=10):
    ''' Calcul l'intervalle entre 2 ticks pour un ensemble de x data, arrondi
    à la puissance de 10 la plus proche '''
    # intervalle réel
    interval = x/nb_ticks
    # nombre de chiffres de l'intervalle
    nb_digit = len(str(interval))
    # puissance de dix la plus proche
    power = 10**(nb_digit - 3)
    # intervalle arrondi à la puissance de 10 la plus proche
    interval = round(interval/power) * power    
    return interval

【问题讨论】:

Zoom_constructor_Frame 没有正确调用超类__init__()(您省略了parent 参数),它正在父级(或未指定父级)而不是自身中创建小部件. 这个问题一次想问太多东西,代码太长了。请尝试一次只关注一个问题,并创建一个minimal reproducible example。 【参考方案1】:

在我的示例中:ZoomConstructorPage 始终可见,不会被其他页面隐藏。此外,我不知道如何将 Zoom_constructor_Frame 小部件包含在 PanedWindow 中。

那是因为您没有将父参数传递给超类的__init__。因为您不这样做,所以框架默认位于根窗口中。

应该是这样的:

class Zoom_constructor_Frame(ttk.Frame):
    def __init__(self, parent, xmin, xmax, *args, **kwargs):
        super().__init__(parent, *args, **kwargs)
        ...
    

此外,我不知道如何将 Zoom_constructor_Frame 小部件包含在 PanedWindow 中。

你似乎做得对。它不起作用的事实可能与没有将父级传递给super.__init__()

【讨论】:

以上是关于使用 Tkinter 中的菜单栏在页面之间切换的主要内容,如果未能解决你的问题,请参考以下文章

按下按钮后可以更改菜单栏文本吗? (Python,tkinter)

如何在 Tkinter 中切换到不同的框架? [复制]

如何使用按钮在 PyQt5 中的两个菜单栏之间切换?菜单栏消失

引导导航栏菜单与文本重叠

OSX 系统菜单栏在 JavaFX 中不起作用

如何从启动页面在不同的 tkinter 画布之间切换并返回子画布中的启动页面