Links (Tags) im Text Widget

Fragen zu Tkinter.
Markus12
User
Beiträge: 195
Registriert: Dienstag 6. März 2007, 19:32
Wohnort: Frankfurt am Main

Hallo,
Ich habe ein Modul geschrieben, dass eine Dokumentation implementiert, wie man sie bei umfangreichen Programmen sieht. Wie es aussieht?
Es hat eine Listbox auf der linken Seite der Tk-Applikation und ein Text Widget auf der rechten Seite. Klickt man auf einen Eintrag der Listbox, erscheint die dazugehörige Beschreibung im Text Widget.
So weit so gut, funktioniert auch alles.

Nun hatte ich die Idee, Links in das Text-Widget einzubauen, die in Wirklichkeit Tags sind, und dessen foreground blau ist, damit es wie ein Link aussieht. Ein Link in dem Text-Widget soll zu anderen Thema weiterleiten. Man kennt das aus Wikipedia.

Da man auf den Link klicken muss, musste ich ein Event an den Tag binden, mein Beispiel dafür ist unten.

Problem ist, dass die Funktion, die aufgerufen wird, wenn man auf den Link klickt, nicht zeigt, an welcher Position im text Widget geklickt wurde, was ich aber benötige, um zu wissen, welcher Link angeklickt wurde im Text-Widget. (In meinem Skript kann man natürlich mehrere Links im selben Text erstellen!)

Kennt dazu jemand die Lösung?
Denke nicht, dass ihr den kompletten Quelltext benötigt, wenn doch, dann poste ich ihn hier.

Code: Alles auswählen

text = Text(self.tk)
...
self.text.tag_add(tagname, index1, index2)
self.text.tag_config(tagname, foreground="blue")
self.text.tag_bind(tagname, "<Enter>", self.aendereCursor)
self.text.tag_bind(tagname, "<Leave>", self.normalerCursor)
self.text.tag_bind(tagname, "<Button-1>", self.verlinken)

Viele Grüße Markus :)
Danke schon mal im Voraus!
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

Eine wirklich einfache Lösung scheint es da nicht zu geben.
So wie ich dich verstanden haben, benutzt du für alles, was als Link gilt das gleiche Tag, so dass man über den Tagnamen und die Indizes tag.first und tag.last nicht weiterkommt.

Meine Lösung sieht so aus: Du bestimmst den Index des Zeichens, das dem Mausklick am nächsten ist über die Konstante CURRENT, nimmst die Liste mit den Positionsangaben aller Textteile, die mit dem Link-Tag verknüpft sind und suchst dir dann die beiden Positionen heraus, zwischen denen die Mausklick-Position liegt. Damit hast du dann den benötigten Textteil extrahiert.

Code: Alles auswählen

# Python 3.0
import tkinter as tk
from bisect import bisect

def klick(e):
    tagposlist = text.tag_ranges("link")
    klickpos = text.index(tk.CURRENT)
    index = bisect(tagposlist,klickpos)
    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()
Markus12
User
Beiträge: 195
Registriert: Dienstag 6. März 2007, 19:32
Wohnort: Frankfurt am Main

Hallo,
Danke für deine Antwort.

Leider hatte ich das bereits berücksichtigt, diese Lösung, allerdings ist mit Current nichts zu machen, wenn der ´state´ vom Text-Widget disabled ist, weil ein Text-Widget, in dem man rumklicken kann und sogar Text löschen kann echt nicht das Wahre ist...
Klar, mit Current wäre es etwas umständlich gewesen, hätte ich aber auch gemacht, wenn die Qualität des Programms nicht darunter leiden würde^^

Achso, ok, mhh hatte gestern abend noch eine Idee, die ich bis jetzt wieder vergessen hatte... Jedes Mal, wo man in den text-widget klickt, könnte er den status wieder auf normal setzen, die position mit current finden, und dann den status wieder auf disabled setzen und das alles passiert so schnell, dass der benutzer es nicht sieht... Muss ich mal versuchen!

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

Hast du meinen Code mal ausprobiert? Er funktioniert unabhängig vom "state" des Text-Widgets, auch wenn es "disabled" ist.
Markus12
User
Beiträge: 195
Registriert: Dienstag 6. März 2007, 19:32
Wohnort: Frankfurt am Main

Mhh, also ich habe es so modifiziert, damit es in mein Programm passt... Nun habe ich endlich auch deine Zeilen verstanden... Kannte das Modul bisect nicht.
Anscheinend funktioniert es aber nicht richtig.

Code: Alles auswählen

