Canvas feste Grösse geben

Fragen zu Tkinter.
Pü-Ton
User
Beiträge: 67
Registriert: Donnerstag 8. Mai 2008, 07:52

Hallo@all,

ich komm einfach nicht weiter...

Ich lese aus einem XML-Flie Daten aus, die dann untereinander in einem Canvas aufgelistet werden.
Ich habe jetzt auch schon eine scrollbar eingefügt, weil ich will, dass das Canvas die feste Grösse behält, selbst wenn mehr Elemente aufgelistet werden, die in das Canvas "passen". Dann soll eben die Scrollbar in action treten.

Ich verwende grid und tkinter.

Das Problem ist, dass sich das Canvas immer der Anzahl und damit dem benötigten Platz der XML-Elemente anpasst. Kann man das verhindern?

Es soll quasi so sein, wie dieses Eingabefenster hier, in das ich gerade schreibe. Sobald der Text grösser wird, wie das Fenster, erscheint die Scrollbar aber das Fenster bleibt in seiner grösse.

Hier etwas Code:

Code: Alles auswählen

    Window1 = Label(roots)
    Window1.config(background="#CCCCCC", relief="groove")
    Window1.grid(sticky=E, row=6, column=0)
   

    Row6 = Canvas(Window1, height=517, width=590)
    Row6.config(background="#CCCCCC")
    Row6.grid(row=1, sticky=N)

    wScrollV = Scrollbar(Window1, orient=VERTICAL, command=Row6.yview)
    wScrollV.grid(sticky=E+N+S, row=1, column=0)

    update_scrollregion()

...

    for elems in variables.findall("Bla"):
        for elem in elems.findall("Blub"):
            #print elem.text
            i = i+1
            DummyVar = Label(Row6, text=elem.text, bg="#CCCCCC").grid(row=i, column=0, sticky=W+N, padx=5, pady=5)
Also Row6 ist das Canvas innerhalb vom Frame Window1, dass wiederum in der GUI eingebettet ist...

--> Row6 soll seine Grösse nicht verändern, egal wieviel über die for-Schleife aus dem XML ausgelesen wird...

- Liegt das am grid?
- gibt es einen Befehl für das festlegen der Canvasgrösse?

Danke
Benutzeravatar
HWK
User
Beiträge: 1295
Registriert: Mittwoch 7. Juni 2006, 20:44

Das dürfte wohl am Grid liegen. Probier doch einmal

Code: Alles auswählen

Window1.rowconfigure(6, minsize=590)
Da hierbei die Option weight nicht benutzt wird, sollte die Zeile sich nicht ausdehnen (ungetestet).
MfG
HWK
Pü-Ton
User
Beiträge: 67
Registriert: Donnerstag 8. Mai 2008, 07:52

hmmm, das ist gut soweit, nur bräucht ich eher was wie maxsize=
...
Benutzeravatar
HWK
User
Beiträge: 1295
Registriert: Mittwoch 7. Juni 2006, 20:44

Übrigens: Wenn Du das Canvas nur für Text benutzt, wäre dann ein ScrolledText-Widget nicht sinnvoller?
Und: Der Effekt von Zeile 22 auf den Inhalt von DummyVar (PEP8: dummy_var) ist Dir hoffentlich klar.
MfG
HWK
Pü-Ton
User
Beiträge: 67
Registriert: Donnerstag 8. Mai 2008, 07:52

Es ist so, dass aus dem XML zum einen Die Namen ausgelesen werden - das sind wirklich nur Texte und daneben (rechts davon) kommen Entrys hin, in denen ein defaultwert steht. Ich kann also kein reines Textwidget nehmen...

Was genau meinst Du mit dem Ihnalt von DummyVar?
Hab ich was übersehen? :?: :?: :?:
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

Mir ist noch nicht so recht klar, was genau du vorhast und warum du dafür ein Canvas-Widget brauchst.

Hilfreich wäre es auch, wenn man nicht nur ein Code-Fragment, sondern ein für sich lauffähiges Stück Code hätte.
Pü-Ton
User
Beiträge: 67
Registriert: Donnerstag 8. Mai 2008, 07:52

