How to set a maximum limit for dragging in tkinter PanedWindow? - python

import tkinter as tk
root = tk.Tk()
my_paned_window = tk.PanedWindow(master=root)
frame1 = tk.Frame(master=my_paned_window, bg='snow')
frame2 = tk.Frame(master=my_paned_window)
tk.Label(master=frame1, text='frame1').pack()
tk.Label(master=frame2, text='frame2').pack()
my_paned_window.add(frame1)
my_paned_window.add(frame2)
my_paned_window.pack(fill=tk.BOTH)
root.mainloop()
In the above code, I don't want the frame1 to expand too much when dragged. How can I set a limit for this?

There are no straightforward ways to do this. You can achieve this either by setting minsize for frame2.
Something like this:
...
my_paned_window.add(frame1)
my_paned_window.add(frame2, minsize=550)
...
Another way is to return "break" when the sash position is greater than the max-width so it no longer can be moved.
minimal example:
import tkinter as tk
class PanedWindow(tk.PanedWindow):
def __init__(self, *args, **kwargs):
super(PanedWindow, self).__init__(*args, **kwargs)
self.max_width = {}
self.bind("<B1-Motion>", self.check_width)
self.bind("<ButtonRelease-1>", self.set_width)
def add(self, child, max_width=None, *args):
super(PanedWindow, self).add(child, *args)
self.max_width[child] = max_width
def check_width(self, event):
for widget, width in self.max_width.items():
if width and widget.winfo_width() >= width:
self.paneconfig(widget, width=width)
return "break"
def set_width(self, event):
for widget, width in self.max_width.items():
if width and widget.winfo_width() >= width:
self.paneconfig(widget, width=width-1)
root = tk.Tk()
my_paned_window = PanedWindow(master=root, sashwidth=5)
max_width = 500
frame1 = tk.Frame(master=my_paned_window, bg='red')
frame2 = tk.Frame(master=my_paned_window, bg='blue')
frame3 = tk.Frame(master=my_paned_window, bg="green")
tk.Label(master=frame1, text='frame1').pack()
tk.Label(master=frame2, text='frame2').pack()
my_paned_window.add(frame1, max_width=500)
my_paned_window.add(frame2)
my_paned_window.pack(fill=tk.BOTH, expand=True)
root.mainloop()

Related

Button doesn't show up tkinter

I have a class that represents a Window that contains a canvas, a label and is soon to contain some color-coordinated buttons.
Here is the code:
class Canvas():
def __init__(self, width, height):
self.root = tk.Tk()
self.width = width
self.height = height
self.text_label = tk.Label(self.root, text="10",font=("Times New Roman", 20, "italic"))
self.text_label.pack()
self.canvas = tk.Canvas(master= self.root,width = self.width,height = self.height)
self.canvas.pack()
#======================
self.redBtn = tk.Button(master=self.root,text="hello",command=lambda:self.changeColor("Red"))
self.redBtn.pack()
#======================
self.root.mainloop()
canvas = Canvas(1980,1080)
Although I used redBtn.pack() on the button it doesn't show up.
Does anyone know why?
It worked. I can see number 10. Change this:
win = Window(1980,1080)
to
win = Canvas(1980,900)
So you can see the button on bottom.

How to insert scrollbar into canvas filled with buttons?

