ttk.Notebook: Auf dynamisch erzeugte Widgets zugreifen

Fragen zu Tkinter.
Antworten
aitsch
User
Beiträge: 18
Registriert: Freitag 18. März 2022, 14:39

Moin aus dem Norden,

ich bin neu hier und noch sehr unsicher in Python (Ein Satz, mit dem wahrscheinlich 85% der ersten Beiträge beginnen).

Derzeit schreibe ich an einer IDE mit einem eigenen Editor. Wie in einer modernen IDE üblich, sollen verschiedene Reiter mit Sourcecodes geöffnet werden können.
Die Tabs erzeuge ich dynamisch, z.Bsp. beim Öffnen einer neuen Datei, etc, über ein ttk.Notebook Widget.

Mein Problem ist, ich verstehe nicht wie ich die einzelnen Widgets aus den einzelnen Tabs ansteuern kann.

z.Bsp.:
Irgendwann möchte ich compilieren. Dazu soll auf das Text-Widget des aktuellen Reiters zugegriffen werden.
Es gibt aber keinen eindeutigen Schlüssel/Namen für das Text-Widget.
In dem Beispiel unten soll einfach nur der Text des aktuellen Tabs auf der Konsole ausgegeben werden.

Google ist natürlich mein Freund aber ich konnte nichts finden.
Vielleicht umschreibe ich das Problem bei der Suche falsch.... :oops:

Ich hoffe, ich habe mich verständlich ausgedrückt.

Zur Veranschaulichung hier mal ein (nicht funktionierender) Beispielcode.

Code: Alles auswählen

from tkinter import Button, Tk, Menu, Text, Frame
from tkinter.ttk import Notebook
from tkinter.filedialog import askopenfile


notebook_list = []

def printText(event):
    print(text)  # dient nur der Veranschaulichung. Mir ist klar, dass das so nicht funktionieren kann.
    #hier soll jede Menge Code hin, der den Text aus dem ativen Tab-Text-Widget entgegennimmt und zurückschreibt.


def add_tab(parent, contents, name):
    frame = Frame()
    text = Text(frame)
    text.pack()
    notebook_list.append(frame)
    frame = notebook_list[len(notebook_list) - 1]
    parent.add(frame, text=name)
    text.insert('end', contents)


def open_file():
    file = askopenfile(filetypes=[('Text file', '*.txt')])

    if file:
        add_tab(notebook, file.read(), file.name.split('/')[-1])


root = Tk()

menu_bar = Menu(root)

file_menu = Menu(menu_bar, tearoff=0)
menu_bar.add_cascade(label='File', menu=file_menu)
file_menu.add_command(label='Open', command=open_file)

root.config(menu=menu_bar)

btn1 = Button(root)
btn1.configure(text='print Text', command=printText)
btn1.pack(anchor='nw', padx='2', pady='2', side='top')
notebook = Notebook(width=800, height=600)
notebook.pack()
add_tab(notebook, 'Hallo Welt', 'Beispiel1')
root.mainloop()
Vielleicht könnt ihr mir helfen.

Gruß
aitsch
Benutzeravatar
__blackjack__
User
Beiträge: 13100
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@aitsch: GUI-Programmierung kommt im Grunde nicht ohne objektorientierte Programmierung (OOP) aus. Du hast da globale Variablen und ”Funktionen” die auf magische Weise auf Variablen ”aus der Umgebung” zugreifen, statt alles was sie ausser Konstanten benötigen als Argument(e) übergeben zu bekommen. Und wenn man sich dann Zustand über Aufrufe hinweg merken muss, und das muss man bei GUIs in der Regel, dann braucht man Objekte und Methoden, also eigene Klassen.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
aitsch
User
Beiträge: 18
Registriert: Freitag 18. März 2022, 14:39

Hallo __blackjack__ ,

danke für deine Antwort. Vermutlich hast du Recht. Wie würdest du die Objekte zu diesem Beispiel strukturieren?

