bad performance with tkinter text widget ?

Fragen zu Tkinter.
Benutzeravatar
ts7343
User
Beiträge: 69
Registriert: Mittwoch 5. Mai 2010, 13:48

Hallo Tkinter-Text-Widget-Experten,

ich hab beim oeffnen von text files mit dem Text Widget so einige Performanceprobleme und frage mich
natuerlich, ob das normal ist und das Text Widget einfach nicht mehr hergibt oder ob es an der Art
meiner Leseroutine liegt.

Und wenn es mehr hergibt, wie man das dann wohl anpacken muesste?

Wenn ich den Code ausfuehre (pls see below), und einen 10MB file dabei lade, dauert das 20 sek,
was ich fuer so eine dicke Linux-Buechse mit 8 Processoren usw. recht viel finde.

Code: Alles auswählen

 :-) l test.txt
-rw-r--r-- 1 ts7343 cae 10324863 2010-11-18 11:42 test.txt
 :-) ./forum.py
12:04:58
12:05:19
 :-) 
Habt ihr aehnliche Erfahrungen mit dem Text Widget von Tkinter? Wenn ich Tags benutzen wuerde oder aehnliches, wuerde ich das ganze
ja noch verstehen, aber einfach nur eine Liste von Strings im Text Widget darstellen sollte meiner Meinung nach viel viel schneller gehen.

Code: Alles auswählen

#!/usr/bin/env python

import Tkinter as tk
import string, re, os, sys, subprocess, datetime

class app_functions(tk.LabelFrame):

   def __init__(self, master): 
    
#     time stamp
      my_time = datetime.datetime.now()
      print my_time.strftime("%H:%M:%S")
#     widget definition
      tk.LabelFrame.__init__(self, master)        
      frame_lsf_view     = tk.LabelFrame(self)
      self.vview         = tk.Scrollbar(frame_lsf_view)
      self.lsf_view_01   = tk.Text(frame_lsf_view)
      frame_lsf_view.grid(       row=0, column=0, rowspan=2, columnspan=2)
      self.lsf_view_01.grid(     row=0, column=0)
      self.grid(                 row=0, column=0, sticky="N"+"S"+"E"+"W") 
      self.lsf_view_01.configure(width=110,height=50)            
#     read file in list
      view_file = open("test.txt", "r")
      my_line_list = view_file.readlines()
      view_file.close()
#     put list into text widget
      self.lsf_view_01.config(state='normal')
      self.lsf_view_01.delete(0.0,"end")
      for i,line in enumerate(my_line_list):
         self.lsf_view_01.insert('end', line)
         self.lsf_view_01.see('end')
      self.lsf_view_01.config(state='disabled')
#     time stamp             
      my_time = datetime.datetime.now()
      print my_time.strftime("%H:%M:%S")

if __name__ == "__main__":
   root = tk.Tk()
   root.geometry("800x800")
   root.option_add('*font',("courier",7,"bold"))
   display = app_functions(root)
   root.mainloop()
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Du stößt hier auf jedenfall an die Grenzen von Tkinter, da jedes einzelne Buchstabe frisst ohne ende, zudem ist es eigentlich egal ob du nun 8 16 oder 32 CPUs hast, denn so weit ich weiß kann Tkinter nur einen Nutzen.

Dennoch lässt sich auf jedenfall dein Auslesen optimieren.
Wozu liest du die Datei Vollständig aus und schreibst sie dann doch nur in das Text Widget ?

Mach doch aus diesen Zeilen:

Code: Alles auswählen

     
      view_file = open("test.txt", "r")
      my_line_list = view_file.readlines()
      view_file.close()
#     put list into text widget
      self.lsf_view_01.config(state='normal')
      self.lsf_view_01.delete(0.0,"end")
      for i,line in enumerate(my_line_list):
         self.lsf_view_01.insert('end', line)
         self.lsf_view_01.see('end')
Diese hier

Code: Alles auswählen

      self.lsf_view_01.config(state='normal')
      self.lsf_view_01.delete(0.0,"end")
      with open("test.txt") as view_file:
         for line in view_file:
            self.lsf_view_01.insert('end', line)
            self.lsf_view_01.see('end')
Aber um wirklich eine bessere Performance zu bekommen, würde ich den Text tatsächlich wie du vollständig auslesen und dann nur den Notwendigen Text in das Text-Widget schreiben.
Denn 10MB Text ist nun nicht gerade wenig.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Benutzeravatar
ts7343
User
Beiträge: 69
Registriert: Mittwoch 5. Mai 2010, 13:48

Hallo Xynon1,

das verrueckte ist echt, dass er zum lesen in den memory nicht mal eine Sekunde braucht,
der Rest der Zeit wird dazu benutzt, um die line list ins Widget zu packen.

