tkinter canvas: text object variable font size? - python

I'm making some pretty pictures using a tkinter canvas and overlaying text on top of circles like in the following picture:
http://static.guim.co.uk/sys-images/Guardian/Pix/pictures/2012/11/6/1352220546059/Causes-of-deaths-graphic-008.jpg
I want the font size to be dependent on the same number that the circle size is dependent on.
tempfont = tkFont.Font(family='Helvetica',size=int(round(ms*topnode[1])))
self.display.create_text(center[0],center[1],fill = "#FFFFFF",text = int(round(ms*topnode[1])),font = tempfont)
My problem is that when I use the above code, the overlayed text is a constant size for every text object. The text itself is right, as in it displays the number that I want the font size to be, just not in the correct font size. I've experimented with putting in constant integers in the size definition (works as it's supposed to), and adding a del(tempfont) immediately after the above 2 lines of code, but I haven't found what fixes this problem yet.
What am I doing wrong?
Here's a self-contained little program that reproduces the problem:
from Tkinter import *
import tkFont
class TestApp(Frame):
def __init__(self, master=None, height = 160, width = 400):
Frame.__init__(self, master)
self.grid()
self.createWidgets()
def createWidgets(self):
self.display = Canvas(self, width = 800, height = 320, bg = "#FFFFFF")
self.display.grid(row=0,column=0)
def recurtext(tsize):
if tsize > 20:
recurtext(tsize-10)
tempfont = tkFont.Font(family='Helvetica',size=tsize)
self.display.create_text(800 - (tsize*12),160, text = str(tsize), font = tempfont)
recurtext(60)
app = TestApp()
app.master.title("Test")
app.mainloop()
The gist is that recurtext resizes the font recursively, and shows writes out the font size in that size... or I think it should. Maybe this is a bug with tkinter, but I'm still holding on to some hope that I'm the one who made a mistake in the logic here.

I've never run across this behavior before; it looks like a Tkinter bug. The good news is, there appears to be a workaround. If you give each font a unique name the problem seems to vanish.
The following example shows multiple lines, each with a different font size:
import Tkinter as tk
import tkFont
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.display = tk.Canvas(self, width=400, height=600, background="black")
self.display.pack(side="top", fill="both", expand=True)
y = 10
for size in range (2, 38, 2):
tempfont = tkFont.Font(family='Helvetica',size=size,
name="font%s" % size)
self.display.create_text(10, y, fill = "#FFFFFF",text = size,
font = tempfont, anchor="nw")
y = y + tempfont.metrics()["linespace"]
if __name__ == "__main__":
root = tk.Tk()
frame = Example(parent=root)
frame.pack(side="top", fill="both", expand=True)
root.mainloop()

Related

Having difficulties placing a frame within a frame with Tkinter

I am having issues trying to place a frame within a frame using classes/objects with python/tkinter. My goal is to simply place a frame in the north west corner of the outer frame instead of the whole window itself. I think I'm incorrectly referencing the outer frame in the inner frame class but I'm not 100% sure. I am fairly new to OOP and tkinter so forgive my ignorance and I appreciate the help.
Sample code:
from tkinter import *
class window():
def __init__(self, master):
self.master = master.minsize(500, 500)
master.maxsize(500,500)
self.outer_frame = Frame(master, width = 250, height = 250, bg = "red").place(anchor = CENTER, relx = 0.5, rely = 0.5)
def create_inner_window(self):
self.inner_frame = inner_frame(self.outer_frame)
class inner_frame():
def __init__(self, outer_frame):
self.inner_frame = Frame(master = outer_frame, width = 125, height = 125, bg = "blue").place(anchor = NW)
root = Tk()
my_window = window(root)
my_window.create_inner_window()
root.mainloop()
What I'm trying to accomplish:
What I get instead:

My Python GUI does not open [duplicate]

So I'm making an rss reader using the tkinter library, and in one of my methods I create a text widget. It displays fine until I try to add scrollbars to it.
Here is my code before the scrollbars:
def create_text(self, root):
self.textbox = Text(root, height = 10, width = 79, wrap = 'word')
self.textbox.grid(column = 0, row = 0)
Here is my code after:
def create_text(self, root):
self.textbox = Text(root, height = 10, width = 79, wrap = 'word')
vertscroll = ttk.Scrollbar(root)
vertscroll.config(command=self.textbox.yview)
vertscroll.pack(side="right", fill="y", expand=False)
self.textbox.config(yscrllcommand=vertscroll.set)
self.textbox.pack(side="left", fill="both", expand=True)
self.textbox.grid(column = 0, row = 0)
This gives me the error
_tkinter.TclError: cannot use geometry manager pack inside .56155888 which already has slaves managed by grid on the line
vertscroll.pack(side="right", fill="y", expand=False)
Any ideas how to fix this?
Per the docs, don't mix pack and grid in the same master window:
Warning: Never mix grid and pack in the same master window. Tkinter
will happily spend the rest of your lifetime trying to negotiate a
solution that both managers are happy with. Instead of waiting, kill
the application, and take another look at your code. A common mistake
is to use the wrong parent for some of the widgets.
Thus, if you call grid on the textbox, do not call pack on the scrollbar.
import Tkinter as tk
import ttk
class App(object):
def __init__(self, master, **kwargs):
self.master = master
self.create_text()
def create_text(self):
self.textbox = tk.Text(self.master, height = 10, width = 79, wrap = 'word')
vertscroll = ttk.Scrollbar(self.master)
vertscroll.config(command=self.textbox.yview)
self.textbox.config(yscrollcommand=vertscroll.set)
self.textbox.grid(column=0, row=0)
vertscroll.grid(column=1, row=0, sticky='NS')
root = tk.Tk()
app = App(root)
root.mainloop()
The reason of the code is simple, you CANNOT use pack and grid inside the same class or for the same frame. Thus, use only one.
You can't use pack and grid inside the same class or the same frame.use only one

