I'm trying to make a custom Tkinter widget by subclassing a Frame to make a scrollable widget that contains items. I have 2 windows, my root window and a toplevel window and I need my custom widget to pack to the toplevel window.
My problem is, that despite making 'top' the parent of my custom widget, it still packs in the root window. I've tried other widgets and they pack to the toplevel window fine.
my code:
from tkinter import *
root = Tk()
root.config(bg="#000000")
root.wm_attributes("-alpha","0.7")
top = Toplevel()
top.config(bg="#000001")
top.wm_attributes("-topmost",1)
top.wm_attributes("-transparentcolor","#000001")
top.wm_title("TOPLEVEL")
class Scrollygrid(Frame):
def __init__(self, parent, columns, h, w):
super(Scrollygrid, self).__init__()
self.scrollbar = Scrollbar(self)
self.scrollbar.pack(side = RIGHT, fill = Y)
self.area = Canvas(self, yscrollcommand=self.scrollbar.set, width = w, height = h, bg = "#000001", bd = 0, highlightthickness = 0)
self.gridframe = Frame(height = h*10, width = w, bg = "#FF0000")
self.gridframe.pack_propagate(0)
self.area.create_window((0, 0), window = self.gridframe)
for i in range(500):
Label(self.gridframe, text = i).pack()
self.area.pack()
self.area.config(scrollregion = (self.area.bbox("all")))
self.scrollbar.config(command = self.area.yview)
def onScroll(event):
self.area.yview_scroll(int(-1*(event.delta/60)), "units")
self.area.bind_all("<MouseWheel>", onScroll)
self.scrollbar.pack_forget() #scroll wheel still works!
testgrid = Scrollygrid(top, 1,root.winfo_screenheight()-80,root.winfo_screenwidth()-(root.winfo_screenwidth()/10))
testgrid.pack(side = RIGHT, anchor = CENTER)
root.mainloop()
the transparent color and alpha level have been left in to make it more immediately obvious which window is which.
top isn't the parent of your custom widget. You're never passing it. Your Scrollygrid is being passed a parent parameter and that's it. Nothing tells it to assign it as the parent as an attribute even. So the parent defaults to the Tk instance, root.
Replace:
super(Scrollygrid, self).__init__()
with:
super(Scrollygrid, self).__init__(parent)
In order to pass the given widget argument as the parent to the superclass, Frame. Essentially making the Toplevel, top your custom class' parent. Upon which you'll get further error(s) but the parent is correctly assigned. Verify by:
print(repr(self.winfo_toplevel()))
Related
I made a button that plus the x axis with 50 if i press it.If i press the button tho it doesn't move,yes it changes the value but doesn't move. I tried a while loop but it crashed the program.
In general we use pack and grid geometry managers to handle widget placement in Tk. However, if you want to explicitly control the placement then you can use place which allows you to specify the location in either pixel or relative coordinates.
Here is an example of a frame with a button inside that moves when you click the button. Note that the button is positioned relative to its parent container so moves with the frame.
import tkinter as tk
import tkinter.ttk as ttk
class App(ttk.Frame):
def __init__(self, master, **kwargs):
super(App, self).__init__(master=master, **kwargs)
master.wm_geometry('640x480')
self.frame = f = tk.Frame(self, width=200, height=80, relief=tk.SUNKEN, borderwidth=2)
b = ttk.Button(f, text="Move", command=self.move_frame)
b.place(x=2, y=2)
f.place(x=2, y=2)
self.place(relheight=1.0, relwidth=1.0)
def move_frame(self):
x = self.frame.winfo_x()
x = x + 10
self.frame.place(x=x)
def main():
root = tk.Tk()
app = App(root)
root.mainloop()
if __name__ == '__main__':
main()
I am aware that you cannot use different types of geometry managers within the same Tkinter window, such as .grid() and .pack(). I have a window that has been laid out using .grid() and I am now trying to add a status bar that would be snapped to the bottom of the window. The only method I have found online for this is to use .pack(side = BOTTOM), which will not work since the rest of the window uses .grid().
Is there a way that I can select the bottom of the window to place widgets from when using .grid()?
from tkinter import *
from tkinter.ttk import *
import tkinter as tk
class sample(Frame):
def __init__(self,master=None):
Frame.__init__(self, master)
self.status = StringVar()
self.status.set("Initializing")
statusbar = Label(root,textvariable = self.status,relief = SUNKEN, anchor = W)
statusbar.pack(side = BOTTOM, fill = X)
self.parent1 = Frame()
self.parent1.pack(side = TOP)
self.createwidgets()
def createwidgets(self):
Label(self.parent1,text = "Grid 1,1").grid(row = 1, column = 1)
Label(self.parent1,text = "Grid 1,2").grid(row = 1, column = 2)
Label(self.parent1,text = "Grid 2,1").grid(row = 2, column = 1)
Label(self.parent1,text = "Grid 2,2").grid(row = 2, column = 2)
if __name__ == '__main__':
root = Tk()
app = sample(master=root)
app.mainloop()
So using labels since I was kinda lazy to do other stuff, you can do frames to ensure that each section of your window can be packed/grid as required. Frames will be a useful tool for you to use when trying to arrange your widgets. Note that using a class can make things a little easier when deciding your parents. So imagine each frame is a parent and their children can be packed as required. So I would recommend drawing out your desired GUI and see how you will arrange them. Also if you want to add another frame within a frame simply do:
self.level2 = Frame(self.parent1)
You can check out additional settings in the docs
http://effbot.org/tkinterbook/frame.htm
PS: I am using a class hence the self, if you don't want to use classes then its okay to just change it to be without a class. Classes make it nicer to read though
Just give it a row argument that is larger than any other row. Then, give a weight to at least one of the rows before it.
Even better is to use frames to organize your code. Pack the scrollbar on the bottom and a frame above it. Then, use grid for everything inside the frame.
Example:
# layout of the root window
main = tk.Frame(root)
statusbar = tk.Label(root, text="this is the statusbar", anchor="w")
statusbar.pack(side="bottom", fill="x")
main.pack(side="top", fill="both", expand=True)
# layout of the main window
for row in range(1, 10):
label = tk.Label(main, text=f"R{row}")
label.grid(row=row, sticky="nsew")
main.grid_rowconfigure(row, weight=1)
...
I am trying to get the position of the menu that is the child of the option menu widget in tkinter. I want to do this so that I can know when the menu has been shifted due to location on the screen (see picture 1)
However, when I try and use the menu widget winfo_rootx or winfo_x they both just show position 0 regardless of where the menu actually is. See example code:
from tkinter import Tk, Frame, BOTH, Menu, Label, SUNKEN, X, BOTTOM
import tkinter as tk
class Application(Frame):
def __init__(self, parent):
Frame.__init__(self, parent, background = "white")
self.parent = parent
self.parent.geometry("400x100")
vals = ["1","2","3","4","5","6","7"]
var = tk.StringVar()
var.set("1")
option = tk.OptionMenu(root,var,*vals)
option.pack()
self.t = option.children["menu"]
tk.Menu
#Do I need to unbind
#t.bind("<<MenuSelect>>", self.test_func)
self.t.bind("<<MenuSelect>>", self.test_func)
def test_func(self,event = None):
print(self.t.winfo_children())
print("x,y position",event.widget.winfo_x(),event.widget.winfo_y())
print("x,y root position",event.widget.winfo_rootx(),event.widget.winfo_rooty())
#if self.parent.call(event.widget,"index","active") == 0:
#print(self.t)
root = tk.Tk()
Application(root)
root.mainloop()
I have potentially other options to try and fix this but it seems that there should be a way to get the menu widgets position correctly. My other way is rather hacky and uses the position and lengths of the widgets to try and calculate the adjusted position.
I'm kind of new to tkinter. I tried to create a Text widget on the left side at 0,0, but it appears in the middle, like a default pack().
Here is my code:
from Tkinter import *
# the ui of the main window
class Ui(object):
# the init of the client object
def __init__(self):
self.root = Tk()
self.mid_height = self.root.winfo_screenheight() / 2
self.mid_width = self.root.winfo_screenwidth() / 2
self.root.title("Journey-opening")
self.root.geometry("600x600+{}+{}".format(self.mid_width - 300, self.mid_height - 300))
self.root.resizable(width=0, height=0)
self.cyan = "#0990CB"
self.root["background"] = self.cyan
self.frame = Frame(self.root)
self.frame.pack()
self.chat_box = Text(self.frame, height=30, width=50)
self.chat_box.pack(side=LEFT)
def open(self):
self.root.mainloop()
wins = Ui()
wins.open()
I also tried with grid method but it did not change anything, and also created another widget because maybe it needs 2 widgets at least.
I guess its something with my frame but I follow a tutorial and everything seems fine.
"Pack a text widget on the side doesn't work"
That is incorrect the line self.chat_box.pack(side=LEFT) does pack the Text widget to side. It's just that it is done inside self.frame which allocates exactly as much space needed for the widgets it encapsulates(in this case that is only the text widget) by default. So in a way, the Text widget is packed, not just to left, but to all sides.
In order to have self.chat_box on the upper left corner, you should let frame to occupy more space than needed, in this case, it can simply occupy all space in the x-axis inside its parent(self.root). In order to do that, replace:
self.frame.pack()
with:
self.frame.pack(fill='x') # which is the same as self.frame.pack(fill=X)
I want to make a window in Tk that has a custom titlebar and frame. I have seen many questions on this website dealing with this, but what I'm looking for is to actually render the frame using a canvas, and then to add the contents to the canvas. I cannot use a frame to do this, as the border is gradiented.
According to this website: http://effbot.org/tkinterbook/canvas.htm#Tkinter.Canvas.create_window-method, I cannot put any other canvas items on top of a widget (using the create_window method), but I need to do so, as some of my widgets are rendered using a canvas.
Any suggestions on how to do this? I'm clueless here.
EDIT: Bryan Oakley confirmed that rendering with a canvas would be impossible. Would it then be possible to have a frame with a custom border color? And if so, could someone give a quick example? I'm sort of new with python.
You can use the canvas as if it were a frame in order to draw your own window borders. Like you said, however, you cannot draw canvas items on top of widgets embedded in a canvas; widgets always have the highest stacking order. There is no way around that, though it's not clear if you really need to do that or not.
Here's a quick and dirty example to show how to create a window with a gradient for a custom border. To keep the example short I didn't add any code to allow you to move or resize the window. Also, it uses a fixed color for the gradient.
import Tkinter as tk
class GradientFrame(tk.Canvas):
'''A gradient frame which uses a canvas to draw the background'''
def __init__(self, parent, borderwidth=1, relief="sunken"):
tk.Canvas.__init__(self, parent, borderwidth=borderwidth, relief=relief)
self._color1 = "red"
self._color2 = "black"
self.bind("<Configure>", self._draw_gradient)
def _draw_gradient(self, event=None):
'''Draw the gradient'''
self.delete("gradient")
width = self.winfo_width()
height = self.winfo_height()
limit = width
(r1,g1,b1) = self.winfo_rgb(self._color1)
(r2,g2,b2) = self.winfo_rgb(self._color2)
r_ratio = float(r2-r1) / limit
g_ratio = float(g2-g1) / limit
b_ratio = float(b2-b1) / limit
for i in range(limit):
nr = int(r1 + (r_ratio * i))
ng = int(g1 + (g_ratio * i))
nb = int(b1 + (b_ratio * i))
color = "#%4.4x%4.4x%4.4x" % (nr,ng,nb)
self.create_line(i,0,i,height, tags=("gradient",), fill=color)
self.lower("gradient")
class SampleApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.wm_overrideredirect(True)
gradient_frame = GradientFrame(self)
gradient_frame.pack(side="top", fill="both", expand=True)
inner_frame = tk.Frame(gradient_frame)
inner_frame.pack(side="top", fill="both", expand=True, padx=8, pady=(16,8))
b1 = tk.Button(inner_frame, text="Close",command=self.destroy)
t1 = tk.Text(inner_frame, width=40, height=10)
b1.pack(side="top")
t1.pack(side="top", fill="both", expand=True)
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
Here is a rough example where the frame, titlebar and close button are made with canvas rectangles:
import Tkinter as tk
class Application(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
# Get rid of the os' titlebar and frame
self.overrideredirect(True)
self.mCan = tk.Canvas(self, height=768, width=768)
self.mCan.pack()
# Frame and close button
self.lFrame = self.mCan.create_rectangle(0,0,9,769,
outline='lightgrey', fill='lightgrey')
self.rFrame = self.mCan.create_rectangle(760,0,769,769,
outline='lightgrey', fill='lightgrey')
self.bFrame = self.mCan.create_rectangle(0,760,769,769,
outline='lightgrey', fill='lightgrey')
self.titleBar = self.mCan.create_rectangle(0,0,769,20,
outline='lightgrey', fill='lightgrey')
self.closeButton = self.mCan.create_rectangle(750,4,760, 18,
activefill='red', fill='darkgrey')
# Binds
self.bind('<1>', self.left_mouse)
self.bind('<Escape>', self.close_win)
# Center the window
self.update_idletasks()
xp = (self.winfo_screenwidth() / 2) - (self.winfo_width() / 2)
yp = (self.winfo_screenheight() / 2) - (self.winfo_height() / 2)
self.geometry('{0}x{1}+{2}+{3}'.format(self.winfo_width(),
self.winfo_height(),
xp, yp))
def left_mouse(self, event=None):
obj = self.mCan.find_closest(event.x,event.y)
if obj[0] == self.closeButton:
self.destroy()
def close_win(self, event=None):
self.destroy()
app = Application()
app.mainloop()
If I were going to make a custom GUI frame I would consider creating it with images,
made with a program like Photoshop, instead of rendering canvas objects.
Images can be placed on a canvas like this:
self.ti = tk.PhotoImage(file='test.gif')
self.aImage = mCanvas.create_image(0,0, image=self.ti,anchor='nw')
More info →here←