Window mit mehrern frames

Fragen zu Tkinter.
Antworten
WinsBlack
User
Beiträge: 3
Registriert: Mittwoch 11. Januar 2023, 21:35

Guten abend,
ich versuche seit einigen tagen, eine GUI zu basten die aus zwei Frames besteht eine mit buttons um die andere mit vorgefertigten frames zufüllen. Ich da auch schon was hinbekommen allerdings ging dies nur mit einer Treeview welche mir [setting_name] mit geschliffen hat, was mit Buttons nicht funktioniert.

Code: Alles auswählen

import tkinter as tk
from tkinter import ttk

class SampleApp(tk.Tk):
    def __init__(self):
        super().__init__()

        settings = SettingsView(self)
        settings.add_pages(setting_name=StartPage,
                           page=StartPage
                           )
        settings.add_pages(setting_name=PageTwo,
                           page=PageTwo)

        settings.pack(fill=tk.BOTH, expand=True)

class SettingsView(ttk.Frame):
    def __init__(self, master, **kw):
        super().__init__(master, **kw)

        self.pages = {}

        self.grid_rowconfigure(1, weight=1)
        self.grid_columnconfigure(0, weight=1)

        self.create_header().grid(row=0, column=0, sticky='enw')
        self.create_frame_page().grid(row=1, column=0)

        self.show_page(StartPage)


    def on_button_clicked(self, setting_name):
        setting_name = self.pages[setting_name]
        self.show_page(setting_name)


    def show_page(self, setting_name):
        for page_name in self.pages.keys():
            self.pages[page_name].pack_forget()
            #

        self.pages[setting_name].pack(fill=tk.BOTH, expand=True)


    def add_pages(self, setting_name, page):

        self.pages[setting_name] = page(self.frame_page)


    def create_frame_page(self) -> ttk.Frame:

        self.frame_page = ttk.Frame(self)

        return self.frame_page

    def create_header(self) ->ttk.Frame:
       
        self.frame_header = ttk.Frame(self)
        btn_1 = ttk.Button(self.frame_header,
                                  text='Start Page',
                                  #command=lambda : SettingsView.on_button_clicked(StartPage)
                                  )
        btn_1.grid(row=0, column=0)

        btn_2 = ttk.Button(self.frame_header,
                                   text='Page Two',
                                   #command=lambda : self.on_btn_clicked()
                                   )
        btn_2.grid(row=0, column=1)

        return self.frame_header


######### Pages ######## 

class Page (ttk.Frame):
    def __init__(self, master, **kw):
        super().__init__(master, **kw)


class StartPage(Page): #ToDo Startpage, mit Matplotlib
    def __init__(self, master, **kw):
        super().__init__(master, **kw)

        self.create_frame_content().pack(fill=tk.BOTH, expand=True)

    def create_frame_content(self) ->ttk.Frame:
        """
        Create the widget specific to this Settings
        :return: ttk.Frame
        """
        self.frame_content = ttk.Frame(self)
        lbl_StartPage = ttk.Label(self.frame_content,
                              text='Startpage')
        lbl_StartPage.pack()

        return self.frame_content


class PageTwo(Page):  # ToDo
    def __init__(self, master, **kw):
        super().__init__(master, **kw)

        self.create_frame_content().pack(fill=tk.BOTH, expand=True)


    def create_frame_content(self) -> ttk.Frame:
        """
        Create the widget specific to this Settings
        :return: ttk.Frame
        """
        self.frame_content = ttk.Frame(self)
        lbl_PageTwo = ttk.Label(self.frame_content,
                                  text='Page two')
        lbl_PageTwo.pack()

        return self.frame_content


if __name__=='__main__':

    app = SampleApp()
    app.mainloop()
Das spuckt er mir aus
self.pages[setting_name].pack(fill=tk.BOTH, expand=True)

Also Frage:
Geht das überhaupt, wenn ja bin ich auf den richtigen weg? Für einen denkanstoß wäre ich sehr dankbar
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Was wurde geschliffen? Und wo wird was ausgespuckt? Bitte die vollständige Fehlermeldung inklusive Traceback ausspucken, denn dann wird klarer, wo welcher Fehler liegt.

