Embedd Buttons with images and scrollbar on a frame using Tkinter (Python) - python

I am trying to make a on-button click event in a Tkinter window. I have a Tkinter Window on which there are buttons. Pressing one of those buttons,opens up a new Tkinter Window using Toplevel. This window would have a Scrollbar and some other buttons with images on it which can be vertically scrolled down. I could create the two functionalities separately,i.e, I could embedd a button with an image on a Tkinter window and use the Scrollbar but was unable to call the same function with the previous Tkinter window.
The code I am using is -
from Tkinter import *
from ttk import *
class VerticalScrolledFrame(Frame):
def __init__(self, parent, *args, **kw):
Frame.__init__(self, parent, *args, **kw)
# create a canvas object and a vertical scrollbar for scrolling it
vscrollbar = Scrollbar(self, orient=VERTICAL)
vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE)
canvas = Canvas(self, bd=0, highlightthickness=0,
yscrollcommand=vscrollbar.set)
canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)
vscrollbar.config(command=canvas.yview)
# reset the view
canvas.xview_moveto(0)
canvas.yview_moveto(0)
# create a frame inside the canvas which will be scrolled with it
self.interior = interior = Frame(canvas)
interior_id = canvas.create_window(0, 0, window=interior,
anchor=NW)
def _configure_interior(event):
# update the scrollbars to match the size of the inner frame
size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
canvas.config(scrollregion="0 0 %s %s" % size)
if interior.winfo_reqwidth() != canvas.winfo_width():
# update the canvas's width to fit the inner frame
canvas.config(width=interior.winfo_reqwidth())
interior.bind('<Configure>', _configure_interior)
def _configure_canvas(event):
if interior.winfo_reqwidth() != canvas.winfo_width():
# update the inner frame's width to fill the canvas
canvas.itemconfigure(interior_id, width=canvas.winfo_width())
canvas.bind('<Configure>', _configure_canvas)
#if __name__ == "__main__":
class SampleApp(Tk):
def __init__(self, *args, **kwargs):
#from Tkinter import *
print "in constructor"
import Tkinter
import ImageTk
import Image
import cv2
import numpy as np
import cv2.cv as cv
import math
import tkFileDialog
import tkMessageBox
import Tkinter as tk
root = Tk.__init__(self, *args, **kwargs)
def Print():
print "print function"
self.frame = VerticalScrolledFrame(root)
self.frame.pack()
self.label = Label(text="Shrink the window to activate the scrollbar.")
self.label.pack()
compare_button_path = "compare-button.jpg"
image_compare = Image.open(compare_button_path)
image_compare.thumbnail((70,45))
photo_compare = ImageTk.PhotoImage(image_compare)
button = tk.Button(self.frame, width=120, height=40, image=photo_compare,fg='black',bg='medium turquoise', activebackground='indian red',cursor="hand2",bd=6,relief=RAISED, compound=tk.LEFT, text="Compare",command = Print)
button.image = photo_compare
button.pack(side=LEFT)
buttons = []
for i in range(10):
buttons.append(Button(self.frame.interior, text="Button " + str(i)))
buttons[-1].pack()
app = SampleApp()
app.mainloop()
The above written function gives a pretty good result.
But how do I call this function on a button click from another Tkinter window? Changing the initial declaration of root to root = Tk() instead of root = Tk.init(self, *args, **kwargs) throws a
RuntimeError: maximum recursion depth exceeded while calling a Python object.
If I try to keep the function in some other file and import it into my original Tk file and create the object of that class on a button click, the second file gets automatically called during complilation of the original Tk file.
Can somebody please suggest a way out.

