ein paar Designfragen zu mehreren Listboxen mit Scrollbar

Fragen zu Tkinter.
Antworten
ippurk
User
Beiträge: 61
Registriert: Mittwoch 8. Juli 2009, 20:40

Hallo liebe Leute,

ich baue hier grad eine Mehrfachlistbox mit Python 2.6, die scrollbar sein soll, falls die Box nicht in das Fenster passt. Soweit läufts ganz gut, nur:

Erstens: Um die einzelnen Listboxes gibt es so einen Rahmen, in meiner Version ist der weiß, ich vermute, der kommt vom Relief, was bei mir auf flat steht. Kann man das ganz wegbekommen ?

Zweitens: Zwischen den einzelnen Boxen ist ein grauer Streifen, der wohl vermutlich eher vom GridManager kommt, kann man den auch ganz wegbekommen ?
Ziel währe also, die einzelnen Boxen direkt nebeneinander zu haben, so dass man gar nicht sieht, dass da mehrere sind.

Drittens: Bei meiner Version haben die Boxen eine feste Höhe. Das soll eigentlich nicht sein, die Boxen sollen natürlich bis zur Unterkante des Fensters reichen. Kann man da irgendwie die aktuelle Höhe des Fensters auslesen, um das dann in eine Höhe für die Listboxen umrechnen zu können, oder geht das auch anders und schicker ?

Viertens: Das Mousewheel sorgt hier für leichtes Chaos, da nur die aktive Listbox durch das Rad bewegt wird. Natürlich sollen aber alle Boxen gescrollt werden.

Hier liegt der Code: http://paste.pocoo.org/show/130066/.

Eigentlich habe ich auch noch einen Screenshot von dem Fenster, leider weiß ich aber grad nicht, wo ich dieses Bild hochladen könnte. Aber ihr könnt das Programm ja auch ausführen.

Schönen Gruß aus Köln, ippurk
ippurk
User
Beiträge: 61
Registriert: Mittwoch 8. Juli 2009, 20:40

Zu Drittens konnte ich mir schon selber helfen:

Das "WindowResizeEvent" (configure) an meine resizeFunktion gebunden und die Höhe in Spalten umgerechnet:

Code: Alles auswählen

self.fensterchen.bind("<Configure>", self.resize)

def resize(self, *args):
    # Hier wird die Größe der Listboxen an das aktuelle Fenster
    # angepasst. Der Divisor 14 hängt von der Schriftgröße in der Listbox
    # ab. -4 ist noch eine kleine Feinanpassung.
    
    listheight = (self.fensterchen.winfo_height() - 4) / 14
    
    # Maximale Größe für die Listboxen ist die Menge der Items.
    
    if listheight > len(self.liste1):
        listheight = len(self.liste1)
    
    # Irgendwie gibt's hier einen AttributeError, wenn die GuiInstanz
    # erzeugt wird, danach allerdings nicht mehr.
    # Kann man dagegen was machen, außer folgendes:
    
    try:
        self.listbox1.configure(height=listheight)
        self.listbox2.configure(height=listheight)
        self.listbox3.configure(height=listheight)
    except AttributeError:
        pass
ippurk
User
Beiträge: 61
Registriert: Mittwoch 8. Juli 2009, 20:40

ok, zu erstens, nämlich dem weißen Rahmen hab ich auch was gefunden:

Code: Alles auswählen

listbox.config(borderwith=0)
Die Borderwith steht nämlich überraschenderweise standartmäßig auf dem Wert 2.
ippurk
User
Beiträge: 61
Registriert: Mittwoch 8. Juli 2009, 20:40

äh, ok der graue Rahmen war wohl:

Code: Alles auswählen

listbox.config(highlightthickness=0)
ippurk
User
Beiträge: 61
Registriert: Mittwoch 8. Juli 2009, 20:40

so, hab jetzt auch das vierte Problem gelöst, nämlich daß das scrollen mit dem Mausrad funktioniert und auch auf alle Boxen wirkt:

Code: Alles auswählen

self.listbox3.bind('<Button-4>', self.scrollUpOrDown)
self.listbox3.bind('<Button-5>', self.scrollUpOrDown)
self.listbox3.bind('<MouseWheel>', self.scrollUpOrDown)
# und natürlich auch für Listbox 1 und 2 die Bindings machen
    
def scrollUpOrDown(self, event):
        # Listboxen durch MouseWheel hoch oder runter scrollen
        # Das Standart-Tk-Binding für das MouseWheel wird durch
        # return "break" unterdrückt.
        if event.num == 5 or event.keycode == -120:
            self.listbox1.yview_scroll(4, 'units')
            self.listbox2.yview_scroll(4, 'units')
            self.listbox3.yview_scroll(4, 'units')
            return "break"
        elif event.num == 4 or event.keycode == 120:
            self.listbox1.yview_scroll(-4, 'units')
            self.listbox2.yview_scroll(-4, 'units')
            self.listbox3.yview_scroll(-4, 'units')
            return "break"
So, Thread durch Eigenarbeit gelöst, ist doch auch mal was. Also, wenn mein Monolog hier mal irgendjemandem helfen sollte, hat sichs dann ja auch gelohnt, oder ?