Bei `setting_name` würde ich ja einen String erwarten und keine Klasse.
Wenn man die Werte eines Wörterbuchs braucht, dann benutzt man `values()` und geht nicht über die Keys. Eigentlich müßte man pack_forget ja nur für die aktuell sichtbare Seite aufrufen, die Du Dir aber gar nicht merkst.
Mit den create_...-Methoden wäre ich etwas sparsamer, vor allem weil die so trivial sind, dass man den Code auch direkt in __init__ stehen haben könnte.
Variablennamen werden komplett_klein_geschrieben.
Die Klasse Page ist überflüssig, weil sie nichts anderes ist als ttk.Frame.
WinsBlack
User
Beiträge: 3
Registriert: Mittwoch 11. Januar 2023, 21:35

Danke für das Feedback. Hier ist die Fehlermeldung:

Code: Alles auswählen

File "C:\****test2.py", line 133, in <module>
    app = SampleApp()
          ^^^^^^^^^^^
  File "****l\test2.py", line 8, in __init__
    settings = SettingsView(self)
               ^^^^^^^^^^^^^^^^^^
  File "C:\****\test2.py", line 31, in __init__
    self.show_page(StartPage)
  File "C:\****\test2.py", line 44, in show_page
    self.pages[setting_name].pack(fill=tk.BOTH, expand=True)
    ~~~~~~~~~~^^^^^^^^^^^^^^
KeyError: <class '__main__.StartPage'>
hier ist der original Code: ich möchte die TreeView umgehen und dafür Buttons verwenden.

Code: Alles auswählen

import tkinter as tk
from tkinter import ttk

class SettingsView(ttk.Frame):
    def __init__(self, master, **kw):
        super().__init__(master, **kw)

        # Dict Key: setting name (such as 'Sprache' or 'Audio')
        # Value: Page object (ttk.Frame)
        self.pages = {}


        # Give row 0 and colum 1 as much room as it needs
        self.grid_rowconfigure(0, weight=1)
        self.grid_columnconfigure(1, weight=1)

        self.create_frame_treeview().grid(row=0, column=0, sticky='ens')
        self.create_frame_page().grid(row=0, column=1)

    def create_frame_page(self)  -> ttk.Frame:

        self.frame_page = ttk.Frame(self)

        return self.frame_page



    def create_frame_treeview(self)-> ttk.Frame:

        self.frame_treeview = ttk.Frame(self)

        self.treeview_settings = SettingsTreeview(self.frame_treeview)
        self.treeview_settings.bind('<<TreeviewSelect>>', self.on_treeview_selection_changed)
        self.treeview_settings.pack(fill=tk.BOTH, expand=True)

        return self.frame_treeview

    def on_treeview_selection_changed(self, event):

        selected_item = self.treeview_settings.focus()

        setting_name = self.treeview_settings.item(selected_item).get('text')

        self.show_page(setting_name)


    def show_page(self, setting_name: str):

        for page_name in self.pages.keys():
            self.pages[page_name].pack_forget()

        self.pages[setting_name].pack(fill=tk.BOTH, expand=True)

    def add_pages(self,  setting_name: str, page):

        # Add page to dictionary so we can show it when needed
        self.pages[setting_name] = page(self.frame_page)

        # Insert the setting name into the setting treeview

        self.treeview_settings.add_setting(section_text=setting_name)
        
class SettingsTreeview(ttk.Treeview):
    def __init__(self, master, **kw):
        super().__init__(master, **kw)

        self.heading('#0', text='Settings')

    def add_setting(self,  section_text: str):

        self.insert(parent='',
                     index=tk.END,
                     text=section_text)

class Page (ttk.Frame):
    def __init__(self, master, **kw):
        super().__init__(master, **kw)

class LanguagePage(Page):
    def __init__(self, master, **kw):
        super().__init__(master, **kw)

        self.create_frame_content().pack(fill=tk.BOTH, expand=True)

    def create_frame_content(self) -> ttk.Frame:


        self.frame_content = ttk.Frame(self)
        lbl_title = ttk.Label(self.frame_content,
                              text='This is the Language Page')
        lbl_title.pack()

        return self.frame_content