I really don't understand your question, even after asking for clarification. You finally wrote in the comments of the question "I simply want to open a tkinter window with buttons and images, on a button click from another Tkinter window".
I don't see what's preventing you from doing that. The only thing I see wrong with your code is that you're simply not creating an instance of Toplevel (well, except for a confusing set of imports). Other than that, your code should work.
For example, if you modify your sample app to look something like this, you can open as many windows as you want:
class SampleApp(Tk):
def __init__(self, *args, **kwargs):
Tk.__init__(self, *args, **kwargs)
b = Button(self, text="Open a new window", command=self.open_new)
b.pack()
def open_new(self):
top = Toplevel()
self.frame = VerticalScrolledFrame(top)
self.frame.pack()
def Print():
print "print function"
button = Button(self.frame, text="Compare",command = Print)
button.pack(side=LEFT)
buttons = []
for i in range(10):
buttons.append(Button(self.frame.interior, text="Button " + str(i)))
buttons[-1].pack()

Related

My scrollbar doesn't work - tkinter in python

Well, I'm adding many buttons to my Canvas called "itemsFrame" in order to test the ScrollBar, but it doesn't work
Well, I haven't tried much because I don't know about the subject, I've investigated how to do it but it only comes out with small buttons without being able to resize them
from tkinter import Frame, Label, Button, Entry, messagebox, ttk, Scrollbar, Canvas
import base64
import json
class WatchNewsFrame(Frame):
name = "WatchNewsFrame"
def __init__(self, parent):
super().__init__()
self.Parent = parent
self.initializecomponents()
pass
def set_news(self):
data = json.load(open(self.file))["News"]
cont = 1
for key in range(10):
Panel = Button(self.itemsFrame)
Panel.config(bg="#656565", activebackground="#808080")
Panel.place(relwidth=.95, relheight=0.245, rely=.028+key*0.25, relx=.5, anchor="n")
pass
def initializecomponents(self):
Frame.__init__(self, self.Parent)
self.itemsFrame = Canvas(self)
self.ItemsScrooll = Scrollbar(self, orient="vertical")
# this frame
self.config(background=self.Parent["background"])
self.place(relwidth=0.95, relheight=0.95, relx=0.5, rely=0.5, anchor="center")
# ItemsScrooll
self.ItemsScrooll.pack(side="right", fill="y")
# itemsFrame
self.itemsFrame.config(bg="#606060", highlightbackground="#FFFFFF", yscrollcommand=self.ItemsScrooll.set)
self.itemsFrame.place(relheight=0.85, relwidth=0.9, relx=0.5, rely=0.5, anchor="center")
self.itemsFrame.bind_all("<MouseWheel>", self.on_mouse_wheel)
self.ItemsScrooll.config(command=self.itemsFrame.yview)
# events
self.set_news()
pass
pass

Tkinter - I created a custom scrollable frame class. Is it possible for it to automatically call a function when a child is added to it?

