How to use tk.menu.unpost under windows - python-3.x

The question is how to use the tk.menu.unpost method under windows.
The code:
if __name__ == "__main__":
from tkinter import Menu, Tk, Label
t = Tk()
label = Label(t, text="Label")
label.pack()
menu = Menu(t, tearoff=0)
menu.add_command(label="command")
def unpost(event=None):
print(event)
menu.unpost()
def post(event=None):
print(event)
menu.after(3000, unpost)
try:
menu.tk_popup(menu.winfo_pointerx(), menu.winfo_pointery())
finally:
menu.grab_release()
label.bind("<Button-3>", post)
t.bind("<u>", unpost)
label.bind("<u>", unpost)
menu.bind("<u>", unpost)
t.mainloop()
On Linux, the unpost method works when triggered by the events or via menu.after.
On the windows platform the keybindings are apparently suppressed, the function unpost is executed as desired after three seconds, nevertheless the call remains eventless.

Related

Webcam, Tkinter, optionmenu, thread

The following script is an attempt to choose webcam from OptionMenu in tkinter and open using thread:
class wcbm():
def __init__(self, nCam):
# a lot of code
from tkinter import *
import threading
def main():
main_window = Tk()
# Webcam list
options = [0,1,2,3,4,5,6,7,8,9]
clicked = StringVar()
clicked.set("Choose a webcam")
drop = OptionMenu(main_window, clicked, *options).pack()
btn = Button(main_window, text="Botão", command=lambda:threading.Thread(target=wbcm(clicked.get())).start()).pack()
main_window.mainloop()
if __name__ == "__main__":
main()
wbcm is a class to open webcam (opencv, dlib, ...). The only one required variable for this class is nCam.
The above script gives an error:
TypeError: 'wbcm' object is not callable
What's going on, please?
I expect with my script I may choose and show (thread) webcam from OptionMenu list options.

How to trap a Text object selection event in Python3 / tkinter

Per the following Stackoverflow wisdom
Python/Tkinter: Trap text selection via keyboard/mouse as an event?
I tried to detect the creation of a user selection in a Text object as follows
import tkinter as tk
def handle_selection(e=None):
print('here')
try:
sel_first = input_text.index(tk.SEL_FIRST)
sel_last = input_text.index(tk.SEL_LAST)
print(sel_first, sel_last)
except:
return
def main():
window = tk.Tk()
window.title("Simple Text Editor")
input_text = tk.Text(window)
input_text.bind("<<Selection>>", handle_selection)
input_text.pack()
window.mainloop()
if __name__ == "__main__":
main()
However the event is triggered (apparently) for each horizontal pixel selected rather than when the selection is completed, which was my intention. I also tried the following
input_text.bind("<<Selection-ButtonRelease>>", handle_selection)
which wasn't triggered at all.
Is there a way to capture the event when the user releases the button after making the selection?

Python 3 Tkinter button command not working (very specific scenario)