I'm trying to insert a scrollbar into a canvas filled with buttons (where, buttons can be switched to clickable text as well).
With the code below, the window seems OK.
However, when I uncomment the lines with the scrollbar, the window geometry scrambles.
Can anybody give a hand to figure out what's wrong with the code?
System: Windows 10, python 3.9.6.
import tkinter as tk
class tWindow(tk.Tk):
frame = None
canvas = None
scrollbar = None
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.geometry("640x480+50+50")
self.setup_frame()
self.setup_canvas()
def setup_frame(self):
self.frame = tk.Frame(master=self, width=640, height=480)
self.frame.configure(bg="#003163")
self.frame.place(x=0, y=0)
def setup_canvas(self):
self.update()
self.canvas = tk.Canvas(master=self.frame, bg="#006331",
width=int(self.frame.winfo_width() / 4), height=self.frame.winfo_height())
self.canvas.place(x=0, y=0)
self.update()
# self.scrollbar = tk.Scrollbar(master=self.canvas, orient=tk.VERTICAL)
# self.scrollbar.pack(side=tk.RIGHT, fill=tk.BOTH, expand=tk.TRUE)
# self.canvas.configure(yscrollcommand=self.scrollbar.set)
# self.scrollbar.configure(command=self.canvas.yview)
# self.update()
tmp_pos = 0
for i in range(20):
btn_tmp = tk.Button(master=self.canvas, text=f"testing testing testing testing testing {i:02} ...",
justify=tk.LEFT, wraplength=self.canvas.winfo_width(), bg="#00c0c0", fg="#000000")
btn_tmp.place(x=0, y=tmp_pos)
self.update()
tmp_pos = btn_tmp.winfo_y() + btn_tmp.winfo_height()
def main():
app = tWindow()
app.mainloop()
if __name__ == "__main__":
main()
See comments in code as additional to the link I have provided. Also see why update is considered harmfull and how do I organize my tkinter gui for more details.
import tkinter as tk
class tWindow(tk.Tk):
frame = None
canvas = None
scrollbar = None
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.geometry("640x480+50+50")
self.setup_frame()
self.setup_canvas()
def setup_frame(self):
self.frame = tk.Frame(master=self, width=640, height=480,bg="#003163")
self.frame.pack(side='left',fill='both',expand=True)##pack left
## self.frame.place(x=0, y=0) ##place requiers too think to much
def setup_canvas(self):
## self.update() ##update is harmfull
self.canvas = tk.Canvas(master=self.frame, bg='orange',#"#006331",
width=int(self.frame.winfo_reqwidth() / 4), height=self.frame.winfo_reqheight())
##use reqwidth/hight to avoid the use of update.
##It takes the requested size instead the actual
self.canvas.pack(side='left')
self.canvas_frame = tk.Frame(self.canvas,bg="#006331")
##frame to pack buttons in canvas
self.canvas.create_window((0,0), window=self.canvas_frame, anchor="nw")
##show frame in canvas
self.scrollbar = tk.Scrollbar(master=self.frame, orient=tk.VERTICAL)
self.scrollbar.pack(side='left',fill='y')
self.canvas.configure(yscrollcommand=self.scrollbar.set)
self.scrollbar.configure(command=self.canvas.yview)
self.canvas_frame.bind('<Configure>',self.oncanvasconfigure)
##bind event configure to update the scrollregion
for i in range(20):
btn_tmp = tk.Button(master=self.canvas_frame, text=f"testing testing testing testing testing {i:02} ...",
justify=tk.LEFT, wraplength=self.canvas.winfo_reqwidth(), bg="#00c0c0", fg="#000000")
btn_tmp.pack()
def oncanvasconfigure(self,event):
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
def main():
app = tWindow()
app.mainloop()
if __name__ == "__main__":
main()

tkinter- subclass of button not placing on screen from inside another class

