Keyboard event inside a class not working in tkinter - python-3.x

I am trying to capture a keypress after a canvas object is displayed in order to delete the latter, and it's not working. The canvas is displayed properly, but the keypress event is not getting captured. I am a total newbie at Python and this is just a test-code to check my understanding of Tkinter so far. I'm sure it's something fairly basic that I'm missing, so thank you for your patience.
from tkinter import *
class Main:
def __init__(self,master):
# create splash screen
splash = Canvas(master, bg='white')
splash.bind("<Key>",self.splash_key)
splash.pack(fill="both", expand=True)
def splash_key(event):
print('key captured!')
splash.delete("all")
root = Tk()
root.wm_title('Test')
root.attributes('-zoomed',True)
app = Main(root)
root.mainloop()

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!)

stop ttk.Entry() from reading keyboard

Environment: macOS Catalina, Python 3.7.4, Tcl/Tk 8.6.9, VSC 1.39.1
I have a situation where I am using a bar/qr code scanner to provide a string to a ttk.Entry() method, which then fires off a function.
The reader is seen by the OS as an HID keyboard, so the text from the QR code is received by the ttk.Entry() widget that I give focus to during code execution. I have bound the widget to the key because the scanner sends a cr/lf at the end of the text string, which works as needed.
However, I am running into an issue where if the qr code lingers over the scanner too long it will rescan the qr code and the widget receives the qr code text again, which then causes it to be processed again.
I have tried disabling the ttk.Entry() in the function, deleting the widget contents, and removing focus to no avail. The behavior I'm seeing is occurring even though the widget is disabled and does not have focus, it is still getting input and executing the function again if the scanner rescans the qr code while the function is executing.
In this first example, I simply tried to disable the widget, but that doesn't work. The widget still gets the later scans while in the function.
# test-ttk-entry1.py
import time
import tkinter as tk
from tkinter import StringVar, ttk
root = tk.Tk()
def print_text(event):
global kbEntry
textValue = kbEntry.get()
kbEntry.configure(state="disabled")
time.sleep(2) # Add in a delay to allow for repeat scan
print(textValue)
time.sleep(2) # Add in a delay to allow for repeat scan
kbEntry.configure(state="active")
kbText = StringVar()
kbEntry = ttk.Entry(root, width=10, textvariable=kbText)
kbEntry.bind("<Return>", print_text)
kbEntry.pack()
kbEntry.focus_set()
root.mainloop()
The second attempt was to disable the entry widget and upon making it active again delete the text in the field.
# test-ttk-entry2.py
import time
import tkinter as tk
from tkinter import END, StringVar, ttk
root = tk.Tk()
def print_text(event):
global kbEntry
textValue = kbEntry.get()
kbEntry.delete(0, END)
kbEntry.configure(state="disabled")
time.sleep(2) # Add in a delay to allow for repeat scan
print(textValue)
time.sleep(2) # Add in a delay to allow for repeat scan
kbEntry.configure(state="active")
kbEntry.delete(0, END)
kbText = StringVar()
kbEntry = ttk.Entry(root, width=10, textvariable=kbText)
kbEntry.bind("<Return>", print_text)
kbEntry.pack()
kbEntry.focus_set()
root.mainloop()
And finally, I was reading about taking focus from a widget and giving focus to the root window, so I added that in and it still prints multiple times to the console like there is a keyboard buffer being read by the ttk.Entry() widget. The weird thing is it seems like widgets don't normally respond to any calls to methods when they are disabled, but it appears the ttk.Entry() widget's properties/attributes (excuse me if my OOP terms are not correct) can be manipulated while disabled.
# test-ttk-entry2.py
import time
import tkinter as tk
from tkinter import END, StringVar, ttk
root = tk.Tk()
def print_text(event):
global kbEntry
textValue = kbEntry.get()
kbEntry.delete(0, END)
kbEntry.configure(state="disabled")
root.focus_set()
time.sleep(2) # Add in a delay to allow for repeat scan
print(textValue)
time.sleep(2) # Add in a delay to allow for repeat scan
kbEntry.configure(state="active")
kbEntry.delete(0, END)
kbEntry.focus_set()
kbText = StringVar()
kbEntry = ttk.Entry(root, width=10, textvariable=kbText)
kbEntry.bind("<Return>", print_text)
kbEntry.pack()
kbEntry.focus_set()
root.mainloop()
So how can I prevent the ttk.Entry() widget from accepting any input from the HID/keyboard device while my function is executing?
Since I am unable to find a way to temporarily disable the .Entry() widget programmatically to keep it from reading successive inputs, I have come up with this solution:
# test-ttk-entry2.py
import time
import tkinter as tk
from tkinter import END, StringVar, ttk
root = tk.Tk()
tempStr = "" # Global temporary string variable to trip repeated scans
def print_text(event):
global tempStr
textValue = kbEntry.get()
if tempStr == textValue:
kbEntry.delete(0, END)
print ("Duplicate scan")
return
tempStr = textValue
kbEntry.delete(0, END)
print(textValue)
time.sleep(3) # Simulate the time it takes for the function to complete
kbEntry = ttk.Entry(root, width=10)
kbEntry.bind("<Return>", print_text)
kbEntry.grid(row=0, column=0, padx=10, pady=10)
kbEntry.focus_set()
root.mainloop()