I am using these calendar modules found in this post for my program, with some slight modifications to the imports to make it work for the latest python version.
I'll just show the snippets of my code that I feel does matter to this problem.
So I have this pop up window that I made that I use for alerts:
#class for pop-up windows for alerts, errors etc.
class PopUpAlert():
def __init__(self, alert='Alert!'):
self.root = tk.Tk()
tk.Label(self.root,
text=alert,
font="Verdana 15",
fg='red',
padx=10,
pady=5).pack(side=tk.TOP)
self.root.bind('<Return>', (lambda event: self.ok()))
tk.Button(self.root,
text='ok',
pady=10,
command=self.ok).pack(side=tk.TOP)
def ok(self):
print('ok clicked')
self.root.destroy()
The function ok was made just for me to test if the function is even being called. This window works completely fine in my code, except when I try to implement with the calendar, where the "ok" button of my PopUpAlert (which is supposed to destroy the window) stops working:
class CalendarDialog(tkSimpleDialog.Dialog):
"""Dialog box that displays a calendar and returns the selected date"""
def body(self, master):
self.calendar = ttkcalendar.Calendar(master)
self.calendar.pack()
def apply(self):
self.result = self.calendar.selection
def validate(self):
if self.calendar.selection == None:
PopUpAlert(alert='Please select a date or click cancel!')
return False
return True
The calendar has an "ok" button that is used to confirm selection of the date and close the calendar window. What I was trying to do is make it such that the user cannot click "ok" to close the window if he/she has not picked a date. For that, I used the function validate which is pre-defined in the class tkSimpleDialog.Dialog which my CalendarDialog inherits from. I overwrote the function in my CalendarDialog class to call up PopUpAlert, then returned False to the parent function ok (which is called when the "Ok" button is pressed on the calendar window):
def ok(self, event=None):
if not self.validate():
self.initial_focus.focus_set() # put focus back
return
self.withdraw()
self.update_idletasks()
self.apply()
self.cancel()
def cancel(self, event=None):
# put focus back to the parent window
self.parent.focus_set()
self.destroy()
(The whole thing can be found in the tkSimpleDialog file that's linked in the other SO page that I linked above.)
After commenting out lines one by one I found that the "ok" button on my PopUpAlert only didn't work when self.root.destroy() isn't called on the calendar. Why? How do I fix this?
I already tried changing my PopUpAlert to be a Toplevel window, which also didn't work.
It would be a lot nicer of you to provide a mcve instead of asking us to make it.
The problem is that a dialog by default disables clicks to other windows, including windows it spawns. To fix this you need to use a Toplevel instead of Tk (as mentioned) AND add this line of code to the end of PopUpAlert.__init__:
self.root.grab_set()
It would be a lot neater if you subclassed Toplevel rather than that weird wrapper. Here's a mcve:
try:
import Tkinter as tk
import tkSimpleDialog as sd
except:
import tkinter as tk
from tkinter import simpledialog as sd
#class for pop-up windows for alerts, errors etc.
class PopUpAlert(tk.Toplevel):
def __init__(self, master, alert='Alert!', **kwargs):
tk.Toplevel.__init__(self, master, **kwargs)
tk.Label(self,
text=alert,
font="Verdana 15",
fg='red',
padx=10,
pady=5).pack(side=tk.TOP)
self.bind('<Return>', self.ok)
tk.Button(self,
text='ok',
pady=10,
command=self.ok).pack(side=tk.TOP)
self.grab_set() # this window only gets commands
def ok(self, *args):
print('ok clicked')
self.destroy()
class CalendarDialog(sd.Dialog):
"""Dialog box that displays a calendar and returns the selected date"""
def body(self, master):
self.calendar = tk.Label(master, text="Whatever you do, don't click 'OK'!")
self.calendar.pack()
def validate(self):
PopUpAlert(self, alert='Please select a date or click cancel!')
def display():
CalendarDialog(root)
root = tk.Tk()
tk.Button(root, text='data data data', command=display).pack()
root.mainloop()
Note I also got rid of that useless lambda, which happens to be a pet peeve of mine. lambda is great in some cases, but it's very rarely needed.

App crashes when using QWidgets.QMessageBox

So I've been trying my luck with PyQT5 to give a GUI to an app I've been working on.
I've encountered an issue with QMessageBox feature.
I've been trying to create an "Exit" Action on the MenuBar of the app.
And at first I only made it exit when clicked and it worked.
Now I want to make it give a pop up message of "Are you sure?", which is exactly what the QMessageBox does. So this is my code now:
class Window(QtWidgets.QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.ui = uic.loadUi('rent_creation.ui', self)
self.home()
def home(self):
self.ui.actionExit.triggered.connect(self.close_application)
self.show()
def close_application(self):
choice = QMessageBox.question(self, 'Quit?',
"Are you sure you want to quit?",
QMessageBox.Yes | QMessageBox.No)
if choice == QMessageBox.Yes:
sys.exit()
else:
pass
Now every time I click on the Exit button when I run this code, The Python crashes.
I'm not sure what I'm doing wrong... I've been looking around the internet and it all look well.... I've tried all the variation possible of passing QmessageBox (for example I tried adding QWidgets.QMessageBox.Yes/No and it didn't fix this issue).
I've been following a tutorial on the internet where This code is practically the same as his, and it works for him in the tutorial somehow.
caveat: I am on linux, so things are likely a bit different.
However I wouldn't be surprised if the problem is related with the fact that you use sys.exit to quit the GUI. You probably should cleanly close the window, the QApplication and then exit the program.
The following example might solve your issue. Since I don't have you ui file, I just added a menu action to close the the window and connect it with the QMainWindow.close slot and then override the closeEvent method. See the comments in the code:
import sys
from PyQt5 import QtWidgets
class Window(QtWidgets.QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.home()
def home(self):
# add a menu bar with a File menu and a Close action
menu_bar = QtWidgets.QMenuBar(self)
menu = QtWidgets.QMenu('File', menu_bar)
menu_bar.addMenu(menu)
action = menu.addAction('Close')
# connect the Close action with the QMainWindow.close slot
action.triggered.connect(self.close)
self.setMenuBar(menu_bar)
def closeEvent(self, event):
"""override the QMainWindow.closeEvent method to:
* fire up a QMessageBox with a question
* accept the close event if the user click yes
* ignore it otherwise.
Parameters
----------
event : QtCloseEvent
emitted when someone or something asks to close the window
"""
if self.ask_quit():
event.accept()
else:
event.ignore()
def ask_quit(self):
choice = QtWidgets.QMessageBox.question(self, 'Quit?',
"Are you sure you want to quit?",
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
return choice == QtWidgets.QMessageBox.Yes
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = Window()
w.resize(250, 150)
w.move(300, 300)
w.setWindowTitle('Simple')
w.show()
sys.exit(app.exec_())
The above way of closing the window, i.e. using the closeEvent and connect the menu action to close, has the advantage that the confirmation box is opened every time someone asks to close the window, independently of the method: you get the message box also clicking on the window X button or with alt+F4
Edit: example of how to cleanly close the QApplication only from the Close menu. This should be more in line with the original behavior of the app in the question (see comment).
class Window(QtWidgets.QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.home()
def home(self):
menu_bar = QtWidgets.QMenuBar(self)
menu = QtWidgets.QMenu('File', menu_bar)
menu_bar.addMenu(menu)
action = menu.addAction('Close')
# connect the Close menu to the ``ask_quit`` slot to ask and exit the
# application on "yes"
action.triggered.connect(self.ask_quit)
self.setMenuBar(menu_bar)
def closeEvent(self, event):
"""Ignore all ways of closing"""
event.ignore()
#QtCore.pyqtSlot()
def ask_quit(self):
choice = QtWidgets.QMessageBox.question(self, 'Quit?',
"Are you sure you want to quit?",
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
if choice == QtWidgets.QMessageBox.Yes:
QtWidgets.QApplication.quit()

tkinter how to open 2 or more root windows in different locations on the screen

I have written a task reminder application in python and tkinter. It schedules the reminders using the Task Scheduler. The reminders are displayed by a small GUI program in a certain location on the screen. My problem is that the reminders overlap each other. When multiple reminders appear, how can I make them appear in distinct positions? Please note that I am referring to a separate invocation of the GUI program for each reminder.
The situation is similar to opening, say, multiple copies of the calculator program. They open in distinct locations on the screen. How does it happen?
The program that creates the reminders is as follows -
from tkinter import *
import shelve
import sys
def showTask(parent, key):
parent.title('Reminder')
parent.geometry('300x100-0-40')
parent.rowconfigure(0, weight=1)
parent.columnconfigure(0, weight=1)
shelfFile = shelve.open('C:\\Users\\hp\\pypgms\\data\\tasks')
message = shelfFile[key]['name']
shelfFile.close()
Label(parent, text=message).grid(padx=20, pady=20, sticky=NW)
btn = Button(parent, text='Ok', command=parent.quit)
btn.grid(pady=5)
btn.bind('<Return>', lambda e: parent.quit())
key = sys.argv[1]
root = Tk()
showTask(root, key)
root.mainloop()
Tkinter windows have a convenient method called geometry() to define their size and position on the screen following this format:
root.geometry('250x150+300+300') # width=250, height=150, position=(300,300)
See below how you can use it to open reminder windows next to one another:
import Tkinter as tk
class MainApp():
def __init__(self, root):
self.root = root
self.reminderWindows = []
self.button = tk.Button(self.root, text="New reminder",
command=self.open_new_reminder)
self.button.pack()
def open_new_reminder(self):
reminder = tk.Toplevel(self.root)
self.reminderWindows.append(reminder)
windowNumber = len(self.reminderWindows)
reminder.geometry("100x120+{}+200".format(str(150*windowNumber)))
if __name__ == "__main__":
app = tk.Tk()
MainApp(app)
app.mainloop()

Resources