So I'm learning python and the book that is teaching me gives me two ways to create a label using tkinker:
self.canvas.create_text(30,10,text="Welcome",tags="text")
&
self.lbl = Label(frame1, text = "Welcome")
In the former example, moving it is easy:
self.canvas.move("text", 1, 0)
In the latter example, changing it's background color is easy:
self.lbl["bg"] = "red"
However I do not know how to both move it AND change it's background color in either example, at least not how to move it incrementally. I can do this:
self.lbl.place(x=2)
But unless I can get the x coordinate ahead of time, I can only move it once. I could set it ahead of time, but I'd like to avoid that option if possible.
There are ways to do both.
Firstly, Canvas text does not have a background, but you can create your own with a rectangle.
text = self.canvas.create_text(30, 10, text="Welcome", tags="text")
# The canvas.bbox method returns the corner coordinates of the provided item id.
rect = self.canvas.create_rectangle(self.canvas.bbox(text), fill='red')
# Then you need to reposition the rectangle so that it is behind the text.
self.canvas.lower(rect, text)
From there you just move them the same way as you mentioned in your question. Adding a group tag to both the text and rectangle would save you from having to move both items separately.
Secondly, you can get the current x, y coordinates of a widget with the .winfo_x() and .winfo_y() methods. So moving the Label becomes a simple matter of addition/subtraction:
self.lbl.place(x=self.lbl.winfo_x()+2)
I do not know of a method that moves a widget in increments as the move method does for the canvas.
As for which is best, I can't think of much between them. I suppose using a Canvas would mean you couldn't overlap any other widgets that may be in the window, since the text would just scroll out of view, and if you start using the ttk version of Label then styling isn't quite as straight forward, although it's not difficult.
Related
So, I'm using the place method to have a widget overlap other widgets, but its position is relative (with winfo) to a widget that uses pack. When the parent frame is resized, the pack position will change, but the place position will not.
This is my code:
from tkinter import *
root = Tk()
root.geometry("200x300")
search = Entry(root)
search.pack()
search.update()
x = search.winfo_x()
y = search.winfo_y()
width = search.winfo_width()
height = search.winfo_height()
frame = LabelFrame(root, width=width, height=200)
frame.place(x=x, y=y+height)
root.mainloop()
The LabelFrame stays in its x and y position when the window is resized. The Entry widget will be used as a search bar and I want autocompletion under it. There will be widgets under the entry widget and the autocompletion will only appear when you are typing (That's not what I'm looking for though. Its just more exposition if you need it). So, is there a way to have the place widget always be relative to the pack widget. If you have any answers, thank you:)
If your goal is to put one widget relative to another, place lets you do that. It's great for things like tooltips or other transient widgets that don't otherwise fit into a normal layout.
The easiest way to do that is to make the widget a child of the controlling widget. For example, for your frame to be placed relative to the search box you can make it a child of the search box. If it's inconvenient to do that, you can use the in_ parameter to tell place which widget is the other widget.
For example, to place your labelframe immediately below the search box and with the same width as the search box you might do it something like this:
frame.place(
in_=search,
bordermode="outside",
anchor="nw",
relx=0,
rely=1.0,
y=5,
relwidth=1.0
)
This is what the options mean:
in_=search: place the frame relative to the search box
bordermode="outside": relative measurements are from the outside of the border (default is "inside")
anchor="nw": place the widget so that the northwest corner of the frame is at the computed coordinate
relx=0: place the anchor point 0% from the left edge of the search box
rely=1.0: place the frame at 100% of the height of the search box
y=5: add 5 pixels to the computed position so it floats just a little below the window
relwidth=1.0: make the width of the frame 100% the width of the search box.
Obviously you don't have to use y=5, I just added it to illustrate the additive behavior of using rely and y.
I have defined a Label object as:
panel = Label(image_frame, image=self.img, cursor="cross")
Now, I'd like to do draw polygon on top of this, and I have created a function called draw() that binds to a canvas and will allow me to draw a polygon on top of it. So, I know my draw() command works.
However, I need to do this on top of the panel that I've defined as a Label.
The biggest problem I'm having is this line in my draw() command
if event.widget.canvasx(event.x)-2 < orig_x < event.widget.canvasx(event.x)+2 and event.widget.canvasy(event.y)-2 < orig_y < event.widget.canvasy(event.y)+2 :
I'm producing the following error:
AttributeError: 'Label' object has no attribute 'canvasx'
Is there an analog for canvasx for Label object? How can I bypass this without changing Label? Or is changing Label to canvas my only option?
The only other thing I can think of is to have a transparent canvas behind the Label, but then on-resize, things get messy.
Is there an analog for canvasx for Label object? How can I bypass this without changing Label? Or is changing Label to canvas my only option?
No, because there is no need. canvasx exists because the canvas can be scrolled in any direction, and you need to be able to convert from widget coordinates to the inner canvas coordinates.
In the case of a label, if you click at the 0,0 coordinate of a label, that will always be the upper left corner of the label, because the interior of the label cannot be scrolled.
Note: it is not possible to embed a label widget (or any other widget) on a canvas, and then draw on top of the label. The official canvas documentation says this about that:
Note: due to restrictions in the ways that windows are managed, it is not possible to draw other graphical items (such as lines and images) on top of window items. A window item always obscures any graphics that overlap it, regardless of their order in the display list.
My model is a list of more or less complex objects. My view displays a list of rows, one per object. Each row show a couple of identifying properties of the corresponding object. The list is long, so I have a scrollbar.
To edit one object, the user clicks on the corresponding row and the row "expands" into an editor (which is still part of the list of rows). I.e., I'm not popping up a dialog box but just replacing the simple row with a more complex widget.
The way I do this is to grid.forget the old widget, create the editor widget and .grid in the same row. This works without problem.
In addition to this, I want to ensure that the editor widget is completely visible (even when, say, the row was very low in the window). To do this, I want to scroll the list so the editor widget is at the top of the visible region. This is some pseudo-code:
# find the clicked row
...
# get rid of the old widget
w = self.widgets[row]
w.grid_forget()
w.destroy()
# create the editor widget and .grid it in place
w = Editor(...)
w.grid(row=row, ...)
# find the editor y-position and scroll so that it is on
# the upper edge of the viewing area
y = w.winfo_y()
scrollcanvas.yview('moveto', 0.0)
scrollcanvas.yview('scroll', y, 'units') # units are pixels
This does not work. When I print y I get zero. I believe this is because
the position returned by winfo_y has not (yet) been updated because Tkinter updates those things asynchronously. But I don't know what to do about it except some disgusting hack such as finding y as the position of the previous row plus its height (aaaak!!!)
Would delaying the scrolling to the editor 'Configure' event help?
I want to split a LabelFrame into two label frames. So first, I created an other LabelFrame and tested if it displays well. But no, it is not displayed.
But when I change childLabelFrame to a simple Label or a simple Frame I see it displayed well.
I read some similar questions such as this one, but I did not do those errors in my case.
mainLabelFrame=LabelFrame(parent,text="Description:",padx=20,pady=20,200, width=400,relief=RIDGE)
childLabelFrame=LabelFrame(mainLabelFrame,text="Help",relief=RIDGE)
childLabelFrame.grid(row=0,column=0)
mainLabelFrame.grid(row=3,column=0,columnspan=3,sticky=E+W)
How to resolve this ?
It seems like childLabelFrame has zero size and thus is not drawn. Indeed, both childLabelFrame.winfo_width() and childLabelFrame.winfo_height() return 1.
It is drawn correctly if
you specify a size, like childLabelFrame = LabelFrame(mainLabelFrame, text="Help", height=100, width=200), or
you add something inside the child label frame, e.g. Label(childLabelFrame, text="label").grid().
I'm using Python and Tkinter to create a GUI for a program I'm writing, and I'm having a couple of problems.
I have three objects descended from LabelFrame in an object descended from Frame. One of the LabelFrame descendants is two columns of corresponding Label and Entry objects.
The problem is that there are a varying number of Label and Entry pairs, and there can be more than fit on the screen. I need a way to make a scrollbar for this LabelFrame so that everything fits on the screen. I've tried various ways of making a Scrollbar object, but nothing seems to work. How can I bind a scrollbar to this frame?
Also, I need to be able to refresh or reload this LabelFrame when the load_message() method is called, but it just redisplays the new pairs on top of the old ones (so when there are less pairs in the new set, the old set is still visible at the bottom). I've tried using grid_forget() but either nothing changes or the whole frame doesn't display. How can I forget this display and then redisplay it?
Here is the code for this class:
class freq_frame(LabelFrame):
def __init__(self, master = None, text = 'Substitutions'):
LabelFrame.__init__(self, master, text = text)
self.grid()
def load_message(self):
self.frequency = get_freq(message)
self.create_widgets()
def create_widgets(self):
self.label_list = [Label(self, text = get_label(char, self.frequency[char]), justify = LEFT) for char in self.frequency.keys()]
self.entry_list = [Entry(self, width = 1) for char in self.frequency.keys()]
for n in range(len(self.label_list)):
self.label_list[n].grid(column = 0, row = n)
for n in range(len(self.entry_list)):
self.entry_list[n].grid(column = 1, row = n)
If anyone can help with either of these problems, I'd appreciate it.
Also, this question seems like it might be a little thin, but I don't know what to add. Don't hesitate to ask for more information (but be specific).
Thanks!
Labelframes don't support scrolling. So the short answer to your question is "you can't". It sounds obvious, but if the documentation for a widget doesn't say it supports scrolling, it doesn't support scrolling.
However, there is a simple solution. First, add a canvas as a child to the labelframe and pack it so that it fills the labelframe. Attach scrollbars to the canvas and add them to the labelframe too. Then embed a frame within the canvas, add your widgets to that inner frame, and then adjust the scrollregion of the canvas to match the size of the frame after you've added all the inner labels and entries.
It sounds complicated, but it's really very straight-forward.
As for re-creating the widgets when you call load_message, calling grid_forget only removes them from view, it doesn't actually destroy the widgets. Over time you could potentially end up with hundreds of non-visible widgets which is almost certainly not what you want.
Instead, you want to first destroy all the existing widgets. That's pretty easy if they all are in the same parent, since you can ask the parent for a list of all its children. Just iterate over that list to delete each child, then add any new children. An even easier solution is to destroy and recreate that inner frame that contains the labels and entries. When you delete a widget, all child widgets get automatically destroyed. So, delete that inner frame, create a new one, and add your labels and entries again.