class AudioPage(Page):
    def __init__(self, master, **kw):
        super().__init__(master, **kw)

        self.create_frame_content().pack(fill=tk.BOTH, expand=True)

    def create_frame_content(self) ->ttk.Frame:


        self.frame_content = ttk.Frame(self)
        lbl_title = ttk.Label(self.frame_content,
                              text='This is the Audio Page')
        lbl_title.pack()

        return self.frame_content



if __name__ == "__main__":
    root = tk.Tk()
    root.geometry('700x500')

    style = ttk.Style()
    style.configure('Treeview.Heading', relief='flat',
                    background='white')
    style.configure('Treeview', rowheight=25)
    style.map('Treeview',
              foreground=[('selected', 'darkgreen')],
              background=[('selected', 'lightgreen')])
    style.configure('TLabel', font=('tkDefaultFont', 18))
    settings = SettingsView(root)
    settings.add_pages(
                       setting_name='Language',
                       page=LanguagePage)
    settings.add_pages(
                       setting_name='Audio',
                       page=AudioPage)
    settings.pack(fill=tk.BOTH, expand=True)

    root.mainloop()
mit " durchgeschliffen" meine ich folgendes
in der Root-Classe wird setting_name als section_text (k.A kann man sagen) implementiert ... diese wird dann bei:

Code: Alles auswählen

setting_name = self.treeview_settings.item(selected_item).get('text') 
in setting_name umgewandelt

und hier fangen die fehler an
Benutzeravatar
grubenfox
User
Beiträge: 432
Registriert: Freitag 2. Dezember 2022, 15:49

Naja, der Fehler tritt in Zeile 44 auf, die Ursache ist in der Fehlermeldung eine Zeile höher (Zeile 31 im Code). Da soll schon eine Seite aus dem Dictionary self.pages angezeigt werden die noch gar nicht hinzugefügt wurde.
Benutzeravatar
sparrow
User
Beiträge: 4195
Registriert: Freitag 17. April 2009, 10:28

@WinsBlack: Schade, dass du die Hinweise von Sirius3 nicht beachtet hast. Die sind nicht grundlos.

Was denkst du denn, was "setting_name" ist?
Denn laut Fehlermeldung suchst du mit dem Wert als key in einem dict. Und diesen Schlüssel gibt es nicht.
Der Wert von setting_name ist eine Klasse. Sirius3 hat bereits darauf hingewiesen, dass man bei dem Namen keine Klasse erwartet. Ist also die Benennung der Variabel schlecht, oder hast du den falschen Wert an den Namen gebunden?

Auch wenn es schon in der Fehlermeldung steht: lass dir doch mal in der Zeile vor der jetzigen Zeile 44 ausgeben, was setting_name enthält.
Benutzeravatar
grubenfox
User
Beiträge: 432
Registriert: Freitag 2. Dezember 2022, 15:49

und das was bei 'self.pages.keys()' zurück kommt....
WinsBlack
User
Beiträge: 3
Registriert: Mittwoch 11. Januar 2023, 21:35

Zunächst möchte ich mich bedanken, für die Hinweise, die haben wirklich weitergeholfen.

Das Resultat sieht jetzt wie folgt aus:

Code: Alles auswählen

    def add_pages(self, setting_name: str, page): 
    
        self.pages[setting_name] = page(self.frame_page)

    def on_button_clicked(self, setting_name: str):

        self.show_page(setting_name)

    def show_page(self, setting_name: str):

        for page_name in self.pages.keys():

            self.pages[page_name].pack_forget()

            self.pages[setting_name].pack(fill=tk.BOTH, expand=True)
Wahrscheinlich nich elegant, aber es tut was es soll.

@sirius3
Eigentlich müßte man pack_forget ja nur für die aktuell sichtbare Seite aufrufen, die Du Dir aber gar nicht merkst.

Ist das denn notwendig?

@ sparrow:
ich wollte lediglich die komplette fehlermeldung ausgeben (wie angemerkt), und erklären woher ich komme. Sollte man das als Undank oder Forderung verstanden haben, entschuldige ich mich aufrichtig dafür und gelobe an meiner Kommunikation zu arbeiten.
Antworten