1

I have created a canvas widget and added some objects such as rectangles and texts.

I have also bound a zoom function to the mouse wheel. It works for rectangles; however, it does not work for texts.

I want to make the zoom work for the text too, but I couldn't find the way to do it.

Any help would be appreciated.

class Layout(tk.Frame):

    def __init__(self, root):
        tk.Frame.__init__(self, root)[![enter image description here][1]][1]
        self.canvas = tk.Canvas(self, width=200, height=750, background="white")
        self.xsb = tk.Scrollbar(self, orient="horizontal", command=self.canvas.xview)
        self.ysb = tk.Scrollbar(self, orient="vertical", command=self.canvas.yview)
        self.canvas.configure(yscrollcommand=self.ysb.set, xscrollcommand=self.xsb.set)
        self.canvas.configure(scrollregion=(0,0,1000,1000))

        self.xsb.grid(row=1, column=0, sticky="ew")
        self.ysb.grid(row=0, column=1, sticky="ns")
        self.canvas.grid(row=0, column=0, sticky="nsew")
        self.grid_rowconfigure(0, weight=1)
        self.grid_columnconfigure(0, weight=1)

        #self.canvas.create_text(50,10, text="Click and drag to move the canvas\nScroll to zoom.")

        # This is what enables using the mouse:
        self.canvas.bind("<ButtonPress-1>", self.move_start)
        self.canvas.bind("<B1-Motion>", self.move_move)
        #linux scroll
        self.canvas.bind("<Button-4>", self.zoomerP)
        self.canvas.bind("<Button-5>", self.zoomerM)
        #windows scroll
        self.canvas.bind("<MouseWheel>",self.zoomer)

    #move
    def move_start(self, event):
        self.canvas.scan_mark(event.x, event.y)
    def move_move(self, event):
        self.canvas.scan_dragto(event.x, event.y, gain=1)

    #windows zoom
    def zoomer(self,event):
        if (event.delta > 0):
            self.canvas.scale("all", event.x, event.y, 1.1, 1.1)
        elif (event.delta < 0):
            self.canvas.scale("all", event.x, event.y, 0.9, 0.9)
        self.canvas.configure(scrollregion = self.canvas.bbox("all"))

    #linux zoom
    def zoomerP(self,event):
        self.canvas.scale("all", event.x, event.y, 1.1, 1.1)
        self.canvas.configure(scrollregion = self.canvas.bbox("all"))
    def zoomerM(self,event):
        self.canvas.scale("all", event.x, event.y, 0.9, 0.9)
        self.canvas.configure(scrollregion = self.canvas.bbox("all"))


root = Tk()
root.title("Seoul Cell")
root.geometry("1300x1000")
root.resizable(True, True)

if __name__ == "__main__":

    global monitor1
    monitor1 = Layout(root)
    monitor1.pack(fill="both", expand=True)


for i in range(10):
    x1= random.randrange(0, 1000)
    x2 = random.randrange(0, 1000)
    y1 = random.randrange(0, 1000)
    y2 = random.randrange(0, 1000)
    monitor1.canvas.create_rectangle(x1, x2, y1, y2, fill = "black", stipple= "gray50")
    monitor1.canvas.create_text(x1, y1, text = "random word!", font = ("Helvetica", 10))
root.mainloop()

enter image description here

enter image description here

Neuron
  • 5,141
  • 5
  • 38
  • 59
onixcurve
  • 11
  • 2
  • well, simply configure font size to a bigger font when zooming in – Matiiss Aug 10 '21 at 07:12
  • can you explain more please? – onixcurve Aug 10 '21 at 07:39
  • give all texts some tag like `"text"`: `tags=("text", )` (when creating them in the `.create_text()` method) then when you are zooming in simply do `.itemconfigure("text", font=("font family", integer_size))` where you need to pass the canvas instance here: `` (this is a placeholder), font family well the family of the font like "Times New Roman" and integer_size is just the size of new font like 16 or sth like that and when zooming out set it to some smaller size – Matiiss Aug 10 '21 at 07:54
  • 1
    @Matiiss: it would be better to create a single font object and change its size rather than to continue to create different fonts. – Bryan Oakley Aug 10 '21 at 17:55

2 Answers2

4

Ben Mega's solution works fine and answers well the question. I am just showing below a different way of implementing the fontsize change when zooming in and out.

