Trouble with Tkinter coordinates and intialization using place() - python

Very new to tkinter and came across my first major roadbloack. I have a list of labels that is follows around a parent label. I have a function that displays all of these labels based off the coordinates of this parent. Right now the coordinates for the parent label are set at 0,0 no matter where I place it.
Here is the initialization and call of DisplayUnits:
#operations
OperationsChildren = []
templabel1 = Label(MyCanvas, text = "Operations", font=("Arial Bold", 12))
templabel1.place(x = 30, y = 0,)
Operations = CreateSection("Operations", OperationsChildren, None, OperationsList)
Operations.HeaderLabel = ObjectLabel("Operations", templabel1, Operations, MouseObject)
Operations.ChildLabels = DisplayUnits(Operations, MyCanvas, MouseObject)
#medical
MedicalChildren = []
templabel2 = Label(MyCanvas, text = "Medical", font=("Arial Bold", 12))
templabel2.place(x =200, y = 50,)
Medical = CreateSection("Medical", MedicalChildren, None, MedicalList)
Medical.HeaderLabel = ObjectLabel("Medical", templabel2, Medical, MouseObject)
Medical.ChildLabels = DisplayUnits(Medical, MyCanvas, MouseObject)
And here is my display function
def DisplayUnits(Section, Frame, DebugLabel):
#remove labels inside of sections current label list
for x in Section.ChildLabels:
x.label.destroy()
#initialize label list that will be populated by the new labels
LabelList = []
#get initial x and y coordinates
startX = Section.HeaderLabel.label.winfo_x()
startY = Section.HeaderLabel.label.winfo_y()
print("start values are " + str(startX) + ", " + str(startY))
#set an iterator which will determine how far apart each label is spaced
SpaceIterator = startY + 20
for index, x in enumerate(Section.SecUnitList): #indexing not needed or used yet
tempName = x.name
tempLabel = Label(Frame, text = tempName, font=("Arial Bold", 12))
#set the location equal to the startx and the iterator which is the advancing Y value
tempLabel.place(x=startX,y=SpaceIterator)
#create ObjectLabel class using templabel,
UnitLabel = ObjectLabel(tempName, tempLabel, None, DebugLabel)
LabelList.append(UnitLabel)
print("Display Iterator is at " + str(SpaceIterator) + " for the unit "+ tempName)
SpaceIterator = SpaceIterator+20 #create label one row below last row
return LabelList
this is what I am seeing
this is what I want and am expecting to happen
changing to pack and grid based for the parent label. Adding debugs that show the header label location is considered to be at 0,0 even though it is not. I think it might have to do with local vs global positioning but don't know where to even start on checking that.

While I don't normally recommend using place, you can achieve what you want by place's ability to do relative placement. Since we can't reproduce your problem from the code snippets you've presented, here's a simple example that illustrates the concept.
The key is using the in_ parameter which defines what the relative coordinates are relative to, and the relx (relative x) and rely (relative y) parameters. It also sets bordermode to "outside", which means that the relative coordinates are relative to the widget edges rather than the interior portions of the widget.
import tkinter as tk
root = tk.Tk()
canvas = tk.Canvas(root, background="blue")
canvas.pack(fill="both", expand=True)
medical_label = tk.Label(canvas, text="Medical")
r2_label = tk.Label(canvas, text="R2")
r3_label = tk.Label(canvas, text="R3")
medical_label.place(x=100, y=100)
r2_label.place(in_=medical_label, relx=0.0, rely=1.0, bordermode="outside")
r3_label.place(in_=r2_label, relx=0.0, rely=1.0, bordermode="outside")
root.mainloop()

Related

Insert Image in a Line of other Images, Tkinter

