Links (Tags) im Text Widget

Fragen zu Tkinter.
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

Dienstag 13. Januar 2009, 16:38

Da man beim Setzen der einzelnen Tags im Text-Widget die Anfangs- und Endpositionen ohnehin explizit in der Form "x.y" angeben muss, könnte man diese einfach in einer Liste ablegen und darauf dann zugreifen, statt die Methode tag_ranges zu verwenden, die hier die Probleme bereitet.
Benutzeravatar
wuf
User
Beiträge: 1483
Registriert: Sonntag 8. Juni 2003, 09:50

Dienstag 13. Januar 2009, 18:07

@numerix: Ja so funktioniert es einwandfrei. Mich nimmt es wirklich Wunder ob ich der einzige in diesem Forum bin der mit diesem Problem konfrontiert ist.

Hallo gibt es in unserem Forum Leute, welche gleiche Probleme mit dem Code-Snippet hatten wie ich?

Danke für jede Antwort.

Gruss an alle :wink:
Take it easy Mates!
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

Dienstag 13. Januar 2009, 18:28

wuf hat geschrieben:@numerix: Ja so funktioniert es einwandfrei. Mich nimmt es wirklich Wunder ob ich der einzige in diesem Forum bin der mit diesem Problem konfrontiert ist.
Bei mir hinterlässt die Sache auch ein ungutes Gefühl, bedeutet es doch auch, dass ich damit rechnen muss, dass ein Tkinter-Programm, das auf (m)einem Rechner sauber läuft und keine externen Abhängigkeiten hat, möglicherweise auf einem anderen Rechner nicht läuft, obwohl dort - dem Anschein nach - die gleiche Python-Umgebung installiert ist. :shock:

So etwas habe ich im Zusammenhang mit Tkinter bisher erst ein einziges Mal an einem weniger gravierenden Beispiel erlebt (http://www.python-forum.de/topic-15674.html)
yipyip
User
Beiträge: 418
Registriert: Samstag 12. Juli 2008, 01:18

Dienstag 13. Januar 2009, 19:24

Unter meinem Python 2.5.2 läufts einwandfrei
(mit den Zeilen und Spaltenindizes als Strings).

Unter 3.0 hab' ich das gleiche Problem wie wuf:

Code: Alles auswählen

Exception in Tkinter callback
Traceback (most recent call last):
  File "/usr/local/lib/python3.0/tkinter/__init__.py", line 1405, in __call__
    return self.func(*args)
  File "linktext3.py", line 8, in klick
    index = bisect(tagposlist, klickpos)
TypeError: unorderable types: str() < _tkinter.Tcl_Obj()


Python 3.0 (r30:67503, Dec  4 2008, 13:25:00) 
Mehr fällt mir da im Moment auch nicht ein.

:wink:
yipyip
Benutzeravatar
wuf
User
Beiträge: 1483
Registriert: Sonntag 8. Juni 2003, 09:50

Dienstag 13. Januar 2009, 19:36

Hallo yipyip

Danke für deine Versuche. Da WINDOWS VISTA auf meinem Laptop verfügbar ist installierte ich auch noch Python 3.0 unter diesem OS. Stellte das gleiche Fehlverhalten fest. Ich sehe meine Zukunft wird wieder mit viel Spielereien und Zeitvergeudung belastet :P Es lebe der Wechsel zu Python 3.0

P.S. Nachträgliche Feststellung: '2to3' entschärft die Sache doch schon ein wenig.

Viel Vergnügen an alle Spielkameraden. Gruss wuf :wink:
Take it easy Mates!
Benutzeravatar
wuf
User
Beiträge: 1483
Registriert: Sonntag 8. Juni 2003, 09:50

Mittwoch 14. Januar 2009, 20:12

..... Hier ein Lösungsansatz für das Problem:

Code: Alles auswählen

# Python 3.0

import tkinter as tk
from bisect import bisect

def klick(e):
    tagposlist = [str(tag_pos) for tag_pos in list(text.tag_ranges("link"))]
    klickpos = text.index(tk.CURRENT)
    index = bisect(tagposlist,klickpos)
    print(index, tagposlist, klickpos, end=" -> ")
    print(text.get(tagposlist[index-1],tagposlist[index]))

root = tk.Tk()
text = tk.Text(root)
text.pack()
text.insert("0.0","Das ist ein Mustertext, man kann auf Klick drücken!")
text.tag_add("link","1.37","1.42")
text.tag_add("link","1.12","1.22")
text.tag_config("link",background="green")
text.tag_bind("link","<Button-1>",klick)
root.mainloop()
Gruss wuf :wink:
Take it easy Mates!
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

Mittwoch 14. Januar 2009, 21:58

Ich hatte schon vermutet, dass man die textindex-Objekte via str() in die gewünschte Form bringen kann, konnte es aber in Ermangelung dieser Objekte nicht testen. Danke dafür.

Die explizite Umwandlung in eine Liste via list() in der LC scheint mir überflüssig. Testen kann ich es nicht.
Markus12
User
Beiträge: 195
Registriert: Dienstag 6. März 2007, 19:32
Wohnort: Frankfurt am Main

Donnerstag 15. Januar 2009, 02:29

Hallo,
Danke, hatte das durchdacht und bemerkt, dass ein einzelner Tag die vielen Tags ersetzen würde und den gleichen Job ausführt. Nun habe ich meinen Code verändert und bemerkt, dass wenn ich meine Maus über den Link bewege, alle Links im Text unterstrichen angezeigt werden, da ich den Tag an ein Event gebunden habe, der eine Funktion aufruft, der genau das tut... Aber natürlich, denn beide Links verwenden den selben Tag... Würde ich das weglassen, würde es generell gehen... Will ich aber nicht^^ Deswegen werde ich die Tag Benennung doch lassen :)