I'm making a ide for brainf*ck in python using tkinter and I'm adding a recent projects section but when I'm placing the buttons they do not appear on the screen.
Here is the code for the Scene:
from tkinter import *
from tkinter import filedialog as File
import tkinter as tk
class HoverButton(tk.Button):
def __init__(self, master, **kw):
tk.Button.__init__(self, master=master, **kw)
self.defaultBackground = "#5d5d5d"
self['background'] = self.defaultBackground
self['activebackground'] = "#6d6d6d"
self.bind("<Enter>", self.on_enter)
self.bind("<Leave>", self.on_leave)
def on_enter(self, e):
self['background'] = "#6d6d6d"
def on_leave(self, e):
self['background'] = self.defaultBackground
class ProjectPage(Frame):
def __init__(self, master, projects=[]):
super().__init__(master)
self.projects = projects
self.buttons = []
self.mstr = self.master.master
self.title = "PesolIde: Projets"
self.width = 800
self.height = 500
self.color = "#4d4d4d"
self.projectFrame = tk.Frame(self.mstr,width=800,height=50,bg="#5d5d5d")
self.newProject = HoverButton(self.mstr,text="New Project", height=1, bg="#6d6d6d")
self.openProject = HoverButton(self.mstr,text="Open Project", height=1,bg="#6d6d6d", command=OpenAsk)
self.projectdisplay = tk.Frame(self.mstr, width=700, height=300, bg="#5d5d5d", highlightbackground="black", highlightthickness=1)
for i in range(len(self.projects)):
self.buttons.append(HoverButton(master, text=self.projects[i].split(':')[0], width=50, height=1))
if len(self.buttons)>=40:
break
self.loaded = False
def show(self):
self.projectFrame.place(x=0, y=0)
self.newProject.place(x=20, y=10)
self.openProject.place(x=120, y=10)
self.projectdisplay.place(x=50,y=100)
self.y = 100
print(len(self.buttons))
for i in range(len(self.buttons)):
print("placing " + str(self.buttons[i]))
self.buttons[i].place(x=50,y=100+(20*i))
self.master.set(title=self.title,width=self.width,height=self.height)
self.master.master['bg'] = self.color
def hide(self):
self.newProject.place_forget()
self.openProject.place_forget()
def load(self):
if not self.loaded:
self.newProject.place_forget()
self.openProject.place_forget()
self.loaded = True
def unload(self):
self.newProject.destroy()
self.openProject.destroy()
def OpenAsk():
name = File.askopenfilename()
and here is the code for main.py:
from tkinter import *
import src.framework.modules.Window.Window as windows
import src.framework.src.Scenes.all as Scenes
import tkinter as tk
root = tk.Tk()
window = windows.window(root, "", 800, 500)
window.place()
projects = open("projects.txt",'r').read().split("\n")
start = Scenes.ProjectPage.ProjectPage(window,projects)
start.show()
window.mainloop()
When I make a HoverButton outside the ProjectPage class in the ProjectPage file, it appears as expected but not when initialised from within the class of directly from the main file.
Here are some screenshots.
The output from running main.py:
The output from running from outside the ProjectPage class with the code on the left:
Try to insert values for relx, rely, relwidth, relheight as attributes in "place" or you can as well insert height, width as attributes for place.
Check out the documentation: https://www.tutorialspoint.com/python/tk_place.htm

Tkinter canvas not shrinking more than contents

