From tutorials, I have kind of been under the impression that grid just "kind of figures it out" for width, but it's clearly not the case when it comes to Text (I suppose when combined with list).
With the following code, listbox has is tiny and Text is absolutely massive (Width wise). I can make the listbox equal to the size of the Text by changing sticky="ew", but that's not what I want - I want a reasonable, equivalently styled "grid".
If I hardcode the size of the width, it's even more frustrating, because listbox width seems to equate to approximately 2/3 of Text width.
I've read up on rowconfigure and columnconfiugre, but this seems to actually do nothing with the below code (note - rowconfigure and columnconfigure are not in the below code, but I have tried them, perhaps I'm using them wrong).
Anyways, with the below code - can anyone explain to me how to make these more reasonably sized width wise, and also the same? Should I hardcode a width to Text and then set listbox to sticky="ew"? Seems counter intuitive to the grid layout concept.
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
self.frame = tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.initialize()
def initialize(self):
self.lst_bx = tk.Listbox(self.parent, height = 15)
self.lst_bx.grid(row=0, column=0, columnspan=2, padx=(10,10), sticky="w")
self.exe_field = tk.Text(self.parent, height=15)
self.exe_field.grid(row=1, column=0, columnspan=2, padx=(10,10), sticky="w")
self.pick_exe_btn = tk.Button(
self.parent, text="Choose Location", width=15
)
self.pick_exe_btn.grid(row=0, column=2)
if __name__ == "__main__":
root = tk.Tk()
#window_config(root)
MainApplication(root).grid(stick="nesw")
root.resizable(False, False)
root.mainloop()
I'm sorry for being such a noob, I swear I have searched this a lot before posting here - I just cannot find a straight answer on why ListBox is a completely different width from Text (and even moreso when I specify a width).
The how tkinter calculates the dimensions tkinter.ListBox() and tkinter.Text() widgets is a little bit different from most of other widgets where it uses pixels. For these 2 specific widgets, the width is calculated in terms of characters whereas the height is assessed in terms of lines.
You did not set the width of self.lst_bx, so tkinter sets it by default to 20 characters.
You did not specify the width of self.exe_field either. So tkinter calculates this width based on the current default font size.
From 1) and 2) we can conclude that it is quite normal we can not expect from self.exe_field to have the same width as self.lst_bx. This means, you have no option other than hard coding them and visually check the GUI.
With minor changes of your code (mainly provided in the comments below your question) this is how I solved your issue:
import tkinter as tk
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.initialize()
def initialize(self):
self.lst_bx = tk.Listbox(self, height = 15, width=70)
self.lst_bx.grid(row=0, column=0, padx=(10,10), sticky="w")
self.exe_field = tk.Text(self, height=15, width=80)
self.exe_field.grid(row=1, column=0, padx=(10,10), sticky="w")
self.pick_exe_btn = tk.Button(self, text="Choose Location", width=15)
self.pick_exe_btn.grid(row=0, column=2, columnspan=2)
if __name__ == "__main__":
root = tk.Tk()
#window_config(root)
MainApplication(root).grid(stick="nesw")
root.resizable(False, False)
root.mainloop()
Note where I moved the columnspace option (it quite does not make a sens in the two places where you have set it previously). Note also that tkinter calculate the character somehow differently between the listbox and text widgets. Depending your operating system (and maybe machine also), you may have to change the 2 width dimensions I set to the widgets in questions.
Here is what I got on my machine:
Related
I have been using a widget in my tkinter programs, but the problem with this widget is the fact that it shows under the windows titlebar, here is the widget I am using:
class LabeledEntry(ttk.Frame):
def __init__(self, master, text, width=120, insertion="0"):
super().__init__(master, width=width)
self.pack_propagate(False)
self.label = ttk.Label(self, text=text)
self.entry = ttk.Entry(self)
self.label.pack(side='left')
self.entry.pack(side='right')
Apparently, the problem is with the pack propagate function, but I need it so that multiple widgets can look equal on the same column.
EDIT: So, apparently, the problem wasnt the fact that it was "being placed behind the window", but because i had to manually input a height for the frame since it doesnt depend on its children for size. Thanks to the commenter who said that.
By the way, if you were wondering what it looked like without the height, here:
https://imgur.com/a/hQCQ8eu
Is this what you want?
Change this ttk.Label(self to ttk.Label(maaster
code:
from tkinter import ttk
import tkinter as tk
win = tk.Tk()
class LabeledEntry(ttk.Frame):
def __init__(self, master, text, width=120, insertion="0"):
super().__init__(master, width=width)
self.pack_propagate(False)
self.label = ttk.Label(master, text=text)
self.entry = ttk.Entry(master)
self.label.pack(side='left')
self.entry.pack(side='right')
win.title('3squares6lines')
app =LabeledEntry(win, 'What to enter')
app.mainloop()
screenshot:
I am having trouble changing the font size of a button in Tkinter, when I attempt to do it the button also expands/contracts based on the size of the text. Is there a way I can alter the text size with the button's size anchored in place?
I ran across this while designing a tic-tac-toe application, however to save you the trouble, here is a very minimal example of the problem in practice:
import Tkinter as tk
MyWindow = tk.Tk()
MyWindow.geometry("500x550")
button = tk.Button(MyWindow,text="Hello!",width=17,height=10,font=('Helvetica', '20'))
button.grid(row=1, column=1)
MyWindow.mainloop()
The most important part here is font=('Helvetica', '15') or more specifically, the number 15. If you change that number and run this again, not only will the text be bigger/smaller, but so will the button! How do I get around this?
It's probably a really simple problem. I've just gotten started with Tkinter. Thanks in advance for any help I receive!
Typically, when you give a button a width, that width is measured in characters (ie: width=1 means the width of one average sized character). However, if the button has an image then the width specifies a size in pixels.
A button can contain both an image and text, so one strategy is to put a 1x1 pixel as an image so that you can specify the button size in pixels. When you do that and you change the font size, the button will not grow since it was given an absolute size.
Here is an example that illustrates the technique. Run the code, then click on "bigger" or "smaller" to see that the text changes size but the button does not.
import Tkinter as tk
import tkFont
def bigger():
size = font.cget("size")
font.configure(size=size+2)
def smaller():
size = font.cget("size")
size = max(2, size-2)
font.configure(size=size)
root = tk.Tk()
font = tkFont.Font(family="Helvetica", size=12)
toolbar = tk.Frame(root)
container = tk.Frame(root)
toolbar.pack(side="top", fill="x")
container.pack(side="top", fill="both", expand=True)
bigger = tk.Button(toolbar, text="Bigger", command=bigger)
smaller = tk.Button(toolbar, text="Smaller", command=smaller)
bigger.pack(side="left")
smaller.pack(side="left")
pixel = tk.PhotoImage(width=1, height=1)
for row in range(3):
container.grid_rowconfigure(row, weight=1)
for column in range(3):
container.grid_columnconfigure(column, weight=1)
button = tk.Button(container, font=font, text="x",
image=pixel, compound="center", width=20, height=20)
button.grid(row=row, column=column)
root.mainloop()
All of that being said, there is almost never a time when this is a good idea. If the user wants a larger font, the whole UI should adapt. Tkinter is really good at making that happen, to the point where it all mostly works by default.
The width of the button is defined in units of character width. In your case the button is defined to be 17 characters wide. So changing the character width by (ie changing the font size) changes the width of the button. AFAIK, the only way around that is to put the button into a Frame, because a Frame can define it's size in pixels. Here's a new kind of Button that does exactly that:
import Tkinter as tk
class Warspyking(tk.Frame):
'''A button that has it's width and height set in pixels'''
def __init__(self, master=None, **kwargs):
tk.Frame.__init__(self, master)
self.rowconfigure(0, minsize=kwargs.pop('height', None))
self.columnconfigure(0, minsize=kwargs.pop('width', None))
self.btn = tk.Button(self, **kwargs)
self.btn.grid(row=0, column=0, sticky="nsew")
self.config = self.btn.config
#example usage:
MyWindow = tk.Tk()
MyWindow.geometry("500x550")
from itertools import cycle
fonts = cycle((('Helvetica', '11'),('Helvetica', '15'),('Helvetica', '20')))
def chg():
button.config(font=next(fonts))
button = Warspyking(MyWindow,text="Click me!",width=200,height=100 ,font=next(fonts), command=chg)
button.grid(row=1, column=1)
MyWindow.mainloop()
EDIT: Based on what I learned from Bryan Oakley, here's a much neater implementation:
class Warspyking(tk.Button):
def __init__(self, master=None, **kwargs):
self.img = tk.PhotoImage()
tk.Button.__init__(self, master, image=self.img, compound='center', **kwargs)
I should also add that I very much agree with Bryan: Using this is probably a sign that you are doing something wrong. You should let tkinter handle sizing.
I found solution of this problem. I was trying to solve a similar problem: I want to put image on label. I set the image size equal to label size. When I have been trying to put it with command label.config(image=img) the label size grow. The image have the size I set to it, so it didn't cover label completely. I was using grid manager. All size were not entered in advanced but calculated by Tkinter. I was using grid_columnconfigure and grid_rowconfigure. The solution I found is to put this label with image (or button in Your case) to LabelFrame and set grid_propagate to False.
Code example:
MyWindow = tk.Tk()
MyWindow.geometry("500x550")
#create LabelFrame (200x200)
label = tk.LabelFrame(MyWindow, width=200, height=200)
#grid manager to set label localization
labelk.grid(row=0, column=0)
#label row and column configure: first argument is col or row id
label.grid_rowconfigure(0, weight=1)
label.grid_columnconfigure(0, weight=1)
#cancel propagation
label.grid_propagate(False)
#Create button and set it localization. You can change it font without changing size of button, but if You set too big not whole will be visible
button = t.Button(label, text="Hello!", font=('Helvetica', '20'))
#Use sticky to button took up the whole label area
button.grid(row=0, column=0, sticky='nesw')
MyWindow.mainloop()
Result for font size 40 and 20:
Example for creating button with dynamic size by grid manager:
MyWindow = tk.Tk()
MyWindow.geometry("500x550")
#Divide frame on 3x3 regions
for col in range(3):
MyWindow.grid_columnconfigure(col, weight=1)
for row in range(3):
MyWindow.grid_rowconfigure(row, weight=1)
label = tk.LabelFrame(MyWindow)
#Put label in the middle
label.grid(row=1, column=1, sticky='nesw')
label.grid_propagate(False)
label.grid_rowconfigure(0, weight=1)
label.grid_columnconfigure(0, weight=1)
button = tk.Button(label, text="Hello!", font=('Helvetica', '30'))
button.grid(row=0, column=0, sticky='nesw')
MyWindow.mainloop()
It is late answer, but maybe it will help someone.
I'm using python2 on Windows. When I run the followig code, I get a gap between the two canvas (see picture below), although there is no padding specified when I grid them.
Is there any possibility to remove this?
import Tkinter as tk
import ttk
class App(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.c1 = tk.Canvas(master=self, background='white', borderwidth=0,
relief=tk.FLAT)
self.c2 = tk.Canvas(master=self, background='white', borderwidth=0,
relief=tk.FLAT)
self.c1.grid(row=0, column=0, sticky=tk.NSEW)
self.c2.grid(row=1, column=0, sticky=tk.NSEW)
self.mainloop()
App()
Thanks for help!
You need to set highlightthickness to zero as well.
self.c1 = tk.Canvas(..., highlightthickness=0)
From the canvas page of effbot highlightthickness explained as:
The width of the highlight border. The default is system specific
(usually one or two pixels). (highlightThickness/HighlightThickness)
I have this code.
class App(object):
def __init__(self):
self.root = Tk()
self.root.attributes('-zoomed', True)
self.root.grid_rowconfigure(0, weight=1)
self.root.grid_columnconfigure(0, weight=1)
self.root.grid_columnconfigure(1, weight=1)
f1 = Frame(self.root, bd=1, bg="green")
f3 = Frame(self.root, bd=1, bg="blue")
self.image = Image.open("default.png")
self.photo = ImageTk.PhotoImage(self.image)
self.label = Label(image=self.photo)
self.label.grid(row=0, column=1, sticky="nsew")
f1.grid(row=0, column=0, sticky="nsew")
f3.grid(row=1, column=0, columnspan=2, sticky="ew")
app = App()
app.root.mainloop()
and the output is
How can I make image occupy equally to the left frame?
Before providing a solution, there are few remarks I would like to highlight:
You did not use the frame f3for anything. It is not even displayed when running your program. So I will simply ignore it in my solution below.
You set the attribute -zoomed for the main frame' attributes. I do not know which goal you are going to fulfill by using it. Anyway, there are many threads that state it is working only Windows and some Debian distributions like Ubuntu. I did not experience this problem though, however, as you can read on the first link I provide below, those arguments are platform specific. So for the sake of portability of your code, you may use -fullscreen instead. But here again, especially for your case, it will affect even the taskbar of your GUI which will not be displayed and thus you can not close your program by clicking on the close button. So in my solution I will rather use winfo_screenheight() and winfo_screenwidth() to set self.root to the size of the screen of your machine and call them inside the geometry() method.
Let us divide your problem into smaller ones:
First problem you need to resolve is how to set the first frame f1 and self.label to equal widths?. This is done by simply setting the width option of each one of them to the half of the width of your screen: width = self.root.winfo_screenwidth()/2
Once this is done, you will need to resolve the second problem you clarified in your comment. For that, you will need to resize self.image to fit self.label size by applying resize() method on the image, and pass to it the dimensions of the image which must be the ones of your label along with the constant PIL.Image.ANTIALIAS.
Full program
So here is the full program:
'''
Created on May 5, 2016
#author: billal begueradj
'''
import Tkinter as Tk
import PIL.Image
import PIL.ImageTk
class App(object):
def __init__(self):
self.root = Tk.Tk()
self.root.grid_rowconfigure(0, weight=1)
self.root.grid_columnconfigure(0, weight=1)
self.root.grid_columnconfigure(1, weight=1)
# Get width and height of the screen
self.h= self.root.winfo_screenheight()
self.w= self.root.winfo_screenwidth()
# Set the GUI's dimensions to the screen size
self.root.geometry(str(self.w) + "x" + str(self.h))
# Set the width of the frame 'f1' to the half of the screen width
self.f1 = Tk.Frame(self.root, bd=1, bg="green", width= self.w/2)
self.f1.grid(row=0, column=0, sticky="nsew")
# Load the image
self.image = PIL.Image.open("/home/Begueradj/mahomet_pedophile.jpg")
# Resize the image to fit self.label width
self.photo = PIL.ImageTk.PhotoImage(self.image)
self.label = Tk.Label(self.root, image=self.photo, width= self.w/2)
self.label.grid(row=0, column=1, sticky="nsew")
# Launch the main program
if __name__ == '__main__':
app = App()
app.root.mainloop()
Demo
Nota Bene
There is a little imprecision through your comment. It does not say weither you want the whole picture to appear of the same size as the frame f1 or just its label in which it is appended (self.label). If this is what you want to do, then just ignore this line in the previous program:
self.image = self.image.resize((self.w/2, self.h), PIL.Image.ANTIALIAS)
Demo
The output will then look like this (the image is simply positioned in the center of the label):
I am building a simple GUI with multiple entry boxes, check boxes, etc. and came across a very peculiar behavior in the Tkinter.Entry class and I'm wondering if anyone else has run into it and/or if I'm just doing something silly.
I created a simple class to wrap each Tkinter.Entry object and interface with. I wanted to implement a way to change how wide each box is, so I added a parameter width to the class. When I did this, all of my boxes became "linked" and when I type into one box, I typed into every box. before I implemented this functionality it worked fine and when I take it out it works again. Here is my class:
import Tkinter as tk
class EntryBox:
def __init__(self, master, row, column, default_val="", width=20):
self.val = tk.StringVar()
self.default_val = default_val
self.width = width
# with the width parameter specified
self.e = tk.Entry(master, text="cb_text", textvariable=self.val, width=self.width)
# without the width parameter specified (defaults to a width of 20)
# self.e = tk.Entry(master, text="cb_text", textvariable=self.val)
self.e.grid(row=row, column=column, sticky=tk.E)
self.e.insert(0, self.default_val)
def get_val(self):
return self.val.get()
def activate(self):
self.e.config(state=tk.NORMAL)
def deactivate(self):
self.e.config(state=tk.DISABLED)
def focus(self):
self.e.focus()
Here is my program WITHOUT the width parameter included:
Here is my program WITH the width parameter included:
As you can see, all of the default values filled in to every box and whenever I edit one, I edit all of them.
Here is how I instantiate each object (I don't suspect this to be the issue, but just to be thorough):
import Tkinter as tk
ip_addr_box = EntryBox(root, 1, 1, default_val="192.168.201.116")
ip_addr_label = tk.Label(root, text="IP Address").grid(row=1, column=2, sticky=tk.W)
# set up the IP port entry box
ip_port_box = EntryBox(root, 2, 1, default_val="8000")
ip_port_label = tk.Label(root, text="IP port").grid(row=2, column=2, sticky=tk.W)
# set up the number of plot points scroll box
# set up the filename entry box
filename_box = EntryBox(root, 4, 1, default_val="log.xlsx")
filename_label = tk.Label(root, text="File Name").grid(row=4, column=2, sticky=tk.W)
# set up how long the test lasts
meas_time_box = EntryBox(root, 3, 4, default_val="5", width=10)
meas_time_label = tk.Label(root, text="Measurement Period")
meas_time_label.grid(row=3, column=5, columnspan=2, sticky=tk.W)
test_time_box = EntryBox(root, 4, 4, default_val="30", width=10)
test_time_label = tk.Label(root, text="Test Duration").grid(row=4, column=5, columnspan=2, sticky=tk.W)
My guess is that this is a weird bug in Tkinter or something to do with namespaces that I don't know enough about.
EDIT: I updated the class code to specify how I don't include the width parameter. I just don't even include it as a named argument when calling tk.Entry.
Remove the attribute text='cb_text'. I don't know what you think that's doing, but text is just an abbreviation of textvariable, so using it is the same as doing Entry(..., textvariable='cb_text', textvariable=self.var, ...).
Apparently there's a bug in how the tkinter Entry widget processes named arguments, and it is triggered when you add the width argument. Every entry widget ends up with the textvariable attribute set to "cb_text", meaning they all share the same storage for the value.
Inheriting from the Tkinter Entry class works well. I have slightly manipulated your code.
import Tkinter as tk
class EntryBox(tk.Entry):
def __init__(self, master, row, column, default_val="", width=20):
tk.Entry.__init__(self, master, text="cb_text")
self.val = tk.StringVar()
self.default_val = default_val
self.width = width
# with the width parameter specified
# self.e = tk.Entry(master, text="cb_text", textvariable=self.val, width=self.width)
# without the width parameter specified (defaults to a width of 20)
# self.e = tk.Entry(master, text="cb_text", textvariable=self.val)
# self.e.grid(row=row, column=column, sticky=tk.E)
self.config(textvariable=self.val, width=self.width)
self.insert(0, self.default_val)
self.grid(row=row, column=column, sticky=tk.E)
def get_val(self):
return self.val.get()
def activate(self):
self.e.config(state=tk.NORMAL)
def deactivate(self):
self.e.config(state=tk.DISABLED)
def focus(self):
self.e.focus()
Unfortunately I don't have enough rep points to comment directly.