Hi pütone,
einen ausführbaren Code kann ich hier nicht posten... ist zu verschachtelt...

Im Grunde gehts wirklich "nur" darum, dass ich ein Canvas-Fenster in seinen Abmessungen fest zu begrenzen, auch wenn ich Daten (Text und Entrys) hineinschreiben lasse.

Es gibt also ein Fenster, in diesem Fenster sind (von oben nach unten) ein Frame, ein Canvas und ein Frame eingebunden.

In das Canvas kommen jetzt die Daten aus einem XML-File. Dieses Canvas soll seine äusseren Masse nicht verändern.
Wenn es mehr Daten werden, wie in die Abmessungen hineinpassen, soll nur über einen scrollbalken der untere (nicht sichtbare) Teil anzeigbar werden.
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

Normalerweise ist das kein Problem.
Guckst du hier (das mit dem "global" bitte nicht nachmachen!):

Code: Alles auswählen

import Tkinter as tk
from random import randrange

def write_more():
    global y
    y += 20
    canv.create_text(20,y,anchor=tk.NW,text=str(randrange(100000,1000000)))

root = tk.Tk()
y = 0
frame_top = tk.Frame(root)
frame_bottom = tk.Frame(root)
button = tk.Button(frame_bottom,text="Schreib mehr",command=write_more)
canv = tk.Canvas(root,width=160,height=400,bg="white")
frame_top.pack()
canv.pack()
frame_bottom.pack()
button.pack()
root.mainloop()
Insofern ist es schwer, dir zu helfen.
Pü-Ton
User
Beiträge: 67
Registriert: Donnerstag 8. Mai 2008, 07:52

Das ist gar nicht so schlecht, jedoch verwende ich grid statt pack und darin liegt wohl auch das Problem. Ich habe hier von Michael Schneider was gefunden, dass mich ein gutes Stück weitergebracht hat:

--> Der ist ausführbar, dann seht ihr in etwa wie das aussehen soll !!!

Code: Alles auswählen

import Tkinter as TK
import Canvas

tk = TK.Tk()
yscroll = TK.Scrollbar(tk, orient = TK.VERTICAL)
yscroll.grid(row = 1, column = 2, sticky = TK.NS)

xscroll = TK.Scrollbar(tk, orient = TK.HORIZONTAL)
xscroll.grid(row = 2, column = 1, sticky = TK.EW)

canvas = TK.Canvas(tk,
                   width = 200,
                   height = 200,                       
                   scrollregion = "0 0 400 400",        ##  Scrollregion im Bereich 0,0 - 400, 400
                   yscrollcommand = yscroll.set,        ##  Canvashoehe an yscroll uebergeben
                   xscrollcommand = xscroll.set)        ##  Canvasbreite an xscroll uebergeben
canvas.grid(row = 1, column = 1)

rect = Canvas.Rectangle(canvas, (40, 40), (280, 380), fill="#9999ff")   ##  um was zu sehen

yscroll.config(command = canvas.yview)                  ##  yscrollwerte an canvas uebergeben
xscroll.config(command = canvas.xview)                  ##  xscrollwerte an canvas uebergeben

tk.mainloop()
Jetzt liegt mein Problem nur noch daran, dass ich in das Lila-Fenster meine Daten reinschreiben will.

Also in rect !

Wenn ich das versuche kommt der Fehler:

Code: Alles auswählen

Rectangle instance has no attribute 'tk'
mein code:

Code: Alles auswählen

from Tkinter import*
from xml.dom.minidom import*
import xml.etree.ElementTree as et
import Tkinter as TK
import Canvas

    yscroll = TK.Scrollbar(Window1, orient=TK.VERTICAL)
    yscroll.grid(row=1, column=0, sticky=TK.E+N+S)

    canvas = TK.Canvas(Window1,
                       width=560,
                       height=516,
                       scrollregion="0 0 1500 1500",      ##  Scrollregion im Bereich 0,0 - 400, 400
                       bg="#CCCCCC",
                       yscrollcommand=yscroll.set)        ##  Canvashoehe an yscroll uebergeben

    canvas.grid(row=1, sticky=W, padx=5)

    rect = Canvas.Rectangle(canvas, (0, 0), (560, 1499), fill="#990000")   