When I expand my window, the Canvas expands properly. However, when I shrink the window, the Canvas does not shrink back like I want it to. At the very least, I want to make absolutely sure that the controls at the bottom do not get cut off and that the scroll bar activates if the contents of the Canvas don't all fit on the screen.
The only solution I could think of was binding to the <Configure> event of the Toplevel and having the program manually preform all of the calculations to figure out exactly how big the canvas should be to fit all of the items, but the canvas refused to shrink to the size I assigned to it.
Can anyone help me fix this?
Here is an image of what I mean:
Here is the relevant code:
import sys
if sys.version_info[0] < 3:
import Tkinter as tk
import ttk
else:
import tkinter as tk
from tkinter import ttk
class ResultsWindow(tk.Toplevel):
"""
Window that displays the results of the search.
"""
def __init__(self, master, results, *args, **kwargs):
tk.Toplevel.__init__(self, master, *args, **kwargs)
self.__master = master
self.__lg = ListsGroup(self, results)
self.__controlsFrame = ttk.Frame(self)
self.__okButton = ttk.Button(self.__controlsFrame, text = 'Delete Selection')
self.__cancelButton = ttk.Button(self.__controlsFrame, text = 'Cancel')
self.__lg.pack(side = 'top', expand = True, fill = 'both')
self.__controlsFrame.pack(side = 'bottom')
self.__okButton.pack(side = 'left')
self.__cancelButton.pack(side = 'right')
class ListsGroup(ttk.Frame): # TODO: Come up with a better name
"""
The grouping thing that contains the results list.
"""
def __init__(self, master, results, *args, **kwargs):
ttk.Frame.__init__(self, master, *args, **kwargs)
self.__sb = ttk.Scrollbar(self, orient = tk.VERTICAL)
self.__can = tk.Canvas(self, height = 0, yscrollcommand = self.__sb.set)
self.__sb.configure(command = self.__can.yview)
self.grid_columnconfigure(0, weight = 1)
self.grid_columnconfigure(1, weight = 0)
self.grid_rowconfigure(0, weight = 1)
self.__can.grid(column = 0, row = 0, sticky = 'nsew')
self.__sb.grid(column = 1, row = 0, sticky = 'nsew')
self.__packable = []
self.__entries = []
for x in results:
self.__entries.append(ResultEntry(self.__can, x))
self.__entries[-1].pack(side = 'top', expand = False, fill = 'x')
class ResultEntry(ttk.Frame):
"""
A single result group.
"""
def __init__(self, master, group, *args, **kwargs):
ttk.Frame.__init__(self, master, *args, **kwargs)
self.__listbox = tk.Listbox(self, selectmode = 'extended', height = len(group), borderwidth = 1)
self.__statusbox = tk.Canvas(self, width = 20, height = 0, bg = 'yellow', borderwidth = 0)
for x in group:
self.__listbox.insert('end', x)
self.__listbox.pack(side = 'left', expand = True, fill = 'both')
self.__statusbox.pack(side = 'right', expand = False)
self.__len = len(group)
self.bind('<Configure>', self.fix_size)
def fix_size(self, event = None):
"""
Function to fix the size of the canvas to the size of the tree view.
"""
self.__statusbox.configure(height = self.winfo_height() - 4)
Here is the function I used to test it and that created the picture:
def rtest():
a = tk.Tk()
b = ResultsWindow(a, [['1','2','3','4'], ['2', 'a'], ['h', '5']])
a.mainloop()
Apparently my problem was that I was using the Pack geometry manager inside the canvas as opposed to the Grid geometry manager. Changing the geometry manager fixed all of my problems.
Thank you to #BryanOakley for pointing out that you can't scroll with the pack manager. I had absolutely no idea.

Tkinter <enter> and <leave> Events Not Working Properly

