tk.Scrollbar Positionierung/Höhe

Fragen zu Tkinter.
Antworten
Rigoletto
User
Beiträge: 28
Registriert: Freitag 14. Februar 2014, 21:05

Hallo,

ich krieg einfach nicht raus wo der Fehler liegt, die scrollbar sollte über die gesamte canvas-Höhe (grüne Fläche) gehen was sie aber leider nicht tut.
Zeile 22 canvas, Zeile 30 scrollbar.

Code: Alles auswählen

import tkinter as tk
import xml.etree.ElementTree as ET


class codegui(tk.Tk):

    xmltree = None
    xmlroot = None
    mframe = None
    canvas = None
    vscrollbar = None

    def __init__(self, parent=None):
        tk.Tk.__init__(self, parent)
        self.parent = parent

    def test(self):
        self.xmltree = ET.parse("test.xml")
        self.xmlroot = self.xmltree.getroot()

        opt = {'background':'green'}
        self.canvas = tk.Canvas(None, opt)
        self.canvas.pack(anchor=tk.W, fill=tk.X)
        
        opt = {'background':'yellow', 'relief':tk.RAISED, 'borderwidth':'10'}
        self.mframe = tk.Frame(self.canvas, opt)
        self.mframe.pack(anchor=tk.W)

        opt = {'background':'blue'}
        self.vscrollbar = tk.Scrollbar(self.canvas, opt)
        self.vscrollbar.pack(anchor=tk.E, fill=tk.Y)
        
        self.xmlToGui(self.xmlroot)

    def dump(self):
        for e in self.slaves():
            print(type(e), e)

    def xmlToGui(self, r):
        pframes = []
        pframes.append(self.mframe)
        for event, node in ET.iterparse("test.xml", events=('start', 'end')):
            if event == 'start':
                pframes.append(self.xmlFrame(node, pframes[-1]))
            if event == 'end':
                pframes.pop()

    def xmlFrame(self, e, p=None):
        opt = {'background':'red', 'relief':tk.RAISED, 'borderwidth':'5'}
        f = tk.Frame(p, opt)
        f.pack(anchor=tk.W)
        e = tk.Label(f, text=e)
        e.pack(anchor=tk.W)
        return f

gui = codegui()
gui.test()
#gui.dump()
gui.mainloop()
Was mach ich hier falsch?
Oder kennt jemand schon ein Modul was mir xml in derart in gui umsetzt?

Grüße
BlackJack

@Rigoletto: Als erstes sollte man den Hauptframe nicht mit `pack()` oder einem anderen Layout-Manager auf das `Canvas` packen, denn dann expandiert das nicht nach unten ins ”unsichtbare”, sondern ist weiterhin durch das Fenster begrenzt. Wenn man Widgets als Objekte auf dem `Canvas` haben möchte gibt es die `create_window()`-Methode auf `Canvas`-Exemplaren.

Dann gehört der Scrollbalken nicht auf das `Canvas` sondern daneben.

Und zuguter Letzt muss man beim Canvas noch die 'scrollregion' passend setzen nachdem die Widgets im darauf angezeigten `Frame` layoutet wurden, damit der Scrollbalken passend dargestellt werden und steuern kann.

Sonstiges: Die Namensschreibweisen halten sich nicht an den Style Guide for Python Code. Klassen fangen per Konvention mit einem Grossbuchstaben an, und Methoden `klein_mit_unterstrichen`. Es gibt auch einige sehr schlechte Namen. Einbuchstabige Namen ausser den weit verbreiteten `i`, `j`, `k` für ganzzahlige Index/Laufvariablen sind ausserhalb sehr begrenzter Gültigkeitsbereiche („list comprehension”, Generatorausdruck, ``lambda``-Funktion) selten wirklich geeignet um dem Leser zu vermitteln was die Werte dahinter *bedeuten*. Diese nichtsagenden Namen innerhalb einer Funktion dann auch noch an verschiedene Dinge zu binden (`e` in `xmlFrame()`) ist schon fast kriminell.

Die Klassenattribute haben da nichts zu suchen, die gehören in die `__init__()` denn das sind alles Attribute die auf das Exemplar gehören. Die API dieser Klasse ist auch ein wenig komisch verteilt. Damit das irgendetwas sinnvolles macht *muss* man nach dem erstellen die `test()`-Methode aufrufen. Damit gehört zumindest ein Teil davon eigentlich in die `__init__()`-Methode.

Das was Du da in der `__init__()` als `parent` bezeichnest ist etwas anderes. `Tk`-Exemplate haben kein Elternwidget, das ist *das* Hauptfenster, dementsprechend erwartet deren `__init__()` auch kein solches Argument. Kannst ja mal in der Dokumentation nachsehen was Du da stattdessen auf `None` setzt. :-)

`self.xmltree` wird nicht wirklich verwendet. An der Stelle empfehle ich `lxml.etree` anstelle des `ElementTree` aus der Standardbibliothek, denn dort gibt es neben `iterparse()` auch eine `iterwalk()`-Funktion die statt eine Datei zu parsen die gleiche API für einen bereits bestehenden `ElementTree` beziehungsweise für ein beliebiges `Element` bietet.

`xmlFrame()` ist keine Methode. Das sollte man entweder entsprechend deutlich machen in dem man eine `staticmethod()` daraus macht, oder es als die Funktion aus der Klasse herausziehen die das eigentlich ist.

Ich komme dann ungefähr bei so etwas heraus (ungetestet):

Code: Alles auswählen

import tkinter as tk
from lxml import etree


def create_label_frame(parent, text):
    frame = tk.Frame(parent, background='red', relief=tk.RAISED, borderwidth=5)
    tk.Label(frame, text=text).pack(side=tk.TOP)
    return frame


class CodeGui(tk.Tk):

    def __init__(self, xml_filename):
        tk.Tk.__init__(self)
        self.xml_tree = etree.parse(xml_filename)
        self.xml_root = self.xml_tree.getroot()

        self.canvas = tk.Canvas(self, background='green')
        self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        self.xml_frame = tk.Frame(
            self.canvas, background='yellow', relief=tk.RAISED, borderwidth=10
        )
        self.canvas.create_window((0, 0), anchor=tk.NW, window=self.xml_frame)

        self.vscrollbar = tk.Scrollbar(
            self,
            background='blue',
            orient=tk.VERTICAL,
            command=self.canvas.yview
        )
        self.vscrollbar.pack(side=tk.LEFT, fill=tk.Y)
        self.canvas['yscrollcommand'] = self.vscrollbar.set

        self._frames_from_xml()

    def dump_slaves(self):
        for slave in self.slaves():
            print(type(slave), slave)

    def _frames_from_xml(self):
        parent_frames_stack = []
        parent_frames_stack.append(self.xml_frame)
        for event, node in etree.iterwalk(
            self.xml_root, events=('start', 'end')
        ):
            if event == 'start':
                frame = create_label_frame(parent_frames_stack[-1], str(node))
                frame.pack(side=tk.TOP)
                parent_frames_stack.append(frame)
            elif event == 'end':
                parent_frames_stack.pop()
            else:
                assert False, event
        self.update_idletasks()
        self.canvas['scrollregion'] = self.canvas.bbox(tk.ALL)


def main():
    gui = CodeGui('test.xml')
    # gui.dump_slaves()
    gui.mainloop()


if __name__ == '__main__':
    main()
Rigoletto
User
Beiträge: 28
Registriert: Freitag 14. Februar 2014, 21:05

@BlackJack: Erstmal größten Danke für die ausführliche Antwort. :o Danke.

Yep, das der Scrollbalken nicht aufs Canvas gehört hätte ich mir auch denken können, der würde sich dann ja selber auch scrollen :D

Ja, meine Namensgebung ist schluderig. Werd versuchen mich umzugewöhnen. Sollte halt nur ein kurzer Test werden ob tkinter auch nicht schlappmacht wenn ich da 4stellig Widgets draufpacke.
Das iterparse kein handle nimmt hat mich auch gestört, aber ich bleib lieber bei der Standard Lib und tausch das in deiner Korrektur wieder aus.

xmlFrame soll mal ein eigenes Widget werden. Ja, da gehört es wirklich nicht in die Klasse.
Rigoletto
User
Beiträge: 28
Registriert: Freitag 14. Februar 2014, 21:05

Rigoletto hat geschrieben:Sollte halt nur ein kurzer Test werden ob tkinter auch nicht schlappmacht wenn ich da 4stellig Widgets draufpacke.
Hm, wie befürchtet scheint so circa bei > 2000 Elementen tkinter schlapp zu machen, von teilweise nicht mehr anzeigen bis total keine ausgabe.
Keine Ahnung ob das jetzt an tkinter liegt oder am OS.

Bleibt mir wohl nicht anderes übrig als das scrollen selber zu übernehmen und immer nur die nächsten Einträge ab Pos. x anzuzeigen, oder?
Antworten