I have a certain number of elements which I have saved as photos. These elements, and therefore the photos, all have different lengths and I want to keep them that way. (See pictures below)
The elements are all lined up in a certain order.
I would now like to use a dropdown menu (or something similar) to select the element and a second dropdown menu to determine the position where the image should be inserted. But the order of the other elements should not be changed by this.
Structure
This is the code, that i have by now:
from tkinter import *
from tkinter import ttk
from tkinter import messagebox
tkFenster = Tk()
tkFenster.title('Test')
tkFenster.geometry('2700x1000')
frameElement = Frame(master=tkFenster, bg='#FBD975')
frameElement.place(x=5, y=340, width=2010, height=70)
imageElement1 = PhotoImage(file='E1.gif')
imageElement2 = PhotoImage(file='E2.gif')
imageElement3 = PhotoImage(file='E3.gif')
imageElement4 = PhotoImage(file='E4.gif')
labelElement = Label(master=frameElement, borderwidth=0, image=imageElement1)
labelElement.pack( side = LEFT)
labelElement2 = Label(master=frameElement, borderwidth=0, image=imageElement2)
labelElement2.pack( side = LEFT)
labelElement3 = Label(master=frameElement, borderwidth=0, image=imageElement4)
labelElement3.pack( side = LEFT)
labelElement4 = Label(master=frameElement, borderwidth=0, image=imageElement3)
labelElement4.pack( side = LEFT)
labelElement5 = Label(master=frameElement, borderwidth=0, image=imageElement4)
labelElement5.pack( side = LEFT)
labelElement6 = Label(master=frameElement, borderwidth=0, image=imageElement2)
labelElement6.pack( side = LEFT)
tkFenster.mainloop()
Repositioning labels in tkinter is relatively easy since most of the work is performed by tkinter.
In order to control label positions it is better to use the grid manager.
This method uses point and click as the easiest control.
Step 1:
Create a tk.Label and put an image into it:
label = tk.Label(labelFrame, image = a, anchor = tk.NW)
label.grid(row = 0, column = c)
Create a binding for each label:
label.bind('<ButtonPress-1>', swapImage)
Do this for all tk.Labels with images.
Step 2:
Images can now be selected via a single mouse click on label.
Since you need two images to swap, 'swapImage' must accumulate both label references before any action can take place.
Function swapImage must:
Find the label reference via label = event.widget
Store label reference in a list
Once this has been done for BOTH labels, extract their grid data with '.grid_info()'.
This will return a dictionary that contains all grid management data.
Now with both label grids available simply swap them.
Here is one example:
def swapImage(event):
labelB = event.widget
if items:
labelA = items[0]
ag = labelA.grid_info()
bg = labelB.grid_info()
labelA.grid(**bg)
labelB.grid(**ag)
items.clear()
else:
items.append(labelB)
NOTE: I've used a list called 'items' to accumulate user label selections.
To add more label images just repeat Step 1.

When using python Tkinter, how can I stop two entry box's in the same class showing the same input text?