Und du meinst, eine eigene Routine, die einen kleineren Textfile einliest, um diesen Bottleneck
zu umgehen? Also sowas wie:
1) head -100 test.txt > tmp_file_for_my_slow_tkinter_text_widget.txt
2) view_file = open("tmp_file_for_my_slow_tkinter_text_widget.txt", "r")
3) ...
aber wenn dann der User mehr sehen will als die ersten 100 Zeilen krieg ich wieder ein Problem,
oder man arbeitet dann mit:
split --bytes=1m --verbose test.txt
und laedt dann immer wieder die einzelnen Bloecke, mit leichtem time-lag beim Scrollen ...

oh man, das kann es doch nicht sein.


Gibt es da nicht noch etwas anderes?
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Versuch es mal so: (Vorsicht ungetestet)

Code: Alles auswählen

#!/usr/bin/env python

from ScrolledText import ScrolledText
import Tkinter as tk
import datetime

class MyScrolledText(ScrolledText):

   def __init__(self, master, textfile, rows, columns, cnf={}):
      cnf["width"] = columns
      cnf["height"] = rows
      ScrolledText.__init__(self, master, **cnf)      

      with open(textfile) as view_file:
         self.text = view_file.read()

      self.step = columns
      self.create(0, len(self.text))

   def create(self, first, last):
      self.config(state="normal")
      self.delete(1.0, "end")
      self.insert("end", self.text[first:last])
      self.config(state='disabled')
      
if __name__ == "__main__":
   root = tk.Tk()
   root.option_add('*font',("courier",7,"bold"))

   print(datetime.datetime.now().strftime("%H:%M:%S"))
   
   display = MyScrolledText(root, "test.txt", 50, 80)
   display.pack()
   
   print(datetime.datetime.now().strftime("%H:%M:%S"))
   
   root.mainloop()
Das sollte auf jedenfall Performanter sein, bei create kannst du den Text auch noch mal einschränken, im moment wird so auch der gesamte Text ausgelesen.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Benutzeravatar
ts7343
User
Beiträge: 69
Registriert: Mittwoch 5. Mai 2010, 13:48

Geilomat!

sofortiges Display des ersten Blocks und anschliessend kann der User sehen,
dass etwas passiert und wie der Scrollbar kleiner wird, also genaus so,
wie man es sich vorstellt,

nun hab ich nur noch das alte Problem, dass ich OOP- und Tkinter-maessig
nicht so bewandert bin, das ganze in mein Environment zu verankern,

(Vielleicht erinnerst du dich, das Konstrukt, welches ich hier im orig file benutze
war so aufgesetzt, damit ich ein Optionmenue in run-time aendern kann)

Wie verstricke ich das ganze in meinem original Konstrukt? Kannst du mir da helfen?

Oh mist, ich muss erst mal was essen ....
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Nach außen hin sehe ich eigentlich keinen großen Unterschied, könntest du eventuell etwas detailierter werden, wie du es einbindest bzw es einbinden willst ?
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Benutzeravatar
ts7343
User
Beiträge: 69
Registriert: Mittwoch 5. Mai 2010, 13:48

Hier nochmal etwas erweitert aber doch vereinfacht, wie der File geladen wird,

was mir zum Beispiel nicht klar ist:

1)
Definiere ich eine komplett eigene Klasse fuer das neue Verfahren auf
den File zuzugreifen? Wenn ja, vererbe ich sie dann in meiner Klasse,
z.B. mit: class app_functions(tk.LabelFrame, ScrolledText): ?

1a)
Oder bekomme ich das ganze auch in meine init function hinein?


2)
Wieso hat deine Routine ein vertikal Scrollbar, ich sehe keine Definition dafuer,
oder ist die defaultmaessig mit dabei. Ich braeuchte am Ende noch ein horizontal Scrollbar.

3)
Wie uebergebe ich rows und columns des Textwidgets in meiner eigentlichen Definition,
zur Zeit setze ich ja mit:
self.lsf_view_01.configure(width=110,height=50)
in der urspruenlichen init function, geht es irgendwie dann aehnlich,
dass ich nicht ueber die main function gehen muss?

