Canvas + Scrollbars + Skalieren

Fragen zu Tkinter.
Daniel_SGe
User
Beiträge: 28
Registriert: Montag 8. September 2008, 19:39

Dienstag 30. September 2008, 21:04

Hallo!

Würde gerne mit dem Canvas-Widget ein paar Linien in einem Koordinatensystem ziehen. Das wird das einzige Fenster sein, aus dem das Programm besteht. Nun hab ich es bereits mit Scrollbars versehen. Dies funktioniert sogar einwandfrei. Mein Problem ist jetzt allerdings, dass das Canvas-Widget immer nur einen bestimmten Teil meines gesamten Fensters ausmacht. Wenn ich also maximiere, sehe ich den gleichen Ausschnitt wie vorher; wie kann ich das ändern?!

Weitere Frage: Gibt es eine Möglichkeit das Bild zu skalieren(eventuell per Mausrad)? Höhe und Breite variieren nämlich , je nach Eingabe stark. Eventuell käme auch in betracht, dass das Bild automatisch so skaliert wird, dass min. 2/3 zu sehen sind, oder so ähnlich..




Gruß

Daniel
Zuletzt geändert von Daniel_SGe am Mittwoch 5. November 2008, 20:07, insgesamt 1-mal geändert.
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

Dienstag 30. September 2008, 22:10

Zunächst einmal solltest du dir den Sternchenimport von Tkinter abgewöhnen.

Als nächstes würde ich empfehlen, sich eher mit dem pack()-Manager als mit dem grid()-Manager zu beschäftigen. Aus meiner Sicht, lassen sich die meisten (einfachen) GUIs (so wie deine) damit einfacher und nachvollziehbarer aufbauen (ist aber auch Geschmacks-/Gewöhnungssache).

Auf jeden Fall ist es nicht empfehlenswert, beides zu mischen, wie du es am Ende mit dem frame tust. Dann auch konsequent bleiben.

Offenbar arbeitest du mit IDLE als IDE, sonst würde sich dein Fenster sofort wieder schließen, denn am Schluss fehlt ein root.mainloop().

Jetzt zu deinem (ersten) Problem:
Du musst - z.B. nach der Instanziierung - die folgenden Zeilen einfügen:

Code: Alles auswählen

root.rowconfigure(0,weight=1)
root.columnconfigure(0,weight=1)
Und statt frame.pack():

Code: Alles auswählen

frame.grid(row=0, column=0,sticky=N+S+E+W)
Daniel_SGe
User
Beiträge: 28
Registriert: Montag 8. September 2008, 19:39

Mittwoch 1. Oktober 2008, 18:55

Zunächst einmal solltest du dir den Sternchenimport von Tkinter abgewöhnen.
Und stattdessen wie vorgehen? Sry, hab erst vor kurzem angefangen mich überhaupt mit Programmieren zu beschäftigen. Ich weiß also noch nich so was man eher "macht" und was eben nicht;) .


Als nächstes würde ich empfehlen, sich eher mit dem pack()-Manager als mit dem grid()-Manager zu beschäftigen. Aus meiner Sicht, lassen sich die meisten (einfachen) GUIs (so wie deine) damit einfacher und nachvollziehbarer aufbauen (ist aber auch Geschmacks-/Gewöhnungssache).

Auf jeden Fall ist es nicht empfehlenswert, beides zu mischen, wie du es am Ende mit dem frame tust. Dann auch konsequent bleiben.
okay, werd ich machen;)
Offenbar arbeitest du mit IDLE als IDE, sonst würde sich dein Fenster sofort wieder schließen, denn am Schluss fehlt ein root.mainloop().
Sorry, hatte vergessen den restlichen code zu pasten;)

Und zu meinem 1. Problem: Ist damit gelöst, vielen Dank :)

//Edit:

Weiteres Problem: Ich hätte gerne 2 Beschriftungen der Y bzw. X-Achse, jeweils jedoch unabhängig vom Scrollen der X bzw. Y-Achse. Muss ich dafür eine neue Instanz "Canvas" ausführen? Ich möchte schließlich 2 Fenster neben den Scrollbalken, die unabhängig davon reagieren...
Leonidas
Administrator
Beiträge: 16024
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Donnerstag 2. Oktober 2008, 15:16

Daniel_SGe hat geschrieben:
Zunächst einmal solltest du dir den Sternchenimport von Tkinter abgewöhnen.
Und stattdessen wie vorgehen? Sry, hab erst vor kurzem angefangen mich überhaupt mit Programmieren zu beschäftigen. Ich weiß also noch nich so was man eher "macht" und was eben nicht;) .

Code: Alles auswählen

import Tkinter as tk
Und dann vor alle Namen aus Tkinter ein ``tk.`` davorsetzen.
My god, it's full of CARs! | Leonidasvoice vs Modvoice
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

Donnerstag 2. Oktober 2008, 15:23

