Tkinter canvas mit Fenstergröße skalieren

Fragen zu Tkinter.
Antworten
aug_lager
User
Beiträge: 26
Registriert: Sonntag 4. Januar 2015, 12:01

Hallo beisammen,

ich möchte, dass sich ein canvas dynamisch skaliert, wenn ich die Fenstergröße anpasse. Ich habe noch nicht besonders viel Ahnung von Tkinter und habe das Internet nach einer Lösung durchforstet. Gefunden habe ich das:
https://stackoverflow.com/questions/228 ... ndow-width
Nun habe ich aber noch eine Bedingung, die hier nicht erfüllt ist. Das canvas soll seine Proportionen beibehalten. Also habe ich versucht den Code anzupassen und habe folgendes Resultat erzielt:

Code: Alles auswählen

from tkinter import *

# a subclass of Canvas for dealing with resizing of windows
class ResizingCanvas(Canvas):
    def __init__(self,parent,**kwargs):
        Canvas.__init__(self,parent,**kwargs)
        self.bind("<Configure>", self.on_resize)
        self.height = self.winfo_reqheight()
        self.width = self.winfo_reqwidth()
        self.initial_ratio = self.width/self.height

    def on_resize(self,event):
        # determine the ratio of old width/height to new width/height
        wscale = float(event.width)/self.width
        hscale = float(event.height)/self.height
        self.width = event.width
        self.height = event.height
        # resize the canvas 
        self.config(width=self.height, height=self.height)
        # rescale all the objects tagged with the "all" tag
        if (self.width/self.height) > self.initial_ratio:
            self.scale("all",0,0,hscale,hscale)
            print("1")
        elif (self.width/self.height) < self.initial_ratio:
            self.scale("all",0,0,wscale,wscale)
            print("2")
        elif (self.width/self.height) == self.initial_ratio:
            self.scale("all",0,0,hscale,wscale)
            print("3")


def main():
    root = Tk()
    myframe = Frame(root)
    myframe.pack(fill=BOTH, expand=YES)
    mycanvas = ResizingCanvas(myframe,width=850, height=400, bg="red", highlightthickness=0)
    mycanvas.pack(fill=BOTH, expand=YES)

    # add some widgets to the canvas
    mycanvas.create_line(0, 0, 850, 400)
    mycanvas.create_rectangle(50, 25, 150, 75, fill="blue")

    # tag all of the drawn widgets
    mycanvas.addtag_all("all")
    root.mainloop()

if __name__ == "__main__":
    main()
(Ich weiß, *-Importe sind böse, mach ich auch nicht. Wollte mich nur am vorgegebenen Codebeispiel entlang hangeln. :) )

Nun verstehe ich überhaupt nicht, was hier passiert. Um das nachzuvollziehen, lasse ich mir die Nummer des if/elif-Zweiges ausgeben, dessen Bedingung erfüllt ist. Bereits zum Start wird bemerkt, dass das Seitenverhältnis dem Seitenverhältnis zu Beginn entspricht. Soweit nachvollziehbar. Dann bemerkt er, dass das Seitenverhältnis kleiner als das ursprüngliche ist, ohne dass der Anwender etwas dazu beigetragen hätte. Das verstehe ich nicht. Die Anzeige, die ich zu Gesicht bekomme ist dann auch quadratisch, die diagonale Linie endet nicht in der Ecke sondern in der Mitte der senkrechten Begrenzung.
Wenn ich ich das Fenster mit der Maus packe und die Größe änder, passiert dann wenigstens das, was ich gerne hätte. Aber auch nich so wirklich, denn Diagonale und Rechteck schrumpfen. :shock:
Ich verstehs nicht. :K Kann mir jemand auf die Sprünge helfen?
Und eine Frage am Rande: Wie packe ich meinen Code in die Codebox mit Syntax-Highlighting? Dann muss BlackJack das nicht immer für mich übernehmen...
Zuletzt geändert von Anonymous am Dienstag 29. November 2016, 16:44, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
simplesimon
User
Beiträge: 11
Registriert: Samstag 3. Dezember 2016, 21:50

Der Code enthält zwei Schreibfehler:
Zeile 19 muss lauten: self.config(width=self.width, height=self.height)

Zeile 28 muss lauten: self.scale("all",0,0,wscale,hscale)

Dann klappt es!
aug_lager
User
Beiträge: 26
Registriert: Sonntag 4. Januar 2015, 12:01

Vielen Dank für deine Hilfe. Der Fehler in Zeile 19 ist offensichtlich und mir gerade etwas peinlich. Deine Korrektur in Zeile 28 leuchtet mir auch ein. Aber klappen tut es leider trotzdem nicht. Durch die Korrektur in Zeile 19 ist zumindest die Darstellung so, wie ich sie zu Beginn gerne hätte. Wenn ich jetzt aber das Fenster beliebig skaliere, dann schrumpfen die items auf dem canvas. Am einfachsten lässt sich das nachvollziehen, wenn man das Fenster am unteren rechten Eck mit der Maus packt und das Fenster kreisförmig vergrößert und verkleinert. Man wird recht schnell feststellen, dass sich die items in Richtung oberer Linke Ecke verziehen.
simplesimon
User
Beiträge: 11
Registriert: Samstag 3. Dezember 2016, 21:50

Ich habe leider vergessen zu schreiben, dass die Zeilen 21 bis 27 überflüssig sind!
aug_lager
User
Beiträge: 26
Registriert: Sonntag 4. Januar 2015, 12:01

Mag sein, dass sie nicht zielführend sind, aber wenn ich sie weglasse, dann behalten die items beim Skalieren nicht die ursprünglichen Seitenverhältnisse. Und darauf kommt es mir letztlich an.
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hi aug_lager