The idea is to use a common tkinter.font.Font for all text items so that we can just change the size of this font object instead of reconfiguring all the items. The font size can then be changed globally using .configure(size=<new size>).

Here is the code. I have added a create_text() method to the Layout class to avoid having to set the font manually when creating the item.

import tkinter as tk
from tkinter.font import Font
import random

class Layout(tk.Frame):

    def __init__(self, root):
        tk.Frame.__init__(self, root)
        self.font = Font(self, "Arial 10")  # create font object
        self.fontsize = 10  # keep track of exact fontsize which is rounded in the zoom

        self.canvas = tk.Canvas(self, width=200, height=750, background="white")
        self.xsb = tk.Scrollbar(self, orient="horizontal", command=self.canvas.xview)
        self.ysb = tk.Scrollbar(self, orient="vertical", command=self.canvas.yview)
        self.canvas.configure(yscrollcommand=self.ysb.set, xscrollcommand=self.xsb.set)
        self.canvas.configure(scrollregion=(0,0,1000,1000))

        self.xsb.grid(row=1, column=0, sticky="ew")
        self.ysb.grid(row=0, column=1, sticky="ns")
        self.canvas.grid(row=0, column=0, sticky="nsew")
        self.grid_rowconfigure(0, weight=1)
        self.grid_columnconfigure(0, weight=1)

        # This is what enables using the mouse:
        self.canvas.bind("<ButtonPress-1>", self.move_start)
        self.canvas.bind("<B1-Motion>", self.move_move)
        #linux scroll
        self.canvas.bind("<Button-4>", self.zoomerP)
        self.canvas.bind("<Button-5>", self.zoomerM)
        #windows scroll
        self.canvas.bind("<MouseWheel>",self.zoomer)


    #create text
    def create_text(self, *args, **kwargs):
        self.canvas.create_text(*args, **kwargs, font=self.font)

    #move
    def move_start(self, event):
        self.canvas.scan_mark(event.x, event.y)

    def move_move(self, event):
        self.canvas.scan_dragto(event.x, event.y, gain=1)

    #windows zoom
    def zoomer(self,event):
        if (event.delta > 0):
            self.fontsize *= 1.1
            self.canvas.scale("all", event.x, event.y, 1.1, 1.1)
        elif (event.delta < 0):
            self.fontsize *= 0.9
            self.canvas.scale("all", event.x, event.y, 0.9, 0.9)
        self.font.configure(size=int(self.fontsize))  # update fontsize
        self.canvas.configure(scrollregion = self.canvas.bbox("all"))

    #linux zoom
    def zoomerP(self,event):
        self.fontsize *= 1.1
        self.font.configure(size=int(self.fontsize))
        self.canvas.scale("all", event.x, event.y, 1.1, 1.1)
        self.canvas.configure(scrollregion = self.canvas.bbox("all"))

    def zoomerM(self,event):
        self.fontsize *= 0.9
        self.font.configure(size=int(self.fontsize))
        self.canvas.scale("all", event.x, event.y, 0.9, 0.9)
        self.canvas.configure(scrollregion = self.canvas.bbox("all"))

root = tk.Tk()
root.title("Seoul Cell")
root.geometry("1300x1000")
root.resizable(True, True)

monitor1 = Layout(root)
monitor1.pack(fill="both", expand=True)

for i in range(10):
    x1= random.randrange(0, 1000)
    x2 = random.randrange(0, 1000)
    y1 = random.randrange(0, 1000)
    y2 = random.randrange(0, 1000)
    monitor1.canvas.create_rectangle(x1, x2, y1, y2, fill = "black", stipple= "gray50")
    monitor1.create_text(x1, y1, text="random word!")
root.mainloop()
j_4321
  • 15,431
  • 3
  • 34
  • 61
2

The reason just scaling the canvas doesn't work is that the text font size is stuck at 10. To fix this, we need to add a property for font size, scale this, and then modify the text widgets. This isn't perfect due to the fact you can't do fractional font sizes but I think it works alright.

Here is the modified code.

import tkinter as tk
from tkinter import Tk
import random


#fontSize = 10