TkInter Label Change Font Size by Text Length

Good morning,
I have a Tkinter label with a fixed width. In this label I have set a dynamic text. I need to change the font size (decrease or increase) when the text width is longer than label width.
This is an example:
To do this you need to give the label a unique font, and then use the measure method of the font to compute how much space is needed for a given string in that font. Then you just need to keep increasing or decreasing the font size until it fits in the label.
A simple way to create a label with a custom font looks something like this (for python 2.x; for 3.x the imports will be a little different):
import Tkinter as tk
import tkFont
label = tk.Label(...)
original_font = tkFont.nametofont(label.cget("font"))
custom_font = tkFont.Font()
custom_font.configure(**original_font.configure())
label.configure(font=custom_font)
Now you can use custom_font.measure(...) to figure out how many pixels you need for the label at the current font size. If the number of pixels is too big, change the size of the font and measure again. Repeat, until the font is just big enough to hold the text.
When you change the size of the font, the label will automatically redraw the text in the new font size.
Here's a complete working example that illustrates the technique:
import Tkinter as tk
import tkFont
class DynamicLabel(tk.Label):
def __init__(self, *args, **kwargs):
tk.Label.__init__(self, *args, **kwargs)
# clone the font, so we can dynamically change
# it to fit the label width
font = self.cget("font")
base_font = tkFont.nametofont(self.cget("font"))
self.font = tkFont.Font()
self.font.configure(**base_font.configure())
self.configure(font=self.font)
self.bind("<Configure>", self._on_configure)
def _on_configure(self, event):
text = self.cget("text")
# first, grow the font until the text is too big,
size = self.font.actual("size")
while size < event.width:
size += 1
self.font.configure(size=size)
# ... then shrink it until it fits
while size > 1 and self.font.measure(text) > event.width:
size -= 1
self.font.configure(size=size)
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.label = DynamicLabel(self, text="Resize the window to see the font change", width=20)
self.label.pack(fill="both", expand=True, padx=20, pady=20)
parent.geometry("300x200")
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()

Cannot use geometry manager pack inside

So I'm making an rss reader using the tkinter library, and in one of my methods I create a text widget. It displays fine until I try to add scrollbars to it.
Here is my code before the scrollbars:
def create_text(self, root):
self.textbox = Text(root, height = 10, width = 79, wrap = 'word')
self.textbox.grid(column = 0, row = 0)
Here is my code after:
def create_text(self, root):
self.textbox = Text(root, height = 10, width = 79, wrap = 'word')
vertscroll = ttk.Scrollbar(root)
vertscroll.config(command=self.textbox.yview)
vertscroll.pack(side="right", fill="y", expand=False)
self.textbox.config(yscrllcommand=vertscroll.set)
self.textbox.pack(side="left", fill="both", expand=True)
self.textbox.grid(column = 0, row = 0)
This gives me the error
_tkinter.TclError: cannot use geometry manager pack inside .56155888 which already has slaves managed by grid on the line
vertscroll.pack(side="right", fill="y", expand=False)
Any ideas how to fix this?
Per the docs, don't mix pack and grid in the same master window:
Warning: Never mix grid and pack in the same master window. Tkinter
will happily spend the rest of your lifetime trying to negotiate a
solution that both managers are happy with. Instead of waiting, kill
the application, and take another look at your code. A common mistake
is to use the wrong parent for some of the widgets.
Thus, if you call grid on the textbox, do not call pack on the scrollbar.
import Tkinter as tk
import ttk
class App(object):
def __init__(self, master, **kwargs):
self.master = master
self.create_text()
def create_text(self):
self.textbox = tk.Text(self.master, height = 10, width = 79, wrap = 'word')
vertscroll = ttk.Scrollbar(self.master)
vertscroll.config(command=self.textbox.yview)
self.textbox.config(yscrollcommand=vertscroll.set)
self.textbox.grid(column=0, row=0)
vertscroll.grid(column=1, row=0, sticky='NS')
root = tk.Tk()
app = App(root)
root.mainloop()
The reason of the code is simple, you CANNOT use pack and grid inside the same class or for the same frame. Thus, use only one.
You can't use pack and grid inside the same class or the same frame.use only one