4)
Was ist dieses Konstrukt:
with open(textfile) as view_file:
self.text = view_file.read()
Ist das irgendwie effizienter als erst den File in den Memory zu lesen (dauert ja nicht lange,
wuerde ich zur Not so lassen.
Wieso kriegst du diesen Effekt dann hin, dass er nach und nach den Inhalt
in das Textwidget schreibt, es gibt keinen loop oder so, wie kann man sich
das vorstellen? Hat das was mit dem step zu tun? Aber das ist doch keine Methode oder
eine Eigenschaft von Textwidget, oder? Das krieg ich nicht so auf die Reihe.


Code: Alles auswählen

#!/usr/bin/env python
import Tkinter as tk
import string, re, os, sys, subprocess, datetime

class app_functions(tk.LabelFrame):

   def __init__(self, master): 
    
#     widget definitions
      tk.LabelFrame.__init__(self, master)        
            
      frame_lsf_view     = tk.LabelFrame(self)
      self.scr_lsf_vview = tk.Scrollbar(frame_lsf_view)
      self.scr_lsf_hview = tk.Scrollbar(frame_lsf_view)
      self.lsf_view_01   = tk.Text(frame_lsf_view, \
                                   yscrollcommand = self.scr_lsf_vview.set, \
                                   xscrollcommand = self.scr_lsf_hview.set  )
      self.scr_lsf_vview.config(command=self.lsf_view_01.yview, orient="vertical")
      self.scr_lsf_hview.config(command=self.lsf_view_01.xview, orient="horizontal")
      button_09          = tk.Button(frame_lsf_view)
#     widget positions
      frame_lsf_view.grid(    row=0, column=0, rowspan=2, columnspan=2)
      self.scr_lsf_vview.grid(row=0, column=1, sticky="N"+"S")
      self.scr_lsf_hview.grid(row=1, column=0, sticky="W"+"E")      
      self.lsf_view_01.grid(  row=0, column=0)
      button_09.grid(         row=2, column=0, padx=2, sticky="W"+"E")
      self.grid(              row=0, column=0, sticky="N"+"S"+"E"+"W") 
#     widget configurations
      self.lsf_view_01.configure(width=110,height=50) 
      button_09.configure(text="get_file",command=self.get_file)           


   def get_file(self):
#     read file in list
      view_file = open("test.txt", "r")
      my_line_list = view_file.read()
      view_file.close()
      print "now i am in memory ..."
#     put list into text widget
      self.lsf_view_01.config(state='normal')
      self.lsf_view_01.delete(0.0,"end")
      for i,line in enumerate(my_line_list):
         self.lsf_view_01.insert('end', line)
         self.lsf_view_01.see('end')
      self.lsf_view_01.config(state='disabled')      

   
if __name__ == "__main__":
   root = tk.Tk()
   root.geometry("800x800")
   root.option_add('*font',("courier",7,"bold"))
   display = app_functions(root)
   root.mainloop()
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

ts7343 hat geschrieben: 1)
Definiere ich eine komplett eigene Klasse fuer das neue Verfahren auf
den File zuzugreifen? Wenn ja, vererbe ich sie dann in meiner Klasse,
z.B. mit: class app_functions(tk.LabelFrame, ScrolledText): ?
Nein, genauer genommen ersetzt das deine Klasse, und vorallem niemals von zwei Tkinter.Widget Klassen ableiten, da sich die Atrribute gegnseitig Überschreiben würden.
zB. ist in Scrolledtext die "width" Angabe für die Anzahl der Columns und nicht wie beim Labelframe in Pixel.
Generell gilt doppelte Ableitungen unter allen Umständen zu vermeiden.
ts7343 hat geschrieben: 1a)
Oder bekomme ich das ganze auch in meine init function hinein?
Wie schon gesagt Brauchst du nicht mehr, du kannst die neue Klasse wie ein normales Widget nutzen, also zB. wie einen Frame.

ts7343 hat geschrieben: 2)
Wieso hat deine Routine ein vertikal Scrollbar, ich sehe keine Definition dafuer,
oder ist die defaultmaessig mit dabei. Ich braeuchte am Ende noch ein horizontal Scrollbar.
Weil ich von ScrolledText abgeleitet habe und wie der Name schon sagt, ist dies eine Erweiterte Textbox, imho braucht eine Textbox keine horizontale Scrollbar, da dies seh unübersichtlich wird.
Wenn du wirklich eine brauchts, müssten entsprechende Funktion noch überladen werden.
Habe ich noch nie gemacht, müsste ich mir mal genauer ansehen wie das beim ScrolledText funktioniert.
ts7343 hat geschrieben: 3)
Wie uebergebe ich rows und columns des Textwidgets in meiner eigentlichen Definition,
zur Zeit setze ich ja mit:
self.lsf_view_01.configure(width=110,height=50)
in der urspruenlichen init function, geht es irgendwie dann aehnlich,
dass ich nicht ueber die main function gehen muss?
main-funktion ?

Du musst diese als Parameter mitliefern wenn du Standardparameter haben möchtest, kannst du diese gerne setzen.
Du müsstest dann in meiner init folgendes schreiben:

Code: Alles auswählen