Danke für den Code, der nun bewirkt, dass ich herausfinden kann, welcher Link angeklickt wurde! :) Hat mir sehr geholfen!


Nun aber noch eine Frage:
Wenn man mit der Maus über den Link scrollt, soll er unterstrichen werden. Gemacht habe ich das mit einem Event, das an eine Funktion gebunden ist, die dann ausgeführt wird. Problem: Woher soll die Funktion wissen, über welchen Link gescrollt wird, damit sie ihn dann unterstreichen kann? :(

Grüße Markus :)
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

Donnerstag 15. Januar 2009, 15:58

Markus12 hat geschrieben:Wenn man mit der Maus über den Link scrollt, soll er unterstrichen werden. Gemacht habe ich das mit einem Event, das an eine Funktion gebunden ist, die dann ausgeführt wird. Problem: Woher soll die Funktion wissen, über welchen Link gescrollt wird, damit sie ihn dann unterstreichen kann? :(
Geht im Prinzip genauso. Du musst halt zusätzlich die Events "Enter" und "Leave" binden und entweder mit drei Handlern arbeiten oder mit einem, der anhand des Event-Types feststellt, welches Event den Handler aufgerufen hat. Das sähe dann so aus (läuft so unter 2.x und 3.0):

Code: Alles auswählen

if __import__("sys").version_info[0] == 3:
    import tkinter as tk
else:
    import Tkinter as tk
from bisect import bisect

def handler(e):
    tagpositions = [str(pos) for pos in text.tag_ranges("link")]
    mouseposition = text.index(tk.CURRENT)
    index = bisect(tagpositions, mouseposition) # ACHTUNG: Zeile muss ersetzt werden -> siehe Beitrag unten!
    linkposition = tagpositions[index-1:index+1]
    if e.type == "7": # <Enter>
        text.config(cursor="hand1")
        text.tag_add("tmp", *linkposition)
        text.tag_config("tmp", underline=True)
    elif e.type == "8": # <Leave>
        text.config(cursor="")
        text.tag_delete("tmp")
    elif e.type == "4": # <Button-1>
        print(text.get(*linkposition))

root = tk.Tk()
text = tk.Text(root)
text.pack(padx=5, pady=5)
text.insert("0.0","Das ist ein Mustertext, man kann auf Klick drücken!")
text.config(bg="white", state="disabled")
text.tag_add("link","1.37","1.42")
text.tag_add("link","1.12","1.22")
text.tag_config("link",foreground="blue")
text.tag_bind("link","<Button-1>", handler)
text.tag_bind("link","<Enter>", handler)
text.tag_bind("link","<Leave>", handler)
root.mainloop()
Zuletzt geändert von numerix am Freitag 16. Januar 2009, 11:23, insgesamt 1-mal geändert.
Markus12
User
Beiträge: 195
Registriert: Dienstag 6. März 2007, 19:32
Wohnort: Frankfurt am Main

Freitag 16. Januar 2009, 01:13

Hey,
danke! Das Attribut type hatte ich gar nicht gekannt, wieder was gutes dazu gelernt :)
Dachte auch nicht, dass das mit text.index und Current funktioniert, wenn man nicht in den Text klickt... Interessant =) Das löst mein Problem.