#    rect.minsize=(500, 520)
#    rect.maxsize=(1499, 560)
    yscroll.config(command=canvas.yview)

    for elems in variables.findall("Bla"):
        for elem in elems.findall("Blubb"):
            #print elem.text
            i = i+1
            DummyVar = Label(rect, text=elem.text, bg="#CCCCCC").grid(row=i, column=0, sticky=W+N, padx=5, pady=5)
"Window1" ist ein Frame, in dem das ganze drin steckt.

Was kann ich tun, damit ich nicht Rectangle (was ja kein Attribut tk hat) benutzen muss, sondern etwas equivalentes, benutzbares ???

Hat jm eine Idee ???
Benutzeravatar
HWK
User
Beiträge: 1295
Registriert: Mittwoch 7. Juni 2006, 20:44

Pü-Ton hat geschrieben:Was genau meinst Du mit dem Ihnalt von DummyVar?
Hab ich was übersehen? :?: :?: :?:
DummyVar enthält den Rückgabewert von Label(...).grid(...). Ich glaube nicht, dass Du den brauchst. Du willst doch wahrscheinlich den Rückgabewert von Label(...) speichern. Wozu brauchst Du denn DummyVar? Der PEP8-konforme Name wäre übrigens dummy_var.
Pü-Ton hat geschrieben:Was kann ich tun, damit ich nicht Rectangle (was ja kein Attribut tk hat) benutzen muss, sondern etwas equivalentes, benutzbares ???
Ich denke, Canvas ist nicht das richtige Widget. Nimm doch einfach einen Frame und speichere da alles drin ab. In diesem Frame verwendest Du einen neuen Frame statt des Rectangles.
MfG
HWK
Pü-Ton
User
Beiträge: 67
Registriert: Donnerstag 8. Mai 2008, 07:52

...ursprünglich wollte ich ja auch ein Frame nehmen, jedoch kann ich das doch nicht über einen scrollbalken bewegen...oder? Dachte das geht nur mit Canvas und Text-widges ???!!!

Es stimmt, dass ich nur den Rückgabewert von Label haben will, aber ich muss den doch einer Variable ---> DummyVar übergeben oder nicht?

Wie ist denn dann die Syntax ??

Code: Alles auswählen

    for elems in variables.findall("Bla"):
        for elem in elems.findall("Blubb"):
            #print elem.text
            i = i+1
            DummyVar = Label(rect, text=elem.text, bg="#CCCCCC").grid(row=i, column=0, sticky=W+N, padx=5, pady=5)

            Return Label
Oder lautet die DummyVar-Zeile dann ganz anders?

--> dies ist mir ein noch unbekanntes Gebiet <-- aber Danke für Deine Hilfe...
Benutzeravatar
HWK
User
Beiträge: 1295
Registriert: Mittwoch 7. Juni 2006, 20:44

Code: Alles auswählen

dummy_var = tk.Label(rect, text=elem.text, bg="#CCCCCC")
dummy_var.grid(row=i, column=0, sticky=tk.W+tk.N, padx=5, pady=5)
MfG
HWK
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

Du brauchst überhaupt keine Variable, wenn du später nicht noch einmal darauf zugreifen willst:

Code: Alles auswählen

tk.Label(rect, text=elem.text, bg="#CCCCCC").grid(row=i, column=0, sticky=tk.W+tk.N, padx=5, pady=5)
Pü-Ton
User
Beiträge: 67
Registriert: Donnerstag 8. Mai 2008, 07:52

o-ha, ja klar, jetzt weiss ich auch was Du gemeint hast...

Manchmal sieht man halt vor lauter Zeilen den Monitor nicht mehr :))))

Also gut, soweit gefixt, nur leider bleibt das Problem mit dem Scrollbalken an einem Canvas mit Inhalt...

trotzdem danke...
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

Pü-Ton hat geschrieben:Hat jm eine Idee ???
Also, wenn ich deinen import-Block sehe, dann gruselt es mich.