Ich habe mein Problem jetzt so gelöst, dass ich für die einzelnen Elemete innerhalb des Tabs eine dynamische Variable erzeuge.
Eine bessere Lösung konnte ich nicht finden. Vermutlich nicht ganz sauber und ich versoße gegen alle möglichen Programmierstandards aber es funktioniert.

Hier der Code:

Code: Alles auswählen

from tkinter import Button, Tk, Menu, Text, Frame
from tkinter.ttk import Notebook
from tkinter.filedialog import askopenfile

def printText():
    x = int(notebook.index("current"))
    text=globals()['text%i'%x].get('1.0','end')
    print(text)

def add_tab(parent, contents, name):
    frame = Frame()
    parent.add(frame, text=name)
    x = int(notebook.index("end"))-1
    globals()['text%i' %x] = Text(frame)
    globals()['text%i' %x].pack()
    globals()['text%i' %x].insert('end', contents)

def open_file():
    file = askopenfile(filetypes=[('Text file', '*.txt')])

    if file:
        add_tab(notebook, file.read(), file.name.split('/')[-1])

root = Tk()
menu_bar = Menu(root)
file_menu = Menu(menu_bar, tearoff=0)
menu_bar.add_cascade(label='File', menu=file_menu)
file_menu.add_command(label='Open', command=open_file)
root.config(menu=menu_bar)
btn1 = Button(root)
btn1.configure(text='print Text', command=printText)
btn1.pack(anchor='nw', padx='2', pady='2', side='top')
notebook = Notebook(width=800, height=600)
notebook.pack()
add_tab(notebook, 'Hallo Welt', 'Beispiel1')
add_tab(notebook, 'Hallo Welt2', 'Beispiel2')
add_tab(notebook, 'Hallo Welt3', 'Beispiel3')
root.mainloop()
EDIT:
alternativ kann man es auch etwas übersichlicher mittels einer globalen Liste machen:

Code: Alles auswählen

...
gText=[]					# globale Tabelle für Textelemente
def printText():
    x = int(notebook.index("current"))
    print(gText[x].get('1.0','end'))

def add_tab(parent, contents, name):
    frame = Frame()
    parent.add(frame, text=name)
    x = int(notebook.index("end"))-1		# Index des neuen Tabs ...
    gText.append('text')			# ...
    gText[x] = Text(frame)			# ... gleich Index des TextWidgets in der globalen gText-Liste
    gText[x].pack()
    gText[x].insert('end', contents)
    ...
mal schauen, was sich besser handhaben läßt bzgl. Tabs schließen, öffnen, neu anlegen, etc
Zuletzt geändert von aitsch am Montag 21. März 2022, 10:45, insgesamt 2-mal geändert.
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

Warum heißt btn1 btn1? Benutze keine Abkürzungen und unsinnige Nummern bei Variablennamen.
Globale Variablen sind schlecht, dynamisch globale Variablen zu erzeugen ist nochmal viel schlechter.
Wenn man anfängt, Variablennamen durchzunummerieren, dann will man eigentlich eine passende Datenstruktur verwenden, hier eine Liste.
Und um globale Variablen zu vermeiden, muß man sie von einer Funktion zur nächsten durchreichen.
Da das irgendwann ziemlich unübersichtlich wird, muß man früher oder später Klassen verwenden.

Code: Alles auswählen

from functools import partial
from tkinter import Button, Tk, Menu, Text, Frame
from tkinter.ttk import Notebook
from tkinter.filedialog import askopenfile


def print_text(notebook, text_frames):
    index = notebook.index("current")
    text = text_frames[index].get('1.0','end')
    print(text)

def add_tab(notebook, text_frames, contents, name):
    frame = Frame()
    notebook.add(frame, text=name)
    text = Text(frame)
    text.insert('end', contents)
    text.pack()
    text_frames.append(text)

def open_file(notebook, text_frames):
    file = askopenfile(filetypes=[('Text file', '*.txt')])
    if file:
        add_tab(notebook, text_frames, file.read(), file.name.split('/')[-1])