def __init__(self, master, textfile, rows=50, columns=110, cnf={}):
Denk aber daran, das sind jetzt Zeichen, keine Pixel mehr.
ts7343 hat geschrieben: 4)
Was ist dieses Konstrukt:
with open(textfile) as view_file:
self.text = view_file.read()
Ist das irgendwie effizienter als erst den File in den Memory zu lesen (dauert ja nicht lange,
wuerde ich zur Not so lassen.
Diese Konstruckt, dient dem normalen einlesen seit Python 2.5 für Datein, damit sparst du dir
das aufpassen, ob eine Datei wirklich geschlossen wurde.
Und ja das ist aber nicht wegen dem Aufruf effizienter.
Du hast vorher folgendes gemacht:

Code: Alles auswählen

Hole mir das handle auf die Datei test.txt.
Schreibe jedes Byte einer Zeile in die Variable my_line_list.
Dann gehe jedes Byte in my_line_list durch und zähle in Einserschritten mit.
#Welche du im endeffekt gar nicht benötigst
Dann jedes Byte in die Textbox schreiben
Textbox alle Zeichen aktualisieren lassen.
Also sehr viele unötige Sachen, ich mach nun nur noch

Code: Alles auswählen

Mit open von der Datei test.txt ein handle auf view_file geben.
Alle Bytes in die Variable self.text schreiben.
ts7343 hat geschrieben: Wieso kriegst du diesen Effekt dann hin, dass er nach und nach den Inhalt
in das Textwidget schreibt, es gibt keinen loop oder so, wie kann man sich
das vorstellen? Hat das was mit dem step zu tun? Aber das ist doch keine Methode oder
eine Eigenschaft von Textwidget, oder? Das krieg ich nicht so auf die Reihe.
Oha, ja den step kannst du dir sparen, der ist unötig, da war ich gedanklich noch einen Schritt weiter, der speichert nur die Länge einer Zeile, falls man später eventuell etwas Performanter gestalten müsste und eventuell einzelne Zeilen hinzufügt oder anderes.
Habe mir damit nur ein paar Optionen offen gelassen.

Ok, wie schreib ich nun Sachen in meine Textbox, an folgendes erinnerst du dich noch, oder ?:

Code: Alles auswählen

   self.create(0, len(self.text))

   def create(self, first, last):
      #normal setzen, damit die Box editierbar ist
      self.config(state="normal")
      # Box leeren
      self.delete(1.0, "end")
      #Box füllen
      self.insert("end", self.text[first:last])
      #Box editierung wieder verbieten
      self.config(state='disabled')
Hier lege ich eine Methode an, welche eine bestimmte Zeichenkette in die Textbox schreibt,
oben im __init__, rufe ich create() auf.
Damit fülle ich die Box und zwar über self.text.
self.text enthählt den vorhin ausgelesen Text, richtig.
Strings können wie Listen geteilt werden, so kann ich mit dem first, last sagen von wo bis wo ich den Text angezeigt haben möchte. zB.

Code: Alles auswählen

>>> string = "Hallo Welt"
>>> print(string[0:4])
Hallo
Das brauchst du aber nur wenn es wirklich zu groß wird.
So wird im Moment nur der gesamte text eingelesen, also würde auch einfach "self.insert('end', self.text)" reichen.

Achso und diese Nach-und-nach effekt ist eine Fähigkeit des Text-Widgets, ist aber auch bei der normalen Textbox vorhanden, nur hast du diesen effekt mit deiner aufwendigen Schleife ausgeschalten.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Benutzeravatar
ts7343
User
Beiträge: 69
Registriert: Mittwoch 5. Mai 2010, 13:48

Mist ich steh leider etwas auf dem Schlauch, und das Unvermoegen kommt natuerlich dazu,

ich hab deine Sachen mal versucht zu uebernehmen, jedoch kann ich den Filenamen nicht initialisieren,
da ich ihn erst zur Laufzeit kenne, unten ist der Code,
was ich nun gemacht habe:

1)
die Klassendefinition uebernommen, aber,

1a)
der File soll ja erst in einer Funktion meiner Klasse aufgemacht werden, also quasi
erst in self.get_file()
somit kann ich den Filenamen gar nicht in deine Klasse mit initialisieren, da dieser
noch nicht bekannt ist, also hab ich den Filenamen aus der Argumentliste gestrichen,
also mit: def open_text_file(self,textfile)

1b)
nun kennt er aber nicht die Funktion, wenn ich sie ueber meine app_functions Klasse
anziehe:
def get_file(self):
self.MyScrolledText.open_text_file("test.txt")

es funktioniert auch nicht:
self.open_text_file("test.txt")

und auch nicht:
MyScrolledText.open_text_file("test.txt")




oh man, das ist wieder eine Klasse zu hoch fuer mich, aber so genial performant ...

thanx




Code: Alles auswählen


#!/usr/bin/env python
from ScrolledText import ScrolledText
import Tkinter as tk
import string, re, os, sys, subprocess, datetime

class MyScrolledText(ScrolledText):

   def __init__(self, master, rows, columns, cnf={}):
      cnf["width"] = columns
      cnf["height"] = rows
      ScrolledText.__init__(self, master, **cnf)      
      self.step = columns

   def open_text_file(self,textfile):
      with open(textfile) as view_file:
         self.text = view_file.read()
      self.create(0, len(self.text))

   def create(self, first, last):
      self.config(state="normal")
      self.delete(1.0, "end")
      self.insert("end", self.text[first:last])
      self.config(state='disabled')
      