Tkinter custom window

I want to make a window in Tk that has a custom titlebar and frame. I have seen many questions on this website dealing with this, but what I'm looking for is to actually render the frame using a canvas, and then to add the contents to the canvas. I cannot use a frame to do this, as the border is gradiented.
According to this website: http://effbot.org/tkinterbook/canvas.htm#Tkinter.Canvas.create_window-method, I cannot put any other canvas items on top of a widget (using the create_window method), but I need to do so, as some of my widgets are rendered using a canvas.
Any suggestions on how to do this? I'm clueless here.
EDIT: Bryan Oakley confirmed that rendering with a canvas would be impossible. Would it then be possible to have a frame with a custom border color? And if so, could someone give a quick example? I'm sort of new with python.
You can use the canvas as if it were a frame in order to draw your own window borders. Like you said, however, you cannot draw canvas items on top of widgets embedded in a canvas; widgets always have the highest stacking order. There is no way around that, though it's not clear if you really need to do that or not.
Here's a quick and dirty example to show how to create a window with a gradient for a custom border. To keep the example short I didn't add any code to allow you to move or resize the window. Also, it uses a fixed color for the gradient.
import Tkinter as tk
class GradientFrame(tk.Canvas):
'''A gradient frame which uses a canvas to draw the background'''
def __init__(self, parent, borderwidth=1, relief="sunken"):
tk.Canvas.__init__(self, parent, borderwidth=borderwidth, relief=relief)
self._color1 = "red"
self._color2 = "black"
self.bind("<Configure>", self._draw_gradient)
def _draw_gradient(self, event=None):
'''Draw the gradient'''
self.delete("gradient")
width = self.winfo_width()
height = self.winfo_height()
limit = width
(r1,g1,b1) = self.winfo_rgb(self._color1)
(r2,g2,b2) = self.winfo_rgb(self._color2)
r_ratio = float(r2-r1) / limit
g_ratio = float(g2-g1) / limit
b_ratio = float(b2-b1) / limit
for i in range(limit):
nr = int(r1 + (r_ratio * i))
ng = int(g1 + (g_ratio * i))
nb = int(b1 + (b_ratio * i))
color = "#%4.4x%4.4x%4.4x" % (nr,ng,nb)
self.create_line(i,0,i,height, tags=("gradient",), fill=color)
self.lower("gradient")
class SampleApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.wm_overrideredirect(True)
gradient_frame = GradientFrame(self)
gradient_frame.pack(side="top", fill="both", expand=True)
inner_frame = tk.Frame(gradient_frame)
inner_frame.pack(side="top", fill="both", expand=True, padx=8, pady=(16,8))
b1 = tk.Button(inner_frame, text="Close",command=self.destroy)
t1 = tk.Text(inner_frame, width=40, height=10)
b1.pack(side="top")
t1.pack(side="top", fill="both", expand=True)
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
Here is a rough example where the frame, titlebar and close button are made with canvas rectangles:
import Tkinter as tk
class Application(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
# Get rid of the os' titlebar and frame
self.overrideredirect(True)
self.mCan = tk.Canvas(self, height=768, width=768)
self.mCan.pack()
# Frame and close button
self.lFrame = self.mCan.create_rectangle(0,0,9,769,
outline='lightgrey', fill='lightgrey')
self.rFrame = self.mCan.create_rectangle(760,0,769,769,
outline='lightgrey', fill='lightgrey')
self.bFrame = self.mCan.create_rectangle(0,760,769,769,
outline='lightgrey', fill='lightgrey')
self.titleBar = self.mCan.create_rectangle(0,0,769,20,
outline='lightgrey', fill='lightgrey')
self.closeButton = self.mCan.create_rectangle(750,4,760, 18,
activefill='red', fill='darkgrey')
# Binds
self.bind('<1>', self.left_mouse)
self.bind('<Escape>', self.close_win)
# Center the window
self.update_idletasks()
xp = (self.winfo_screenwidth() / 2) - (self.winfo_width() / 2)
yp = (self.winfo_screenheight() / 2) - (self.winfo_height() / 2)
self.geometry('{0}x{1}+{2}+{3}'.format(self.winfo_width(),
self.winfo_height(),
xp, yp))
def left_mouse(self, event=None):
obj = self.mCan.find_closest(event.x,event.y)
if obj[0] == self.closeButton:
self.destroy()
def close_win(self, event=None):
self.destroy()
app = Application()
app.mainloop()
If I were going to make a custom GUI frame I would consider creating it with images,
made with a program like Photoshop, instead of rendering canvas objects.
Images can be placed on a canvas like this:
self.ti = tk.PhotoImage(file='test.gif')
self.aImage = mCanvas.create_image(0,0, image=self.ti,anchor='nw')
More info →here←

Categories

Resources