How to make Text widgets adaptive in Tkinter? - python

Ok so let's say I've got a Text object. How can I make it so that it gets an extra line (or height + 1) whenever I fill in the current line? like when it starts hiding the left of the line to show you the end?
Edit: since the question wasn't clear, I'll describe it more carefully.
Take this code as reference:
from tkinter import *
root = Tk()
text = Text(root, width=50, height=1)
text.pack
What it does, is creating a new Text widget of 1 line, and packs it. You may ask: Why don't you use the Entry widget? Because I want it to add more lines, instead of hiding what you already wrote to make some room for what you're writing, as shown below:
from tkinter import *
from threading import Thread
def adjustheight():
while True:
#check if whatever it's written takes more than a line to show
if takesmorethan1line == True:
text.config(height=(text.cget("height") + 1)
root = Tk()
text = Text(root, width=50, height=1)
text.pack
Thread(target = adjustheight).start()
root.mainloop()

I couldn't understand your question very well, But I assume that you have problem with wrapping a text. For instance when you reduce the window size, the text should break into multi lines. Here is as example:
import tkinter as tk
class MainWindow(tk.Tk):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.my_label = tk.Label(
self, text="Helllllooooooooooooooooooooooooo")
self.my_label.bind('<Configure>',
lambda e: self.my_label.config(
wraplength=self.my_label.winfo_width()
))
self.my_label.pack()
self.mainloop()
if __name__ == '__main__':
window = MainWindow()

Line breaking, also known as word wrapping, is breaking a section of text into lines so that it will fit into the available width of a page, window or other display area, Read more.
Solution
Tkinter's Text widget have this feature already built in. wrap is the option that will do it.
From the docs:
This option controls the display of lines that are too wide.
With the default behavior, wrap=tk.CHAR, any line that gets too long will be broken at any character.
Set wrap=tk.WORD and it will break the line after the last word that will fit.
Example
import tkinter as tk
root = tk.Tk()
text = tk.Text(root, wrap=tk.WORD)
text.pack(fill=tk.BOTH, expand=1)
root.mainloop()

Related

Why is my tkinter toplevel huge when I stated size constraints?

I'm aware this is probably a newb question, but I have yet to be able to find an answer. Here's a snippet of my code, that has a root window containing a button to open a Toplevel. The Toplevel pulls a random line from a text file to function as a sort of idea generator.
import random, fileinput
import tkinter as tk
from tkinter import *
root = tk.Tk()
root.title('Daydreamer')
#fname should be the file name of the image in working directory
fname = "bg.gif"
bg_image = tk.PhotoImage(file=fname)
#get width and height of image
w = bg_image.width()
h = bg_image.height()
#size window correctly
root.geometry("500x400")
cv = tk.Canvas(width=w, height=h)
cv.pack(side='top', fill='both', expand='yes')
cv.create_image(0,0,image=bg_image,anchor='nw')
#add a frame for text
mainframe=tk.Frame(root)
#new window for inspirations
def inspirations():
top = Toplevel(root)
top.geometry=("100x100")
top.title("Inspiration")
def idea():
textidea=None
for line in fileinput.input('textlist.txt'):
if random.randrange(fileinput.lineno())==0:
textidea=line
entrytext=tk.Text(top)
entrytext.insert(INSERT, textidea)
entrytext.insert(END, "Or press the Inspire Me button again for another idea!")
entrytext.pack()
idea()
top.mainloop()
#add buttons
btn1 = tk.Button(cv, text="Inspire Me", command=inspirations)
btn1.pack(side='left', padx=10, pady=5, anchor='sw')
root.mainloop()
Problem is, that Toplevel always comes out absolutely huge (larger than my root window), which looks incredibly silly for the small amount of content being displayed in it. Am I missing something really minute and stupid here? Help much appreciated.
The problem is that you aren't calling the geometry method, you're replacing it with a string.
Change this:
top.geometry=("100x100")
to this:
top.geometry("100x100")

How to wrap the text in a tkinter label dynamically?

The text in a label in tkinter can be wrapped into multiple lines when exceeding the limit given in the parameter wraplength.
However, this is a number of pixels, but instead, I'd like to use the full window width for this, and the wrap lenght should change whenever the user is changing the window size.
One approach could be to update the parameter manually with something like this:
def update_wraplength(id, root):
id.configure(wraplength=root.winfo_width())
root.after(10, lambda: update_wraplength(id,root))
Is there another way of doing this, maybe a parameter I do not know about?
You would have to update the wraplength every time the window size changes. You can detect when the window size changes with the "<Configure>" event.
my_label.bind('<Configure>', update_wraplength)
Remember it only works if you have the Label set up to expand to all available space.
Lets see if you can make sense of this code:
import Tkinter as tk
class WrappingLabel(tk.Label):
'''a type of Label that automatically adjusts the wrap to the size'''
def __init__(self, master=None, **kwargs):
tk.Label.__init__(self, master, **kwargs)
self.bind('<Configure>', lambda e: self.config(wraplength=self.winfo_width()))
def main():
root = tk.Tk()
root.geometry('200x200')
win = WrappingLabel(root, text="As in, you have a line of text in a Tkinter window, a Label. As the user drags the window narrower, the text remains unchanged until the window width means that the text gets cut off, at which point the text should wrap.")
win.pack(expand=True, fill=tk.X)
root.mainloop()
if __name__ == '__main__':
main()

change size of tkinter messagebox

In python, I am attempting the change the width of the tkinter messagebox window so that text can fit on one line.
import tkinter as tk
from tkinter import messagebox
root = tk.Tk()
messagebox.showinfo("info","this information goes beyond the width of the messagebox")
root.mainloop()
It's not possible to adjust the size of messagebox.
When to use the Message Widget
The widget can be used to display short text messages, using a single font. You can often use a plain Label instead. If you need to display text in multiple fonts, use a Text widget. -effbot
Also see:
Can I adjust the size of message box created by tkMessagebox?
#CharleyPathak is correct. You either need to put a newline in the middle of the text, because message boxes can display multiple lines, or create a custom dialog box.
Heres another method that gets the effect youre looking for but doesnt use messagebox. it looks a lot longer but it just offers much more in terms of customization.
def popupmsg():
popup = tk.Tk()
def leavemini():
popup.destroy()
popup.wm_title("Coming Soon")
popup.wm_attributes('-topmost', True) # keeps popup above everything until closed.
popup.wm_attributes("-fullscreen", True) # I chose to make mine fullscreen with transparent effects.
popup.configure(background='#4a4a4a') # this is outter background colour
popup.wm_attributes("-alpha", 0.95) # level of transparency
popup.config(bd=2, relief=FLAT) # tk style
# this next label (tk.button) is the text field holding your message. i put it in a tk.button so the sizing matched the "close" button
# also want to note that my button is very big due to it being used on a touch screen application.
label = tk.Button(popup, text="""PUT MESSAGE HERE""", background="#3e3e3e", font=headerfont,
width=30, height=11, relief=FLAT, state=DISABLED, disabledforeground="#3dcc8e")
label.pack(pady=18)
close_button = tk.Button(popup, text="Close", font=headerfont, command=leavemini, width=30, height=6,
background="#4a4a4a", relief=GROOVE, activebackground="#323232", foreground="#3dcc8e",
activeforeground="#0f8954")
close_button.pack()
I managed to have a proper size for my
"tkMessageBox.showinfo(title="Help", message = str(readme))" this way:
I wanted to show a help file (readme.txt).
def helpfile(filetype):
if filetype==1:
with open("readme.txt") as f:
readme = f.read()
tkMessageBox.showinfo(title="Help", message = str(readme))
I opened the file readme.txt and EDITED IT so that the length of all lines did not exeed about 65 chars. That worked well for me. I think it is important NOT TO HAVE LONG LINES which include CR/LF in between. So format the txt file properly.

I cannot change the text of a Label

I'm trying to source out code for a GUI program. I made a simple test and I cannot change the text value on the GUI, no error and nothing happens. Some issue with the mainloop of Tkinter?
serial.py:
import gui
gui.chiplabel.config(text="A.3f V" )
gui.py:
from Tkinter import *
root = Tk()
chiplabel = Label(root, relief=RIDGE, width = 9 , text ="Unknown",
padx=0, pady=0).grid(row = 0,column=5, sticky =W)
root.mainloop()
You have two main problems with your code. It needs to be restructured, and you're making a very common mistake with laying out your widgets.
Organizing your code
The way you have your code structured, your call to configure happens after mainloop exits, and after the widgets have been destroyed. You need to reorganize your code so that the call to mainloop is the last line of code that is executed.
In my opinion this is best accomplished by using classes and objects, but that's not strictly necessary. You simply need to not have any code after you call mainloop.
Laying out the widgets
The problem is this line:
chiplabel = Label( root, relief=RIDGE, width = 9 , text ="Unknown", padx=0, pady=0).grid(row = 0,column=5, sticky =W)
In python, when you do x=y().z(), x is given the value of z(). So, when you do chiplabel = Label(...).grid(...), chiplabel is given the value of grid(...). Grid always returns None, so chiplabel will always be None. Because of this, you can't reconfigure it because you've lost the reference to the widget.
The solution is to create the widget and lay out the widget in two steps.
One way to do this would be to create the UI in a class, e.g.:
import Tkinter as tk # note don't use wildcard imports
class GUI(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.chiplabel = tk.Label(
self,
padx=0,
pady=0,
relief=tk.RIDGE,
text="Unknown",
width=9,
) # note alphabetical order and consistent spacing
self.chiplabel.grid(
column=5,
row=0,
sticky=tk.W,
) # note grid is separate step
and don't run it in-place, so that you can import the class without running anything. Then your serial.py looks more like:
from gui import GUI
interface = GUI()
interface.chiplabel.config(text="A.3f V")
interface.mainloop()
If you want multiple frames, you could do something like Switching between frames in tkinter menu.

When I use update() with tkinter my Label writes another line instead of rewriting the same text

When I call the update() method using tkinter instead of rewriting the label it just writes the label under the previous call. I would like for this to rewrite over the previous line.
For Example:
root=Tk()
while True:
w=Label(root, text = (price, time))
w.pack()
root.update()
Your problem is simply this: when you do while True, you create an infinite loop. The code in that loop will run until you force the program to exit. In that loop you create a label. Thus, you will create an infinite number of labels.
If you want to update a label on a regular basis, take advantage of the already running infinite loop - the event loop. You can use after to schedule a function to be called in the future. That function can reschedule itself to run again, guaranteeing it will run until the program quits.
Here's a simple example:
import Tkinter as tk
import time
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.clock = tk.Label(self, text="")
self.clock.pack()
# start the clock "ticking"
self.update_clock()
def update_clock(self):
now = time.strftime("%H:%M:%S" , time.gmtime())
self.clock.configure(text=now)
# call this function again in one second
self.after(1000, self.update_clock)
if __name__== "__main__":
app = SampleApp()
app.mainloop()
No.
I suspect, without having seen it, that there are at least a couple of confusions in the code wDroter has written. In general, it is NOT necessary in well-structured Tkinter code to use update() at all. Here's a small example that illustrates updates to the text of a Label:
import Tkinter
import time
def update_the_label():
updated_text = time.strftime("The GM time now is %H:%M:%S.", time.gmtime())
w.configure(text = updated_text)
root = Tkinter.Tk()
w = Tkinter.Label(root, text = "Hello, world!")
b = Tkinter.Button(root, text = "Update the label", command = update_the_label)
w.pack()
b.pack()
root.mainloop()
Run this. Push the button. Each time you do so (as long as your pushes differ by at least a second), you'll see the text update.
you want to use .configure insted
while True:
w.Configure(text = (price, time))
root.update()
instead of
w.pack()
you can write
w.grid(row=0, column=0)
pack() in tkinter usually packs things in a single row/column. It lays things out along the sides of a box. Whereas, grid() has more of a table like structure. So when you write row=0 and column=0, it has no choice but to replace the previous if it exists. Because you have provided a very specific position instead of just pushing it to the window (which is hat pack() does)
The BadRoot class should demonstrate the problem that you are having. You can comment out the call to the class to verify with a complete, working example. If you run the code as written, it will update the label in the GoodRoot class. The first line that is commented out shows an alternative syntax for changing the text in your label.
from tkinter import Tk, Label
from time import sleep
from random import random
class BadRoot(Tk):
def __init__(self, price, time):
super().__init__()
self.labels = []
while True:
self.labels.append(Label(self, text=(price, time)))
self.labels[-1].pack()
self.update()
sleep(1)
class GoodRoot(Tk):
def __init__(self, callback):
super().__init__()
self.label = Label(self, text=str(callback()))
self.label.pack()
while True:
## self.label['text'] = str(callback())
self.label.configure(text=str(callback()))
self.update()
sleep(1)
if __name__ == '__main__':
## BadRoot('$1.38', '2:37 PM')
GoodRoot(random)
The problem with your original code is that a new label is created and packed into the interface each time through the loop. What you actually want to do is just edit the text being displayed by the label instead replacing the label with a new one. There are others ways of doing this, but this method should work for you.

Categories