class app_functions(tk.LabelFrame):

   def __init__(self, master): 

      tk.LabelFrame.__init__(self, master)                    
      frame_lsf_view     = tk.LabelFrame(self)
      self.scr_lsf_vview = tk.Scrollbar(frame_lsf_view)
      self.scr_lsf_hview = tk.Scrollbar(frame_lsf_view)
#     !!!      
      self.lsf_view_01   = MyScrolledText(frame_lsf_view, 50, 80 )
#     !!!      
      button_09          = tk.Button(frame_lsf_view)
      frame_lsf_view.grid(    row=0, column=0, rowspan=2, columnspan=2)
      self.lsf_view_01.grid(  row=0, column=0)
      button_09.grid(         row=2, column=0, padx=2, sticky="W"+"E")
      self.grid(              row=0, column=0, sticky="N"+"S"+"E"+"W") 
      self.lsf_view_01.configure(width=110,height=50) 
      button_09.configure(text="get_file",command=self.get_file)           

   def get_file(self):
# !!!!!!!!!!!!!!!!!!!! this is not working ....
      self.MyScrolledText.open_text_file("test.txt")      
   
if __name__ == "__main__":
   root = tk.Tk()
   root.geometry("800x800")
   root.option_add('*font',("courier",7,"bold"))
   display = app_functions(root)
   root.mainloop()




Benutzeravatar
ts7343
User
Beiträge: 69
Registriert: Mittwoch 5. Mai 2010, 13:48

Ich glaub jetzt hab ich es:

self.lsf_view_01 = MyScrolledText(frame_lsf_view, 50, 80 )
nutzt ja nun die neue Klasse und kennt somit auch deren Methoden,
also self.open_text_file und self.create ....

und somit muss ich die Instanz selber anwenden?
darf man das so sagen?

def get_file(self):
self.lsf_view_01.open_text_file("test.txt")
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Ich sagte doch, deine Klasse brauchst du da nicht mehr. :roll:

Pass auf, meine Klasse ist so angelegt, das die wichtigsten Parameter von außen gehandelt werden müssen.
So das man diese Parameter dann beim Instanzieren festlegt.

Folgendes Beispiel:
Lege dir meine Klasse unverändert in einer Datei als textwidget.py ab.

Nun kannst du sie frei importieren.
Mach folgenden import in deinem Script wo du die Textbox nutzen möchtest.

Code: Alles auswählen

from textwidget import MyScrolledText
Nun kannst du in deinem Script diese Textbox, wie ein normales Widget, heißt also Label, Frame, Button, Text, Spinbox,...
nutzen.
zB.: so hier

Code: Alles auswählen

from textwidget import MyScrolledText
import Tkinter as tk

root = tk.Tk()

frame_left = tk.Frame(root)
frame_left.pack(side="left")

frame_right = tk.Frame(root)
frame_right.pack(side="right")

for _ in xrange(10):
    lb = tk.Label(frame_left, text="Hello World")
    lb.pack()

display = MyScrolledText(frame_right, "test.txt", 30, 30)
display.pack()

bt = tk.Button(frame_right, text="Push me")
bt.pack()

root.mainloop()
Mit anderen Worten, behandle die Klasse wie jede andere von Tkinter.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

ts7343 hat geschrieben:Ich glaub jetzt hab ich es:

self.lsf_view_01 = MyScrolledText(frame_lsf_view, 50, 80 )
nutzt ja nun die neue Klasse und kennt somit auch deren Methoden,
also self.open_text_file und self.create ....

und somit muss ich die Instanz selber anwenden?
darf man das so sagen?

def get_file(self):
self.lsf_view_01.open_text_file("test.txt")
Du machst es dir echt zu schwer, du kannst doch die Klasse Instanzieren wann du willst,
was versuchst du da eigentlich, willst du an den Text heran kommen, oder an den Dateinamen ?
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Benutzeravatar
ts7343
User
Beiträge: 69
Registriert: Mittwoch 5. Mai 2010, 13:48

Hallo Xynon1,

vielen Dank, wie kriegt man eigentlich so ein Know-How wie du es hast? Hast du da
evtl einen Tip fuer mich, wie ich am besten anfangen kann, um in der Lage zu sein,
solche Sachen mal eben aufzusetzen, wie du es mit der Textwidget-Klasse gemacht hast?
Welchen Schritt muesste ich da als erstes tun?

Das mit dem:

Code: Alles auswählen

  from textwidget import MyScrolledText  

werd ich auf alle Faelle so aufsetzen,

wenn du sagst, dass viele Parameter von aussen gehandelt werden muessen,
sind das dann auch solche wie Farben usw? Meine derzeitige configure sieht so aus:
[/code]
self.lsf_view_01.configure(bg="#333333",fg="#FFFFFF",width=110,height=50,cursor="xterm",wrap="none")
self.scr_lsf_vview.configure(bg="#999999",activebackground="#555555",troughcolor="#333333")
self.scr_lsf_hview.configure(bg="#999999",activebackground="#555555",troughcolor="#333333")
[/code]
soll ich dann deine Init-argumentliste dahingehend erweitern, dass ich diese Sachen alle in der Init
uebernehme und dort auch gleich setze?