Daniel_SGe hat geschrieben:Weiteres Problem: Ich hätte gerne 2 Beschriftungen der Y bzw. X-Achse, jeweils jedoch unabhängig vom Scrollen der X bzw. Y-Achse. Muss ich dafür eine neue Instanz "Canvas" ausführen? Ich möchte schließlich 2 Fenster neben den Scrollbalken, die unabhängig davon reagieren...
Verstehe ich nicht. :(
Daniel_SGe
User
Beiträge: 28
Registriert: Montag 8. September 2008, 19:39

Donnerstag 2. Oktober 2008, 15:55

Gut. Also nochmal;)

Stell dir mal ein Koordinatensystem aus mehreren Tausenden Pixeln in sowohl x als auch y-Richtung vor (nur 1. Quadrant). Das ganze passt natürlich nicht auf den Bildschirm. Zur besseren Orientierung sind sowohl x als auch y-Achse beschriftet. Wenn ich jetzt allerdings scrolle, "verschwinden" diese Linien ja, da sie am Rand angebracht waren.

Viel sinnvoller wäre es jedoch, wenn ich die Achsnebeschriftungen immer sehen könnte. Dafür müsste aber die Y-Achse wahrscheinlich unabhängig vom x-scrollbalken machen und die y-achse unabhängig vom x-scrollbalken. Meine Frage ist, ob ich dafür zwingend 2 neue canvas-Instanzen (-Fenster oder was auch immer) benötige, und 2. eventuell einen Lösungstipp/Ansatz, wie ich diese Instanzen dann schön in das Fenster unterbringen kann.
Benutzeravatar
wuf
User
Beiträge: 1483
Registriert: Sonntag 8. Juni 2003, 09:50

Donnerstag 2. Oktober 2008, 16:01

Hallo Daniel_SGe

Ich nehme an du suchst die Canvas-Option 'scrollregion'

Gruss wuf :wink:
Take it easy Mates!
Daniel_SGe
User
Beiträge: 28
Registriert: Montag 8. September 2008, 19:39

Donnerstag 2. Oktober 2008, 16:37

hmm...

Was ich vielleicht noch erwähnen sollte, ist dass ich so´päter in Meinem code noch

Code: Alles auswählen

cv.config(scrollregion=cv.bbox(ALL))
verwende um den Scrollbereich anzupassen... Gibt es irgendwelche Optionen dafür, dass ich erst ab bestimmten x bzw. y-Werten den Bereich zählen kann?
Benutzeravatar
wuf
User
Beiträge: 1483
Registriert: Sonntag 8. Juni 2003, 09:50

Donnerstag 2. Oktober 2008, 17:48

Hallo Daniel_SGe

Sorry habe das ganze falsch interpretiert. Ja es sieht fast so aus, dass du zwei weitere Canvas Instanzen brauchst um die vertikale und horizontale Skalabeschriftung darzustellen. Dann sind die beiden Skalen-Canvas mit den zugehörigen Scrollbars zu synchronisieren. Also die vertikale Sakal-Canvas mit der vertikalen Scrollbar und die horizontale Skala-Canvas mit der horizontalen Scrollbar. Vorab ist zu sagen, dass es bei Tkinter nicht möglich ist vertikal gedrehter Text anzuzeigen!

Wenn das letztere wichtig ist musst du dies noch abklären bevor du beabsichtigst mit dem Einsatz von Tkinter als GUI weiter zu arbeiten.

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

Donnerstag 2. Oktober 2008, 17:48

Nochmal zum Verständnis, damit ich nicht in die falsche Richtung weiterdenke:

Beim Starten hast du ein Canvas mit links und unten einer beschrifteten Achse, z.B. von 0 .. 400. Dann verschiebst du mit den Scrollbalken den sichtbaren Ausschnitt des Canvas und die Anzeige auf den Achsen ändert sich mit, so dass z.B. die x-Achse irgendwann die Werte von 500 .. 900 und die y-Achse die von 1200 .. 1600 anzeigt.

Richtig?

Wie soll sich das Koordinatensystem verhalten, wenn der Anwender die gesamte Fenstergröße ändert? Oder hat das Canvas ein fixe Größe (oben also 400x400 px), die unverändert bleibt?
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

Donnerstag 2. Oktober 2008, 17:51

wuf hat geschrieben:Vorab ist zu sagen, dass es bei Tkinter nicht möglich ist vertikal gedrehter Text anzuzeigen!
Klar geht das: Du musst nur allen benötigten Text mit einem geeigneten Programm als GIF-Grafik erstellen und diese dann im richtigen Moment anzeigen .. :wink:
Benutzeravatar
wuf
User
Beiträge: 1483
Registriert: Sonntag 8. Juni 2003, 09:50

Donnerstag 2. Oktober 2008, 17:58

Hallo numerix

Ist schon klar habe das auch schon gemacht. Das heisst aber, dass du für jede Beschriftung ein .gif auf Lager haben musst. Ich wollte nur andeuten das es bei Tkinter kein vertikaler Text gibt der einfach mit Stringmanipulationen veränderbar ist!

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

Donnerstag 2. Oktober 2008, 18:02

wuf hat geschrieben:Hallo numerix

