Tkinter: Issue with height/width scaling in tk.Text widget - python

I cannot seem to get the scroll on this text to work. The text will scroll to a certain extent, but then not appear afterwards. I believe that the height of the text widget is not what I want. For instance, the image below shows only about a half of what the actual result is for 遺伝子 (which I can find out by attempting to drag from a piece of text in the middle of the frame to the bottom). The width also is not the same size of the frame I would like it to be: the number 23 was just something that appeared to work.
If I do
txt = tk.Text(self.SEARCH_RESULTS_TEXT_FRAME,width=self.SEARCH_RESULTS_FRAME_WIDTH,height=20,background='#d9d9d9',relief=RIDGE)
... the width becomes much too large. I would have thought that it would only take up the size of the frame, which is 240.
Note the SEARCH_RESULTS_TEXT_FRAME is the frame in red.
Relevant code:
#search text frame
self.SEARCH_RESULTS_FRAME_HEIGHT = 240 #240
self.SEARCH_RESULTS_FRAME_WIDTH = self.TITLE_FRAME_WIDTH - 23
self.SEARCH_RESULTS_TEXT_FRAME = Frame(self.SEARCH_RESULTS_FRAME,height=self.SEARCH_RESULTS_FRAME_HEIGHT,width=self.SEARCH_RESULTS_FRAME_WIDTH)
self.SEARCH_RESULTS_TEXT_FRAME.place(x=10,y=10,anchor = NW)
self.SEARCH_RESULTS_TEXT_FRAME.config(background ="#adadad")
def print_dict_to_frame(self,results_list):
txt = tk.Text(self.SEARCH_RESULTS_TEXT_FRAME,width=34,height=20,background='#d9d9d9',relief=RIDGE)
txt.place(x=0,y=0)
txt.tag_configure('header',justify = 'center',font=("Meiryo",12,'bold'))
txt.tag_configure('entry',font=('Meiryo',8))
for r_list in results_list:
header = r_list[0]
entry = r_list[1]
txt.insert(tk.END, "{}\n".format(header),'header')
for single_result in entry:
txt.insert(tk.END,single_result+"\n",'entry')
txt.configure(state=DISABLED)
How can I get the text widget to only take up the width of the frame and the height of the frame, allowing for scrolling?

If I do txt = tk.Text(self.SEARCH_RESULTS_TEXT_FRAME,width=self.SEARCH_RESULTS_FRAME_WIDTH,height=20,background='#d9d9d9',relief=RIDGE) ... the width becomes much too large. I would have thought that it would only take up the size of the frame, which is 240.
The width option specifies a width in the number of characters, not pixels. If you use the value 240, the width will be 240 multiplied times the width of an average character in the font you are using.
How can I get the text widget to only take up the width of the frame and the height of the frame, allowing for scrolling?
Give the text widget a width and height of 1, and then let the geometry manager (pack, place, or grid) be responsible for stretching it to fit the frame.
Since you're using place, you can use the relwidth and relheight options to make it the full size of the frame:
txt.place(x=0,y=0, anchor="nw", relwidth=1.0, relheight=1.0)
Personally, I recommend using pack or grid in almost all cases. It's much easier to create a responsive UI with them than it is with place.

Related

Why does winfo_width() return a larger size of button widget than the size of what it exactly looks like?

I want buttons/labels in the same column will share the same width with a specific/first button/label. The problem is winfo_width() seems not to return what I want. The return value of winfo_width() is multiple times the button.
I don't want to make width fixed by a number I select. Thus, I did not find a solution to my problem.
Here is part of my code:
button_1.update_idletasks()
print(button_1.winfo_width())
new_label = Label(frame_1, bg= "#8432C7", width = 30, height = 5)
new_label.grid(row = 2, column = 0)
Since I don't have 10 reputation to post images, here is the link for the generated interface:
If you could see the above image, you should find the lower label (width = 30) is larger than the upper button (width = 157 ?).
But, according to my attempts, 157 seems not to be the width of button_1. I feel confused about what exactly winfo_width() returns here. Thus, I want to know what winfo_width() returns (why winfo_width() return 157 which should be a smaller number than 30) and how to get the exact width of the button.
I am stuck here for an hour since I just started to learn Tkinter recently.
Thanks in advance for anyone who can give me suggestions.
Fun fact: you don't have to bother with this at all.
Just pass sticky when you grid your widgets:
import tkinter as tk
root = tk.Tk()
button_1 = tk.Button(root,text="button_1")
button_1.grid(row = 1, column = 0, sticky="ew")
new_label = tk.Label(root, bg= "#8432C7", height = 5)
new_label.grid(row = 2, column = 0, sticky="ew")
root.mainloop()
Then your columns will be auto-fit and scaled to the same size.
This is because the width used in the arguments to create a button are in different units than what tkinter uses.
From the Documentation of a Button in tkinter. Width is The width of the button. If the button displays text, the size is given in text units. If the button displays an image, the size is given in pixels (or screen units). If the size is omitted, or zero, it is calculated based on the button contents. (width/Width)
You will find that if you use tkinters .place() to set a size, the size that .winfo_width() returns will be the same.
For example:
button_1.update_idletasks()
print(button_1.winfo_width())
new_label = Label(frame_1, bg= "#8432C7")
new_label.place(x=40, y=0, width=157, height=20)
You will find that the new_label will now have the same width as the button