Wenn es wie ein normales Widget funktioniert, muesste ich mit dem Positioning doch arbeiten koennen,
(zur Zeit noch:

Code: Alles auswählen

 self.lsf_view_01.grid(  row=0, column=0)  
)
werd ich gleich mal probieren.

Nun hab ich natuerlich noch das Problem mit dem horizontal Scrollbar, ich muss teilweise Files mit
130 Spalten darstellen (das ist ein bisschen old-style-text-file, die wurden frueher auf diesen
grossen Druckern ausgedruckt, wo links und rechts solche Transport-Loch-Raender waren).
Wenn ich das Textwidget 135 Spalten breit mache, sprengt es mir das gesamte Gui ...

Kannst du da nochmal gucken, wie man das horizontal Scrollbar einbindet?

Du machst es dir echt zu schwer, du kannst doch die Klasse Instanzieren wann du willst,
was versuchst du da eigentlich, willst du an den Text heran kommen, oder an den Dateinamen ?
Die eigentliche Herangehensweise ist so, dass das Textwidget nicht nur zum darstellen von Files
dient, sondern auch teilweise Terminal output von diversen unix Befehlen, also von verschiedensten
Funktionen kommt eine Liste und dann wird ueber eine andere Funktion nur noch das Textwidget
refreshed und die Liste wieder leer gefegt. Somit kann ich auch nicht jedesmal einen Textfile
an das Widget uebergeben sondern es sind teilweise Listen aus dem stdout die ich ueber
q = subprocess.Popen .... bekomme.
Im Falle der grossen Dateien, wird aus einer Listbox ein File von einem remote Server
ausgewaehlt und ueber scp temporaer rueberkopiert um ihn dann in dem Textwidget zu oeffnen.
Aber wenn das alles so nicht geht, koennte ich mir auch vorstellen, den File ueber ein Top-Level Fenster
aufzumachen, dann kann das alte Textwidget so bleiben wie es ist, aber dann ist das alles nicht mehr so
schoen kompakt ...
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

ts7343 hat geschrieben:vielen Dank, wie kriegt man eigentlich so ein Know-How wie du es hast? Hast du da evtl einen Tip fuer mich, wie ich am besten anfangen kann, um in der Lage zu sein,
solche Sachen mal eben aufzusetzen, wie du es mit der Textwidget-Klasse gemacht hast?
Welchen Schritt muesste ich da als erstes tun?
Drei Sachen:
1. OOP in Python verstehen und nutzen können
2. Die Tkinter API lesen.
3. Tkinters Quelltext ansehen mal etwas genauer ansehen, soviel ist es nicht.
(Ach richtig:- google ist immer dein Freund) :D
ts7343 hat geschrieben: wenn du sagst, dass viele Parameter von aussen gehandelt werden muessen,
sind das dann auch solche wie Farben usw? Meine derzeitige configure sieht so aus:
[/code]
self.lsf_view_01.configure(bg="#333333",fg="#FFFFFF",width=110,height=50,cursor="xterm",wrap="none")
self.scr_lsf_vview.configure(bg="#999999",activebackground="#555555",troughcolor="#333333")
self.scr_lsf_hview.configure(bg="#999999",activebackground="#555555",troughcolor="#333333")
[/code]
soll ich dann deine Init-argumentliste dahingehend erweitern, dass ich diese Sachen alle in der Init
uebernehme und dort auch gleich setze?
Configure kannst du immer bei tkinter.Widgets nutzen, da ich entfernt gesehen nur vom TkinterWidget abgeleitet habe, kannst du configure auch weiter nutzen.

Code: Alles auswählen

display = MyScrolledText(frame_right, "test.txt", 30, 30)
display.pack()

display.configure(bg="#333333",fg="#FFFFFF",width=110,height=50,cursor="xterm",wrap="none")
Alternativ bleibt wie bei jedem tkinter.Widget auch die Parameter Möglichkeit, aber nur die dict Methode, diese ist Prinzipiell bei jedem Widget möglich, wird aber in den Tutorials kaum erwähnt.

Code: Alles auswählen