Forgive me in advance for my code, but I'm simply making this for friend of mine to automatically populate a GUI interface with song information, channel information for each song, and things such as images attached to the songs. Right now I'm only scraping from a playlist on Youtube and a playlist on Soundcloud. I have all of that properly working, but me being new to frontend development left me in a horrible spot to make a decent application for him. I had a lot in mind that I could have done, but now I'm simply creating buttons with each song title as the text. Here is an image of my progress. I still have to find a way to attach each image to each button for the on_enter event, but that is for later on. As you can see, I have a on_leave function commented out. I was using that to delete the self.image_window each time I left a button. Problem is even a minuscule amount of mouse movement would cause the window to be delete and recreated dozens of times. How do I make it static so when I am hovering over a button it doesn't spam create/delete the window?
Thanks!
from Tkinter import *
import json
import os
import webbrowser
class GUIPopulator(Frame):
def __init__(self, parent):
Frame.__init__(self)
self.parent = parent
self.configure(bg='PeachPuff2')
self.columnconfigure(20, weight=1)
self.rowconfigure(30, weight=1)
self.curtab = None
self.tabs = {}
self.pack(fill=BOTH, expand=1, padx=5, pady=5)
self.column = 0
self.row = 0
def on_enter(self, event):
self.image_window = Toplevel(self)
self.img_path = os.getcwd() + '/Rotating_earth_(large).gif'
self.img = PhotoImage(file=self.img_path)
#self.image_window.minsize(width=200, height=250)
self.image_window.title("Preview")
canvas = Canvas(self.image_window, width=200, height=200)
canvas.pack()
canvas.create_image(100, 100, image=self.img)
#def on_leave(self, enter):
def addTab(self, id):
tabslen = len(self.tabs)
tab = {}
if self.row < 30:
btn = Button(self, text=id,highlightbackground='PeachPuff2' ,command=lambda: self.raiseTab(id))
btn.grid(row=self.row, column=self.column, sticky=W+E)
btn.bind("<Enter>", self.on_enter)
#btn.bind("<Leave>", self.on_leave)
tab['id']=id
tab['btn']=btn
self.tabs[tabslen] = tab
self.raiseTab(id)
self.row +=1
else:
self.row = 0
self.column +=1
btn = Button(self, text=id,highlightbackground='PeachPuff2' ,command=lambda: self.raiseTab(id))
btn.grid(row=self.row, column=self.column, sticky=W+E)
tab['id']=id
tab['btn']=btn
self.tabs[tabslen] = tab
self.raiseTab(id)
def raiseTab(self, tabid):
with open(os.getcwd() + '/../PlaylistListener/CurrentSongs.json') as current_songs:
c_songs = json.load(current_songs)
print(tabid)
if self.curtab!= None and self.curtab != tabid and len(self.tabs)>1:
try:
#webbrowser.open(c_songs[tabid]['link'])
webbrowser.open_new('http://youtube.com')
except:
pass
def main():
root = Tk()
root.title('Playlist Scraper')
root.geometry("1920x1080+300+300")
t = GUIPopulator(root)
with open(os.getcwd() + '/../PlaylistListener/CurrentSongs.json') as current_songs:
c_songs = json.load(current_songs)
for song in c_songs:
t.addTab(song)
root.mainloop()
if __name__ == '__main__':
main()
Example of JSON file provided:
{
"F\u00d8RD - Shadows (feat. Samsaruh)": {
"page_title": "youtube",
"link": "youtube.com/watch?v=CNiV6Pne50U&index=32&list=PLkx04k4VGz1tH_pnRl_5xBU1BLE3PYuzd",
"id": "CNiV6Pne50U",
"channel": "youtube.com/watch?v=CNiV6Pne50U&index=32&list=PLkx04k4VGz1tH_pnRl_5xBU1BLE3PYuzd",
"image_path": [
"http://i4.ytimg.com/vi/CNiV6Pne50U/hqdefault.jpg",
"CNiV6Pne50U.jpg"
]
},
"Katelyn Tarver - You Don't Know (tof\u00fb remix)": {
"page_title": "youtube",
"link": "youtube.com/watch?v=7pPNv38JzD4&index=43&list=PLkx04k4VGz1tH_pnRl_5xBU1BLE3PYuzd",
"id": "7pPNv38JzD4",
"channel": "youtube.com/watch?v=7pPNv38JzD4&index=43&list=PLkx04k4VGz1tH_pnRl_5xBU1BLE3PYuzd",
"image_path": [
"http://i4.ytimg.com/vi/7pPNv38JzD4/hqdefault.jpg",
"7pPNv38JzD4.jpg"
]
},
"Illenium - Crawl Outta Love (feat. Annika Wells)": {
"page_title": "youtube",
"link": "youtube.com/watch?v=GprXUDZrdT4&index=7&list=PLkx04k4VGz1tH_pnRl_5xBU1BLE3PYuzd",
"id": "GprXUDZrdT4",
"channel": "youtube.com/watch?v=GprXUDZrdT4&index=7&list=PLkx04k4VGz1tH_pnRl_5xBU1BLE3PYuzd",
"image_path": [
"http://i4.ytimg.com/vi/GprXUDZrdT4/hqdefault.jpg",
"GprXUDZrdT4.jpg"
]
}
}
After some testing I have come up with some code I think you can use or is what you are looking for.
I have changes a few things and add some others.
1st we needed to create a place holder for the top window that we can use later in the code. So in the __init__ section of GUIPopulatior add self.image_window = None. We will talk about this part soon.
next I created the image path as a class attribute on init this can be changed later with update if need be.
next I added a bind() to the init that will help us keep the self.image_window placed in the correct location even if we move the root window. so we add self.parent.bind("<Configure>", self.move_me) to the __init__ section as well.
next we create the method move_me that we just created a bind for.
the way it is written it will only take effect if self.image_window is not equal to None this should prevent any errors while self.image_window is being used however I have not created the error handling to deal with what happens after the toplevel window is closed by the user. Its not difficult but I wanted to answer for the main issue at hand.
Here is the move_me method:
def move_me(self, event):
if self.image_window != None:
h = self.parent.winfo_height() # gets the height of the window in pixels
w = self.parent.winfo_width() # gets the width of the window in pixels
# gets the placement of the root window then uses height and width to
# calculate where to place the window to keep it at the bottom right.
self.image_window.geometry('+{}+{}'.format(self.parent.winfo_x() + w - 250,
self.parent.winfo_y() + h - 250))
next we need to modify the on_enter method to create the toplevel window if out class attribute self.image_window is equal to None if it is not equal to None then we can use the else portion of the if statement to just update the image.
Here is the modified on_enter method:
def on_enter(self, event):
if self.image_window == None:
self.image_window = Toplevel(self)
#this keeps the toplevel window on top of the program
self.image_window.attributes("-topmost", True)
h = self.parent.winfo_height()
w = self.parent.winfo_width()
self.image_window.geometry('+{}+{}'.format(self.parent.winfo_x() + w - 250,
self.parent.winfo_y() + h - 250))
self.img = PhotoImage(file=self.img_path)
self.image_window.title("Preview")
self.canvas = Canvas(self.image_window, width=200, height=200)
self.canvas.pack()
self.canv_image = self.canvas.create_image(100, 100, image=self.img)
else:
self.img = PhotoImage(file= self.img_path)
self.canvas.itemconfig(self.canv_image, image = self.img)
all that being said there are some other issues with your code that need to be addressed however this answer should point you in the right direction.
Below is a section of your code you need to replace:
class GUIPopulator(Frame):
def __init__(self, parent):
Frame.__init__(self)
self.parent = parent
self.configure(bg='PeachPuff2')
self.columnconfigure(20, weight=1)
self.rowconfigure(30, weight=1)
self.curtab = None
self.tabs = {}
self.pack(fill=BOTH, expand=1, padx=5, pady=5)
self.column = 0
self.row = 0
def on_enter(self, event):
self.image_window = Toplevel(self)
self.img_path = os.getcwd() + '/Rotating_earth_(large).gif'
self.img = PhotoImage(file=self.img_path)
#self.image_window.minsize(width=200, height=250)
self.image_window.title("Preview")
canvas = Canvas(self.image_window, width=200, height=200)
canvas.pack()
canvas.create_image(100, 100, image=self.img)
#def on_leave(self, enter):
With this:
class GUIPopulator(Frame):
def __init__(self, parent):
Frame.__init__(self)
self.parent = parent
self.configure(bg='PeachPuff2')
self.columnconfigure(20, weight=1)
self.rowconfigure(30, weight=1)
self.curtab = None
self.image_window = None
self.img_path = os.getcwd() + '/Rotating_earth_(large).gif'
self.tabs = {}
self.pack(fill=BOTH, expand=1, padx=5, pady=5)
self.parent.bind("<Configure>", self.move_me)
self.column = 0
self.row = 0
def move_me(self, event):
if self.image_window != None:
h = self.parent.winfo_height()
w = self.parent.winfo_width()
self.image_window.geometry('+{}+{}'.format(self.parent.winfo_x() + w - 250,
self.parent.winfo_y() + h - 250))
def on_enter(self, event):
if self.image_window == None:
self.image_window = Toplevel(self)
self.image_window.attributes("-topmost", True)
h = self.parent.winfo_height()
w = self.parent.winfo_width()
self.image_window.geometry('+{}+{}'.format(self.parent.winfo_x() + w - 250,
self.parent.winfo_y() + h - 250))
self.img = PhotoImage(file=self.img_path)
self.image_window.title("Preview")
self.canvas = Canvas(self.image_window, width=200, height=200)
self.canvas.pack()
self.canv_image = self.canvas.create_image(100, 100, image=self.img)
else:
self.img = PhotoImage(file= self.img_path)
self.canvas.itemconfig(self.canv_image, image = self.img)

Categories

Resources