nummer = int(self.listbox.curselection()[0])
titel = self.titel[nummer]
link = self.links[titel][0]
tagposlist = self.text.tag_ranges(link[0])
klickpos = self.text.index(CURRENT)
#tagposlist = list(tagposlist)+[klickpos]
#tagposlist.sort()
#print tagposlist
index = bisect(tagposlist,klickpos)
#print index
self.text.get(tagposlist[index-1])
print(self.text.get(tagposlist[index-1],tagposlist[index]))
self.links ist ein Dictionary, dessen keys die Themen sind, die man auch in der Listbox findet. Zurückgegeben wird ein Tuple, das den Tag des Links enthält, Index1, Index2 und das Thema, zu dem verlinkt werden soll.
self.titel enthält alle themennamen.

Die Zeilen mit einem # Zeichen davor habe ich hinzugefügt um zu sehen, wie das Programm arbeitet.

Folgendes wurde zurückgegeben im gesamten Lauf:

Code: Alles auswählen

['1.0', '1.4', '1.9']
2
eller
Der "eller" Texteil ist korrekt, denn er ist Teil des Wortes "Ersteller" in einer Beschreibungen zu einem Thema.

Sollte aber der Index (der in meinem Lauf "2" war) nicht eins sein?? Denn wenn ich mitten zwischen ein Wort geklickt habe, sollte der angeklickte Index doch dann auch in der Mitte der Liste sein oder nicht?

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

Ich verstehe das Problem nicht.

So wie ich deinen ersten Post verstanden habe, hast du ein Text-Widget, in dem sich eine gewisse Anzahl an Wörtern oder Satzteilen befinden, die durch ein und dasselbe Tag (in meinem Beispiel habe ich es "link" genannt) markiert sind und du gerne erreichen möchtest, dass beim Klick auf eine dieser Stellen zurückgeliefert wird, wie der Text lautet, auf den geklickt wurde.

Falls es das ist, was du wollst: Genau das macht mein Code.

Falls du etwas anderes wolltest, müsstest du es nochmal beschreiben.
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hallo numerix

Dein Code-Snippet wirft bei mir folgende Exception:

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 "text_widget_with_tags_01.py", line 9, in klick
    index = bisect(tagposlist,klickpos)
TypeError: unorderable types: str() < _tkinter.Tcl_Obj()
Setup hier ist: Linux SuSE 11.0 Python 3.0

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

Das verstehe ich nicht.
Alle Elemente von tagposlist sind Positionsangaben der Form "x.y" als Zeichenketten und klickpos ist ebenfalls eine solche Positionsangabe. Die Fehlermeldung sagt, dass das bei dir nicht so ist und darum die von bisect() durchgeführten Vergleiche nicht funktionieren. Kannst du das mal via type() prüfen.

Ich habe ActivePython 3.0.0 mit tkinter "Revision 67095" verwendet, bei mir läuft es aber ebenso unter Python 2.5.2 und 2.6.1 einwandfrei.
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hallo numerix

Die Tkinter Revisions-Nummer stimmt bei mir mit deiner überein. Dein Code-Snippet habe ich unter Python 3.0 noch nicht zum laufen gebracht. Wenn ich einen der markierten Links in der Textzeile anklicke bekomme ich immer noch die erwähnte Exception.

Frage: Was ist bei dir Wert für 'index' in Zeile Nr. 8, wenn du einen Klick ausführst?

Code: Alles auswählen

index = bisect(tagposlist,klickpos) 
Habe das Snippet auch unter Python 2.6 ausprobiert. Hier bekomme ich einen Wert 4 für 'index'. Das löst folgerichtig die Exception:

Code: Alles auswählen

IndexError: tuple index out of range
beim abarbeiten der Codezeile Nr.9:

Code: Alles auswählen

print(text.get(tagposlist[index-1],tagposlist[index]))
aus.

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

Mir ist das immer noch rätselhaft. Egal, wo ich genau im Wort hinklicke, es funktioniert stets einwandfrei. Damit ich sicher bin, dass ich exakt den gleichen Code verwende, habe ich ihn nochmal aus meinem eigenen Post kopiert. Alles okay.

Ich habe jetzt mal folgende Ausgabe mit eingebaut - zur Sicherheit nochmal der ganze Code:

Code: Alles auswählen

import tkinter as tk
from bisect import bisect

def klick(e):
    tagposlist = 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()
Die Ausgabe sieht bei mir z.B. so aus - je nachdem, wo genau ich hinklicke:

Code: Alles auswählen