options = dict(bg="#333333",fg="#FFFFFF",width=110,height=50,cursor="xterm",wrap="none")
display = MyScrolledText(frame_right, "test.txt", 30, 30, [u]options[/u])
display.pack()
ts7343 hat geschrieben:Wenn es wie ein normales Widget funktioniert, muesste ich mit dem Positioning doch arbeiten koennen.
Ja, ist kein Problem.
ts7343 hat geschrieben:Kannst du da nochmal gucken, wie man das horizontal Scrollbar einbindet?
Kann ich machen, dauert nur ein wenig, da ich nach dem Post erstmal noch was machen muss,
ich hoffe so viel Geduld kannst du noch aufwenden. :wink:
ts7343 hat geschrieben: Die eigentliche Herangehensweise ist so, dass das Textwidget nicht nur zum darstellen von Files
dient, sondern auch teilweise Terminal output von diversen unix Befehlen, also von verschiedensten
Funktionen kommt eine Liste
Ist doch kein Problem, sieh dir nochmal genau nach wie ich den Text reinschreibe, nämlich über die create-Methode, und diese nutzt das Klassenattribut self.text.
Also kannst du den Text jederzeit von außen manipulieren und über create die Textbox neu füllen lassen.
Hier wäre es angebracht die MyScrolledText vieleicht um eins zwei Methoden erweitern, welche du für die Textmanipulation benötigst.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

So, habe jetzt wieder etwas Zeit, setzt mich gleich mal ran.

erstmal aber noch ein Hinweis, wenn du Scriptausschnitte postest, dann nutze bitte die Pythontags und nicht die normalen code tags.
Also, [code=python][/code] oder [python][/python], was dann in ersteres umgewandelt wird.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

So dass war es schon, einfacher als ich dachte.
Nur musste ich wieder von Text direkt ableiten, wegen der Horizontalen, sieht also wieder etwas komplexer aus.
Aber ich habe es gleich nochmal "schön" gemacht :D

Sie dir bitte genau an was ich gemacht habe, und frage wenn du etwas nicht verstehst.

Code: Alles auswählen

#!/usr/bin/env python

from datetime import datetime
from Tkconstants import *
import Tkinter as tkinter

class MyScrolledText(tkinter.Text):
   """Ein Text-Widget, mit einer vertikalen und horizontalen Scrollbar.

   @type text: str
   @ivar text: Beinhaltet den Text, welcher beim Aufruf der create-Methode in
               das Text-Widget geschrieben wird.

   @type frame: tkinter.Frame
   @ivar frame: Der Frame, auf welchem das Text-Widget und die Scrollbars
                gepackt werden.

   @type vbar: tkinter.Scrollbar
   @ivar vbar: Die vertikale Scrollbar.

   @type hbar: tkinter.Scrollbar
   @ivar hbar: Die horizontale Scrollbar.
   """

   def __init__(self, master, rows, columns, cnf=()):
      """Initialisiert eine neue Instanz von MyScrolledText.

      @type master: tkinter.Widget
      @param master: Das \xdcbergeordnete Widget, auf welches das Text-Widget
                     gepackt werden soll.

      @type rows: int
      @param rows: Die Anzahl der sichtbaren Zeilen des Text-Widgets.

      @type columns: int
      @param columns: Die Anzahl der sichtbaren Spalten des Text-Widgets.

      @type cnf: dict
      @param cnf: Weitere Optionen, welche an das tkinter.Text Widget weiter
                  gegeben werden sollen.
      """
      self.frame = tkinter.Frame(master)
      
      self.vbar = tkinter.Scrollbar(self.frame)
      self.hbar = tkinter.Scrollbar(self.frame, orient=HORIZONTAL)
      
      self.vbar.pack(side=RIGHT, fill=Y)
      self.hbar.pack(side=BOTTOM, fill=X)
      
      cnf["width"] = columns
      cnf["height"] = rows
      cnf.update({"yscrollcommand": self.vbar.set})
      cnf.update({"xscrollcommand": self.hbar.set})
         
      tkinter.Text.__init__(self, self.frame, cnf)

      self.pack(side=LEFT, fill=BOTH, expand=YES)
      
      self.vbar.configure(command=self.yview)
      self.hbar.configure(command=self.xview)
      
      methods = vars(tkinter.Pack).keys()
      methods += vars(tkinter.Grid).keys()
      methods += vars(tkinter.Place).keys()
      for m in methods:
         if m[0] != '_' and m != 'config' and m != 'configure':
             setattr(self, m, getattr(self.frame, m))
                
      self.text = ""

   def create(self, first=0, last=0):
      """Setzt den entsprechenden Text in das Text-Widget.

      Ben\xf6tigt first und last als Angaben um zu gro\xdfe Texte
      einzuschr\xe4nken.
      Bei keiner Beschr\xe4nkung, wird der gesamte Text angezeigt.

      @type first: int
      @param first: Index des ersten Zeichens von dem aus der Text angezeigt
                    werden soll.

      @type last: int
      @param last: Index des letzten Zeichens welches vom Text angezeigt werden
                   soll.
      """
      self.config(state=NORMAL)
      self.delete(1.0, END)
      
      if first == 0 and last == 0:
         self.insert(END, self.text)
      else:
         self.insert(END, self.text[first:last])
         
      self.config(state=DISABLED)

   def text_from_file(self, textfile):
      """L\xe4dt einen Text aus einer Datei und zeigt diesen an.

      @type textfile: str
      @param textfile: Der Pfad zur Textdatei.
      """
      with open(textfile) as view_file:
         self.text = view_file.read()
      self.create()
      