nun gibt es aber noch etwas unerwartetes, aber behebbares.
Wenn man bisect die beiden indexwerte in einer liste gibt und dann noch den Index, wo man geklickt hat, kann und wird es oft zu Fehlern kommen. Warum? Die Liste, die die Funktion intern berechnet, sortiert die Liste natürlich und schaut dann, wo das zweite Argument (wo man geklickt hatte) in der Liste sein würde und gibt diesen Index zurück.

Dieses Beispiel beschreibt diesen Fall ganz gut:

tagposlist = ["1.4", "1.9"]
geklickt = "1.33"
bisect intern = ["1.33", "1.4", "1.9"]

Natürlich, denn die Argumente sind ja Strings, und somit werden die von Anfang an durchgegangen und dann sortiert. Da die 3 aber vor der 4 vorkommt, wird "1.33" natürlich auch an den Anfang gestellt... Was für den gesamten Programmlauf bedeuten würde, dass in meinem Programm immer ein falscher oder immer die falsche Linkfunktion ausgeführt werden würde ;)

Kann man beheben, indem man die spalte (33, 4 und 9) zu Zahlen konvertiert und da dann noch mit herumspielt...

Grüße Markus :)
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

Freitag 16. Januar 2009, 11:21

Markus12 hat geschrieben:nun gibt es aber noch etwas unerwartetes, aber behebbares. Dieses Beispiel beschreibt diesen Fall ganz gut:

tagposlist = ["1.4", "1.9"]
geklickt = "1.33"
bisect intern = ["1.33", "1.4", "1.9"]

Natürlich, denn die Argumente sind ja Strings, und somit werden die von Anfang an durchgegangen und dann sortiert. Da die 3 aber vor der 4 vorkommt, wird "1.33" natürlich auch an den Anfang gestellt... Was für den gesamten Programmlauf bedeuten würde, dass in meinem Programm immer ein falscher oder immer die falsche Linkfunktion ausgeführt werden würde ;)
Da hast du natürlich Recht. Ich hatte meine Beispielpositionen so geschickt gewählt, dass mir das nicht aufgefallen ist. Meine Lösung sähe so aus, dass man lediglich Zeile 10 durch die folgende ersetzt:

Code: Alles auswählen

index = bisect([tuple(map(int,pos.split("."))) for pos in tagpositions], tuple(map(int,mouseposition.split("."))))
Da map() in Python 3.0 keine Liste mehr liefert, sondern einen Iterator, muss man nochmal explizit in eine Sequenz umwandeln. Für Python 2.x wäre das nicht nötig. Statt mit map() könnte man es natürlich auch mit LC machen. Mir hat es so besser gefallen.
Markus12
User
Beiträge: 195
Registriert: Dienstag 6. März 2007, 19:32
Wohnort: Frankfurt am Main

Samstag 17. Januar 2009, 01:12

Danke für die Lösung, hatte mich aber gestern abend nach meinem Post noch hingesetzt und die Lösung schnell ausgearbeitet... Habe es mit List Comprehension gemacht, deine Lösung gefällt mir allerdings auch sehr gut!

Code: Alles auswählen

p = self.text.index(CURRENT)
neuepos = [(int(p.split(".")[0]), int(p.split(".")[-1]))]
akt_links = self.links[thema] 
for tag, index1, index2, zu in akt_links:
    tagposliste = [(int(i.split(".")[0]), int(i.split(".")[-1])) for i in (index1, index2)]
    tagposliste.sort()
    a = list(tagposliste+neuepos)
    a.sort()
    print a
    print bisect(tagposliste, neuepos)
    if bisect(tagposliste, neuepos) == 1:
         #do whatever
return tag
So, nun ist da ein weiteres Problem... bisect macht nicht das, was es machen soll? :)

thema ist nebenbei die die angeklickte auswahl in der listbox.
self.links ist ein dictionary, und jeder wert zum Schlüssel (schlüssel = thema) enthält eine liste mit tuples. Jedes tuple repräsentiert den link mit einem Tag, index1, index2, und verlinke zu Thema XY.

