Seite 1 von 2

Canvas + Scrollbars + Skalieren

Verfasst: Dienstag 30. September 2008, 21:04
von Daniel_SGe
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

Verfasst: Dienstag 30. September 2008, 22:10
von numerix
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)

Verfasst: Mittwoch 1. Oktober 2008, 18:55
von Daniel_SGe
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...

Verfasst: Donnerstag 2. Oktober 2008, 15:16
von Leonidas
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.

Verfasst: Donnerstag 2. Oktober 2008, 15:23
von numerix
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. :(

Verfasst: Donnerstag 2. Oktober 2008, 15:55
von Daniel_SGe
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.

Verfasst: Donnerstag 2. Oktober 2008, 16:01
von wuf
Hallo Daniel_SGe

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

Gruss wuf :wink:

Verfasst: Donnerstag 2. Oktober 2008, 16:37
von Daniel_SGe
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?

Verfasst: Donnerstag 2. Oktober 2008, 17:48
von wuf
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:

Verfasst: Donnerstag 2. Oktober 2008, 17:48
von numerix
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?

Verfasst: Donnerstag 2. Oktober 2008, 17:51
von numerix
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:

Verfasst: Donnerstag 2. Oktober 2008, 17:58
von wuf
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:

Verfasst: Donnerstag 2. Oktober 2008, 18:02
von numerix
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 ...

Verfasst: Donnerstag 2. Oktober 2008, 18:10
von Daniel_SGe
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...

Verfasst: Donnerstag 2. Oktober 2008, 20:58
von numerix
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.

Verfasst: Donnerstag 2. Oktober 2008, 21:54
von Daniel_SGe
Hi!

Der Ansatz sieht schonmal ganz nett aus.
Du hast ja jetzt pack verwendet. Ich überlege, ob ich nicht vll. doch komplett auf pack umsteigen soll. Dann müsstest du mir eventuell allerdings noch sagen, wie ich einen Befehl wie

Code: Alles auswählen

cv.config(scrollregion=cv.bbox(ALL))
hinbekomme. Das Fenster muss sich nämlich dynamisch an eine Eingabe anpassen können.

Verfasst: Donnerstag 2. Oktober 2008, 22:18
von numerix
Daniel_SGe hat geschrieben:Dann müsstest du mir eventuell allerdings noch sagen, wie ich einen Befehl wie

Code: Alles auswählen

cv.config(scrollregion=cv.bbox(ALL))
hinbekomme.
Wo sollte da das Problem sein?
Daniel_SGe hat geschrieben:Das Fenster muss sich nämlich dynamisch an eine Eingabe anpassen können.
Inwiefern: Soll der sichtbare Ausschnitt dann in den Bereich wechseln, wo gerade ein neues Item eingefügt wurde?

Verfasst: Freitag 3. Oktober 2008, 04:48
von Daniel_SGe
Wo sollte da das Problem sein?
Ich bekomm die Fehlermeldung:

cv.config(scrollregion=cv.bbox(ALL))
NameError: name 'ALL' is not defined
Inwiefern: Soll der sichtbare Ausschnitt dann in den Bereich wechseln, wo gerade ein neues Item eingefügt wurde?
Der sagen wir mal "Graph" ist je nach Eingabe größer oder kleiner. So dann auch das Canvas. Diese maximale Größe soll dann auch als maximaler Scrollbereich genommen werden...

Verfasst: Freitag 3. Oktober 2008, 06:55
von numerix
Naja sicher bekommst du einen NameError, weil ALL eine Tkinter-Konstante ist, du Tkinter mittels Sternchenimport eingebunden hast und ich nicht.

Also muss es - für meinen Code - heißen tk.ALL oder zu verwendest stattdessen eine Zeichenkette "all".

Das Ändern des maximalen Scrollbereichs ist völlig unproblematisch.
Du musst nur die scrollregion neu setzen, wenn du ein neues Item auf das Canvas platzierst. Das heißt: Nachdem das Item hinzugefügt wurde, genügt deine Zeile mit dem bbox("all").

Verfasst: Samstag 4. Oktober 2008, 20:56
von Daniel_SGe
Hmm. Dummer Fehler. Hab jetzt allerdings ein weiteres Problem. Der Graph, den ich zeichnen möchte fängt bei einem bestimmten y-Wert an. Da der Graph so nie von Anfang an im Fenster auftaucht, würde ich den Graphen gerne auch dynamisch je nach maximaler Höhe des Graphen zeichnen lassen. Da die maximale Höhe jedoch von einem Parameter und einem Zufallswert abhängt, wäre die einzige Möglichkeit, die mir einfällt den ganzen Code in eine Funktion zu packen und diese dann das 2. Mal mit dem Höhenparameter aufzurufen. (Neuzeichnen kommt wohl eher nicht in Betracht, je nachdem sind es über 1000 Elemente.

Wenn dies dann funktioniert sollte der 1. Ausschnitt den man sieht, nicht links oben sein, sondern rechts oben. Lässt sich das einfach realisieren?

Noch ein kleiner "Schönheitsfehler": Beim Scrollen bleiben die x und Y-Achse zwar am Rand der Graph "schiebt" sich aber auch durch diese hindurch. Eine Option um dies abzustellen?