Hier eine mögliche Lösung:

Code: Alles auswählen

try:
    #~~ For Python 2.x
    import Tkinter as tk
except ImportError:
    #~~ For Python 3.x
    import tkinter as tk
 
# a subclass of Canvas for dealing with resizing of windows
class ResizingCanvas(tk.Canvas):
    def __init__(self,parent,**kwargs):
        tk.Canvas.__init__(self,parent,**kwargs)
        self.bind("<Configure>", self.on_resize)
        
        self.create_line(0, 0, 0, 0, tags='line')
        self.create_rectangle(50, 25, 150, 75, fill="blue", tags='rectangle')
        self.init = False
        self.width = 0
        self.height = 0
        
    def on_resize(self,event):
        if not self.init:
            self.init = True
        else:
            wscale = float(event.width) / self.width
            hscale = float(event.height) / self.height
            self.scale('rectangle', 0, 0, wscale, hscale)

        self.coords('line', 0, 0, self.winfo_width(), self.winfo_height())
        
        self.width = event.width
        self.height = event.height
 
def main():
    root = tk.Tk()
    root.geometry('850x400+0+0')
    myframe = tk.Frame(root)
    myframe.pack(fill=tk.BOTH, expand=tk.YES)
    mycanvas = ResizingCanvas(myframe, bg="red", highlightthickness=0)
    mycanvas.pack(fill=tk.BOTH, expand=tk.YES)
    root.mainloop()
 
if __name__ == "__main__":
    main()
Gruss wuf :wink:
Take it easy Mates!
aug_lager
User
Beiträge: 26
Registriert: Sonntag 4. Januar 2015, 12:01

Hi wuf, danke für deine Mühen. Das ist leider nicht die Lösung, die ich suche. Hätte das vielleicht schon im Titel des Threads klarmachen sollen. Was ich gerne erreichen würde ist, dass die items auf dem canvas ihre Proportionen immer beibehalten. Bspw. ein Diagramm mit einem Verhältnis von L:B von 3:2 soll immer dieses Seitenverhältnis beibehalten und den ihm zu Verfügung stehenden Raum auf dem Canvas bestmöglichst ausfüllen. Wenn ich das Fenster in die Breite ziehe und ein Verhältnis von 3:2 überschreite, darf sich das Diagramm natürlich nicht mehr ändern. In die andere Richtung analog. Der Code im Eröffnungspost macht das prinzipiell auch. Erreicht werden soll das in erster Linie hierdurch:

Code: Alles auswählen

if (self.width/self.height) > self.initial_ratio:
    self.scale("all",0,0,hscale,hscale)
    print("1")
elif (self.width/self.height) < self.initial_ratio:
    self.scale("all",0,0,wscale,wscale)
    print("2")
elif (self.width/self.height) == self.initial_ratio:
    self.scale("all",0,0,hscale,wscale)
    print("3")
Wenn ich jetzt aber mit der Fenstergröße rumspiele beobachte ich, dass die items zwar immer ihre Proportionen behalten, aber schrumpfen, bis sie irgendwann kaum noch zu sehen sind. Und wie ich das löse und wie das überhaupt zustandekommt verstehe ich nicht. :K
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hi aug_lager

Vermute diese mögliche Lösung entspricht eher deiner Erwartung:

Code: Alles auswählen

try:
    #~~ For Python 2.x
    import Tkinter as tk
except ImportError:
    #~~ For Python 3.x
    import tkinter as tk

 
# a subclass of Canvas for dealing with resizing of windows
class ResizingCanvas(tk.Canvas):
    def __init__(self, parent,**kwargs):
        
        tk.Canvas.__init__(self,parent,**kwargs)
        self.bind("<Configure>", self.on_resize)
        
        self.create_line(0, 0, 350, 200, fill='yellow')
        self.create_rectangle(50, 25, 150, 75, fill="blue")
        self.create_rectangle(100, 80, 250, 130, fill="green")
                   
    def on_resize(self,event):
        width = self.winfo_width()
        height = self.winfo_height()
        x0, y0, x1, y1 = self.coords('all')
        xratio = float(width) / x1
        yratio = float(height) / y1
        
        if xratio < yratio:
            self.scale_objects(xratio)
        else:
            self.scale_objects(yratio)

    def scale_objects(self, scale):
        self.scale('all', 0, 0, scale, scale)
     
def main():
    app_win = tk.Tk()
    app_win.geometry('850x400+0+0')
    app_win.title('')
    
    myframe = tk.Frame(app_win)
    myframe.pack(fill=tk.BOTH, expand=tk.YES)
    mycanvas = ResizingCanvas(myframe, bg="red", highlightthickness=0)
    mycanvas.pack(fill=tk.BOTH, expand=tk.YES)
    
    app_win.mainloop()
 
if __name__ == "__main__":
    main()
Gruss wuf :wink:
Take it easy Mates!
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hi aug_lager

Korrektur in Zeile 23 bei meinem letzten Skript:

Code: Alles auswählen

x0, y0, x1, y1 = self.coords('all')
muss ersetzt werden durch:

Code: Alles auswählen

x0, y0, x1, y1 = self.bbox('all')
Gruss wuf :wink:
Take it easy Mates!
aug_lager
User
Beiträge: 26
Registriert: Sonntag 4. Januar 2015, 12:01

Genial, vielen, vielen Dank! Das funktioniert wunderbar und ich denke ich kann es auch ganz gut nachvollziehen. Was ich immernoch nicht verstehe ist das Verhalten meiner Lösung. Hast du da vielleicht noch irgendeine Idee?
Antworten