Home > Back-end >  How to place a widget in a specific position relative to the button clicked. (Tkinter)
How to place a widget in a specific position relative to the button clicked. (Tkinter)

Time:07-16

it's a code for a Calendar, recording the subjects for a specific day.

enter image description here

I want it to open a dropdown list of the subject list when the day is clicked. And when multiple subjects are selected, I want the selected strings to be displayed a little under the button that was clicked, on the screen.

enter image description here

PROBLEM, This is the code so far, I can’t accustom it to open the list, just under the specific button clicked. it opens in the center, I want it to open just under the specific button that is clicked. (I don't use an Optionbox because I want to select multiple items at once and it's not quite going well.)

import tkinter as tk
from tkinter import ttk
from tkinter import *
from PIL import ImageTk, Image

screen = tk.Tk()
screen.geometry("1423x817")
title = screen.title("Class Calendar")

# SCROLL BAR

# main frame that holds everything.
main_frame = Frame(screen, bg = 'blue')
main_frame.pack(fill = BOTH, expand = 1)

# CANVAS THAT IS SCROLLED

Canvasheight, Canvaswidth = 817, 1408
canvas = Canvas(main_frame, height = Canvasheight, width = Canvaswidth, borderwidth=0,
highlightthickness =0, relief = 'flat', highlightcolor="white", highlightbackground= 'white')
canvas.config(bg = '#a1d0ea')
canvas.pack(side = LEFT, fill= BOTH, expand = 1)

# ADDING SCROLLBAR TO CANVAS

scroll_bar = ttk.Scrollbar( main_frame, orient=VERTICAL, command = canvas.yview)
scroll_bar.pack(side = RIGHT, fill= Y)

# BIND TO MOUSE SCROLLING, CONFIGURING

canvas.configure(yscrollcommand=scroll_bar.set)
canvas.bind('<Configure>', lambda e: canvas.configure(scrollregion=canvas.bbox("all")))

# A FRAME TO HOLD THINGS ON THE CANVAS 

second_frame = Frame(main_frame, height = Canvasheight*8, width = Canvaswidth 5, borderwidth=0, highlightthickness =0, relief= 'flat', highlightcolor="white", highlightbackground='white')
second_frame.config(bg = '#a1d0ea')
canvas.create_window((0,0), window = second_frame, anchor='nw', width= Canvaswidth) # 'w' near middle lower half in scroll bar


# BACKGROUND PICTURE

img1 = ImageTk.PhotoImage(Image.open("Screenshot 2022-06-28 at 10.29.24 AM.png"))
label1 = Label(second_frame, image = img1, borderwidth=0, highlightthickness =0, relief= 'flat', highlightcolor="white", highlightbackground="white")
label1.config( bg = '#a1d0ea')
label1.pack()

img2 = ImageTk.PhotoImage(Image.open("Screenshot 2022-06-28 at 10.29.24 AM.png"))
label2 = Label(second_frame, image = img2, borderwidth=0, highlightthickness =0, relief= 'flat', highlightcolor="white", highlightbackground="white")
label2.config(bg = '#a1d0ea')
label2.pack()


# A SIDE MENUE

def toggle_win():
    bg1 = ("#262626")
    f3 = Frame(second_frame, width = 340, height = 817, bg = bg1)
    f3.place(x = 0, y =0)

    def dele():
        f3.destroy()

    menubtnclose = Button(f3, text = 'X', command = dele, border = 0, activebackground = '#12c4c0', borderwidth=0, highlightthickness =0).place(x = 5, y = 10)
anotherbtn = Button(second_frame, command = toggle_win, text =  '=', font = (39), borderwidth=0, highlightthickness =0, bg = '#9ad0f0', highlightbackground='#9ad0f0', highlightcolor='#9ad0f0').place(x= 5, y = 10)


###################################################################
# THE OPTIONS LIST BOX

def optionbox():
    #frame for list
    global f4
    f4 = Frame(second_frame, width = 200, height = 270, border = 1)
    f4.place(x = 570, y = 260)

    # func for closing
    def delet():
        def select():
            var = StringVar
            subject = []   
            sname = list2.curselection()
            for i in sname:
                op = list2.get(i)
                subject.append(op)
                for i in subject:                   
                    Label(second_frame, text = subject, borderwidth=0, highlightthickness =0, relief="flat", highlightcolor="white").place(x = newx, y = newy)
        select()
        f4.destroy()
    fclose = Button(f4, text = 'X', command = delet, border = 0, activebackground = '#12c4c0', borderwidth=0, highlightthickness =0).place(x = 1, y = 1)
    #listbox
    global clicked
    global sublist
    clicked = StringVar()
    global list2

    list2 = Listbox(f4, font = ("arial", 20), width = 16, height = 9, selectborderwidth=1, selectmode=MULTIPLE)
    list2.place(x = 0, y = 26)
   
    # list
    sublist = ["Biology", "Physics", "chemistry", "Science", "Sinhala"]

    #insert items
    for item in sublist:
        list2.insert(0, item)
   
###############################################################
# CALENDAR