class Layout(tk.Frame):

    def __init__(self, root):
        tk.Frame.__init__(self, root) #[![enter image description here][1]][1]
        self.canvas = tk.Canvas(self, width=200, height=750, background="white")
        self.xsb = tk.Scrollbar(self, orient="horizontal", command=self.canvas.xview)
        self.ysb = tk.Scrollbar(self, orient="vertical", command=self.canvas.yview)
        self.canvas.configure(yscrollcommand=self.ysb.set, xscrollcommand=self.xsb.set)
        self.canvas.configure(scrollregion=(0,0,1000,1000))

        self.xsb.grid(row=1, column=0, sticky="ew")
        self.ysb.grid(row=0, column=1, sticky="ns")
        self.canvas.grid(row=0, column=0, sticky="nsew")
        self.grid_rowconfigure(0, weight=1)
        self.grid_columnconfigure(0, weight=1)

        self.canvas.fontSize = 10
        #self.canvas.create_text(50,10, text="Click and drag to move the canvas\nScroll to zoom.")

        # This is what enables using the mouse:
        self.canvas.bind("<ButtonPress-1>", self.move_start)
        self.canvas.bind("<B1-Motion>", self.move_move)
        #linux scroll
        self.canvas.bind("<Button-4>", self.zoomerP)
        self.canvas.bind("<Button-5>", self.zoomerM)
        #windows scroll
        self.canvas.bind("<MouseWheel>",self.zoomer)

    #move
    def move_start(self, event):
        self.canvas.scan_mark(event.x, event.y)

    def move_move(self, event):
        self.canvas.scan_dragto(event.x, event.y, gain=1)

    #windows zoom
    def zoomer(self,event):
        if (event.delta > 0):
            self.canvas.scale("all", event.x, event.y, 1.1, 1.1)
            self.canvas.fontSize = self.canvas.fontSize * 1.1
        elif (event.delta < 0):
            self.canvas.scale("all", event.x, event.y, 0.9, 0.9)
            self.canvas.fontSize = self.canvas.fontSize * 0.9
        for child_widget in self.canvas.find_withtag("text"):
            self.canvas.itemconfigure(child_widget, font = ("Helvetica", int(self.canvas.fontSize)))
        self.canvas.configure(scrollregion = self.canvas.bbox("all"))

    #linux zoom
    def zoomerP(self,event):
        self.canvas.fontSize = self.canvas.fontSize * 1.1
        self.canvas.scale("all", event.x, event.y, 1.1, 1.1)
        for child_widget in self.canvas.find_withtag("text"):
            self.canvas.itemconfigure(child_widget, font = ("Helvetica", int(self.canvas.fontSize)))
        self.canvas.configure(scrollregion = self.canvas.bbox("all"))

    def zoomerM(self,event):
        self.canvas.fontSize = self.canvas.fontSize * 0.9
        self.canvas.scale("all", event.x, event.y, 0.9, 0.9)
        for child_widget in self.canvas.find_withtag("text"):
            self.canvas.itemconfigure(child_widget, font = ("Helvetica", int(self.canvas.fontSize)))
        self.canvas.configure(scrollregion = self.canvas.bbox("all"))


root = Tk()
root.title("Seoul Cell")
root.geometry("1300x1000")
root.resizable(True, True)

if __name__ == "__main__":

    global monitor1
    monitor1 = Layout(root)
    monitor1.pack(fill="both", expand=True)


for i in range(10):
    x1= random.randrange(0, 1000)
    x2 = random.randrange(0, 1000)
    y1 = random.randrange(0, 1000)
    y2 = random.randrange(0, 1000)
    monitor1.canvas.create_rectangle(x1, x2, y1, y2, fill = "black", stipple= "gray50")
    monitor1.canvas.create_text(x1, y1, text = "random word!", font = ("Helvetica", 10), tags="text")
root.mainloop()
j_4321
  • 15,431
  • 3
  • 34
  • 61
Ben Mega
  • 502
  • 2
  • 10
  • 1
    Instead of modifying the font of each text item, you can also use a `tkinter.font.Font` for all the items and just modify the font size of this `Font` object. – j_4321 Aug 10 '21 at 08:01
  • I can add the Linux section if you want, it's the same as the `zoomer()` but the parts of the if statement are in separate functions. – j_4321 Aug 10 '21 at 08:07
  • By all means! If there's a more elegant way I want to see it. – Ben Mega Aug 10 '21 at 08:22
  • I have added the font size zooming for Linux in your answer and I have posted an answer implementing the suggestion from my first comment. – j_4321 Aug 10 '21 at 08:57