How does size work within objects Tkinter

Midway through making my program I realise there are some discrepancies between the height / width properties of objects.
For example in my document here, the main green frame has a width of 640 whereas the buttons are only a size of 3 yet they appear so much larger. How exactly is width and height calculated (is it always pixels)
Image
The width and height attributes don't always refer to pixels. For some widgets, the width and height is in characters. For example, `Label(root, text="Hello", width=10) will make a widget that is wide enough to hold 10 average sized characters in the default font.
Just about any widget that has text will measure width and height in characters (Text, Label, Button, etc). Widgets that do not have text as part of their normal appearance (Frame, Canvas, etc) are measured in pixels.
For some, the value of width and height depends on other options. For example, in a Button or Label, if the widget sets the image attribute then the values will be in pixels, and if the image attribute is not set then the values will be in characters.
The documentation for each widget will say what the width and height represents.
Ya, Height and Width is measured in pixels

Tkinter - relation between font type and width

I apologize in advance if my question is a duplicate however I have not found an answer to this question.
I'm learning Tkinter and I'm struggling with understanding the relation between a label's font type, it's size and it's width and the length of the string in it.
Specifically, what my problem is:
I have created a widget: a 800x640 canvas on which I want to place other
widgets.
On this canvas I want to place a label with some text which has the following
attributes: font: Helvetica, font size: 20, text = "main application". I want
to place this label widget at the very most top left corner of the
widget(meaning at point 0,0 with respect to the canvas). I want the label to
be 200 in width meaning it's background to take almost 1/3 of the canvas's
size(after I manage to do this I plan to add 2 more labels as well). I guess
the height of the label is determined by the font size in this case 20. I
placed the label at coordinate y=20 and this coordinate seems to be ok.
I did some googling and found out that the width parameter of the label widget is not the actual width but something related to the font and size of the label's text: something like if I understood correctly: if the width is 6 than the label will be wide enough to contain 6 characters of, in my case verdana size 20. But I was not able to figure out what width and what x coordinate I should give my label so it starts at the x point of the canvas. Here is the code that I wrote:
from tkinter import *
from tkinter.ttk import *
from tkinter import messagebox
from tkinter import Menu
# Define the application class where we will implement our widgets
class Application(Frame):
def __init__(self, master):
super(Application, self).__init__(master)
# CANVAS COLOUR DEFAULTS TO THE COLOUR OF THE WORKING WINDOW
canvas = Canvas(master, width=800, height = 640, bg="gray") # IF YOU DO .PACK() HERE IT WILL RETURN NONE AND THEN YOU WILL HAVE PROBLEMS BECAUSE .PACK() RETURNS A 'NONE' TYPE OBJECT
canvas.place(relx=0.5, rely=0.5, anchor=CENTER)
# The 'menu' of the application. The selection labels
main_application_label = Label(master, text="main_application", font=("Helvetica", 20))
main_application_window = canvas.create_window(103,20, window=main_application_label)
main_application = Tk()
main_application.title("main_application")
app = Application(main_application)
app_width = 800
app_height = 640
screen_width = main_application.winfo_screenwidth()
screen_height = main_application.winfo_screenheight()
x_coord = (screen_width/2) - (app_width/2)
y_coord = (screen_height/2) - (app_height/2)
main_application.geometry("%dx%d+%d+%d" % (app_width, app_height, x_coord, y_coord))
main_application.mainloop()
I have managed to somehow get the label at around point 0,0(by giving more values till I got it right) but the actual width of the label is not 200 pixels(~1/3 of the canvas). Please help me understand what values to the width parameter I should give so that my label's background is 1/3 of the canvas's size and if possible explain the relation between character font and width parameter so I can do that for any widgets regardless of their text's length. Thank you for reading my post!
Edit: What I wanted to do was to place 3 widgets(labels in this case but it doesn't matter) on the canvas. I did not understand what the 'anchor' option does and that was confusing me because I was expecting the center of the widget to be placed at the given coordinates all times but as I was changing anchor the placement of the center of the widget was changing and that was confusing me. It's all clear now thanks to #Bryan Oakley. Thanks.
If you want the upper left corner of the text to be at (0,0), you don't have to adjust the coordinates based on the width. You can use the anchor option when creating the canvas object:
main_application_window = canvas.create_window(0, 0, anchor="nw",
window=main_application_label)
If you really need to compute the actual size of the string, you can create a Font object and then use the measure method to find the actual width of a string in the given font.
from tkinter.font import Font
font = Font(family="Helvetica", size=20)
string_width = font.measure("main_application")
string_height = font.metrics("linespace")
This gives you the size of the rendered string. If you're using a label widget you'll also need to take into account the amount of padding and borders that the widget uses.
When you create items on a canvas, you can specify the width and height. For example, this makes the widget 200 pixels wide:
main_application_window = canvas.create_window(0, 0, anchor="nw", width=200,
window=main_application_label, width=400)

change size of Frame even if there widget python

hi is there any way to change width and height of widget even if there's widget?
i have code like this
form = Tk()
form.geometry("500x500")
def click():
global frame
frame.config(height = 0 ,width = 0)
frame = LabelFrame(form , text = "vaaja")
frame.place(x = 20 , y = 30)
Label(frame, text ="1").grid(row = 0,column = 0 )
Label(frame, text = "2").grid(row = 1 ,column = 0 )
Button(form , text="Click", command = click).place(x = 200 , y = 200)
form.mainloop()
and when I click the button the size of the frame is the same ( I'cant use grid_forget() for labels and then change the size of frame)
Because you are using place, you have two solutions: you can use place to set the width and height to zero, or you can turn geometry propagation off.
Using place to set the width and height
place allows you to define the width and the height of the placed widget, so in your click function you can do this:
def click():
frame.place_configure(width=0, height=0)
Turning geometry propagation off
A frame is resized to fit its contents by something called "geometry propagation". If you turn this off, you can control the size of the frame with the width and height options of the frame itself. Usually it's better to let Tkinter decide the size for you, but sometimes there's a need to have an explicit size, which is why it's possible to turn geometry propagation off.
Since you are using grid to manage the widgets internal to the frame, you need to use grid_propagate(False) to turn geometry propagation off for that frame:
frame.grid_propagate(False)
By doing so, you're responsible for setting the initial width and height of the widget, though you could leave propagation on to get the initial size, then turn it off with the button click in order to work around that issue.
There's an interesting bug (or feature...) in that if you set the width and height to zero, Tkinter won't redraw the window. At least, it doesn't on the Mac. I don't recall the workaround for that because I never, ever need to set a widget to a zero size, but setting it to 1x1 pixel makes it nearly invisible.

Tkinter Frame container color not visible

Why can't I see a red frame with the following code?
import Tkinter
root = Tkinter.Tk()
root.geometry("220x300")
container_frame = Tkinter.Frame(background = "red", width = 100, height = 120)
container_frame.pack()
widget_button = Tkinter.Button(master = container_frame)
widget_button.pack()
root.mainloop()
You don't see it because you have no padding between the button and the frame. By default, containers "shrink to fit" around their contents. Even if you add an explicit width or height to the frame, it will shrink to exactly fit its children.
There are several ways to achieve the effect you're looking for, but it's not clear exactly what effect you want. You can turn off this "shrink-to-fit" behavior (using container_frame.pack_propagate(False)). Or, you can add padding around the widget. Or, you can apply the background to the container of the frame. Or you could pack the frame to fill its container (the main window), then make sure the containing window is large enough to expose the frame.
For an example of that last suggestion, you can change one line to be this:
container_frame.pack(side="top", fill="both", expand=True)
If you change to:
widget_button.pack(padx=10, pady=10)
You can see that the frame has been resized when call widget_button.pack(...)

Categories

Resources