tkcalendar takes a lot of time to load when I try to add more than 10 calendar widgets

Hi I have a code where I have a button which adds calender widgets, when I add more than 10 or 11 widgets, the program lags a lot. It takes a lot of time to insert the widget, could someone please help me how to circumvent this issue ?
from tkinter import Button, Tk
from tkcalendar import DateEntry
root = Tk()
Date = []
def Add():
Date.append({})
n = len(Date)-1
Date[n]= DateEntry(root)
Date[n].grid(row=n+1, column=0)
print (n)
button = Button(root, text='Add', command=Add).grid(row=0, column=0)
When I add root.mainloop() to the end of your example the code runs fine.
Make sure you have the mainloop in your code before you compile as it is required.

Scrollbar on canvas

I'm currently trying to implement a scrollbar on a canvas, since I learned that I can't do it instantly on a frame. I can make it appear, but I cannot actually make it work. I'm still a beginner when it comes to python and tkinter and the previous posts in this matter haven't helped me that much. Here's my code(I'm open to advice on anything else I've done that's considered bad practice as well):
from tkinter import *
class myApp():
def __init__(self,root):
myApp.f2=Frame(root)
myApp.f2.pack()
myApp.canv=Canvas(self.f2)
myApp.canv.pack()
myApp.f1=Frame(self.canv)
myApp.f1.pack(side=LEFT, fill=BOTH, expand=TRUE)
myApp.scroll=Scrollbar(self.f1,orient=VERTICAL,
command=myApp.canv.yview)
myApp.scroll.grid(row=0,column=6)
myApp.canv.config(yscrollcommand=myApp.scroll.set)
I have to use grid for the rest of the widgets, that I haven't included here.
i don't really understand how the bind works, but here's the code i use for a scrollbar in a Toplevel, it's not from me but i don't remember where i found it (i think it's on stackoverflow, you should search more, i'm sure you will find something). it should work but you can scroll the bar only if you hover it with the mouse though
Toplevel = tk.Toplevel(self)
#create canvas to make a scrollbar
canvas = tk.Canvas(Toplevel, borderwidth=0)
#create frame which will contains your widgets
frame = tk.Frame(canvas)
#create and pack your vsb to the Toplevel and link it to the canvas yview
vsb = tk.Scrollbar(Toplevel, orient="vertical", command=canvas.yview)
canvas.configure(yscrollcommand=vsb.set)
vsb.pack(side="right", fill="y")
canvas.pack(side="left", fill="both", expand=True)
canvas.create_window((5,5), window=frame, anchor="nw")
#i don't understand this line
frame.bind("<Configure>", lambda event, canvas=canvas: canvas.configure(scrollregion=canvas.bbox("all")))
#add your widgets to the frame
...
PS : Don't use from tkinter import * you can(and will) have name collisions, use import tkinter or import tkinter as tk
Edit : this question is my source.

okay so I created this code to create a GUI but i need to add buttons and when ever i try it creates a new window. what should I do?

this is my code so far o cant add buttons with out it creating more windows
////////
#import tkinter
import tkinter
#import tkmessagebox(buttons)
from tkinter import *
#create a new window
window = tkinter.Tk()
#title <------ put it before .mainloop
window.title("yeahh boiiii")
#window size
window.geometry("500x500")
#set a window icon
window.iconbitmap('N:\downloads\icon.ico.ico')#<---- 8bit file name
master = Tk()
def callback():
print ("click!")
b = Button(master, text="OK", command=callback)
b.pack()
#draws the window
window.mainloop()
////////
please help
Your problem is that you create 2 instances of Tk(). This is a bad idea, and you don't need to do it since you can make your button a child of the window object:
# Import tkinter
import tkinter as tk
# Create a new window
window = tk.Tk()
# Title <------ put it before .mainloop
window.title("yeahh boiiii")
# Window size
window.geometry("500x500")
# Set a window icon
window.iconbitmap('N:\downloads\icon.ico.ico') #<---- 8bit file name
def callback():
print ("click!")
b = tk.Button(window, text="OK", command=callback)
b.pack()
# Draw the window
window.mainloop()
I also rewrote your tkinter import, because you were importing it twice...

Resources