Meine Idee: Lies erst mal etwas zu den Grundlagen von Tkinter. Wenn dir die Grundlagen klar(er) sind, dann wird dein Code vermutlich auch klarer und die Lösung deines Problems wird sich dann auch finden.

Empfehlen könnte ich: http://www.ferg.org/thinking_in_tkinter ... grams.html

Hat zwar ein sch*-Layout, ein gründliches Durcharbeiten kann aber sehr hilfreich sein.
Benutzeravatar
HWK
User
Beiträge: 1295
Registriert: Mittwoch 7. Juni 2006, 20:44

Normale Frames kann man wirklich nicht scrollen. Wie wäre es dann aber z.B. mit ScrolledWindow in Tix: http://tix.sourceforge.net/
MfG
HWK
Benutzeravatar
Michael Schneider
User
Beiträge: 569
Registriert: Samstag 8. April 2006, 12:31
Wohnort: Brandenburg

Pü-Ton hat geschrieben:Es ist so, dass aus dem XML zum einen Die Namen ausgelesen werden - das sind wirklich nur Texte und daneben (rechts davon) kommen Entrys hin, in denen ein defaultwert steht. Ich kann also kein reines Textwidget nehmen...
Hi Pü-Ton!

Danke, dass Du mich mal aus meinem Winterschlaf geweckt hast. :-)

Da traust Du dem Text-Widget aber ganz schön wenig zu. Dabei ist es mindestens genauso vielseitig wie ein Canvas. Du kannst nämlich auch in Text-Widgets beliebige andere Tkinter-Widgets über die Methode window_create einbetten. Hier mal ein minimalistisches Beispiel:

Code: Alles auswählen

import Tkinter as tk
root = tk.Tk()

# Textwidget statt Canvas, das kann mehr als man denkt
text = tk.Text(root, width=60, height=20)
text.grid(row=2, column=1)

# nur die Scrollbar einfuegen
scroll_y = tk.Scrollbar(root, orient=tk.VERTICAL, command=text.yview)
scroll_y.grid(row=2, column=2, sticky=tk.NS)

# xml-Datendict und variablen-Zwischenspeicher initialisieren
xml_dict = {"Vorname":"Michael", "Nachname":"Ballack", "Position":"Mittelfeld"}
variable_dict = {}

# xml-Daten einfuellen, pro Eintrag eine tk-Variable definieren und ein
#   Entry einfuegen
for tag_name, default_value in xml_dict.iteritems():
    text.insert(tk.END, "%s: "%tag_name)
    tk_var = tk.StringVar()
    tk_var.set(default_value)
    variable_dict[tag_name] = tk_var
    text.window_create(tk.END, window=tk.Entry(text, width=20, textvariable=tk_var))
    text.insert(tk.END, "\n")
    
# Scrollbalken verknuepfen und Textaenderungen unterbinden
text.config(yscrollcommand=scroll_y.set, state=tk.DISABLED)
root.mainloop()

# Ergebnisdict aus den xml-Schluesseln und den tk-Variablen ermitteln
result_dict = dict([(tag, variable_dict[tag].get()) for tag in xml_dict])
print result_dict
Meiner Erfahrung nach ändert sich die Größe eines Canvas beim Gridden NICHT! Vielleicht liegt es daran, dass Du ein Label-Widget als Container (Window1) missbrauchst? Im Zweifelsfall würde ich immer auf die Standard-Container Frame und Toplevel zurückgreifen.
Das Rectangle ist kein Tkinter-Widget, sondern ein Objekt das ein grafisches Element eines Canvas darstellt und es ist nicht als Container für andere Tkinter-Widgets geeignet.
Bei Verwendung des Canvas musst Du beachten, dass Du regelmäßig die Ressource "scrollregion" updatest, die dem Canvas sagt, über welchen Bereich er scrollen soll.

Code: Alles auswählen

canvas.config(scrollregion=canvas.bbox("all"))
Ich schlage vor, dass Du es erstmal mit Text-Widget und eingebetteten Entries versuchst. Wenn es unbedingt ein Canvas sein muss, kriegen wir das auch noch gebacken. Dafür brauche ich aber minimalen funktionierenden Code, bei dem sich die Größe des Canvas verändert.

