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()
Related
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()
How can one automatically resize a text widget to fit a text widget's height
There will not be any \n om the text widget, instead the text will wrap (whole word) around and continue down. wrap=WORD
How can this done?
My idea of approaching the problem statement: I was wondering if it was possible to count every time the text was wrapped around in the text widget, and by that given value somehow calculate the height of the text widget? - Just a thought .. I have no clue whether it's possible.
**WHY THIS IS NOT A DUPLICATE **
This is not a duplicate, link to the question you claim it is a duplicate of if you think so. They all have implemented a solution in which it check a '\n' in each keystroke in the text widget. My text widget won't have any '\n' in it at all. But instead wrap the words around !
This is NOT the solution I am looking for, since it is looking for '\n' and changes the height accordingly to how many of them it finds. Since I won't be using any '\n' but instead wrap the words around (Text(frame, wrap=WORDS)) no '\n' will not appeare making that solution USELESS!"
That is why this code, from the question people claim this is a duplicate of, WONT fix this question, this is NOT a duplicate.
wont fix my problem since it looks for '\n':
import Tkinter
class TkExample(Tkinter.Frame):
def __init__(self, parent):
Tkinter.Frame.__init__(self, parent)
self.init_ui()
def init_ui(self):
self.pack()
text_box = Tkinter.Text(self)
text_box.pack()
text_box.bind("<Key>", self.update_size)
def update_size(self, event):
widget_width = 0
widget_height = float(event.widget.index(Tkinter.END))
for line in event.widget.get("1.0", Tkinter.END).split("\n"):
if len(line) > widget_width:
widget_width = len(line)+1
event.widget.config(width=widget_width, height=widget_height)
if __name__ == '__main__':
root = Tkinter.Tk()
TkExample(root)
root.mainloop()
edit example
This is the reason why I am not using message widgets, they doesn't rearrange the text to fill out the text widget.
The tkinter text widget isn't designed to grow or shrink to fit its contents like a label widget. If all you need is to display plain text with no need to interactively edit more text, a Label is probably a better choice than Text.
That being said, it's possible to get the number of displayed lines in a text widget, and with that information you can resize the widget.
Here's an example that shows how to cause it to resize when you insert text programatically. It won't handle resizing as you type, though it can be made to do it.
The trick is to know that internally the text widget has a count method which you can call to get the number of displayed lines. Unfortunately, this method isn't exposed at the tkinter layer and thus requires a bit of knowledge of how tkinter works internally.
class ExpandoText(tk.Text):
def insert(self, *args, **kwargs):
result = tk.Text.insert(self, *args, **kwargs)
self.reset_height()
return result
def reset_height(self):
height = self.tk.call((self._w, "count", "-update", "-displaylines", "1.0", "end"))
self.configure(height=height)
Here is an example of how to use it:
root = tk.Tk()
text = ExpandoText(root, width=20, wrap="word")
text.pack(fill="both", expand=True)
root.update_idletasks()
text.insert("1.0", "This is a line of text that will initially be wrapped.")
root.after(5000, text.insert, "end", "This is more text")
root.mainloop()
When you run the code, you should see a window that looks like this:
If you don't manually resize the window, and wait 5 seconds, the window will grow to show the added text:
Unfortunately you must call update_idletasks before the insertion so that tkinter knows how wide the window will actually be. This might have visual side effects depending on how the rest of your code works (read: you might see a flash when the UI first starts up).
As the title says, when attempting to save the Canvas using Postscript, it works fine with all non-window elements (rects, ovals etc..) and it works perfectly in capturing window elements that are CURRENTLY on screen when I push the button. But none of the window elements outside of the screen at the time.
This issue seems so arbitrary I gotta wonder if there even is a solution, hopefully someone out there has figured something out.
Here is some example code, where I simplify to present the exact issue:
#!/usr/bin/python3
#
# This file is intended as a simplified example for Stack Overflow.
# The original program is far greater and is a writing tool for branching dialogue, much like Twine.
from tkinter import Tk, Canvas, Frame, Text, Label
class Canv(Canvas):
def __init__(self, parent):
"""Simple Canvas class."""
Canvas.__init__(self, parent)
self.parent = parent
self.config(background="white", width=960, height=640)
self.num = 1
self.pack()
self.bindings()
def bindings(self):
"""All the button bindings."""
self.bind("<Button-1>", self.add_window)
self.bind("<ButtonPress-2>", self.mark)
self.bind("<ButtonRelease-2>", self.drag)
self.bind("<Button-3>", self.take_ps)
def add_window(self, e):
"""Here I add the Label as a Canvas window.
And include an Oval to mark its location.
"""
text = "Textwindow {}".format(self.num)
self.num += 1
window = TextWindow(self, text)
pos = (self.canvasx(e.x), self.canvasy(e.y))
self.create_window(pos, window=window)
bbox = (pos[0]-50, pos[1]-50, pos[0]+50, pos[1]+50)
self.create_oval(bbox, width=3, outline="green")
def mark(self, e):
"""Simple Mark to drag method."""
self.scan_mark(e.x, e.y)
def drag(self, e):
"""This drags, using the middle mouse button, the canvas to move around."""
self.scan_dragto(e.x, e.y, 5)
def take_ps(self, e):
"""Here I take a .ps file of the Canvas.
Bear in mind the Canvas is virtually infinite, so I need to set the size of the .ps file
to the bounding box of every current element on the Canvas.
"""
x1, y1, x2, y2 = self.bbox("all")
self.postscript(file="outfile.ps", colormode="color", x=x1, y=y1, width=x2, height=y2)
print("Writing file outfile.ps...")
class TextWindow(Frame):
def __init__(self, parent, text):
"""Very simple label class.
Might have been overkill, I originally intended there to be more to this class,
but it proved unnecesary for this example.
"""
Frame.__init__(self, parent)
self.pack()
self.label = Label(self, text=text)
self.label.pack()
if __name__ == "__main__": #<---Boilerplate code to run tkinter.
root = Tk()
app = Canv(root)
root.mainloop()
This is an example .jpg based on the postscript.
As you can see from the image, all the green circles on the right have the window label intact. Well ALL the green circles are supposed to have them, and in the program they work fine, the are just not showing in the postscript. And yes, my screen was over the right circles when I clicked the take_ps button.
As for alternatives, I need the canvas to be draggable, I need it to expand, potentially vast distances in both direction. And I cannot put text directly on the canvas, as it would take too much space. It is intended to have text fields, not just a label in the windows on the canvas (became too much code for this example), and the reason I need text in a window, and not directly on the screen, is the text might easily take more space then it should. I need the canvas to show the RELATION between the text fields, and the text windows to contain the text for editing, not necessarily full display. As it says, I'm making a branching dialogue tool for a game, much like Twine.
I ran into this issue also. I was able to configure the canvas temporarily to match the size of the output image. Then I configured it back to the original size after I was done creating the postscript file.
height_0 = canvas.winfo_height()
width_0 = canvas.winfo_width()
canvas.config(width= max_width, height= max_height)
root.update()
canvas.postscript(file='filename.ps',colormode='color')
canvas.config(width= width_0, height= height_0)
root.update()
I have a tkinter window which I am able to make fullscreen, using geometry(width+height) and overrideredirect(True), but now when I return the window back to a normal size and execute the command overrideredirect(False), I cannot seem to get the window to automatically follow the size of the widgets inside it, as it would do had I not changed the size. Do you know any way which I could return the window to automatically following the size of the widgets again? Thank You in Advance!
Call the geometry with a value of "" to get it to reset itself to its natural size.
Tkinter is based on tk, and the tk docs say this on the matter:
If newGeometry is specified as an empty string then any existing
user-specified geometry for window is cancelled, and the window will
revert to the size requested internally by its widgets.
I believe you're looking for the winfo_reqwidth/reqheight() methods. These return the required width and height for all the widgets that are children of the widget they're called on. Just plug those into the geometry() method the same way you did to go fullscreen on your restore function, like this:
def fullscreen():
root.overrideredirect(True)
root.geometry('{0}x{1}+0+0'.format(root.winfo_screenwidth(), root.winfo_screenheight()))
def restore():
root.overrideredirect(False)
root.geometry('{0}x{1}'.format(root.winfo_reqwidth(), root.winfo_reqheight()))
root = Tk()
Button(root, text='Full Screen', command=fullscreen).pack()
Button(root, text='Restore', command=restore).pack()
root.mainloop()
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.