Bleibt noch ein Problemchen übrig, falls jemandem aufgefallen ist, schreiben diese Listboxes nämlich irgendwie die ganzen Trennzeichen der einzelnen Listen, aus denen die Boxes ihre Daten kriegen, mit in die Listboxen. Sieht ein bißchen komisch aus, und ich hab im Moment echt keine Ahnung, wo das jetzt herkommt.

Vielleicht krieg ich das aber auch noch raus.
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hallo ippurk

Bei mir wirft dein Skript einige Exceptions. Eine davon ist:

Code: Alles auswählen

Traceback (most recent call last):
  File "multi_listbox_01.py", line 168, in <module>
    gui = Gui()
  File "multi_listbox_01.py", line 81, in __init__
    activestyle="none")
  File "/usr/lib/python2.5/lib-tk/Tkinter.py", line 2476, in __init__
    Widget.__init__(self, master, 'listbox', cnf, kw)
  File "/usr/lib/python2.5/lib-tk/Tkinter.py", line 1930, in __init__
    (widgetName, self._w) + extra + self._options(cnf))
_tkinter.TclError: unknown color name "SystemMenu"
Wäre es dir möglich den letzten Stand deines Skriptes nach:
http://paste.pocoo.org
hochzuladen?

Eine Frage die du noch nicht beantwortet hast ist wie du dein Bild hier zeigen kannst. Eine Mögliche Variante wäre:
http://ubuntu-pics.de/

P.S. Sorry, dass ich deinen Monolog unterbrochen habe. :-)

Gruss wuf :wink:
Take it easy Mates!
Benutzeravatar
HWK
User
Beiträge: 1295
Registriert: Mittwoch 7. Juni 2006, 20:44

Wenn die Bilder nicht zu groß sind, kann man sie auch base64-encodiert nach paste.pocoo.org hochladen.
MfG
HWK
ippurk
User
Beiträge: 61
Registriert: Mittwoch 8. Juli 2009, 20:40

ok, neueste version liegt hier: http://paste.pocoo.org/show/130520/.

at wuf: Ich hab diese Colornames "SystemMenu" mal ganz rausgenommen, braucht man eh nich und wahr ja auch falsch geschrieben, datt müsste ja "SystemMenue" heißen. Englische Sprache schwere Sprache...

Und dann probier ich das mit dem Bild auch mal aus. Das ganze Listboxding sieht bei mir so aus: http://ubuntu-pics.de/bild/19781/mehrfa ... dDWb1Z.jpg, falls das Skript bei euch nicht laufen sollte.

Ja, leider ist bei der MouseWheel action ein kleines Problem, wird man da nämlich etwas hektisch mit dem Mausrad, kann es passieren, dass nicht alle Boxen gescrollt werden, da scheint irgendwo unterwegs was verloren zu gehen.

Und dann bleibt noch diese seltene Eigenart, dass die Trennzeichen aus den Listen mit dargestellt werden.

Mit der Umrechnung der Fensterhöhe auf die Listboxhöhe, das ist glaub ich auch ziemlich fehleranfällig, weil ja irgendwie von Schriftgröße und so weiter abhängig. Muss ich mir noch was anderes überlegen.
Benutzeravatar
HWK
User
Beiträge: 1295
Registriert: Mittwoch 7. Juni 2006, 20:44

Listbox erwartet einen mit Newline separierten String als Daten, also z.B.:

Code: Alles auswählen

self.liste1_werte = tk.StringVar(value='\n'.join(self.liste1))
So verschwinden die "Trennzeichen".
Übrigens mischt Du in Deinem Code Tabs und Spaces. Das kann zu Einrückungsfehlern führen.
MfG
HWK
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hallo ippurk

Danke für deine Antwort. Werde dein Skript einmal näher anschauen. Vielleicht könntest du eine eigene Listbox-Klasse erstellen die von der Tkinter-Listbox-Klasse erbt. Möchtes du einmal mehr als nur drei Kolonnen in deine Mehrfach-Listbox aufnehmen kannst du diese dann einfacher erweitern.

Gruss wuf :wink:
Take it easy Mates!
ippurk
User
Beiträge: 61
Registriert: Mittwoch 8. Juli 2009, 20:40

@ HWK: Danke für den Tipp bezüglich meiner "Trennzeichen". Hat geholfen. So was muss einem auch erst mal einer sagen. Wo steht sowas ? Und ich hoffe, die Tabs sind jetzt alle raus, ich dachte eigentlich, daß IDLE's Editor automatisch nur Leerzeichen zulässt. Naja.

@ everybody:

Auf diverse Hinweise hin, modulbasiert zu arbeiten, habe ich meine tolle SuperListBox jetzt als Modul geschrieben, zumindest hab ichs versucht.

Nur kapier ich da grundlegend etwas nicht und zwar:

Das man ein selbst geschriebenes Modul importieren kann und sozusagen als Befehl benutzen kann, ist klar und begeistert mich total. Nur ist meine Listbox ja jetzt ein Ding, das auf eine Eingabe wartet, in diesem Fall ein Doppelklick oder eine gedrückte Returntaste. In so einem Fall müsste ja also mein Hauptprogramm, welches dieses Modul benutzt, sozusagen wachgerüttelt werden um dann auch entsprechend in Aktion zu treten. Jetzt will ich aber nicht jedes Mal die ganzen entsprechenden Bindings im Hauptprogramm erstellen, sondern sozusagen ein komprimiertes Event von meinem Modul bekommen, so daß ich nur noch sowas schreiben muss wie: megaliste.Megaliste.bind(<bigAction>, machMal). Also vielleicht so ne Art benutzererstelltes Event oder so ??? Wobei das Hauptprogramm das dann natürlich auch kennen müsste. Keine Ahnung, wie das geht.

Und dann noch folgende Kleinigkeit: Wie kann ich mir aus dem Untermodul heraus das Resizeevent des Hauptfensters holen, siehe Kommentar im Skript ?

Über andere Probleme in meinem Modul dürft ihr euch natürlich, so wie bisher immer, auch gerne beschweren.

Und hier ist der Code: http://paste.pocoo.org/show/130757/
ippurk
User
Beiträge: 61
Registriert: Mittwoch 8. Juli 2009, 20:40

ich hab hier mal einen neuen thread gemacht, da die Diskussion hier ja mal wieder leicht den Rahmen der Threadüberschrift sprengt: http://www.python-forum.de/topic-19707.html

at WUF: Ich habe da versucht, deinen Vorschlag umzusetzen, daß man auch eine variable Anzahl von Boxen nebeneinander bekommt. Nur das mit der Vererbung, da werd ich mich demnächst mit auseinandersetzen. Ist im Moment noch nicht integriert und mir auch noch nicht so ganz klar.
ippurk
User
Beiträge: 61
Registriert: Mittwoch 8. Juli 2009, 20:40

bezüglich der MouseWheelproblematik, da hab ich jetzt auch was gefunden:

Unter Windows kriegt man für dieses Keycode-event nicht nur 120 oder -120 ausgegeben, sondern bei schnellem scrollen auch ein Vielfaches davon. Höchstes, was ich geschafft hab, war 360. Deshalb:

Code: Alles auswählen

if event.num == 5 or event.keycode < 0:
    scroll_faktor = 1
    if event.keycode is not None:
        scroll_faktor = event.keycode / 120 * (-1)
    alle_boxen.yview_scroll(4 * scroll_faktor, 'units')
    return "break"
elif event.num == 4 or event.keycode > 0:
    scroll_faktor = 1
    if event.keycode is not None:
        scroll_faktor = event.keycode / 120
    alle_boxen.yview_scroll(4 * scroll_faktor, 'units')
    return "break"
Jetzt werden alle Boxen auch bei nervösem Rollen des Mausrades genau gleich weit gescrollt.
BlackJack

Die Bedingungen sehen mir etwas komisch aus, insbesondere dass `scroll_faktor` immer positiv ist/wird!?
ippurk
User
Beiträge: 61
Registriert: Mittwoch 8. Juli 2009, 20:40

ups, da hab ich beim Posten ein kleines Minus in zeile 11 vor der 4 vergessen, so sollte es besser gehen:

Code: Alles auswählen

if event.num == 5 or event.keycode < 0:
    scroll_faktor = 1
    if event.keycode is not None:
        scroll_faktor = event.keycode / 120 * (-1)
    alle_boxen.yview_scroll(4 * scroll_faktor, 'units')
    return "break"
elif event.num == 4 or event.keycode > 0:
    scroll_faktor = 1
    if event.keycode is not None:
        scroll_faktor = event.keycode / 120
    alle_boxen.yview_scroll(-4 * scroll_faktor, 'units')
    return "break"
Aber was kommt dir an den anderen Bedingungen komisch vor ?
BlackJack

Für meinen Geschmack ist da zuviel Wiederholung in diesem Schnippsel.

Code: Alles auswählen

if event.keycode is not None:
    factor = -(event.keycode / 120)
else:
    factor = -1 if event.num == 4 else 1
    assert event.num in (4, 5)
alle_boxen.yview_scroll(4 * factor, tk.UNITS)
return 'break'
ippurk
User
Beiträge: 61
Registriert: Mittwoch 8. Juli 2009, 20:40

ja, das gefällt mir auch besser, deutlich eleganter gelöst.

Aber zu diesem assert: Ist das nötig ? Die Funktion wird doch sowieso nur aufgerufen, wenn die event.num 4 oder 5 ist (durch mein vorheriges bind). Oder ist das einfach nur guter Stil, bei solchen if else Zuweisungen diese assert-Überprüfung vorzunehmen ?

Ach und danke. Jetzt weiß ich auch, daß es den Befehl assert gibt, wieder was dazugelernt, bin ja noch ein völlig Unwissender.
BlackJack

@ippurk: Das ``assert`` ist nicht nötig, aber genau dafür ist ``assert`` da -- Sachen überprüfen, von denen man 100%ig sicher ist, das sie stimmen. Es ist immer wieder überraschend wie oft man sich irrt und die eben *doch* passieren. :-)
Antworten