nur, damit ihr das nachvollziehen könnt.
Wenn es funktioniert, werde ich die verschiedenen Tags entfernen, was den Code auch schöner macht.

Was der Fehler ist? Also, erstmal unterstreicht es immer den falschen link, was sich darauf zurück führen lässt, dass etwas mit der funktion nicht stimmt.

Habe deswegen also die print befehle eingebaut.

Hier die Ausgabe:

Code: Alles auswählen

[(1, 0), (1, 3), (1, 9)]
0
[(1, 3), (1, 31), (1, 36)]
0
es wird beides zweimal ausgegeben, da mein Text zwei Links enthält und die Funktion bekanntlich eine forschleife hat, die diese durchläuft und prüft.
(1, 0) ist Zeile 1, spalte 0 -> Anfang des ersten Links.
(1, 9) ist Zeile 1, spalte 9 -> Ende des Links
0 ist der Index, an dem bisect (1, 3) eingefügt hat. sollte allerdings 1 sein!

(1, 31) ist Zeile 1, spalte 31 -> Anfang von Link 2!
(1, 36) ist Zeile 1, spalte 36 -> Ende von Link 2!
0 ist der Index, und er ist korrekt.


Was mache ich falsch? Komme vielleicht einfach nur nicht drauf...

Grüße Markus :)
Markus12
User
Beiträge: 195
Registriert: Dienstag 6. März 2007, 19:32
Wohnort: Frankfurt am Main

Freitag 20. März 2009, 03:27

Ein erneutes Hallo,
Ich habe das Programm so weit nun fertig gestellt nach einer kleinen Pause, in der ich leider nicht programmieren konnte. Das Verlinken funktioniert, was noch einmal ein neues Problem aufgeworfen hatte, das ich heute erst gelöst habe...

Nun geht es allerdings darum, dass ich zwei relativ kleine Probleme habe, die ich nicht weiß zu umgehen.

1. Ich möchte das Programm starten und direkt den ersten Eintrag in der Listbox aktivieren. Mit listbox.activate funktioniert es nicht, das habe ich ausprobiert und im Web nachgelesen, und habe deshalb die Funktion listbox.selection_set(index) oder listbox.select_set(index) gefunden. Verwende ich diese in der Shell, funktioniert es auch, aber nicht im Programm...?

2. Bei Doppelklick in das Text-Widget, das den Text enthält wird der Fokus aus der Listbox entfernt, sprich, kein Eintrag in der Listbox ist angewählt. Das genau will ich verhindern; Zu jeder Zeit muss ein Eintrag angewählt sein... Die Listbox verliert den Fokus.

Das wäre das letzte zu diesem Projekt, dannach ist es endlich fertig! :D

Viele Grüße Markus :)
Danke schon mal im Voraus.
Markus12
User
Beiträge: 195
Registriert: Dienstag 6. März 2007, 19:32
Wohnort: Frankfurt am Main

Donnerstag 26. März 2009, 02:18

Hallo,
wusste niemand die Antwort dazu?

Ich habe mittlerweile Problem Nummer 1 gelöst und kann mich nicht mehr daran erinnern, warum es nicht funktionierte... Zumindest tut es das nun mit ´selection_set(index)´.

Mein Problem bleibt allerdings, dass die Listbox nicht dauerhaft den Fokus hat bzw. einen Eintrag aktiviert hat. Klicke ich ins Textfeld, verliert die Listbox anscheinend den Fokus... Und das löst eine Exception aus. Selbst, wenn ich die Exception unterdrücke, was sicherlich möglich ist, ist es nicht das, was man sich vorstellt, wenn man es benutzt.

In dem Fall kann ich es nicht mehr alleine lösen... Habe verschiedene Sachen versucht und komme einfach zu keinem Ergebnis...

Ich wäre froh, wenn jemand bereits damit Erfahrung hatte und mir einen Rat geben kann oder es auch so weiß.

Viele Grüße Markus :)
Benutzeravatar
wuf
User
Beiträge: 1483
Registriert: Sonntag 8. Juni 2003, 09:50

Donnerstag 26. März 2009, 07:44

Hallo Markus12

Könntest du hier vielleicht einmal ein funktionierendes Code-Snippet platzieren, welches sich wie dein Projekt verhält? Da könnten wir dein erwähntes Problem nachvollziehen und eventuell eine Lösung finden.

Gruss wuf :wink:
Take it easy Mates!
Antworten