I want to put a canvas on top of a larger sized grid so that I can create buttons at the bottom of the canvas. Right now I have this:
class Canvas(Frame):
def __init__(self, root):
Frame.__init__(self, root)
self.display = Canvas(root, width=500, height=500, borderwidth=5, background='white')
self.display.pack()
root = Tk()
board = Canvas(root)
root.mainloop()
This creates a 500x500 white canvas. Now let's say I want to add something like a 500x600 grid to go under it. How would I do that?
You can just pack another frame under it, with height=100 and pack it with fill=X. Then you can put the Buttons in this new frame any way you want.
import Tkinter as tk
root = tk.Tk()
display = tk.Canvas(root, width=500, height=500, borderwidth=5, background='white')
display.pack()
button_frame = tk.Frame(root, height=100)
button_frame.pack(fill=tk.X)
button1 = tk.Button(button_frame, text='button1')
button1.pack(side=tk.LEFT)
button2 = tk.Button(button_frame, text='button2')
button2.pack(side=tk.LEFT)
root.mainloop()
Or, if you really want a grid, you can use the following code to place the Frame under the Canvas
display = tk.Canvas(root, width=500, height=500, borderwidth=5, background='white')
display.grid(column=0, row=0)
button_frame = tk.Frame(root, height=100)
button_frame.grid(column=0, row=1, sticky=tk.W+tk.E)
But if you place the Canvas with pack, don't try to put the frame in with grid. Use only one layout manager in root.
Related
i've the problem for make an GUI apps with python tkinter.
here is my sample code
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
root.geometry('300x300')
root.title('CSV Editor')
notebook = ttk.Notebook(root)
notebook.pack(pady=10, expand=True)
tab_home = ttk.Frame(notebook, width=300, height=300)
notebook.add(tab_home, text='Home')
fr_home = tk.Frame(tab_home, background="white")
fr_home.grid(row=0, column=0)
fr_home_container_canvas = tk.Frame(fr_home, background="red")
fr_home_container_canvas.grid(row=0, column=0, sticky='nw')
fr_home_container_canvas.grid_rowconfigure(0, weight=1)
fr_home_container_canvas.grid_columnconfigure(0, weight=1)
fr_home_container_canvas.grid_propagate(False)
canvas_home = tk.Canvas(fr_home_container_canvas)
canvas_home.grid(row=0, column=0, sticky="news")
vsb = tk.Scrollbar(fr_home_container_canvas, orient="vertical", command=canvas_home.yview)
vsb.grid(row=0, column=1, sticky='ns')
canvas_home.configure(yscrollcommand=vsb.set)
fr_home_widget_canvas = tk.Frame(canvas_home, background="yellow")
canvas_home.create_window((0, 0), window=fr_home_widget_canvas, anchor='nw')
fr_home_widget_canvas.config(width=300, height=300, padx=10)
fr_home_container_canvas.config(width=300, height=300)
canvas_home.config(scrollregion=canvas_home.bbox("all"))
text_widget = tk.Text(fr_home_widget_canvas, width = 30, height = 10)
text_widget.grid(column=0, row=0)
root.mainloop()
if i run this code, this is the preview
enter image description here
but when i click inside the text widget, in the frame appear line / border like this
enter image description here
What is that line / border? how to remove it?
thank you so much :)
It is the highlight background which can be removed by setting highlightthickness=0:
canvas_home = tk.Canvas(fr_home_container_canvas, highlightthickness=0)
I created a main root with two frames.
One frame is for program toolbar.
The other frame is for a canvas where data will be displayed and for a scrollbar widget.
Inside of the canvas is a third smaller frame which will be used for scrolling trough data.
However, when I try to define new widgets inside of a function call, the scroll button loses its functionality. That smaller frame is scrollable only if I define these widgets in the top level of the code.
I used a simple for loop to create labels just for testing.
Could anyone tell what I am doing wrong?
from tkinter import *
from tkinter import ttk
#Creating main window
root = Tk()
root.resizable(width=False, height=False)
#Defining Background
toolbar = Frame(root, width=613, height=114)
toolbar.grid(row=0, column=0)
background_frame = Frame(root, width=615, height=560)
background_frame.grid(row=1, column=0)
background = Canvas(background_frame, width=615, height=560)
background.pack(side=LEFT, fill=BOTH, expand=1)
scroll_bar = ttk.Scrollbar(background_frame, orient=VERTICAL, command=background.yview)
scroll_bar.pack(side=RIGHT, fill=Y)
background.configure(yscrollcommand=scroll_bar.set)
background.bind('<Configure>', lambda e:background.configure(scrollregion = background.bbox('all')))
second_frame = Frame(background)
background.create_window(150,100, window=second_frame, anchor='nw')
def confirm1():
for x in range(100):
Label(second_frame, text = x ).grid(row=x, column=1)
show_labels = Button(toolbar, text= "Show labels", fg="black", command=confirm1)
show_labels.grid(row=0, column=2)
root.mainloop()
You need to to update the background canvas' scrollregion whenever you add widgets to the second_frame. That's easy to do by binding to that frame's <Configure> event.
Here's a complete version of your code with a couple of lines added (# WHERE INDICATED) to do that:
from tkinter import *
from tkinter import ttk
#Creating main window
root = Tk()
root.resizable(width=False, height=False)
#Defining Background
toolbar = Frame(root, width=613, height=114)
toolbar.grid(row=0, column=0)
background_frame = Frame(root, width=615, height=560)
background_frame.grid(row=1, column=0)
background = Canvas(background_frame, width=615, height=560)
background.pack(side=LEFT, fill=BOTH, expand=1)
scroll_bar = ttk.Scrollbar(background_frame, orient=VERTICAL, command=background.yview)
scroll_bar.pack(side=RIGHT, fill=Y)
background.configure(yscrollcommand=scroll_bar.set)
# NOT NEEDED
#background.bind('<Configure>',
# lambda e: background.configure(scrollregion=background.bbox('all')))
second_frame = Frame(background)
background.create_window(150,100, window=second_frame, anchor='nw')
# ADDED
second_frame.bind('<Configure>',
lambda e: background.configure(scrollregion=background.bbox('all')))
def confirm1():
for x in range(100):
Label(second_frame, text=x).grid(row=x, column=1)
show_labels = Button(toolbar, text="Show labels", fg="black", command=confirm1)
show_labels.grid(row=0, column=2)
root.mainloop()
Result after clicking button and scrolling the canvas region:
I am using tk.Canvas's .create_window to place buttons on my canvas, to allow a 'scrolling' effect, where I can cycle through a list of buttons:
example
As seen in the gif, there is a scrollbar on the right which allows scrolling vertically of the canvas, and the canvas created windows of buttons can be cycled through. However, as seen, the buttons overlap the scrollbar. Is it possible for the windows to be set to appear only within the canvas widget? Changing the master of the button does not have an effect:
button = tk.Button(self, image=self.data[i].image, anchor='nw', width=400, height=72, highlightthickness=0, bd=0, relief='flat', bg='#dfdbda', compound='left', text="thing", fg='black', font='System, 10')
buttonwindow = self.canvas.create_window(5, 10, anchor='nw', window=button)
No matter which configuration options are given, the window still seems to pop out of the canvas widget. Why does it do this? Is there a better alternative to create_window, where I can put buttons in a canvas widget?
The solution is simple: don't put the scrollbar inside the canvas. Use the same master for the scrollbar as you do for the canvas. Also, the buttons need to be a child of the canvas.
Since you didn't provide enough code to create a working example, here's a contrived example:
import tkinter as tk
class ButtonScroller(tk.Frame):
def __init__(self, parent, **kwargs):
super().__init__(parent, **kwargs)
self.canvas = tk.Canvas(self, bg="lightgray", bd=2, relief="groove")
self.vsb = tk.Scrollbar(self, orient="vertical", command=self.canvas.yview)
self.canvas.configure(yscrollcommand=self.vsb.set)
self.vsb.pack(side="right", fill="y")
self.canvas.pack(side="left", fill="both", expand=True)
self.buttons = []
self.data = []
#property
def count(self):
return len(self.buttons)
def add_button(self, image, text):
bbox = self.canvas.bbox("all") or (0,0,0,0)
x, y = 4, bbox[3]+5
# use a fake image to keep the example simple...
self.data.append(image)
button = tk.Button(self.canvas, image=image, anchor='nw', width=400, height=72,
highlightthickness=0, bd=0, relief='flat', bg='#dfdbda',
compound='left', text=text, fg='black', font='System, 10')
self.buttons.append(button)
self.canvas.create_window(x, y, anchor="nw", window=button)
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
def add_button():
# create a dummy image to simplify this example
image = tk.PhotoImage(width=64, height=64)
n = buttonframe.count + 1
text = f"Button #{n}"
buttonframe.add_button(image, text)
root = tk.Tk()
buttonframe = ButtonScroller(root, bd=1, relief="raised")
toolbar = tk.Frame(root)
toolbar.pack(side="top", fill="x")
buttonframe.pack(side="top", fill="both", expand=True)
add_button = tk.Button(toolbar, text="Add", command=add_button)
add_button.pack(side="left", padx=2, pady=2)
root.mainloop()
Solution I found (the scrollbar was never inside the canvas):
I put the set the master of the buttons to the canvas, which restricted them purely to that widget.
Can anybody please tell me what I am doing wrong? The scrollbar seems not to reflect the area of the widget it is bound to.
Many thanks.
from tkinter import *
import tkinter as tk
#panel1
root = tk.Tk()
frame1 = tk.Frame(master=root, width=900, height=800)
canvas = tk.Canvas(frame1, width=900, height= 900)
vsb = tk.Scrollbar(frame1, orient=VERTICAL)
canvas.configure(yscrollcommand=vsb.set,
scrollregion=canvas.bbox("all"))
vsb.configure(command=canvas.yview)
canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)
vsb.pack(fill=Y, side=RIGHT, expand=FALSE)
# notebook.add(frame1, text="1")
frame1.pack(expand=True, fill=BOTH)
root.mainloop()
If canvas is empty then there is nothing to scroll. And scrollbar reflects it correctly.
You have to add something to Canvas to scroll it.
I put two Frames bigger than window's size and now you have somthing to scroll.
You have to use scrollregion= after you put items in canvas. Or you can use after() to use scrollregion= after tkinter shows window.
import tkinter as tk
#def resize():
# canvas.configure(scrollregion=canvas.bbox("all"))
root = tk.Tk()
frame1 = tk.Frame(root, width=900, height=800)
frame1.pack(expand=True, fill='both')
canvas = tk.Canvas(frame1, width=900, height= 900)
canvas.pack(side='left', fill='both', expand=True)
vsb = tk.Scrollbar(frame1, orient='vertical')
vsb.pack(fill='y', side='right', expand=False)
vsb.configure(command=canvas.yview)
item_1 = tk.Frame(canvas, bg='red', width=500, height=500)
canvas.create_window(0, 0, window=item_1, anchor='nw')
item_2 = tk.Frame(canvas, bg='green', width=500, height=500)
canvas.create_window(500, 500, window=item_2, anchor='nw')
canvas.configure(yscrollcommand=vsb.set, scrollregion=canvas.bbox("all"))
#root.after(100, resize)
root.mainloop()
Given the following grid layout configuration:
It was generated through code:
from Tkinter import *
master = Tk()
frame1 = Frame(master, width=100, height=100, bg="red")
frame1.grid(sticky="nsew", columnspan=4)
frame2 = Frame(master, width=100, height=100, bg="blue")
frame2.grid()
frame3 = Frame(master, width=100, height=100, bg="green")
frame3.grid(row=1, column=1)
frame4 = Frame(master, width=100, height=100, bg="yellow")
frame4.grid(row=1, column=2)
frame4 = Frame(master, width=100, height=100, bg="purple")
frame4.grid(row=1, column=3)
master.mainloop()
I tried to insert a Entry in frame1 so it extends to the whole frame1 width with the following code:
e1 = Entry(frame1)
e1.grid(sticky="we", columnspan=4)
However, I have got the following result:
How can I make the Entry widget occupy the same width as the frame1?
The entry is inside frame1, so the grid it is in is completely unrelated to the grid in the root window.
Since you didn't tell tkinter what to do with extra space inside frame1, it left it unused. If you want the entry widget which is in column 0 to fill the frame, you need to give its column a weight.
frame1.grid_columnconfigure(0, weight=1)
If this is the only widget going in frame1, you might want to consider using pack since you can do it all in one line of code:
e1.pack(side="top", fill="x", expand=True)