Ist schon klar habe das auch schon gemacht. Das heisst aber, dass du für jede Beschriftung ein .gif auf Lager haben musst. Ich wollte nur andeuten das es bei Tkinter kein vertikaler Text gibt der einfach mit Stringmanipulationen veränderbar ist!

Gruss wuf :wink:
Das war auch nicht ganz ernst gemeint. :)

Natürlich hast du Recht und das ist schade. Das ging doch schon vor 20 Jahren mit TurboPascal ...
Daniel_SGe
User
Beiträge: 28
Registriert: Montag 8. September 2008, 19:39

Donnerstag 2. Oktober 2008, 18:10

Zum Thema vertikaler Text. Schade, dass das nicht möglich ist, aber auch nicht zwingend notwendig;)
Beim Starten hast du ein Canvas mit links und unten einer beschrifteten Achse, z.B. von 0 .. 400. Dann verschiebst du mit den Scrollbalken den sichtbaren Ausschnitt des Canvas und die Anzeige auf den Achsen ändert sich mit, so dass z.B. die x-Achse irgendwann die Werte von 500 .. 900 und die y-Achse die von 1200 .. 1600 anzeigt.
Genau. Wenn ich also diesen Ausschnitt [500, 900; 1200, 1600] "sehe" sollten x und y-achse entsprechen 500-1200 bzw. 900-1600 anzeigen.

Das Canvas sollte im Prinzip immer den gesamten Fenster- bzw. Frameinhalt ausfüllen. Andere Widgets, wie ein Menü oder Buttons, würden dann eher festen Fensterinhalt bekommen...
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

Donnerstag 2. Oktober 2008, 20:58

Ich würde es bei einem Canvas belassen und bei jeder Änderung des Fensterausschnittes - sei des durch Scrollen oder Größenänderung des Fensters die entsprechenden Achsen neu zeichnen bzw. beschriften.

Konkret sieht das dann so aus:

Code: Alles auswählen

import Tkinter as tk

def create_axes(arg=None):
    w,h = canv.winfo_width(),canv.winfo_height()
    x0, y0 = int(canv.canvasx(0))+40, int(canv.canvasy(0))+25
    x1, y1 = x0+w-45, y0+h-30
    startx = x0//50*50+50
    starty = y0//50*50+50
    # create x-axis
    canv.delete("xaxis")
    canv.create_line(x0,y0,x1,y0,arrow=tk.LAST,width=2,tags="xaxis")
    for x in xrange(startx,x1,50):
        canv.create_line(x,y0-4,x,y0+4,tags="xaxis")
        canv.create_text(x,y0-4,anchor=tk.S,text=str(x),tags="xaxis")
    # create y-axis
    canv.delete("yaxis")
    canv.create_line(x0,y0,x0,y1,arrow=tk.LAST,width=2,tags="yaxis")
    for y in xrange(starty,y1,50):
        canv.create_line(x0-4,y,x0+4,y,tags="yaxis")
        canv.create_text(x0-6,y,anchor=tk.E,text=str(y),tags="yaxis")

def xmove(*args):
    canv.xview(*args)
    create_axes()

def ymove(*args):
    canv.yview(*args)
    create_axes()

# -----------------
root = tk.Tk()
root.title = "Canvas with Scrollbars and Axis"
root.minsize(400,300)
root.bind("<Configure>",create_axes)
frame = tk.Frame(root)
frame.pack(side=tk.LEFT,fill=tk.BOTH,expand=True)
canv = tk.Canvas(frame,bg="white")
canv.pack(side=tk.TOP,fill=tk.BOTH,expand=True)
scroll_y = tk.Scrollbar(root,command=ymove)
scroll_y.pack(side=tk.LEFT,fill=tk.Y)
scroll_x = tk.Scrollbar(frame,command=xmove,orient=tk.HORIZONTAL)
scroll_x.pack(side=tk.BOTTOM,fill=tk.X)
canv.config(scrollregion=(0,0,20000,20000),width=400,height=300,
            yscrollcommand=scroll_y.set,xscrollcommand=scroll_x.set)
create_axes()
canv.create_oval(140,120,200,180,fill="yellow",outline="darkgreen",width=4)
root.mainloop()
Ob das mit den Abständen zum Rand so passt, hängt von der verwendeten Schriftart ab. Bei meinem Default-Font passt es für die Achsenbeschriftung.

Ich habe die Achsen jetzt mal so orientiert wie das Canvas-Koordinatensystem. Sollen die Achsen entsprechend einem kartesischen System sein, müsste man das anpassen.

Ineffizient ist bei meiner Variante, dass jeweils beide Achsen samt Beschriftung neu gezeichnet werden. Falls das zu langsam ist, könnte man die Performance dadurch erhöhen, dass man z.B. bei horizontalem Scrollen die y-Achse nicht jeweils löscht und neu zeichnen lässt, sondern mittels move(tag) nur verschiebt.
Für den Fall ist die Unterscheidung zwischen den tags "xaxis" und "yaxis" notwendig, für den vorliegenden Code hätte auch ein gemeines tag genügt.
Antworten