# PRINTING MARCH TO WINDOW
march22_label = Label(second_frame, text = "MARCH", font = ("arial", 27),fg='black', borderwidth=0, highlightthickness =0)
march22_label.config(bg = '#9ad0f0')
march22_label.place( x= 664, y = 18)


# PRINTING WEEK TO WINDOW
marchweek22_label = Label(second_frame, text = 
"""
  Monday           Tuesday           Wednesday           Thursday             Friday              Saturday              Sunday  
""",                  
font= ('arial', 25),fg='black', borderwidth=0, highlightthickness =0)
marchweek22_label.config(bg = '#a1d0ea')
marchweek22_label.place(x= 100, y = 82)

def March22():
    i = 1
    global newy
    global newx    
    x, y = 310, 194
    while i < 7:
        newx, newy = x, y   30
        Button(second_frame, text = i, font = ("arial", 25), borderwidth=0, highlightthickness =0, command = optionbox).place(x = x, y = y)
        x = x   185
        i = i   1
    x, y = 125, y   130
    while i < 14:
        newx, newy = x, y   30
        Button(second_frame, text = i, font = ("arial", 25), borderwidth=0, highlightthickness =0, command = optionbox).place(x = x, y = y)
        x = x   185
        i = i   1
    x, y = 125, y   130
    while i < 21:
        newx, newy = x, y   30
        Button(second_frame, text = i, font = ("arial", 25),borderwidth=0, highlightthickness =0, command = optionbox).place(x = x, y = y)
        x = x   185
        i = i   1
    x, y = 125, y   130
    while i < 28:
        newx, newy = x, y   30
        Button(second_frame, text = i, font = ('arial', 25),borderwidth=0, highlightthickness =0, command = optionbox).place(x = x, y = y)
        x = x   185
        i = i   1
    x, y = 125, y   130
    while i < 32:
        newx, newy = x, y   30
        Button(second_frame, text = i, font = ("arial", 25),borderwidth=0, highlightthickness =0, command = optionbox).place(x = x, y = y)
        x = x   185
        i = i   1
March22()

screen.mainloop()

PROBLEM, And when another button is clicked, without closing the previous list. it seems to just open on it and not open a completely different list, so it gets stuck and the Destroy command doesn't work.

PROBLEM, And about displaying the selected subjects, I want it to be displayed under the specific button that was clicked, but I can't get that to work.

enter image description here

I’m quite new to python and Tkinter. I have tried a few ways to make it work, but I am not finding any successful results. Please help me in writing it. Please ask if I anything is unclear.

CodePudding user response:

Instead of using a function to make the option box, it would be better to use a class which subclasses Frame.

class OptionBox(Frame):
    def __init__(self, master, parent_x, parent_y):
        # Initialise frame and place it
        Frame.__init__(self, master, width = 200, height = 270, border = 1)
        self.place(x=parent_x, y=parent_y)
        # Store position of parent
        self.parent_x = parent_x
        self.parent_y = parent_y
        
        fclose = Button(self, text = 'X', command = self.delete, border = 0, activebackground = '#12c4c0', borderwidth=0, highlightthickness =0).place(x = 1, y = 1) 
        self.clicked = StringVar()

        self.list2 = Listbox(self, font = ("arial", 20), width = 16, height = 9, selectborderwidth=1, selectmode=MULTIPLE)
        self.list2.place(x = 0, y = 26)
       
        # list
        self.sublist = ["Biology", "Physics", "chemistry", "Science", "Sinhala"]

        #insert items
        for item in self.sublist:
            self.list2.insert(0, item)
    def delete(self):
        self.select()
        self.destroy()
    def select(self):
        var = StringVar()
        subject = []   
        sname = self.list2.curselection()
        for i in sname:
            op = self.list2.get(i)
            subject.append(op)
        for n, i in enumerate(subject):
            Label(second_frame, text = i, borderwidth=0, highlightthickness =0, relief="flat", highlightcolor="white").place(x = self.parent_x, y = self.parent_y   (n*20))

This is similar to what you had before, but can be created multiple times without breaking the others. If you haven't seen classes before, research object oriented programming in python. This class uses the position of it's parent (the button which was clicked) to work out where to place itself and the subject labels.

There is also a simpler way to create the buttons for each day.

def March22():    
    x, y = 310, 194
    # For each day in the month
    for i in range(1, 32):
        # Create a button
        b = Button(second_frame, text = i, font = ("arial", 25), borderwidth=0, highlightthickness =0)
        # Place it
        b.place(x = x, y = y)
        # Update the frame so we can get the height/width of the button
        second_frame.update_idletasks()
        # Create the option box command, increasing y by the height of the button so it is below it.
        b.config(command = lambda x=x, y=y b.winfo_height(): OptionBox(second_frame, x, y))
        x  = 185 # Increase x by 185 for every 1 across
        
        # Start a new line every time i is 1 less than a multiple of 7
        if i % 7 == 6:
            x = 125 # Reset x
            y  = 130 # Increase y

Instead of using multiple while loops you can refactor it into a single for loop. The button command creates a new instance of OptionBox relative to the position of the button.

  • Related