if __name__ == "__main__":
   root = tkinter.Tk()
   root.option_add("*font", ("courier",7,"bold"))

   print(datetime.now().strftime("%H:%M:%S"))

   text_options = { "bg" : "#333333",
                    "fg" : "#FFFFFF",
                    "cursor" : "xterm",
                    "wrap" : NONE
                  }
   display = MyScrolledText(root, 50, 110, text_options)
   display.text_from_file("test.txt")
   display.pack()

   scrollbar_options = { "bg" : "#999999",
                         "activebackground" : "#555555",
                         "troughcolor" : "#333333"
                       }
   display.vbar.configure(scrollbar_options)
   display.hbar.configure(scrollbar_options)
   
   print(datetime.now().strftime("%H:%M:%S"))
   
   root.mainloop()
Zuletzt geändert von Xynon1 am Montag 22. November 2010, 13:29, insgesamt 1-mal geändert.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Benutzeravatar
ts7343
User
Beiträge: 69
Registriert: Mittwoch 5. Mai 2010, 13:48

Achso, deshalb hat er kein vernuenftiges Syntaxhighliting gehabt, alles klar, mach ich,

Den Code druck ich mir nun aus und nehm ihn quasi auf Papier komplett auseinander,
kann also echt sein, dass ich einige Fragen hab, weil die geplante Autofahrt eine Weile dauert ...

Wenn du Interesse hast, schick ich dir mal einen Screenshot von dem Tool, wenn ich dein Widget eingebaut hab?
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Klar, will doch wissen wo mein Script verwurschtelt wird :).
Schick es mir dann einfach per mail an mich, oder poste es hier, wenn es nicht allzu groß ist.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Benutzeravatar
ts7343
User
Beiträge: 69
Registriert: Mittwoch 5. Mai 2010, 13:48

Hallo,

ich hab mir die Sache recht genau angeschaut und natuerlich einige Fragen, vieles ist mehr eine
Feststellung und da kann man bestimmt mit ja oder nein antworten, ich fang einfach mal an:

1) Die neue Klasse vererbt das Text Widget, und Textwidget und die beiden Scollbars
werden in einen Frame gepackt.

2) Warum hat die init in der Argumentliste rows, columns, und das cnf-dictionary,
koennte man nicht auch die rows und columns ueber das dictionary mitgeben, oder
ist das mehr easy to use fuer die rows und columns?

3) du benutzt hier intern den pack Manager, kann ich trotzdem den Grid Manager
von aussen fuer mich benutzen?

4) warum machst du ein __init__ auf das Text widget, koennte man da nicht auch ein __init__
auf den Frame machen und danach ein Text widget definieren, evtl: self.my_text = Text(self) ?

5) die beiden Zeilen:

Code: Alles auswählen

            
cnf.update({"yscrollcommand": self.vbar.set})
self.vbar.configure(command=self.yview)
sehen irgendwie doppelt gemoppelt aus, du setzt doch in der ersten schon das yscrollcommand,
warum dann noch das configure in der naechsten Zeile?

6) Die method loop sieht aus, als wenn du da alle Layout Manager mit abdecken willst,
aber was genau da passiert versteh ich leider nicht, setzt du da die Location des Frames?
Leider laeuft das ganze auch nicht, er bringt mir hierbei:

Code: Alles auswählen

    
methods = vars(Pack).keys() + vars(Grid).keys() + vars(Place).keys()
NameError: global name 'Pack' is not defined
7)
self.text = "" loeschst du damit erst mal grundsaetzlich den Inhalt? Muss das immer dort
stehen weil du ja in der Funktion create auch hast:
self.delete(1.0, END)
Oder brauch man das ganze, damit fest steht, das der Inhalt ein String ist?

8)
Wenn du in den Kommentaren schreibst: Text anzeigen
Ist damit gemeint, den gesamten Text laden oder nur den Ausschnitt?

9)
in der main hast du: text_options, die setzen width und height auf 110 und 50,
dann setzt du 50 und 80 fuer rows und columns, das versteh ich nicht, welches ist
davon das wirklich benutzte? Und wie passen beide zusammen?

Vielen Dank fuer deine Hilfe!
Benutzeravatar
ts7343
User
Beiträge: 69
Registriert: Mittwoch 5. Mai 2010, 13:48

Inzwischen hab ich einige der Fragen schon etwas geklaert,
z.B.
3) dass ich den Grid Manager weiterhin ohne Probleme benutzen kann

zu 6):
im Originall von ScrolledText importiert er oben Tkinter as ...., Pack, ....
dadurch steht dort unten nur noch Pack, dass hab ich einfach bei mit mit: tkinter.Pack erweitert,
dann kennt er es,


eine interessante Frage in diesem Zusammenhang waere noch, wie man die Tags handelt um evtl Farbe in den Text zu bekommen,
aber da bin ich noch dabei die Methoden auf deine Erweiterung umzustricken,
Antworten