I am attempting to create a form that requires some entry options to be more than one line long. This does not seem possible with the .Entry() function.
This issue is that I dont see anywhere in the documentation how to restrict the size of the text entry box (particularly, number of lines, or height)
To be clear, I am not trying to set the placement height, or text size, I am trying to restrict the number of lines where text can be entered (so the form is a predictable size) this is necessary because for each item I am using .place() to put them at certain pixel locations, and thus .pack() is not a viable solution for my problems either
Some reference material:
Documentation for entry widget
Use the text widget instead. It has a height option
Note: place requires integers that represent pixels, however, within the options of the text box, the width option requires integers that represent number of characters, and the height option requires an integer that represents number of lines. If you attempt to use height=20 expecting 20 pixels, you will be unpleasantly surprised. If you use width = 5, height = 5 it will not be square.
For reference, the default text size and font is an average of 8 pixels wide per character in the text widget, and 6 pixels per character in the default for the entry widget, so if you wanted a form to stretch a certain number of pixels, use width = round(pixel_length /6)
you can combine this with screen width and placement of the corner of forms to fill across the page or to near the end if you like
Related
I am working with a GUI based on PySide. I made a (one line) text box with QLineEdit and the input is just four characters long, a restriction I already successfully applied.
The problem is I have a wider than needed text box (i.e. there is a lot of unused space after the text). How can I shorten the length of the text box?
I know this is something that is easily fixed by designing the text box with Designer; however, this particular text box is not created in Designer.
If what you want is modify your QLineEdit width and fix it, use:
#setFixedWidth(int w)
MyLineEdit.setFixedWidth(120)
Looking at the source of QLineEdit.sizeHint() one sees that a line edit is typically wide enough to display 17 latin "x" characters. I tried to replicate this in Python and change it to display 4 characters but I failed in getting the style dependent margins of the line edit correctly due to limitations of the Python binding of Qt.
A simple:
e = QtGui.QLineEdit()
fm = e.fontMetrics()
m = e.textMargins()
c = e.contentsMargins()
w = 4*fm.width('x')+m.left()+m.right()+c.left()+c.right()
is returning 24 in my case which however is not enough to display four characters like "abcd" in a QLineEdit. A better value would be about 32 which you can set for example like.
e.setMaximumWidth(w+8) # mysterious additional factor required
which might still be okay even if the font is changed on many systems.
I'm trying to write a music program that would display Chordpro files in python. Similar to this image, I want the chords, comments, and lyrics to each have different colours. I've tried these widgets:
I tried separating chords, comments and lyrics into multiple strings that could overlap on a canvas (with a different colour for each string) to make the full song, but sadly whitespace overrides previously rendered text, so I could only see the last layer.
Label/Message doesn't have functionality for multiple colours unless make a label for each line, which is very tedious, considering I want the font size to be adjustable too.
Text is editable, which I don't want.
Is there some kind of module or other tkinter widget that would allow separately coloured lines?
Just draw the separate lines at different y positions (heights) on the canvas. It's the first two parameters of the create_text() function. E.g.
self.canvas = Canvas(root, width=800, height=650, bg = '#afeeee')
self.canvas.create_text(100,10,fill="darkblue",font="Times 20 italic bold",
text="Greensleeves are my...")
So here, change 10 to the line position you want etc. Code copied from Python: how to add text inside a canvas?
I draw texts with wxPython on my DC. The position of the text is depending on the width of my frame, in this case the variable "w".
It is calculated in my code like this:
dc.DrawText("Overview", w/2-wt/2, 10)
But now, if the forumlar "w/2-wt/2" is smaller than a certain value I want to set it to a fix value.
I know how to do it with an if than else, but my personal favorit would be to do it inline, to keep my code short an simple.
Is there a way to do this?
I have this GUI program, and one of the Frames has an OptionMenu in it. Whenever I select something in the OptionMenu, it resizes the parent frame, even though it has more than enough room to fit in the current panel. It's hard to describe in words, so here's an image of what I mean:
You can see that the purple, blue, and red frames have expanded with the size increase of the OptionMenu.
The window is split into two Frames, the left and right, with a Grid Layout, with a weight of 3 and 2, respectively. Inside of the two frames is a Pack layout of each colored panel, set to fill X. Inside of Purple, I have set this optionMenu, and packed it to the left of the panel.
When it changes its contents, it resizes the entire right frame, ignoring the grid weights and throwing off the balance of the GUI. If I set a fixed width for the OptionMenu, it doesn't resize, but still grows the frame out of alignment with the GridLayout. How can I get the frames to not resize based off of the width of this one element, and just place the element inside of the frame, which has more than enough room to handle it, even at it's widest?
Here's a boiled-down version of the GUI code I'm using, the full code is a bit too long since each panel is broken into classes for future functionality:
root = Tk()
root.geometry('640x480')
#Begin defining left frame areas
leftFrame = Frame(root)
grayPanel = Frame(leftFrame,bg="gray")
whitePanel = Frame(leftFrame,bg="white", height=50)
grayPanel.pack(expand=True,fill=BOTH)
whitePanel.pack(fill=X)
#Begin defining right frame areas
rightFrame = Frame(root)
purplePanel = Frame(rightFrame,bg="purple", height=50)
bluePanel = Frame(rightFrame,bg="blue")
redPanel = Frame(rightFrame,bg="red", height=100)
purplePanel.pack(fill=X)
bluePanel.pack(expand=True,fill=BOTH)
redPanel.pack(fill=X)
#create the options dropdown for purple
currentOption = StringVar(purplePanel)
currentOption.set("None")
optList = ["None","Some incredibly long string that breaks everything"]
options = OptionMenu(purplePanel,currentOption,*optList)
options.pack(side=LEFT)
leftFrame.grid(row=0,column=0,sticky=N+S+E+W)
rightFrame.grid(row=0,column=1,sticky=N+S+E+W)
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=3)
root.grid_columnconfigure(1, weight=2)
root.mainloop()
The root of the problem lies in the fact you don't give any widgets a size, and all the frames are empty. The following description may sound confusing, but it's actually pretty logical, deterministic, and consistent with the documented behavior.
The natural size of the widgets and columns
Because none of the frames have an explicit width, they default to a width of 0 (zero). The optionmenu has the behavior that it adapts its size to be large enough to show the value (except on the Mac, I think). It is the only widget with a non-zero requested (or "natural") size.
The grid is responsible for the left and right sides. The left size is of zero width, and the right side is the width of the option menu. For the sake of this discussion let's say that it is 140 pixels wide just to make math easy; the actual size may be bigger or smaller depending on your fonts, and we're ignoring borders to keep the math simple. Therefore, grid thinks the left wants to be zero, and the right wants to be 140. That is the only information it starts with when it tries to make everything fit.
Dealing with extra space
You force the window to be 640 pixels wide but its children are only require 140 pixels, thus it has 500 extra pixels. You use column weights of three for the left and two for the right, so for every five pixels of extra space, three will go to the left and two will go to the right. That means that the left column gets 300 pixels of the extra space and the right column will get 200 pixels of the extra space. That makes the left column 300 pixels (0+300) and the right 340 (140+200). That's why they appear roughly the same size at startup.
What happens when you change the size of the optionmenu
Now, you change the value of the optionmenu, causing it to grow. Let's say the new width of the optionmenu is 440 pixels. The left side still wants to be zero, but now the right side wants to be 440. That means there are only 200 pixels of extra space: 120 for the left and 80 for the right. Remember, the left column's natural size is zero, so zero plus 120 means the left column will be 120 pixels wide. On the right, the natural size is 440. 440 plus the extra 80 means the right side will now be 520 pixels wide.
That is why, when there's a long option menu, the left side shrinks and the right side has extra space. You requested the left be zero, so it only gets to use the extra space after all of the other widgets with an actual size are laid out. The right side has a non-zero natural size, but the grid algorithm has space left over that it has to allocate to the right.
How to get uniform column widths
You didn't ask, but I'm going to assume a follow-up question will be "how do I make the two columns the same size?" The answer is to use the uniform option with grid. Tell grid you want the two columns to be of a uniform size like this:
root.grid_columnconfigure(0, weight=3, uniform="column")
root.grid_columnconfigure(1, weight=2, uniform="column")
uniform takes any string as an argument. All columns (or rows) with the same value are considered a "uniform group".
Note that with uniform, grid still honors the weight. it's just that with uniform the columns are guaranteed to strictly follow the column weights. In your case that means you have a 3:2 ratio between the left and right wide
Canonical information on grid and pack algorithms
Tkinter is a wrapper around a tcl interpreter with the tk toolkit. While the syntax is different than python, it's easy to translate the tcl documentation into python. Here are links to the descriptions of how grid and pack work:
The Grid Algorithm
The Packer Algorithm
I'm using Tkinter to design a UI for an application. I'm using grid geometry and while specifying button width (or any widget width), I realized that width should be specified in text units and not pixels. Since I want to make it platform independent and screen size independent Is there any method to get max text unit width ? So that I can do math on basis of that.
For example:
I've 10 buttons in a row, which should be of equal width. If I hard code a width value specific to current screen value, it would not work on diff screen wise.
Thanks.
Using the tkinter.font package you can create Tk font objects to define a font and call the measure method to obtain the screen width of text using that font.
import tkinter as tk
import tkinter.font as tkfont
root = tk.Tk()
font = tkfont.Font(family="Consolas", size=10, weight="normal")
m_len = font.measure("m")
For monospace fonts, any character will do. For proportional text, if you give the whole string you get the screen length of the string. Otherwise you typically get the size of m or n as either a maximum or an average character width.
However, buttons are typically all the same size on many UI styles. Varying the button sizes is likely to look quite poor. You can specify a negative width to set a minimum width for the widget which can be helpful.
If you want to make a GUI that is "platform independent and screen size independent", you definitely do not want to be measuring sizes yourself. Unless, by saying you want something platform independent, you're saying you want a button to be X pixels regardless of pixel density or screen resolution (which seems like a very bad idea).
The whole reason tkinter supports measuring in character units, along with options for widgets to stretch and shrink, is to support platform independence. When you start working at the pixel level, you will have many, many problems when you run the code on other platforms, or on other displays, or with other fonts.
That being said, the measure method of a font can tell you exactly how many pixels a given string will require in a given font. If you want to know how wide "one character" is, you can use the measure method on the string "0", which is what tkinter uses as a base when computing widths based on characters.
If you want buttons to be exactly the same size, using character widths will give you that, because it isn't the width of 10 actual characters in that widget, but ten average character widths. In that case, "10 characters" will be the same for every widget, no matter what the contents of that widget.
You must be having a root variable like-
import tkinter as tk
root = tk.Tk()
You can use root.winfo_sccreenwidth() for width
width = root.winfo_screenwidth() #width of screen
height = root.winfo_screenheight() # height of screen
I am not sure about any way to get the screenwidth in text units, it may not even be possible.
A solution would be to be able to specify the button width in pixels, you can do this by following a simple example given here.
What you do is - put the Button widget inside a frame and specify the height and width for the frame , and then make frame not propagate the size using grid_propagate(False) for the frame and then making the button expand upto the frame (maybe by using grid sticky="we" )