I am having this issue with Python Tkinter. I am trying to make a user interface form screen which requires the user to enter values into entry box's displayed on screen. I have set it so the two Entry Box's are in the same class (that class being the interface screen). The problem is that while I type into one of the box's, the text which I type not only displays in the box in which I am typing into, but also in the other box.
Below is the code in question.
class GenericSkeleton: # The template for all the screens in the program
def __init__(self):
self.GenericGui = Tk()
self.GenericGui.title('Radial Arc Calculator')
self.GenericGui.geometry('360x540')
self.GenericGui.resizable(width = FALSE, height = FALSE)
Label(self.GenericGui,text = 'Radial Arc Calculator',font = ('Ariel',18)).place(x=65,y=35)
def destroy(self):
self.GenericGui.destroy()
class InputScreen(GenericSkeleton):
def __init__(self):
GenericSkeleton.__init__(self)
Button(self.GenericGui,text = 'CALCULATE',height = 1, width = 25, command = calculate, font = ('TkDefaultFont',14)).place(x=37,y=400)
Button(self.GenericGui,text = 'CLOSE',height = 1, width = 11, command = close, font = ('TkDefaultFont',14)).place(x=37, y=450)
Button(self.GenericGui,text = 'HELP', height = 1, width = 11, command = DisplayHelp, font = ('TkDefaultFont',14)).place(x=190, y=450)
Label(self.GenericGui,text = 'Enter Radius (mm):', font = ('TkDefaultFont',14)).place(x=37, y=180)
Label(self.GenericGui,text = 'Enter point distance (mm):', font = ('TkDefaultFont',14)).place(x=37, y=250)
Entry(self.GenericGui,textvariable = Radius, width = 10, font = ('TkDefaultFont',14)).place(x=210, y=180)
Entry(self.GenericGui,textvariable = Distance, width = 5, font = ('TkDefaultFont',14)).place(x=265, y=250)
run = InputScreen()
The entry box's are at the bottom of the code, I hope its enough/not too much to solve the problem.
The problem is that they both share the same textvariable (you use different variable names, but they have the same value which makes them the same in the eyes of tkinter). My advice is to not use the textvariable attribute. You don't need it.
However, if you remove the use of textvariable then you need to separate your widget creation from widget layout so that you can keep a reference to the widget. Then you can use the get method on the widget (rather than on the variable) to get the value:
self.entry1 = Entry(...)
self.entry2 = Entry(...)
self.entry1.place(...)
self.entry2.place(...)
Later, you can get the values like this:
radius = int(self.entry1.get())
distance = int(self.entry2.get())
If you do need the textvariable (usually only if you're using the trace feature of a tkinter variable), you must use a tkinter variable (StringVar, IntVar, etc) rather than a regular variable.

Tkinter - Find closest object of a certain type

The proglem I have here is that I am not able to find the closest object of a certain type. It is best if I give you the code and for you to see what I mean:
from Tkinter import *
root = Tk()
f=Frame(root)
f.grid()
w=Canvas(f)
def identify(event): ## this should identify the tag near to click
item = w.find_closest(event.x, event.y)[0]
print item
line1=w.create_line(50,50,150,150, width=5, tags="line")
line2=w.create_line(100,100,100,350, width=3, tags="line")
line3=w.create_line(150,150,150,450, width=3, tags = "line")
w.grid(row=0, column=0)
w.bind("<ButtonRelease-1>", identify)
u=Frame(f)
u.grid(row=0, column=1)
root.mainloop()
As you can see here, you are able to click anywhere on the screen and it will return the closest object. This is what I would like to achieve but having other objects on the screen which I WISH TO BE IGNORED. I am able to do this by using tags but the problem here is you need to click on the actual object for some reason. This code is being used to show my problem. My actual code is a Towers of Hanoi game, and I am aiming to find the closest pole so the disk is able to snap to it, but I cannot find the closest pole without clicking on every pole before moving a disk.
Here is the code showing my problem. Note: I have only changed "w.bind("", identify)" to "w.tag_bind("line", "", identify)"
from Tkinter import *
root = Tk()
f=Frame(root)
f.grid()
w=Canvas(f)
def identify(event): ## this should identify the tag near to click
item = w.find_closest(event.x, event.y)[0]
print item
line1=w.create_line(50,50,150,150, width=5, tags="line")
line2=w.create_line(100,100,100,350, width=3, tags="line")
line3=w.create_line(150,150,150,450, width=3, tags = "line")
w.grid(row=0, column=0)
w.tag_bind("line", "<ButtonRelease-1>", identify)
u=Frame(f)
u.grid(row=0, column=1)
root.mainloop()
It's little late for you, but maybe it will be useful for others...
I just faced the same problem today, and resolved it using a second Canvas with duplicates of the desired items.
This canvas is never printed. It's a little tricky, but I couldn't find better.
draft = Canvas(w) # if w is deleted, the draft is deleted
draft.delete(ALL) # if you use the fake canvas for other uses
concerned = w.find_withtag("line") # what you want
for obj in concerned: # copy on draft
# first, get the method to use
if w.type(obj) == "line": create = draft.create_line
# use "elif ..." to copy more types of objects
else: continue
# copy the element with its attributes
config = {opt:w.itemcget(obj, opt) for opt in w.itemconfig(obj)}
config["tags"] = str(obj) # I can retrieve the ID in "w" later with this trick
create(*w.coords(obj), **config)
# use coordinates relative to the canvas
x = w.canvasx(event.x)
y = w.canvasy(event.y)
item = draft.find_closest(x,y) # ID in draft (as a tuple of len 1)
if item: item = int( draft.gettags(*item)[0] ) # ID in w
else: item = None # closest not found
print(item)
I wish I could have used create(*w.coords(obj), **w.itemconfig(obj)), but itemconfig without arguments returns a dictionary whose values cannot be reused.
About item copy: Copy Tkinter Canvas Items
By the way, I used python 3, I don't know if it works in python 2.
You can determine the type of the object selected in your identify() via w.type(item). Since you may have lines that aren't poles in your game, you can add tags to identify which one(s) are.
Here's what I mean (note this is Python 3 code, but the same idea would have worked in Python 2.x):
import tkinter as tk
root = tk.Tk()
f = tk.Frame(root)
f.grid()
w = tk.Canvas(f)
def identify(event):
item = w.find_closest(event.x, event.y)[0]
print(f'type of item closest to click: {w.type(item)}')
# Check tags to see if it's a pole.
if w.type(item) == 'line': # Canvas line?
for tag in w.gettags(item): # Check its tags.
if tag.startswith('pole'):
print(f' You clicked near pole {w.gettags(item)[1]}')
break
else:
print(' You did not click near a pole')
line1 = w.create_line(50,50,150,150, width=5)
line2 = w.create_line(100,100,100,350, width=3, tags='pole 1')
line3 = w.create_line(150,150,150,450, width=3, tags='pole 2')
w.grid(row=0, column=0)
w.bind('<ButtonRelease-1>', identify)
u = tk.Frame(f)
u.grid(row=0, column=1)
root.mainloop()

How do I align PanedWindows in Tkinter?

I'm new to Tkinter, and finding it a bit rough to get the hang of it. The point of this piece of code is to calculate time using the equation (Time = (Velocity - Initial Velocity) / Acceleration) But I need to take user input for the variables.
Here's what I have so far. It would be great, except for the fact that the labels don't line up with the text widgets. Is there any easy way to do what I need?
def timF():
timPanel = Toplevel()
timPanel.wm_title("Time")
timCont = PanedWindow(timPanel, orient=VERTICAL)
timCont.pack(fill=BOTH, expand=1)
# Top Paned Window and contents #
timTopCont = PanedWindow(timCont, orient=HORIZONTAL)
timCont.add(timTopCont)
# Velocity label
timFVelL = Label(timTopCont, text="Velocity")
timTopCont.add(timFVelL)
# Initial Velocity label
timFiveL = Label(timTopCont, text="Initial Velocity")
timTopCont.add(timFiveL)
# Acceleration label
timFaccL = Label(timTopCont, text="Acceleration")
timTopCont.add(timFaccL)
# Bottom Paned Window and contents #
timBotCont = PanedWindow(timCont, orient=HORIZONTAL)
timCont.add(timBotCont)
# Velocity entry
timFVelE = Entry(timBotCont)
timBotCont.add(timFVelE)
# Initial Velocity entry
timFiveE = Entry(timBotCont)
timBotCont.add(timFiveE)
# Acceleration entry
timFAccE = Entry(timBotCont)
timBotCont.add(timFAccE)
Just use grid() to place the widgets, instead of pack(). It is the easiest way to do so if you know the concrete row and column of the layout you want to place each widget:
timFVelL.grid(row=0, column=0)
timFVelE.grid(row=0, column=1)
timFiveL.grid(row=1, column=0)
timFiveE.grid(row=1, column=1)
# ...

How do I create a Tiling layout / Flow layout in TkInter?

I want to to fill my window with, say, labels and I want them to wrap once the column would be bigger than the current window (or rather parent frame) size.
I've tried using the grid layout, but then I have to calculate the size of the content of each row myself, to know when to put the next element in the next row.
The reason I ask, is because I want to create some sort of tiled file icons.
Or asked differently, is there something like Swing's FlowLayout for TkInter?
What I do when I want something like this is use the text widget for a container. The text widget can have embedded widgets, and they wrap just like text. As long as your widgets are all the same height the effect is pretty nice.
For example (cut and pasted from the question at the author's request):
textwidget = tk.Text(master)
textwidget.pack(side=tk.LEFT, fill=tk.BOTH)
for f in os.listdir('/tmp'):
textwidget.window_create(tk.INSERT, window=tk.Label(textwidget, text=f))
Here is a way to make flow behavior inside a frame.
I wrote a function that will do this. Basically you pass a frame to the function (not root or top level) and the function will look at all the children of the frame, go through them measure their sizes and place them in the frame.
Here is the placement procedure
Place the first widget, and move x over an amount equal to its width.
Measure the next widget.
If placing the next widget would cause it to goes past the frame width, bump its x value to 0 and bump it down a y value equal to the largest widget in the current row (start a new row).
Reset the value of the largest widget since you are starting a new row.
Keep repeating until all widgets are placed.
Bind that procedure to the resizing of the frame event.
I used 3 functions to make this work:
The function that runs the procedure.
The function that binds the resizing of the frame to the function.
The function that unbinds the resizing of the frame.
Here are the functions:
from tkinter import *
def _reorganizeWidgetsWithPlace(frame):
widgetsFrame = frame
widgetDictionary = widgetsFrame.children
widgetKeys = [] # keys in key value pairs of the childwidgets
for key in widgetDictionary:
widgetKeys.append(key)
# initialization/priming loop
width = 0
i = 0
x = 0
y = 0
height = 0
maxheight = 0
# loop/algorithm for sorting
while i < len(widgetDictionary):
height = widgetDictionary[widgetKeys[i]].winfo_height()
if height > maxheight:
maxheight = height
width = width + widgetDictionary[widgetKeys[i]].winfo_width()
# always place first widget at 0,0
if i == 0:
x = 0
y = 0
width = widgetDictionary[widgetKeys[i]].winfo_width()
# if after adding width, this exceeds the frame width, bump
# widget down. Use maximimum height so far to bump down
# set x at 0 and start over with new row, reset maxheight
elif width > widgetsFrame.winfo_width():
y = y + maxheight
x = 0
width = widgetDictionary[widgetKeys[i]].winfo_width()
maxheight = height
# if after adding width, the widget row length does not exceed
# frame with, add the widget at the start of last widget's
# x value
else:
x = width-widgetDictionary[widgetKeys[i]].winfo_width()
# place the widget at the determined x value
widgetDictionary[widgetKeys[i]].place(x=x, y=y)
i += 1
widgetsFrame.update()
def organizeWidgetsWithPlace(frame):
_reorganizeWidgetsWithPlace(frame)
frame.bind("<Configure>", lambda event: _reorganizeWidgetsWithPlace(frame))
_reorganizeWidgetsWithPlace(frame)
def stopOrganizingWidgetsWithPlace(frame):
frame.unbind("<Configure>")
And here is an example of them in use:
def main():
root = Tk()
root.geometry("250x250")
myframe = Frame(root)
# make sure frame expands to fill parent window
myframe.pack(fill="both", expand=1)
buttonOrganize = Button(myframe, text='start organizing',
command=lambda: organizeWidgetsWithPlace(myframe))
buttonOrganize.pack()
buttonStopOrganize = Button(myframe, text='stop organizing',
command=lambda: stopOrganizingWidgetsWithPlace(myframe))
buttonStopOrganize.pack()
##### a bunch of widgets #####
button = Button(myframe, text="---a random Button---")
canvas = Canvas(myframe, width=80, height=20, bg="orange")
checkbutton = Checkbutton(myframe, text="---checkbutton----")
entry = Entry(myframe, text="entry")
label = Label(myframe, text="Label", height=4, width=20)
listbox = Listbox(myframe, height=3, width=20)
message = Message(myframe, text="hello from Message")
radioButton = Radiobutton(myframe, text="radio button")
scale_widget = Scale(myframe, from_=0, to=100, orient=HORIZONTAL)
scrollbar = Scrollbar(myframe)
textbox = Text(myframe, width=3, height=2)
textbox.insert(END, "Text Widget")
spinbox = Spinbox(myframe, from_=0, to=10)
root.mainloop()
main()
Notice:
That you do not need to grid, pack or place them. As long as you specify the frame, that will all be done at once when the function is called. So that is very convenient. And it can be annoying if you grid a widget, then try to pack another, then try to place another and you get that error that you can only use one geometry manager. I believe this will simply overwrite the previous choices and place them. I believe you can just drop this function in and it will take over management. So far that has always worked for me, but I think you should really not try to mix and match geometry managers.
Notice that initially the buttons are packed, but after pressing the button, they are placed.
I have added the "WithPlace" naming to the functions because I have a similar set of functions that do something very similar with the grid manager.

Categories

Resources