How do you create a Button on a tkinter Canvas? - python

I created a Frame and then a Canvas.
What I want to do next is to add a Button on the Canvas.
However, when I packed the Button I cannot see the Canvas!
Here is what I tried:
from Tkinter import Tk, Canvas, Frame, Button
from Tkinter import BOTH, W, NW, SUNKEN, TOP, X, FLAT, LEFT
class Example(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.initUI()
def initUI(self):
self.parent.title("Layout Test")
self.config(bg = '#F0F0F0')
self.pack(fill = BOTH, expand = 1)
#create canvas
canvas1 = Canvas(self, relief = FLAT, background = "#D2D2D2",
width = 180, height = 500)
canvas1.pack(side = TOP, anchor = NW, padx = 10, pady = 10)
#add quit button
button1 = Button(canvas1, text = "Quit", command = self.quit,
anchor = W)
button1.configure(width = 10, activebackground = "#33B5E5",
relief = FLAT)
button1.pack(side = TOP)
def main():
root = Tk()
root.geometry('800x600+10+50')
app = Example(root)
app.mainloop()
if __name__ == '__main__':
main()

The Tkinter pack manager tries to resize the parent widget to the correct size to contain its child widgets, and no larger, by default. So the canvas is there - but it's precisely the same size as the button, and thus invisible.
If you want to place a widget on a canvas without causing the canvas to dynamically resize, you want the Canvas.create_window() function:
# ... snip ...
button1 = Button(self, text = "Quit", command = self.quit, anchor = W)
button1.configure(width = 10, activebackground = "#33B5E5", relief = FLAT)
button1_window = canvas1.create_window(10, 10, anchor=NW, window=button1)
This will create your button with upper-left corner at (10, 10) relative to the canvas, without resizing the canvas itself.
Note that you could replace the window argument with a reference to any other Tkinter widget. One caveat, though: the named widget must be a child of the top-level window that contains the canvas, or a child of some widget located in the same top-level window.

you can use button1.place(x=0,y=0) geometry manager instead of pack(side =TOP)
pack resize the master widget to makes it large enough to hold the child widget
http://effbot.org/tkinterbook/pack.htm#Tkinter.Pack.pack_propagate-method
http://effbot.org/tkinterbook/place.htm

I had the exact same problem. There isn't an official way that I know, but here's a way around it:
from Tkinter import *
root = Tk()
def clicked(event):
print("pressed")
canvas1 = Canvas(root, relief = FLAT, background = "#D2D2D2")
canvas1.pack()
buttonBG = canvas1.create_rectangle(0, 0, 100, 30, fill="grey40", outline="grey60")
buttonTXT = canvas1.create_text(50, 15, text="click")
canvas1.tag_bind(buttonBG, "<Button-1>", clicked) ## when the square is clicked runs function "clicked".
canvas1.tag_bind(buttonTXT, "<Button-1>", clicked) ## same, but for the text.
root.mainloop()

Related

I can not set title of top level

I want to set title for TopLevel, but TopLevel shows title of Root. I think that my next script corresponds with examples from TkInter documentation, but gives me bad result. Cann You explain me, why my setting master.title = 'Top' in class AppTop does not set new title for TopLevel?
import tkinter as tk
class AppTop(tk.Frame):
def __init__(self, master):
mon_h = 900
mon_w = 1250
master.title = 'Top'
tk.Frame.__init__(self, master)
master.minsize(height = 900, width = 600)
fr_button = tk.Frame(master)
fr_button.place(relx=0.01, rely=0.06)
butArrowPlus = tk.Button(fr_button, text=">", height = 1, width = 20, command=self.Cmd)
butArrowPlus.grid(column= 1, row= 1)
return
def Cmd(self):
return
class Application(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
frRoot = tk.Frame(master, width=700, height=400, bd=2)
frRoot.place(relx=0.1, rely=0.1, anchor="nw")
butIllumBall = tk.Button(frRoot, text= 'Light Ball', height = 1, width = 20, command=self.cmd_illuminated_ball)
butIllumBall.grid(column= 0, row= 0, pady=10)
master.minsize(height = 250, width = 300)
master.title('Root')
def cmd_illuminated_ball(self):
top = tk.Toplevel()
top.transient(self.master)
top.grab_set()
app = AppTop(master = top)
app.mainloop()
return
wndRoot = tk.Tk()
appapp = Application(master=wndRoot)
appapp.mainloop()
You try to set Toplevel title with:
master.title = 'Top'
but the correct syntax is:
master.title('Top')
There are a couple of additional things: You do not need an additional mainloop for the Toplevel window. From the code it looks like you think that the Toplevel is a new application, instantiating it with app = AppTop(master = top). But it's just a new window which runs under the appapp.mainloop().
AppTop() inherits from tk.Frame() but you never use it. Instead you put all the widgets directly in the Toplevel (master) window. Same goes for Application() as well.

Scrollbar into a python tkinter discussion

from tkinter import *
window = Tk()
ia_answers= "test\n"
input_frame = LabelFrame(window, text="User :", borderwidth=4)
input_frame.pack(fill=BOTH, side=BOTTOM)
input_user = StringVar()
input_field = Entry(input_frame, text=input_user)
input_field.pack(fill=BOTH, side=BOTTOM)
def onFrameConfigure(canvas):
'''Reset the scroll region to encompass the inner frame'''
canvas.configure(scrollregion=canvas.bbox("all"))
canvas = Canvas(window, borderwidth=0, background="white")
ia_frame = LabelFrame(canvas, text="Discussion",borderwidth = 15, height = 100, width = 100)
ia_frame.pack(fill=BOTH, side=TOP)
scroll = Scrollbar(window, orient="vertical", command=canvas.yview)
canvas.configure(yscrollcommand=scroll.set)
scroll.pack(side=RIGHT, fill=Y)
canvas.pack(fill=BOTH, expand=True)
canvas.create_window((4,4), window=ia_frame, anchor="nw")
ia_frame.bind("<Configure>", lambda event, canvas=canvas:onFrameConfigure(canvas))
user_says = StringVar()
user_text = Label(ia_frame, textvariable=user_says, anchor = NE, justify = RIGHT, bg="white")
user_text.pack(fill=X)
ia_says = StringVar()
ia_text = Label(ia_frame, textvariable=ia_says, anchor = NW, justify = LEFT, bg="white")
ia_text.pack(fill=X)
user_texts = []
ia_texts = []
user_says_list = []
ia_says_list = []
def Enter_pressed(event):
"""Took the current string in the Entry field."""
input_get = input_field.get()
input_user.set("")
user_says1 = StringVar()
user_says1.set(input_get + "\n")
user_text1 = Label(ia_frame, textvariable=user_says1, anchor = NE, justify = RIGHT, bg="white")
user_text1.pack(fill=X)
user_texts.append(user_text1)
user_says_list.append(user_says1)
ia_says1 = StringVar()
ia_says1.set(ia_answers)
ia_text1 = Label(ia_frame, textvariable=ia_says1, anchor = NW, justify = LEFT, bg="white")
ia_text1.pack(fill=X)
ia_texts.append(ia_text1)
ia_says_list.append(ia_says1)
input_field.bind("<Return>", Enter_pressed)
window.mainloop()
Hi, I try to build a GUI with tkinter but I've got two problems, the LabelFrame/Canvas doesn't fill entirely the window and I can't get the scrollbar to automatically scroll down.
Can you help me with that, thank you very much.
Ilan Rossler.
You need to manually control the width of the inner frame since it is being managed by the canvas. You can change the width in a binding to the <Configure> event of the canvas (ie: when the canvas changes size, you must change the size of the frame).
You'll need to be able to reference the window object on the canvas, which means you need to save the id, or give it a tag.
Here's an example of giving it a tag:
canvas.create_window((4,4), window=ia_frame, anchor="nw", tags=("innerFrame",))
And here's how to change the width when the canvas changes size:
def onCanvasConfigure(event):
canvas = event.widget
canvas.itemconfigure("innerFrame", width=canvas.winfo_width() - 8)
canvas.bind("<Configure>", onCanvasConfigure)
To scroll down, call the yview command just like the scrollbar does. You need to make this happen after the window has had a chance to refresh.
For example, add this as the very last line in Enter_pressed:
def Enter_pressed(event):
...
canvas.after_idle(canvas.yview_moveto, 1.0)

How to get tkinter canvas to dynamically resize to window width?

I need to get a canvas in tkinter to set its width to the width of the window, and then dynamically re-size the canvas when the user makes the window smaller/bigger.
Is there any way of doing this (easily)?
I thought I would add in some extra code to expand on #fredtantini's answer, as it doesn't deal with how to update the shape of widgets drawn on the Canvas.
To do this you need to use the scale method and tag all of the widgets. A complete example is below.
from Tkinter import *
# a subclass of Canvas for dealing with resizing of windows
class ResizingCanvas(Canvas):
def __init__(self,parent,**kwargs):
Canvas.__init__(self,parent,**kwargs)
self.bind("<Configure>", self.on_resize)
self.height = self.winfo_reqheight()
self.width = self.winfo_reqwidth()
def on_resize(self,event):
# determine the ratio of old width/height to new width/height
wscale = float(event.width)/self.width
hscale = float(event.height)/self.height
self.width = event.width
self.height = event.height
# resize the canvas
self.config(width=self.width, height=self.height)
# rescale all the objects tagged with the "all" tag
self.scale("all",0,0,wscale,hscale)
def main():
root = Tk()
myframe = Frame(root)
myframe.pack(fill=BOTH, expand=YES)
mycanvas = ResizingCanvas(myframe,width=850, height=400, bg="red", highlightthickness=0)
mycanvas.pack(fill=BOTH, expand=YES)
# add some widgets to the canvas
mycanvas.create_line(0, 0, 200, 100)
mycanvas.create_line(0, 100, 200, 0, fill="red", dash=(4, 4))
mycanvas.create_rectangle(50, 25, 150, 75, fill="blue")
# tag all of the drawn widgets
mycanvas.addtag_all("all")
root.mainloop()
if __name__ == "__main__":
main()
You can use the .pack geometry manager:
self.c=Canvas(…)
self.c.pack(fill="both", expand=True)
should do the trick.
If your canvas is inside a frame, do the same for the frame:
self.r = root
self.f = Frame(self.r)
self.f.pack(fill="both", expand=True)
self.c = Canvas(…)
self.c.pack(fill="both", expand=True)
See effbot for more info.
Edit: if you don't want a "full sized" canvas, you can bind your canvas to a function:
self.c.bind('<Configure>', self.resize)
def resize(self, event):
w,h = event.width-100, event.height-100
self.c.config(width=w, height=h)
See effbot again for events and bindings
This does it - in my version of Python at least. The canvas resizes both horizontally (sticky=E+W & rowconfigure) and vertically (sticky=N+S & columnconfigure). I've set the canvas background to a disgusting colour - but at least you can see when it works.
from tkinter import *
root=Tk()
root.rowconfigure(0,weight=1)
root.columnconfigure(0,weight=1)
canv=Canvas(root, width=600, height=400, bg='#f0f0c0')
canv.grid(row=0, column=0, sticky=N+S+E+W)
root.mainloop()

Python Tkinter Scrollbar for entire window

Is there a way to add a scrollbar to my entire window without putting everything into a frame? I've set everything up with .grid, and I don't like the idea of wrapping a frame around everything.
root = Tk()
root.maxsize(900,600)
circus()#calls the function to set up everything
root.mainloop()
How to add scrollbars to full window in tkinter ?
here is the answer for python 3...
from tkinter import *
from tkinter import ttk
root = Tk()
root.title('Full Window Scrolling X Y Scrollbar Example')
root.geometry("1350x400")
# Create A Main frame
main_frame = Frame(root)
main_frame.pack(fill=BOTH,expand=1)
# Create Frame for X Scrollbar
sec = Frame(main_frame)
sec.pack(fill=X,side=BOTTOM)
# Create A Canvas
my_canvas = Canvas(main_frame)
my_canvas.pack(side=LEFT,fill=BOTH,expand=1)
# Add A Scrollbars to Canvas
x_scrollbar = ttk.Scrollbar(sec,orient=HORIZONTAL,command=my_canvas.xview)
x_scrollbar.pack(side=BOTTOM,fill=X)
y_scrollbar = ttk.Scrollbar(main_frame,orient=VERTICAL,command=my_canvas.yview)
y_scrollbar.pack(side=RIGHT,fill=Y)
# Configure the canvas
my_canvas.configure(xscrollcommand=x_scrollbar.set)
my_canvas.configure(yscrollcommand=y_scrollbar.set)
my_canvas.bind("<Configure>",lambda e: my_canvas.config(scrollregion= my_canvas.bbox(ALL)))
# Create Another Frame INSIDE the Canvas
second_frame = Frame(my_canvas)
# Add that New Frame a Window In The Canvas
my_canvas.create_window((0,0),window= second_frame, anchor="nw")
for thing in range(100):
Button(second_frame ,text=f"Button {thing}").grid(row=5,column=thing,pady=10,padx=10)
for thing in range(100):
Button(second_frame ,text=f"Button {thing}").grid(row=thing,column=5,pady=10,padx=10)
root.mainloop()
you might be able to set a scrollbarr to root.
scrollderoot = tkinter.Scrollbar(orient="vertical", command=root.yview)
scrollderoot.grid(column=5, row=0, sticky='ns', in_=root) #instead of number 5, set the column as the expected one for the scrollbar. Sticky ns will might be neccesary.
root.configure(yscrollcommand=scrollderoot.set)
Honestly i didn't tried this but "should" work. Good luck.
This approach uses no Frame objects and is different in that it creates a very large Canvas with Scrollbars and asks you for an image to display on it.
The screen is then set with self.root.wm_attributes("-fullscreen", 1)
and self.root.wm_attributes("-top", 1)
Press the Escape key or Alt-F4 to close.
import tkinter as tk
from tkinter import filedialog as fido
class BigScreen:
def __init__( self ):
self.root = tk.Tk()
self.root.rowconfigure(0, weight = 1)
self.root.columnconfigure(0, weight = 1)
w, h = self.root.winfo_screenwidth(), self.root.winfo_screenheight()
self.canvas = tk.Canvas(self.root, scrollregion = f"0 0 {w*2} {h*2}")
self.canvas.grid(row = 0, column = 0, sticky = tk.NSEW)
self.makescroll(self.root, self.canvas )
self.imagename = fido.askopenfilename( title = "Pick Image to View" )
if self.imagename:
self.photo = tk.PhotoImage(file = self.imagename).zoom(2, 2)
self.window = self.canvas.create_image(
( 0, 0 ), anchor = tk.NW, image = self.photo)
self.root.bind("<Escape>", self.closer)
self.root.wm_attributes("-fullscreen", 1)
self.root.wm_attributes("-top", 1)
def makescroll(self, parent, thing):
v = tk.Scrollbar(parent, orient = tk.VERTICAL, command = thing.yview)
v.grid(row = 0, column = 1, sticky = tk.NS)
thing.config(yscrollcommand = v.set)
h = tk.Scrollbar(parent, orient = tk.HORIZONTAL, command = thing.xview)
h.grid(row = 1, column = 0, sticky = tk.EW)
thing.config(xscrollcommand = h.set)
def closer(self, ev):
self.root.destroy()
if __name__ == "__main__":
Big = BigScreen()
Big.root.mainloop()
My previous answer went well beyond the question asked so this is a cut-down version more accurately answers the question.
I did try the answer of Akash Shendage which didn't work for me out of the box. But with a few adjustments go it working.
#!/bin/env python3
from tkinter import ttk
import tkinter as tk
root = tk.Tk()
root.title('Full Window Scrolling X Y Scrollbar Example')
root.geometry("1350x400")
# Create A Main frame
main_frame = tk.Frame(root)
main_frame.pack(fill=tk.BOTH,expand=1)
# Create Frame for X Scrollbar
sec = tk.Frame(main_frame)
sec.pack(fill=tk.X,side=tk.BOTTOM)
# Create A Canvas
my_canvas = tk.Canvas(main_frame)
my_canvas.pack(side=tk.LEFT,fill=tk.BOTH,expand=1)
# Add A Scrollbars to Canvas
x_scrollbar = ttk.Scrollbar(sec,orient=tk.HORIZONTAL,command=my_canvas.xview)
x_scrollbar.pack(side=tk.BOTTOM,fill=tk.X)
y_scrollbar = ttk.Scrollbar(main_frame,orient=tk.VERTICAL,command=my_canvas.yview)
y_scrollbar.pack(side=tk.RIGHT,fill=tk.Y)
# Configure the canvas
my_canvas.configure(xscrollcommand=x_scrollbar.set)
my_canvas.configure(yscrollcommand=y_scrollbar.set)
my_canvas.bind("<Configure>",lambda e: my_canvas.config(scrollregion= my_canvas.bbox(tk.ALL)))
# Create Another Frame INSIDE the Canvas
second_frame = tk.Frame(my_canvas)
# Add that New Frame a Window In The Canvas
my_canvas.create_window((0,0),window= second_frame, anchor="nw")
for thing in range(100):
tk.Button(second_frame ,text=f"Button {thing}").grid(row=5,column=thing,pady=10,padx=10)
for thing in range(100):
tk.Button(second_frame ,text=f"Button {thing}").grid(row=thing,column=5,pady=10,padx=10)
root.mainloop()
From the great effbot docs:
In Tkinter, the scrollbar is a separate widget that can be attached to
any widget that support the standard scrollbar interface. Such widgets
include:
the Listbox widget.
the Text widget.
the Canvas widget
the Entry widget
So, you cannot directly use a scrollbar in a Frame. It may be possible to create your own Frame sub-class that supports the scrollbar interface.
Out of the 4 widgets listed above, only one allows other widgets within it: the Canvas. You can use a Canvas to have scrollable content, but placing widgets within a Canvas does not use pack or grid, but uses explicit pixel locations (i.e. painting on the Canvas).
Here's a class, and some example usage, that uses the .place method to add a scrollbar for the whole window. You can create a Frame object, and place it at your desired (x, y) coordinates. Then, simply pass your Frame object in place of root in main.frame to create a scrollable window at your desired coordinates.
from tkinter import *
class ScrollableFrame:
"""A scrollable tkinter frame that will fill the whole window"""
def __init__ (self, master, width, height, mousescroll=0):
self.mousescroll = mousescroll
self.master = master
self.height = height
self.width = width
self.main_frame = Frame(self.master)
self.main_frame.pack(fill=BOTH, expand=1)
self.scrollbar = Scrollbar(self.main_frame, orient=VERTICAL)
self.scrollbar.pack(side=RIGHT, fill=Y)
self.canvas = Canvas(self.main_frame, yscrollcommand=self.scrollbar.set)
self.canvas.pack(expand=True, fill=BOTH)
self.scrollbar.config(command=self.canvas.yview)
self.canvas.bind(
'<Configure>',
lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all"))
)
self.frame = Frame(self.canvas, width=self.width, height=self.height)
self.frame.pack(expand=True, fill=BOTH)
self.canvas.create_window((0,0), window=self.frame, anchor="nw")
self.frame.bind("<Enter>", self.entered)
self.frame.bind("<Leave>", self.left)
def _on_mouse_wheel(self,event):
self.canvas.yview_scroll(-1 * int((event.delta / 120)), "units")
def entered(self,event):
if self.mousescroll:
self.canvas.bind_all("<MouseWheel>", self._on_mouse_wheel)
def left(self,event):
if self.mousescroll:
self.canvas.unbind_all("<MouseWheel>")
# Example usage
obj = ScrollableFrame(
master,
height=300, # Total required height of canvas
width=400 # Total width of master
)
objframe = obj.frame
# use objframe as the main window to make widget

creating digital time in canvas tkinter

I am newbie in Python using tkinter and I have a problem that I cant solve.Digital time
I want to put digital time in the the upper right corner of my application (Please see the picture). I tried to search on net on how to create a digital time but it is on global root and frame configuration and I cant find a digital clock made for canvas. I also want to put my buttons in middle using grid, but I have no luck finding a solution. Can any one please help me? Ill paste my code here.
from tkinter import *
from tkinter import ttk
from datetime import date
import time
import sys
class main_menu(object):
def __init__(self, root):
self.root = root
self.root.title('System')
self.root.geometry('780x488')
self.background = PhotoImage(file='images/bg.png')
self.canvas = Canvas (root)
self.canvas.grid(sticky=N+S+W+E)
self.canvas.create_image(0,0, image=self.background, anchor="nw")
self.scan_photo = PhotoImage (file='images/scan.png')
self.logs_photo = PhotoImage (file='images/logs.png')
self.settings_photo = PhotoImage (file='images/settings.png')
self.scan_btn = Button (self.canvas, image=self.scan_photo, borderwidth=0, command=self.StartScan)
self.scan_win = self.canvas.create_window(225, 100, anchor="nw", window=self.scan_btn)
self.logs_btn = Button (self.canvas, image=self.logs_photo, borderwidth=0, command=self.Logs)
self.logs_win = self.canvas.create_window(225, 200, anchor="nw", window=self.logs_btn)
self.settings_btn = Button (self.canvas, image=self.settings_photo, borderwidth=0, command=self.Settings)
self.settings_win = self.canvas.create_window(225, 300, anchor="nw", window=self.settings_btn)
self.today = date.today()
self.format = self.today.strftime("%b. %d, %Y")
self.canvas.create_text(730, 30, text=self.format, font=("Helvetica", 10))
self.InstructionsLabel = Label(root, text="""
tadahhhhhh""", fg="black", font=("Calibri", 14))
self.Return_photo = PhotoImage (file='images/back_24x24.png')
self.ReturnMenu_btn = Button (self.canvas, image=self.Return_photo, background='white',activebackground='white', borderwidth=0, command=self.MainMenu)
self.ReturnMenu_win = self.canvas.create_window(0, 0, anchor="nw", window=self.ReturnMenu_btn)
###self.ReturnMenu = Button(root, image=self.back_photo, command=self.MainMenu, )
self.MainMenu()
def MainMenu(self):
self.RemoveAll()
self.ReturnMenu_btn.grid_remove()
self.scan_btn.grid(padx=215)
self.logs_btn.grid(padx=215)
self.settings_btn.grid(padx=215)
def StartScan(self):
self.RemoveAll()
def Logs(self):
self.RemoveAll()
self.ReturnMenu.grid()
def Settings(self):
self.RemoveAll()
self.ReturnMenu.grid()
def RemoveAll(self):
self.scan_btn.grid_remove()
self.logs_btn.grid_remove()
self.settings_btn.grid_remove()
self.InstructionsLabel.grid_remove()
self.ReturnMenu_btn.grid_remove()
if __name__ == '__main__':
root = Tk()
root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
main_menu = main_menu(root)
root.mainloop()
To place the time in the upper right corner you need to know the width of the canvas. So use canvas.winfo_width() to get the width of the canvas and subtract some number to place it at the desired position.
If you want the time to stay at the top-right even if the window is resized then bind Configure to a function and move the text using .coords or .moveto.
Sample code(this code will make sure that the time is always at the upper right corner).
from tkinter import font
class MainMenu:
def __init__(self, root):
...
self.time = self.canvas.create_text(0, 0, text=self.format, font=("Helvetica", 10))
self.canvas.bind('<Configure>', self.adjustTimePosition)
...
def adjustTimePosition(self, event):
family, size = self.canvas.itemcget(self.time, 'font').split() # get the font-family and font size
text = self.canvas.itemcget(self.time, 'text')
txt_font = font.Font(family=family, size=size)
width, height = txt_font.measure(text), txt_font.metrics("ascent") # measures the width and height of the text
self.canvas.coords(self.time, self.canvas.winfo_width()-width, height) # moves the text

Categories

Resources