Scheduling order of events with tkinter OOP - python-3.x

I am using tkinter in python 3.6 to display a blue square in the middle of a window. At each click the blue square should disappear and reappear after 2 seconds on a different random location. When running the following code, the blue square (referred as stimulus) does not disappear. Everything else seem to work properly.
This is the code:
import tkinter as TK
import random as RAN
class THR:
def __init__(self, root):
self.root = root
self.root.config(background='black')
self.screenYpixels = 600
self.screenXpixels = 1024
self.ITI = 2000
self.background = TK.Canvas(root, width=1024, height=600, bg='black',
bd=0, highlightthickness=0, relief='ridge')
self.background.pack()
self.newtrial()
def newtrial(self):
self.xpos = RAN.randrange(200, 1000)
self.ypos = RAN.randrange(100, 500)
self.stimulus = TK.Canvas(root,width=100,height=100,bg='blue', bd=0,
highlightthickness=0, relief='ridge')
self.stimulus.place(x=self.xpos, y=self.ypos, anchor="c")
self.stimulus.bind("<Button-1>", self.response)
self.exitbutton()
def response(self, event):
self.stimulus.place_forget()
self.intertrialinterval()
def intertrialinterval(self, *args):
self.root.after(self.ITI,self.newtrial())
def exitbutton(self):
self.exitButton = TK.Button(self.root, bg="green")
self.exitButton.place(relx=0.99, rely=0.01, anchor="c")
self.exitButton.bind("<Button-1>", self.exitprogram)
def exitprogram(self, root):
self.root.quit()
root = TK.Tk()
THR(root)
root.mainloop()
Here a list of things I tried but that did not work
Using time.sleep instead of root.after
Changing the way a newtrial() is called
Putting the sleep or after in different places
The last couple of hours searching the web for similar problems / solutions did not really help. I would like to fix this and at the same time understand what am I doing wrong.

As it turns out the list of links on the right of this webpage provided me with the solution just 3 minutes after posting the question.
This is the original thread Python Tkinter after event OOPS implementation
and here I write the solution:
dropping the parenthesis in the function called with the after method. So this self.root.after(self.ITI,self.newtrial()) is self.root.after(self.ITI,self.newtrial)
Unfortunately I still do not understand how that fixed the problem..

Related

Python scrollable frame and canvas window sizing

I have been trying to find a way to size a frame inside of a canvas window for quite a while to no avail. I finally came across some posts that helped me begin to understand the problem, and eventually dug up a post that gave the solution below:
import tkinter as tk
def onCanvasConfigure(e):
canvas.itemconfig('frame', height=canvas.winfo_height(), width=canvas.winfo_width())
root=tk.Tk()
canvas = tk.Canvas(root, background="blue")
frame = tk.Frame(canvas, background="red")
canvas.pack(expand=True, fill="both")
canvas.create_window((0,0), window=frame, anchor="nw", tags="frame")
canvas.bind("<Configure>", onCanvasConfigure)
root.mainloop()
This completely solves my problem....if I don't have the GUI in a function, which I need to. I have multiple different GUI's that would need to implement this solution. I have come across other solutions that use OOP, but I haven't yet wrapped my head around OOP. I've also found a way to make the above code work inside of a program myself:
import tkinter as tk
def onCanvasConfigure(e):
canvas.itemconfig('frame', height=canvas.winfo_height(), width=canvas.winfo_width())
def test():
window=tk.Tk()
global canvas
canvas = tk.Canvas(master=window)
frame=tk.Frame(master=canvas, background='red')
canvas.pack(expand=True, fill=tk.BOTH)
canvas.create_window((0,0), window=frame, anchor=tk.NW, tags = 'frame')
canvas.bind("<Configure>", onCanvasConfigure)
root.mainloop()
test()
However, this requires the use of a global variable, which I would rather avoid. Is there anything I'm missing that would help me resize the frame inside of the canvas window? If you have any pointers to where I might even find this information that would also be helpful.
The event object that is passed in has a reference to the widget that received the event. So, just replace your global canvas with e.widget, or initialize a local variable named canvas:
def onCanvasConfigure(e):
canvas = e.widget
canvas.itemconfig('frame', height=canvas.winfo_height(), width=canvas.winfo_width())
If it helps, here's an object-oriented version of your application code. Other than the implementation differences, it should behave the same way as the functional version.
import tkinter as tk
class App(tk.Tk): # create a base app class that inherits from Tk
def __init__(self):
super().__init__() # initialize Tk
self.canvas = tk.Canvas(master=self)
self.frame = tk.Frame(master=self.canvas, background='red')
self.canvas.pack(expand=True, fill=tk.BOTH)
self.canvas.create_window(
(0,0),
window=self.frame,
anchor=tk.NW,
tags='frame',
)
self.canvas.bind('<Configure>', self.on_canvas_configure)
def on_canvas_configure(self, event):
self.canvas.itemconfig(
'frame',
height=self.canvas.winfo_height(),
width=self.canvas.winfo_width(),
)
if __name__ == '__main__':
root = App() # instantiate the App class
root.mainloop() # start the app
Since everything here is contained within the App class, you can avoid globals (thanks to self!)