Viele Grüße,
Michael
Diese Nachricht zersört sich in 5 Sekunden selbst ...
Pü-Ton
User
Beiträge: 67
Registriert: Donnerstag 8. Mai 2008, 07:52

ich habe es jetzt endlich hinbekommen, so wie ich es wollte.

1000 dank an alle Beteiligten.

Zur Lösung bin ich über das create_window gekommen. In ein Canvas kann somit ein Window gesetzt werden, in welches wiederum alle beliebigen widgets eingebaut werden können.

Ich habe also ein Hauptframe, dass das grösste ist.
Da rein baue ich ein scrollbares Canvas, dass wiederum ein gleichgrosses Window hat, in welches ich jetzt die Daten, die ich aus einem XML lese, hineinschreiben lasse, jeweils mit Abstand...

Das war eine schwere Geburt, aber es funzt super.
Bestimmt gibts noch andere Lösungen dafür.

Hier mal der Code, falls jm mal was ähnliches vor hat:

Code: Alles auswählen

from Tkinter import*
import Tkinter as TK
import Canvas

tk = TK.Tk()
yscroll = TK.Scrollbar(tk, orient = TK.VERTICAL)
yscroll.grid(row = 1, column = 2, sticky = TK.NS)


canvas = TK.Canvas(tk,
                   width = 300,
                   height = 300,
                   scrollregion = "0 0 400 400",        ##  Scrollregion im Bereich 0,0 - 400, 400
                   yscrollcommand = yscroll.set,        ##  Canvashoehe an yscroll uebergeben
                   bg="#9999ff")
canvas.grid(row = 1, column = 1)


yscroll.config(command=canvas.yview)                  ##  yscrollwerte an canvas uebergeben

n=0
for i in range(30):
    n = n+25
    Dummy_var=Label(canvas,text="tadaaaa", font=("Arial",12, "bold"), anchor=CENTER, fg="#FFFFFF", bg="#990000", width=20)
    canvas.create_window(45,15+n,window=Dummy_var, anchor=W)


tk.mainloop()
Das gute daran ist jetzt halt, dass die Daten, die mehr Platz einnehmen als angezeigt werden können, erst dann zum Vorschein kommen, wenn gescrollt wird. Bei meinen ganzen Versuchen davor wurde das Fenster immer gleich so gross, wieviel Einträge da waren !!!

mfG Pü-Ton
Benutzeravatar
Michael Schneider
User
Beiträge: 569
Registriert: Samstag 8. April 2006, 12:31
Wohnort: Brandenburg

Hi Pü-Ton!
Pü-Ton hat geschrieben:

Code: Alles auswählen

                   scrollregion = "0 0 400 400",        ##  Scrollregion im Bereich 0,0 - 400, 400
Das geht so lange gut, bis Deine Widgets tiefer als y=400 plaziert werden, während Du auch dann über 400 pixel scrollen kannst/musst, wenn sich weniger Widgets im Canvas befinden. Bitte berücksichtige daher die letzte Empfehlung meines letzten Posts weiter oben.
Pü-Ton hat geschrieben: Bei meinen ganzen Versuchen davor wurde das Fenster immer gleich so gross, wieviel Einträge da waren !!!
Das ist die meist gewünschte und manchmal verhasste Eigenschaft des grid-Managers.

[edit]Passage entfernt, da 'maxsize' kein gültiger Schlüsselwortparameter für rowconfigure/columnconfigure ist [/edit]

Grüße,
Michael
Zuletzt geändert von Michael Schneider am Freitag 27. Juni 2008, 06:58, insgesamt 2-mal geändert.
Diese Nachricht zersört sich in 5 Sekunden selbst ...
Pü-Ton
User
Beiträge: 67
Registriert: Donnerstag 8. Mai 2008, 07:52

hmmm,

wenn ich das

Code: Alles auswählen

canvas.config(scrollregion=canvas.bbox("all"))
verwende, ist die Scrollbar zwar da, sieht aber ausgegraut aus und der eigentliche Schieber fehlt !!!
Antworten