1 ('1.12', '1.22', '1.37', '1.42') 1.15 -> Mustertext
1 ('1.12', '1.22', '1.37', '1.42') 1.12 -> Mustertext
1 ('1.12', '1.22', '1.37', '1.42') 1.16 -> Mustertext
1 ('1.12', '1.22', '1.37', '1.42') 1.17 -> Mustertext
1 ('1.12', '1.22', '1.37', '1.42') 1.21 -> Mustertext
3 ('1.12', '1.22', '1.37', '1.42') 1.37 -> Klick
3 ('1.12', '1.22', '1.37', '1.42') 1.38 -> Klick
3 ('1.12', '1.22', '1.37', '1.42') 1.40 -> Klick
3 ('1.12', '1.22', '1.37', '1.42') 1.41 -> Klick
3 ('1.12', '1.22', '1.37', '1.42') 1.37 -> Klick
Genau so müsste es auch sein. Das Tupel mit den Positionsangaben muss immer eine gerade Anzahl enthalten - jeweils Anfangs- und Endposition der einzelnen Tags. Der Index (hier vor dem Tupel ausgegeben) muss immer ungerade sein, weil die Suchposition nur zwischen Anfangs- und Endposition stehen *kann*. Der Index 4 (wie bei dir) kann nur dann vorkommen, wenn die Mausposition hinter "1.41" liegen würde, was aber nicht sein dürfte, weil dort keine Bindung mehr an das Event besteht. :?: :?: :?:
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hallo numerix

Besten Dank für deine ausführliche Stellungsnahme. Die letzten drei Jahre arbeitete ich auf einem Laptop des Brands 'TOSHIBA'-Satellite unter Linux SuSE 10.0. Der heutige Fortschritt der Hardware-Technologie und Software-Enwicklung zwingt dich geradezu von Zeit zu Zeit ein generelles Update vorzunehmen. Da für mich 'never change a running system' in diesem Zusammenhang noch immer ein wichtiger Leitsatz bleibt, habe ich mich entschlossen einen neuen Laptop, diesmal 'ACER'-Aspire-5930G, anzuschaffen und SuSE 11.0 neben dem bestehenden 'VISTA' darauf zu installieren. Die Installation von SuSE 11.0 lief trotzt aller Horror-Visionen problemlos über die Bühne. Bei dieser Installation wird, wenn du es nicht explizit erwähnst automatisch Python 2.52 mitinstalliert. Nachträglich habe ich Python 2.6 & 3.0 mit 'make altinstall' nachinstalliert. Eventuell ist hier etwas in die Hosen gegangen.

Ich Teste nun dein Code-Snippet auf meinem alten Laptop 'TOSHIBA' unter Python 2.5 aus. In diesem Zusammenhang hätte ich noch eine vielleicht 'naive' Frage, Wie schreibst du die Code-Zeile 8:

Code: Alles auswählen

print(index, tagposlist, klickpos, end=" -> ")
mit dem herkömmlichen 'print'-Syntax

Gruss wuf :wink:
Take it easy Mates!
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Code: Alles auswählen

print index, tagposlist, klickpos, "->",
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Danke Leonidas: Das war genau was ich suchte.

@numerix: Jetzt scheint die Sache auf Python 2.5 ohne Exception zu laufen. Mit einem Klick auf den Link sieht bei mir aber der Output auf der Konsole wie folgt aus:

Code: Alles auswählen

1 (<textindex object at 0x82b6cd0>, <textindex object at 0x82b9df8>, <textindex object at 0x82b72e8>, <textindex object at 0x82b9e40>) 1.14 -> Mustertext
Hier werden die Tag-Indexe als Objekte in Textform ausgegeben, nicht als Floats wie bei dir.

Code: Alles auswählen

1 ('1.12', '1.22', '1.37', '1.42') 1.15 -> Mustertext 
Was könnte dies sein (sorry ist eventuell wieder eine idiotische Frage von mir)

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

Aus irgendwelchen Gründen sind die Tkinter-Indizes bei dir textindex-Objekte, bei mir sind sie vom Typ str (und zwar unter Python 2.5.2, 2.6.1 und 3.0.0), keine Ahnung warum das bei dir anders ist.

Das dürfte dann unter Python 3.0 schon deshalb nicht laufen, weil dort der Vergleich "unpassender Typen" eine Exception auslöst. Bei Python 2.x wurde das nicht so streng gesehen, was allerdings (hier vermutlich auch) nicht immer zum gewünschten Ergebnis führte und darum in Python 3.0 besser ist.
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

OK numerix

Unter Python 3.0 ist bei mir 'klickpos' vom Typ <class 'str'>, und die Elemente in 'tagposlist' vom Typ <class '_tkinter.Tcl_Obj'> offensichtlich der Grund welcher 'bisect' zum Husten anregt. Ich werde noch weiter forschen und dir eventuelle Erkenntnisse mitteilen.

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

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: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

@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

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

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: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

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!
Antworten