Is there a way to bind MouseWheel event on labels inside a frame [duplicate]

I have this scroll-able frame (frame inside canvas actually).
import Tkinter as tk
class Scrollbarframe():
def __init__(self, parent,xsize,ysize,xcod,ycod):
def ScrollAll(event):
canvas1.configure(scrollregion=canvas1.bbox("all"),width=xsize,height=ysize,bg='white')
self.parent=parent
self.frame1=tk.Frame(parent,bg='white')
self.frame1.place(x=xcod,y=ycod)
canvas1=tk.Canvas(self.frame1)
self.frame2=tk.Frame(canvas1,bg='white',relief='groove',bd=1,width=1230,height=430)
scrollbar1=tk.Scrollbar(self.frame1,orient="vertical",command=canvas1.yview)
canvas1.configure(yscrollcommand=scrollbar1.set)
scrollbar1.pack(side="right",fill="y")
canvas1.pack(side="left")
canvas1.create_window((0,0),window=self.frame2,anchor='nw')
self.frame2.bind("<Configure>",ScrollAll)
I would like to bind mouse wheel to the scrollbar so that user can scroll down the frame without having to use arrow buttons on the scrollbar. After looking around, i added a binding to my canvas1 like this
self.frame1.bind("<MouseWheel>", self.OnMouseWheel)
This is the function:
def OnMouseWheel(self,event):
self.scrollbar1.yview("scroll",event.delta,"units")
return "break"
But the scroll bar won't move when i use mousewheel. Can anyone help me with this? All i want is when the user use mousewheel (inside the frame area/on the scrollbar), the canvas should automatically scroll up or down.
Perhaps the simplest solution is to make a global binding for the mousewheel. It will then fire no matter what widget is under the mouse or which widget has the keyboard focus. You can then unconditionally scroll the canvas, or you can be smart and figure out which of your windows should scroll.
For example, on windows you would do something like this:
self.canvas = Canvas(...)
self.canvas.bind_all("<MouseWheel>", self._on_mousewheel)
...
def _on_mousewheel(self, event):
self.canvas.yview_scroll(-1*(event.delta/120), "units")
Note that self.canvas.bind_all is a bit misleading -- you more correctly should call root.bind_all but I don't know what or how you define your root window. Regardless, the two calls are synonymous.
Platform differences:
On Windows, you bind to <MouseWheel> and you need to divide event.delta by 120 (or some other factor depending on how fast you want the scroll)
on OSX, you bind to <MouseWheel> and you need to use event.delta without modification
on X11 systems you need to bind to <Button-4> and <Button-5>, and you need to divide event.delta by 120 (or some other factor depending on how fast you want to scroll)
There are more refined solutions involving virtual events and determining which window has the focus or is under the mouse, or passing the canvas window reference through the binding, but hopefully this will get you started.
Based on #BryanOakley's answer, here is a way to scroll only the focused widget (i.e. the one you have mouse cursor currently over).
Bind to <Enter> and <Leave> events happening on your scrollable frame which sits inside a canvas, the following way (scrollframe is the frame that is inside the canvas):
...
self.scrollframe.bind('<Enter>', self._bound_to_mousewheel)
self.scrollframe.bind('<Leave>', self._unbound_to_mousewheel)
return None
def _bound_to_mousewheel(self, event):
self.canv.bind_all("<MouseWheel>", self._on_mousewheel)
def _unbound_to_mousewheel(self, event):
self.canv.unbind_all("<MouseWheel>")
def _on_mousewheel(self, event):
self.canv.yview_scroll(int(-1*(event.delta/120)), "units")
This link gives you an example as to how to use the scrollwheel.
http://www.daniweb.com/software-development/python/code/217059/using-the-mouse-wheel-with-tkinter-python
I hope this helps!
# explore the mouse wheel with the Tkinter GUI toolkit
# Windows and Linux generate different events
# tested with Python25
import Tkinter as tk
def mouse_wheel(event):
global count
# respond to Linux or Windows wheel event
if event.num == 5 or event.delta == -120:
count -= 1
if event.num == 4 or event.delta == 120:
count += 1
label['text'] = count
count = 0
root = tk.Tk()
root.title('turn mouse wheel')
root['bg'] = 'darkgreen'
# with Windows OS
root.bind("<MouseWheel>", mouse_wheel)
# with Linux OS
root.bind("<Button-4>", mouse_wheel)
root.bind("<Button-5>", mouse_wheel)
label = tk.Label(root, font=('courier', 18, 'bold'), width=10)
label.pack(padx=40, pady=40)
root.mainloop()
To get rid of the weird factor 120 we could just look at the sign of the event.delta value. This makes it easy to use the same handler under Windows, Linux and Mac OS.
# Mouse wheel handler for Mac, Windows and Linux
# Windows, Mac: Binding to <MouseWheel> is being used
# Linux: Binding to <Button-4> and <Button-5> is being used
def MouseWheelHandler(event):
global count
def delta(event):
if event.num == 5 or event.delta < 0:
return -1
return 1
count += delta(event)
print(count)
import tkinter
root = tkinter.Tk()
count = 0
root.bind("<MouseWheel>",MouseWheelHandler)
root.bind("<Button-4>",MouseWheelHandler)
root.bind("<Button-5>",MouseWheelHandler)
root.mainloop()
As an addendum to the above, the "delta" scaling factor is easy to calculate, since platform information is available through the sys and platform modules (and possibly others).
def my_mousewheel_handler(event):
if sys.platform == 'darwin': # for OS X # also, if platform.system() == 'Darwin':
delta = event.delta
else: # for Windows, Linux
delta = event.delta // 120 # event.delta is some multiple of 120
if event.widget in (widget1, widget2, ):
'do some really cool stuff...'
In case you are interested
How to scroll 2 listbox at the same time
#listbox scrollbar
from tkinter import *
root = Tk()
def scrolllistbox2(event):
listbox2.yview_scroll(int(-1*(event.delta/120)), "units")
scrollbar = Scrollbar(root)
#scrollbar.pack(side=RIGHT, fill=Y)
listbox = Listbox(root)
listbox.pack()
for i in range(100):
listbox.insert(END, i)
# attach listbox to scrollbar
listbox.config(yscrollcommand=scrollbar.set)
listbox.bind("<MouseWheel>", scrolllistbox2)
listbox2 = Listbox(root)
listbox2.pack()
for i in range(100):
listbox2.insert(END, i+100)
listbox2.config(yscrollcommand=scrollbar.set)
#scrollbar.config(command=listbox.yview)
root.mainloop()
Or...
from tkinter import *
root = Tk()
root.geometry("400x400")
def scrolllistbox(event):
''' scrolling both listbox '''
listbox2.yview_scroll(int(-1*(event.delta/120)), "units")
listbox1.yview_scroll(int(-1*(event.delta/120)), "units")
def random_insert():
''' adding some numbers to the listboxes '''
for i in range(100):
listbox1.insert(END, i)
listbox2.insert(END, i + 100)
# SCROLLBAR
scrollbar = Scrollbar(root)
#scrollbar.pack(side=RIGHT, fill=Y)
# LISTBOX 1
listbox1 = Listbox(root)
listbox1.pack()
# attach listbox to scrollbar with yscrollcommand
# listbox1.config(yscrollcommand=scrollbar.set)
# The second one
listbox2 = Listbox(root)
listbox2.pack()
listbox2.config(yscrollcommand=scrollbar.set)
# scroll the first one when you're on the second one
# listbox2.bind("<MouseWheel>", scrolllistbox)
root.bind("<MouseWheel>", scrolllistbox)
# scroll also the second list when you're on the first
listbox1.bind("<MouseWheel>", scrolllistbox)
random_insert()
#scrollbar.config(command=listbox.yview)
root.mainloop()
Mikhail T.'s answer worked really well for me. Here is perhaps a more generic set up that others might find useful (I really need to start giving things back)
def _setup_mousewheel(self,frame,canvas):
frame.bind('<Enter>', lambda *args, passed=canvas: self._bound_to_mousewheel(*args,passed))
frame.bind('<Leave>', lambda *args, passed=canvas: self._unbound_to_mousewheel(*args,passed))
def _bound_to_mousewheel(self, event, canvas):
canvas.bind_all("<MouseWheel>", lambda *args, passed=canvas: self._on_mousewheel(*args,passed))
def _unbound_to_mousewheel(self, event, canvas):
canvas.unbind_all("<MouseWheel>")
def _on_mousewheel(self, event, canvas):
canvas.yview_scroll(int(-1*(event.delta/120)), "units")
Then setting a canvas/frame up for mousewheel scrolling is just:
self._setup_mousewheel(frame, canvas)
def onmousewheel(widget, command):
widget.bind("<Enter>", lambda _: widget.bind_all('<MouseWheel>',command ))
widget.bind("<Leave>", lambda _: widget.unbind_all('<MouseWheel>'))
onmousewheel(canvas, lambda e: canvas.yview_scroll(int(-1*(e.delta)), "units"))
Compact solution for just scroll frame which you want.
Thanks for everyone who share their solution.