def main():
    root = Tk()
    text_frames = []
    notebook = Notebook(root, width=800, height=600)
    menu_bar = Menu(root)
    file_menu = Menu(menu_bar, tearoff=0)
    menu_bar.add_cascade(label='File', menu=file_menu)
    file_menu.add_command(label='Open',
        command=partial(open_file, notebook, text_frames))
    root.config(menu=menu_bar)
    Button(root, text='print Text',
        command=partial(print_text, notebook, text_frames)
    ).pack(anchor='nw', padx='2', pady='2', side='top')
    notebook.pack()
    add_tab(notebook, text_frames, 'Hallo Welt', 'Beispiel1')
    add_tab(notebook, text_frames, 'Hallo Welt2', 'Beispiel2')
    add_tab(notebook, text_frames, 'Hallo Welt3', 'Beispiel3')
    root.mainloop()

if __name__ == "__main__":
    main()
Und das ganze mit Klasse:

Code: Alles auswählen

from functools import partial
from tkinter import Button, Tk, Menu, Text, Frame
from tkinter.ttk import Notebook
from tkinter.filedialog import askopenfile

class MainWindow(Tk):
    def __init__(self):
        Tk.__init__(self)
        self.text_frames = []
        self.notebook = Notebook(self, width=800, height=600)
        menu_bar = Menu(self)
        file_menu = Menu(menu_bar, tearoff=0)
        menu_bar.add_cascade(label='File', menu=file_menu)
        file_menu.add_command(label='Open', command=self.open_file)
        self.config(menu=menu_bar)
        Button(self, text='print Text',
            command=self.print_text
        ).pack(anchor='nw', padx='2', pady='2', side='top')
        self.notebook.pack()
        self.add_tab('Hallo Welt', 'Beispiel1')
        self.add_tab('Hallo Welt2', 'Beispiel2')
        self.add_tab('Hallo Welt3', 'Beispiel3')

    def print_text(self):
        index = self.notebook.index("current")
        text = self.text_frames[index].get('1.0','end')
        print(text)

    def add_tab(self, contents, name):
        frame = Frame()
        self.notebook.add(frame, text=name)
        text = Text(frame)
        text.insert('end', contents)
        text.pack()
        self.text_frames.append(text)

    def open_file(notebook, text_frames):
        file = askopenfile(filetypes=[('Text file', '*.txt')])
        if file:
            self.add_tab(file.read(), file.name.split('/')[-1])

def main():
    root = MainWindow()
    root.mainloop()

if __name__ == "__main__":
    main()
aitsch
User
Beiträge: 18
Registriert: Freitag 18. März 2022, 14:39

Danke @Sirius3, das hilft mir sehr.

Was den Zugriff auf das Text-Objekt innerhalb eines Tabs angeht hatte ich mir die Logik immer so vorgestellt:
notebook.tab1.text.get('1.0', 'end') ... (sinngemäß)

Mit der Liste lag ich dann ja gar nicht soweit weg aber global ist natürlich mist.

btn1 heißt nur deshalb bnt1 weil ich, um das Problem punktgenau zu beschreiben, hier ein schnelles reduziertes Beispiel aufgebaut hatte.
In meinem eigenen Programm heißt der Button 'btnAssembleCode' :)

Nur zur Korrektur und ohne jetzt rumklugsch***ern zu wollen, bei der Variante mit der Klasse

muss:
def open_file(notebook, text_frames)

jetzt:
def open_file(self):

heißen.

Vielen Dank nochmals für deine Hilfe.
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

Buttons werden selten direkt angesprochen, so dass man oft gar keinen Variablennamen braucht, wenn doch, wäre die korrekte Schreibweise button_assemble_code, also ohne Abkürzugen und komplett klein geschrieben.
Benutzeravatar
__blackjack__
User
Beiträge: 13100
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Wobei ich Korinthenkacker da noch die Reihenfolge ändern würde, denn ein Schaltflächen-Übersetzungscode ist etwas anderes als eine Übersetze-Code-Schaltfläche. Ausser man heisst Yoda, dann ist das natürlich in Ordnung. 🙂
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten