Frame, als Container für Bilder-Buttons, mit Scrollbar?

Fragen zu Tkinter.
Antworten
VolkerH
User
Beiträge: 20
Registriert: Dienstag 24. September 2013, 07:16
Wohnort: 46499 Hamminkeln

Hallo zusammen,
ich möchte eine Bildervorschau realisieren, quasi 'n' Thumbnails in Reihe nebeneinander in einem Container. Werden mehr Bilder eingefügt, als der Container breit ist, dann soll man per Scrollbar (horizontal) hin und her scrollen können. Ich habe diverse Beispiele gefunden, aber alle mit einem Canvas, Text oder Listbox-Widget. Dass kann ich ja nicht einsetzen für meine Wünsche. Ein Frame könnte ich als Container gut für die Bilder-Buttons nutzen, aber es lässt sich (von mir) leider kein Scroll-Event damit verbinden.
Was kann ich tuen, damit ich 'n' Bilderchen neben einander in ein Container bekomme, der scrollbar ist? Ich stelle mir ein Außen-Frame vor, welcher ein "main" als Parent hat. Darin kann/sollte dann der "Container?" sein, der die Bilder-Buttons beinhaltet, quasi parallel dazu gibt es die Scrollbar. Hat jemand ein Beispiel für die Vorschau oder kann mir sagen, wie ich das mit dem Frame (oder ein anderen passenden Container) hinbekommen kann?

Grüße,
Volker
BlackJack

@VolkerH: Von welchem GUI-Toolkit reden wir denn hier? Damit man das Thema ins passende Unterforum verschieben kann. :-)
VolkerH
User
Beiträge: 20
Registriert: Dienstag 24. September 2013, 07:16
Wohnort: 46499 Hamminkeln

@BlackJack: Oh, sorry. :oops: Ich verwende TK Toolkit. :)
BlackJack

@VolkerH: Wenn man einem `Frame` Scrollbalken verpassen möchte, dann geht das über den Umweg eines `Canvas`. Dem verpasst man Scrollbalken. Und auf einem `Canvas` kann man beliebige Widgets platzieren. Zum Beispiel auch einen `Frame`.
VolkerH
User
Beiträge: 20
Registriert: Dienstag 24. September 2013, 07:16
Wohnort: 46499 Hamminkeln

@BlackJack: Moin. Das habe ich im Netz gelesen und ich hatte es auch versucht nachzubauen. Aber die Scrollbar hatte nicht reagiert, also der Slider wurde nicht angezeigt, obwohl das Canvas breiter war als das Grund-Frame(Parent). Gibt es dazu ein Beispiel? Ich werde es noch einmal nachbauen und hier den Code einfügen. Danke vorerst.
BlackJack

@VolkerH: Hast Du denn auch die `scrollregion` vom `Canvas` entsprechend dem Inhalt gesetzt?
VolkerH
User
Beiträge: 20
Registriert: Dienstag 24. September 2013, 07:16
Wohnort: 46499 Hamminkeln

@BlackJack: Ich habe das Script nachgebaut, so wie ich es wohl hatte. Wenn ich das laufen lasse, dann wird die Breite des Canvas auch erhöht, was die beiden Print-Commands ausgeben. Es
werden insgesamt 18 Bilder-Button erstellt und ins Canvas gehängt, was eine Mindesbreite dessen von 1800 Pixel ergibt. Die Anwendung ist auf 1050 Pixel festgelegt. Die Farben und Kommentare sind nur für Testzwecke eingefügt, wie auch die Prints. In allem sieht es nicht schlecht aus, die Bilder sind zu sehen, allerdings die Scrollbar nicht. Wo liegt der Fehler?

Code: Alles auswählen

intAppWidth = 1050 #Gesamte Breite er Anwendung 
xpos=0 #Zaehler für die Breite des Containers der Bilder-Button-Anzeige

outerFrame = tkinter.Frame(main, width=intAppWidth, height=200, bd=1, \
                            relief=tkinter.SUNKEN)
outerFrame['background']="blue"
outerFrame.place(x=0, y=0)

preViewXScrollbar = tkinter.Scrollbar(outerFrame, orient=tkinter.HORIZONTAL)

preViewContainer = tkinter.Canvas(outerFrame, width=xpos, \
                                   height=145, bg="red", bd=1, highlightthickness=0, \
                                   xscrollcommand=preViewXScrollbar.set)
preViewContainer.place(x=0, y=0)

preViewXScrollbar.config(command=preViewContainer.xview)
preViewXScrollbar.place(x=0, y=180, width=outerFrame['width'])





# Create the galerie of pictures
galerie = Galerie().getGalerie(imgFolder)
# Show previews
for (key, value) in galerie.items():
    print(key)    
    
    newPreView = ImgPreView(preViewContainer, value, key).getPreView()
    print("old: {}".format(preViewContainer['width']))
    preViewContainer['width']=xpos+newPreView['width']+10
    print("new: {}".format(preViewContainer['width']))
    newPreView.place(x=xpos, y=0)
    newPreView['command']=lambda name=newPreView.config('text')[-1]:OnClick(name)  #Verknüpfung mit Funktionsaufruf
    xpos +=newPreView['width']
BlackJack

@VolkerH: Ich habe jetzt nur kurz drübergeschaut und habe nirgends gesehen wo die `scrollregion` vom Canvas festgelegt wird. Meine letzte Frage wäre dann also mit Nein beantwortet. Das fehlt halt. So ein Canvas ist ja ”unendlich” gross, darum muss man den Bereich festlegen der gescrollt werden soll.

Dein Einsatz von `place()` ist übrigens nicht gut. Für die Bilder-Schaltflächen kann man das gerade noch so rechtfertigen, auch wenn man dort besser `pack()` verwendet, aber der Rest der GUI wird auf diese weise unflexibel und bei anderen Rechnern eventuell sogar hässlich bis hin zu technisch unbenutzbar.

Die Namen weichen vom Style Guide for Python Code ab.

Code: Alles auswählen

galerie = Galerie().getGalerie(imgFolder)
sieht nach einem „code smell” aus. Warum muss man ein Objekt erstellen dessen einzige Zweck der Aufruf *einer* Methode ist und dass dann gleich wieder verworfen wird? Ist `getGalerie()` tatsächlich eine Methode oder nur eine Funktion die grundlos in eine Klasse gesteckt wurde? Bei `ImgPreView` wiederholt sich dieses Muster. Zusammen mit der Schreibweise der Namen erinnert das eher an Java als an Python.
VolkerH
User
Beiträge: 20
Registriert: Dienstag 24. September 2013, 07:16
Wohnort: 46499 Hamminkeln

@BlackJack: Dann werde ich versuchen die 'scrollregion' einzurichten. Habe ich bis dato keine Info zu gelesen und hätte somit wahrscheinlich noch Jahre gesucht... Danke! Das Aussehen des Codes, also Formate bzgl. Namen und soweiter, ist sicherlich noch nicht perfekt. Ich bin ein Umsteiger, habe jetzt vielleicht drei bis vier Wochen mehr oder wenige mit Python zu tuen, nur in meiner Freizeit oder in der Mittagspause :D
Tatsächlich habe ich früher einige unterschiedliche Programmiersprachen beruflich genutzt, Java war auch dabei, gut erkannt. Die Klassen habe ich gebaut, um zum einen eine Wiederverwendbarkeit zu haben und zum anderen schien es mir geschickt, damit ich dann bei Änderungen nur in der Klasse hantieren muss. Mein Ziel beim Python-Programmieren ist in erster Linie die Sprache kennen zu lernen, und dann werde ich mich um einen besseren 'Python-Style' kümmern, naja, eigentlich läuft das ja aber auch schon parallel. Danke nochmals!
BlackJack

@VolkerH: Eine Klasse ist ja nicht automatisch wiederverwendbar(er) als eine Funktion. So Sachen wie ``Galerie().getGalerie(imgFolder)`` sind einfach nur zusätzliche Schreibarbeit. Zumal das in diesem Fall auch sehr eigenartig aussieht, denn von einem `Galerie()`-Aufruf erwartet man ein Objekt vom Typ `Galerie` und von einer `getGalerie()`-Methode *ebenfalls*. Da fragt man sich beim Lesen warum da nicht ``Galerie(image_folder)`` steht oder ``Galerie.from_folder(image_folder)``. Wenn da letztendlich nur ein Wörterbuch (`dict`) bei rumkommt, hätte ich eine `get_galerie()`-Funktion erwartet.

Auf der anderen Seite ist der gezeigte Quelltext ganz schön viel Code auf Modulebene der nicht einmal in Funktionen steckt, sich aber geradezu für Klassen anbietet. Zum Beispiel das man so ein `Canvas` + `Frame` + Scrollbalken in einer Klasse zu einem eigenen Widget zusammenfasst.
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hi VolkerH

Hier eine Scroll-Variante auf Modulebene:

Code: Alles auswählen

import sys
try:
    #~~ For Python 2.x
    import Tkinter as tk
except ImportError:
    #~~ For Python 3.x
    import tkinter as tk

script_name = sys.argv[0]

MAIN_WIN_XPOS   = 10
MAIN_WIN_YPOS   = 10
MAIN_WIN_WIDTH  = 500
MAIN_WIN_HEIGHT = 500

SCROLL_WIDTH = 1000
SCROLL_HEIGHT = 1000

app_win = tk.Tk()
app_win.geometry("%dx%d+%d+%d" % (MAIN_WIN_WIDTH, MAIN_WIN_HEIGHT,
    MAIN_WIN_XPOS, MAIN_WIN_YPOS)) 
app_win['bg'] = 'khaki'
app_win.title(script_name)

app_frame = tk.Frame(app_win, bd=0, relief='sunken')
app_frame.grid_rowconfigure(0, weight=1)
app_frame.grid_columnconfigure(0, weight=1)
app_frame.pack(fill='both', expand='yes')

xscrollbar = tk.Scrollbar(app_frame, orient='horizontal')
xscrollbar.grid(row=1, column=0, sticky='ew')

yscrollbar = tk.Scrollbar(app_frame)
yscrollbar.grid(row=0, column=1, sticky='ns')

scroll_geometry = (0, 0, SCROLL_WIDTH, SCROLL_HEIGHT)
plane = tk.Canvas(app_frame, bd=0, scrollregion=scroll_geometry, 
    bg='steelblue3', xscrollcommand=xscrollbar.set, highlightthickness=0,
    yscrollcommand=yscrollbar.set)
plane.grid(row=0, column=0, sticky='nsew')

xscrollbar.config(command=plane.xview)
yscrollbar.config(command=plane.yview)

plane.create_rectangle(100, 50, 800, 500, fill='yellow')

app_win.mainloop() 
Gruß wuf :wink:
Take it easy Mates!
VolkerH
User
Beiträge: 20
Registriert: Dienstag 24. September 2013, 07:16
Wohnort: 46499 Hamminkeln

@wuf: Vielen Dank für Dein Beispiel. Ein paar Unterschiede zwischen Deiner Lösung und meiner habe ich schon gefunden. Meine Lösung läuft zwar, man kann scrollen per Buttons, aber der Schieberegler hat noch ein Problemchen. Ich werde Dein Script mit einbauen und vor allem werde ich meinen Code verbessern. Hinweise habe ich mittlererweile viele bekommen (@BlackJack :wink: ), die ich nun nach und nach umsetzen werde. Nach dem ich nun die Sachen geschaft habe, die auf meiner Liste oben standen, werde ich mich nun erst einmal in den 'Style Guide for Python Code' vergraben. :)
Antworten