In python3 tkinter, the wigdet frame doesn't show in interface

I use the same format of frame but it doesn't show in the interface, hope someone could tell me the solution, thanks.
class Interface(Frame):
def __init__(self,parent=None):
Frame.__init__(self,parent)
self.master.title("measurement")
self.grid()
# fix the size and parameters of widget
self.master.geometry("700x400+100+50")
self.master.Frame1 = Frame(self,relief=GROOVE,bg='white')
self.master.Frame1.grid(column=1,row=9)
self.can =Canvas(self, bg="ivory", width =200, height =150)
self.master.canvas = Canvas(self.master, width=150, height=120, background='snow')
ligne1=self.master.canvas.create_line(75, 0, 75, 120)
if __name__ == "__main__":
window = Tk()
window.resizable(False, False)
Interface(window).mainloop()
I can't figure out why you have 2 Canvas's, but the problem is that you aren't placing them on their respective parents. I cut out a lot of the code that seemed unnecessary and restructured your code to make it more logical:
class Interface(Frame):
def __init__(self, parent):
self.parent = parent
super().__init__(self.parent)
self.Frame1 = Frame(self, relief=GROOVE)
self.Frame1.grid()
self.canvas = Canvas(self.Frame1, bg="ivory", width=200, height=150)
self.canvas.grid()
self.canvas.create_line(75, 0, 75, 120)
if __name__ == "__main__":
root = Tk()
# Tk configurations are not relevant to
# the Interface and should be done out here
root.title('Measurement')
root.geometry('700x400+100+50')
root.resizable(False, False)
Interface(root).pack()
root.mainloop()
i think I don't really understand your problem, you don't see your frame because you don't have any widget in it, that's all
import tkinter as tk
class Interface(tk.Frame):
def __init__(self,parent=None):
tk.Frame.__init__(self,parent)
self.master.title("measurement")
self.grid(row=0, column=0)
# fix the size and parameters of widget
self.master.geometry("700x400+100+50")
self.master.Frame1 = tk.Frame(self,relief='groove',bg='white')
self.master.Frame1.grid(column=1,row=9)
labelExemple =tk.Label(self.master.Frame1, text="Exemple")
labelExemple.grid(row=0,column=0)
self.can = tk.Canvas(self, bg="ivory", width =200, height =150)
self.master.canvas = tk.Canvas(self.master, width=150, height=120, background='snow')
self.ligne1=self.master.canvas.create_line(75, 0, 75, 120)
if __name__ == "__main__":
window = tk.Tk()
window.resizable(False, False)
Interface(window).mainloop()
PS : use import tkinter as tk instead of from tkinter import *
There are several problems with those few lines of code, almost all having to do with the way you're using grid:
you aren't using the sticky option, so widgets won't expand to fill the space they are given
you aren't setting the weight for any rows or columns, so tkinter doesn't know how to allocate unused space
you aren't using grid or pack to put the canvases inside of frames, so the frames stay their default size of 1x1
The biggest problem is that you're trying to solve all of those problems at once. Layout problems are usually pretty simple to solve as long as you're only trying to solve one problem at a time.
Start by removing all of the widgets from Interface. Then, give that frame a distinctive background color and then try to make it fill the window (assuming that's ultimately what you want it to do). Also, remove the root.resizable(False, False). It's rarely something a user would want (they like to be able to control their windows), plus it makes your job of debugging layout problems harder.
Once you get your instance of Interface to appear, add a single widget and make sure it appears too. Then add the next, and the next, adding one widget at a time and observing how it behaves.

Hiding canvas, part 2

Gotten further along but now I can't get rid of canvaspopup, even with pack_forget(). Everything else seems to be working okay, granted I'm testing it with a very dumbed down version of the full program so I can post code here without posting 1000+ lines of code.
import tkinter as tk
from tkinter import *
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.keys = dict.fromkeys(('Left', 'Right', 'Up', 'Down', 'Prior', 'Next', 'Shift_L', 'Alt'))
self.frame = tk.Frame(self,bg='gray', width=1366, height=714)
self.frame.pack()
self.canvas = tk.Canvas(self, background="black", width=714, height=714)
self.canvas.pack(fill='both', expand=True)
self.canvas.place(x=652,y=0)
self.canvas.bind("<Alt_L><w>", self.mgram)
self.canvas.bind("<Left>", self.left)
self.canvas.focus_set()
def left(self,event):
print('canvas left')
def popleft(self,event):
print('popup left')
def popescape(self,event):
self.canvas.focus_set()
self.canvaspopup.pack_forget()
def mgram(self,event):
self.canvaspopup = Canvas(self, width=800, height=614)
self.canvaspopup.pack(fill='both', expand=True)
self.canvaspopup.place(x=284,y=52)
self.png = Label(self.canvaspopup)
self.png.pack()
self.popupimage1 = PhotoImage(file='example.png')
self.canvaspopup.bind("<Left>", self.popleft)
self.canvaspopup.bind("<Escape>", self.popescape)
self.canvaspopup.focus_set()
self.png.config(image=self.popupimage1)
s = Canvas(self.canvaspopup, width=800, height=14)
s.pack_propagate(0)
s.place(x=0,y=600)
v = Label(s, fg='black',borderwidth=0,anchor='center',text = 'Image of: ')
v.pack()
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()
How do I make the popup disappear, supposedly in my previous question Bryan said I could use pack_forget() but that isn't working here. Everything else seems like it working correctly. Still not sure why I couldn't get focus_set to work earlier this morning at home. Not sure what I've changed now???
In an earlier question you asked how to hide a window managed with pack, and I said pack_forget. In this code you're using place so the command would be place_forget.
You call pack but immediately call place right after. Only one geometry manager (pack, place, grid) can manage a widget, and it's always the last one that you use on that widget.

It seems the kernel died unexpectedly. Use 'Restart kernel' to continue using this console (zeromq v4.1.3)

Anaconda3-2.4.0-Windows-x86_64 install including Python 3.5 64-bit and Spyder 2.3.7.
Windows 7 Professional, SP1, 64 bit.
I am trying to follow a series of videos on youtube to add a matplotlib graph to a tkinter window, the first 5 videos cover the initial creation of windows and buttons in the tkinter window which was all fairly simple. The problem occurs when i get to the first matplotlib addition. Even though i copy exactly what the guy in the video does within Python, i get an error where he gets a working program.
Here is a link to the video i am currently attempting to follow:
How to add a Matplotlib Graph to Tkinter Window in Python 3 - Tkinter tutorial Python 3.4 p. 6
There is a list of the other videos in the series to the right when playing.
The error message given within the ipython console is: "It seems the kernel died unexpectedly. Use 'Restart kernel' to continue using this console". The message repeats every few seconds untill 'restart kernel' is selected.
In the code below if it is run as is, no windows are opened. If 'canvas.show()' is commented out, a window opens and a python crash error is shown in Windows.
the program runs and the buttons all function correctly if 'canvas.show()', 'canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand = True)' and 'canvas._tkcanvas.pack()' are all commented out.
I have researched this and found the zeromq issue with versions 4.0.6 and 4.1.1 which were fixed in the newer versions. Using the 'conda list' command in the anaconda prompt shows the version number 4.1.3, therefore i do not believe this to be the issue.
I am running in python 3.5 where 'sentdex' (the guy in the video) is running 3.4, could this cause the issue?
Here is my code (minus 3 classes which open windows with buttons on):
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
import tkinter as tk
from tkinter import ttk
LARGE_FONT = ("Arial, 12")
class Home(tk.Tk):
def __init__(self, *args):
tk.Tk.__init__(self, *args)
tk.Tk.iconbitmap(self, default="icon1.ico")
tk.Tk.wm_title(self, "Converters & Calculators")
container = tk.Frame(self)
container.pack(side = "top", fill = "both", expand = True)
container.grid_columnconfigure(0, weight=1)
container.grid_rowconfigure(0, weight=1)
self.frames = {}
for F in (StartPage, MPL_Tutorial):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row = 0, column = 0, sticky = "nsew")
self.show_frame(StartPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Please select a calculator or convertor from the list:", font = LARGE_FONT)
label.pack(pady=20, padx=20)
MPL_Tutorial_button = ttk.Button(self, text = "Graph Page", command=lambda: controller.show_frame(MPL_Tutorial))
MPL_Tutorial_button.pack()
class MPL_Tutorial(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Graph Page", font = LARGE_FONT)
label.pack(pady=20, padx=20)
Home_button = ttk.Button(self, text = "Home", command=lambda: controller.show_frame(StartPage))
Home_button.pack()
f = Figure(figsize = (5,5), dpi=100)
a = f.add_subplot(111)
a.plot([1,2,3,4,5,6,7,8], [5,8,1,3,7,4,9,5])
canvas = FigureCanvasTkAgg(f, self)
canvas.show()
canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand = True)
toolbar = NavigationToolbar2TkAgg(canvas, self)
toolbar.update()
canvas._tkcanvas.pack()
app = Home()
app.mainloop()
I have edited some of the content while writing the program to reflect the application i eventually want the program for, a calculator / converter for various engineering bits and pieces, such as 'reflection coefficient / VSWR ratio / Return loss conversion'. I believe the only things i have edited are names of things and some padding values.
Any help will be greatly appreciated! I fully expect the answer to be something i have done wrong, i am fairly new to programming as a whole.
Edit: Removed sections of code which referred to the other code i left out as this would undoubtedly cause 'extra' issues if anyone tried to run it.

Resources