I make a custom tkinter widget (ScrollFrame) to make a scrollable frame using the answer from this question: Tkinter scrollbar for frame
Everything is working fine but I have to call the "ConfigureCanvas" function every time new widget is added to ScrollFrame in order to resize the scroll area. Are there any event binds that I could use that would be called when a new widget is packed/grided/placed to the Scroll Frame?
eg:
class ScrollFrame(Frame):
'''
'''
self.packFrame.bind('<NewChildWidgetAdded>', self.ConfigureCanvas)
'''
'''
exampleLabel = Label(packFrame, text='Hello')
exampleLabel.pack() # activate the "NewChildWidgetAdded" event?
Here's working code (python 3+). I created a loop that creates 50 labels to give me something to scroll over.
import tkinter
from tkinter import Tk, Frame, filedialog, Button, Listbox, Label, Entry, Text, Canvas, Scrollbar, Radiobutton, Checkbutton, Menu, IntVar, StringVar, BooleanVar, Grid, OptionMenu, Toplevel, ALL, CURRENT, END
#imported more than i needed, copied from other code where this is all used.
class ScrollFrame(Frame):
def __init__(self, frame, *args, **kwargs):
Frame.__init__(self, frame, *args, **kwargs)
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
self.vScroll = Scrollbar(self, orient='vertical')
self.vScroll.grid(row=0, column=1, sticky='wens')
self.hScroll = Scrollbar(self, orient='horizontal')
self.hScroll.grid(row=1, column=0, sticky='wens')
self.canvas = Canvas(self, bd=0, highlightthickness=0, xscrollcommand=self.hScroll.set, yscrollcommand=self.vScroll.set, bg='green')
self.canvas.grid(row=0, column=0, sticky='wens')
self.vScroll.config(command=self.canvas.yview)
self.hScroll.config(command=self.canvas.xview)
self.packFrame = Frame(self.canvas, bg='blue')
self.packWindow = self.canvas.create_window(0,0, window=self.packFrame, anchor='nw')
def ConfigureCanvas(self):
self.packFrame.update_idletasks()
size = (self.packFrame.winfo_reqwidth(), self.packFrame.winfo_reqheight())
self.canvas.config(scrollregion=(0,0,size[0], size[1]))
mw = Tk()
scrollCavnas = ScrollFrame(mw)
scrollCavnas.pack(side='right', fill='both', expand='yes')
scrollFrame = scrollCavnas.packFrame
temp = 0
while temp < 50:
Label(scrollFrame, text=temp).grid(row=temp, column=temp)
temp += 1
scrollCavnas.ConfigureCanvas() # don't want to have to call this every time a new widget is added.
mw.mainloop()
I tried:
self.packFrame.bind('<map>', self.ConfigureCanvas)
But it looks like this only gets called when the ScrollFrame is created, not when I add a new child widget to the ScrollFrame.
I looked over the documentation (http://tcl.tk/man/tcl8.5/TkCmd/bind.htm#M13) but I didn't notice anything that could do what I wanted.
One solution is to use the Configure event. The downside is this gets called every time the widget is scrolled.
self.packFrame.bind('<Configure>', self.ConfigureCanvas)
def ConfigureCanvas(self, event=None):
self.packFrame.update_idletasks()
size = (self.packFrame.winfo_reqwidth(), self.packFrame.winfo_reqheight())
self.canvas.config(scrollregion=(0,0,size[0], size[1]))
I was able to get at least part of the function to only run when adding a new widget. This seems clunky and I believe it will only work if scrolling horizontally (it could be modified to do vertical or both though). This compares the required width to the current scroll region size and if they're different it will resize the scroll region.
def ConfigureCanvas(self, event=None):
scrollRegion = self.canvas['scrollregion'].split(' ')
try:
scrollRegion = int(scrollRegion[2])
except IndexError:
scrollRegion = None
if self.packFrame.winfo_reqwidth() != scrollRegion:
self.packFrame.update_idletasks()
size = (self.packFrame.winfo_reqwidth(), self.packFrame.winfo_reqheight())
self.canvas.config(scrollregion=(0,0,size[0], size[1]))

import user input from TKinter button to a different .py module

On ticket.py module i have a Tkinter frame; value = Entry(frame), and on the same module I have a button where command=exoutput;
def exoutput():
print value.get()
I would like to import the value to othermodule.py on the button command/ when I hit the button.
Currently when I import, the print is generated from the exoutput() function, rather than from the othermodule.py file.
Suggestions on how to print the value on othermodule.py?
# ticket.py
from Tkinter import*
window = Tk()
window.title("Entry")
frame = Frame(window)
value = Entry(frame)
def exoutput():
print value.get()
btnStage = Button(frame, text='ACTION', command=exoutput)
btnStage.pack(side=RIGHT, padx=2)
value.pack(side=LEFT)
frame.pack(padx=10, pady=10)
window.resizable(0, 0)
window.mainloop()
The other file, I've tried something like this;
# othermodule.py
import ticket
usersinput = ticket.value.get()
print usersinput
I think you either need multi-threading, or swap your file contents. Nothing runs after mainloop until the Tk instance is destroyed.
Alternatively, you could structure your ticket.py in OOP, and fetch GUI object from it by your othermodule to use it as you please. Below is an example:
ticket.py:
#import tkinter as tk
import Tkinter as tk
class Window(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title("Entry")
self.resizable(False, False)
# create widgets
self.frame = tk.Frame(self)
self.value = tk.Entry(self.frame)
self.btn_stage = tk.Button(self.frame, text="ACTION")
# widgets layout
self.frame.pack(padx=10, pady=10)
self.btn_stage.pack(side='right', padx=2)
self.value.pack(side='left')
if __name__ == "__main__":
root = Window()
root.mainloop()
and othermodule.py:
import ticket
def put():
global my_var_in_othermodule
my_var_in_othermodule = ticket_GUI.value.get()
ticket_GUI.destroy()
my_var_in_othermodule = ""
ticket_GUI = ticket.Window()
ticket_GUI.btn_stage['command'] = put
ticket_GUI.mainloop()
print(my_var_in_othermodule)
input()

Tkinter Grid Dynamic Layout

I am wanting to create a grid layout, with a grid that fills the first row until it runs out of space in the window, and will dynamically move items to the row below (like text line-wrapping). As the window width is adjusted, the grid adjusts to fit. The boxes resizing is not desired. I intend to maintain each small box's size, but change where the layout puts each box.
I imagine this functionality is possible by measuring the width of the frame, and if the (number of boxes)*(width of each box) exceeds the width, move to the next row. I was just wondering if there was a better way built in that I'm not understanding.
If the above is the only option, what is the best way to update that? Do I have to set an event on window resize or something? It seems like I shouldn't have to rework a layout manager, which is what that feels like. I just want to check if similar functionality is already built in. Grid seems like a powerful layout manager, but I have not been able to find that option.
The below pics describes the behavior I want using the same set of 6 boxes on a single frame using grid layout.
Window is wide enough to hold all 6 boxes, so they all fit on row 1. They then adjust as window size changes.
If you plan on forcing each box to be a uniform size, the simplest solution is to use the text widget as the container since it has the built-in ability to wrap.
Here is a working example. Click on the "add" button to add additional boxes. Resize the window to see that they automatically wrap as the window grows and shrinks.
import Tkinter as tk
import random
class DynamicGrid(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.text = tk.Text(self, wrap="char", borderwidth=0, highlightthickness=0,
state="disabled")
self.text.pack(fill="both", expand=True)
self.boxes = []
def add_box(self, color=None):
bg = color if color else random.choice(("red", "orange", "green", "blue", "violet"))
box = tk.Frame(self.text, bd=1, relief="sunken", background=bg,
width=100, height=100)
self.boxes.append(box)
self.text.configure(state="normal")
self.text.window_create("end", window=box)
self.text.configure(state="disabled")
class Example(object):
def __init__(self):
self.root = tk.Tk()
self.dg = DynamicGrid(self.root, width=500, height=200)
add_button = tk.Button(self.root, text="Add", command=self.dg.add_box)
add_button.pack()
self.dg.pack(side="top", fill="both", expand=True)
# add a few boxes to start
for i in range(10):
self.dg.add_box()
def start(self):
self.root.mainloop()
Example().start()
Here's a working example:
import Tkinter as tk
class AutoGrid(tk.Frame):
def __init__(self, master=None, **kwargs):
tk.Frame.__init__(self, master, **kwargs)
self.columns = None
self.bind('<Configure>', self.regrid)
def regrid(self, event=None):
width = self.winfo_width()
slaves = self.grid_slaves()
max_width = max(slave.winfo_width() for slave in slaves)
cols = width // max_width
if cols == self.columns: # if the column number has not changed, abort
return
for i, slave in enumerate(slaves):
slave.grid_forget()
slave.grid(row=i//cols, column=i%cols)
self.columns = cols
class TestFrame(tk.Frame):
def __init__(self, master=None, **kwargs):
tk.Frame.__init__(self, master, bd=5, relief=tk.RAISED, **kwargs)
tk.Label(self, text="name").pack(pady=10)
tk.Label(self, text=" info ........ info ").pack(pady=10)
tk.Label(self, text="data\n"*5).pack(pady=10)
def main():
root = tk.Tk()
frame = AutoGrid(root)
frame.pack(fill=tk.BOTH, expand=True)
TestFrame(frame).grid() # use normal grid parameters to set up initial layout
TestFrame(frame).grid(column=1)
TestFrame(frame).grid(column=2)
TestFrame(frame).grid()
TestFrame(frame).grid()
TestFrame(frame).grid()
root.mainloop()
if __name__ == '__main__':
main()
Note this will ruin the rowspan and columnspan features of the grid manager.
Here's a streamlined version of Bryan's answer without classes and a few extra comments for anyone who is confused and is trying to implement this quickly into their own project.
from tkinter import *
import tkinter as tk
#Create main window
root = tk.Tk()
#Create WidgetWrapper
widgetWrapper = tk.Text(root, wrap="char", borderwidth=0,highlightthickness=0,state="disabled", cursor="arrow")
#state = "disabled" is to disable text from being input by user
#cursor = "arrow" is to ensure when user hovers, the "I" beam cursor (text cursor) is not displayed
widgetWrapper.pack(fill="both", expand=True)
def additem():
item = Label(bd = 5, relief="solid", text="O", bg="red") #Create the actual widgets
widgetWrapper.window_create("end", window=item) #Put it inside the widget wrapper (the text)
# add a few boxes to start
for i in range(10):
additem()
#Not needed to implement in other code, just an add button
add_button = tk.Button(root, text="Add", command=additem)
add_button.pack()

Tkinter: Pack new widgets below other widgets

I'm trying to pack the button below the Text and Scrollbar widget.
#!/usr/bin/python
try:
from Tkinter import *
except ImportError:
from tkinter import *
class Chat(Frame):
def __init__(self, master):
Frame.__init__(self, master)
self.pack(anchor=N, fill=BOTH)
self.create_widgets()
self.count = 0
def create_widgets(self):
self.scrolly = Scrollbar(self)
self.scrolly.pack(side=RIGHT, fill=Y)
self.chattext = Text(self, borderwidth=5, yscrollcommand=self.scrolly.set)
self.chattext.pack(side=LEFT)
self.scrolly.config(command=Text.yview(self.chattext))
self.button1 = Button(self, text="Add text", command=self.add_text)
self.button1.pack()
def add_text(self):
self.count += 1
self.chattext.insert("end", "%i\n" % self.count)
self.chattext.update_idletasks()
def main():
root = Tk()
root.title("Test Chat Client")
root.geometry("600x500")
#root.resizable(0,0)
app = Chat(root)
root.mainloop()
if __name__ == "__main__":
main()
This is what it looks like
I want the button to be below and not in between the other widgets.
I have tried the following:
self.button1.pack(after=self.scrolly)
self.button1.pack(after=self.chattext)
How may i pack the button at the bottom?
Another issue is that the scrollbar does not work, when i try to scroll nothing happens.
(Yes, i have tried to fill the Text widget with alot of lines, more than it can view.)
Also, why is the scrollbar viewed/packed outside/"far" away from the Text widget?
Try using the grid geometry manager instead.
http://www.tkdocs.com/tutorial/grid.html
I think you should consider replacing the text field with a ScrolledText field.
It's a lot easier to use and doesn't require manual scrollbar placement.
(Don't use pack to place it though. Use grid)
import tkinter as tk
import tkinter.scrolledtext as tkst
self.chattext = tkst.ScrolledText(
master = self,
wrap = tk.WORD,
width = 20,
height = 10
)

Categories