Why gtk.Dialog disappears directly when entry received no focus - python

I was searching for an solution to hide gtk dialog immediately after get the response. But now I am wondering why it disappears, but only if I don't click into the entry field:
import gtk, time
def get_info():
entry = gtk.Entry()
entry.set_text("Hello")
dialog = gtk.Dialog(title = "Title",
parent = None,
flags = gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
dialog.vbox.pack_start(entry)
dialog.show_all()
response = dialog.run()
if response == gtk.RESPONSE_ACCEPT:
info = entry.get_text().strip()
dialog.destroy()
return info
else:
exit()
info = get_info()
time.sleep(4)
print info
If I just press "OK" the dialog disappears and after 4 seconds the info is printed.
If I click into the entry field and then press "OK" the dialog doesn't disappear until the program ends.
Why is this so?
edit:
Same problem if I make this with a main loop:
#!/usr/bin/env python
# -*- coding: utf8 -*-
import gtk, time
class EntryTest:
def get_info(self):
entry = gtk.Entry()
entry.set_text("Hello")
dialog = gtk.Dialog(title = "Title",
parent = None,
flags = gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
dialog.vbox.pack_start(entry)
dialog.show_all()
response = dialog.run()
if response == gtk.RESPONSE_ACCEPT:
info = entry.get_text().strip()
dialog.destroy()
return info
else:
exit()
def main(self):
gtk.main()
if __name__ == "__main__":
base = EntryTest()
info = base.get_info()
time.sleep(4)
print info

You don't have a main loop running. This usually means Gtk+ doesn't do anything -- windows wouldn't be shown in the first place -- but dialog.run() is special in that it happens to run its own short-lived mainloop so it looks like things are working. After dialog.run() exits you really don't have a mainloop running so Gtk+ cannot do anything.
If you do this in a real app where gtk.main() is running, it should just work.
Example main loop use (EntryTest can stay as it is, but you will need an additional import glib):
def quit ():
print "now quitting"
gtk.main_quit()
return False
if __name__ == "__main__":
base = EntryTest()
print base.get_info()
glib.timeout_add_seconds (3, quit)
gtk.main()
It's worth noting that the main loop is not running when the dialog is visible but only afterwards (because I was lazy). You could start the get_info() code within the main loop as well with e.g. glib.idle_add() but the point is the same: GTK+ usually needs the main loop to be running.

Related

Getting closeEvent when exiting the application

I'm trying to make a small python programs which is able to have several windows. The issue is when I try to implement a menu entry to quit the programs, closing all the windows at once. I've tried to use qApp.close() and qApp.exit() but if those allow to effectively quit the program, there is no close events generated for the windows still opened, which prevent me to save modified data or to prevent leaving the application. What's the best practice for that? I could understand not being able to cancel the exit process, but being able to propose to save modified data is something I really want.
import sys
from PyQt5.QtWidgets import *
opened_windows = set()
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.create_actions()
opened_windows.add(self)
def closeEvent(self, ev):
if QMessageBox.question(self, 'Closing', 'Really close?') == QMessageBox.Yes:
ev.accept()
opened_windows.remove(self)
else:
ev.ignore()
def create_action(self, action_callback, menu, action_name):
action = QAction(action_name, self)
action.triggered.connect(action_callback)
menu.addAction(action)
def create_actions(self):
_file_menu = self.menuBar().addMenu('&File')
self.create_action(self.on_new, _file_menu, '&New')
_file_menu.addSeparator()
self.create_action(self.on_close, _file_menu, '&Close')
self.create_action(self.on_quit, _file_menu, '&Quit')
self.create_action(self.on_exit, _file_menu, '&Exit')
def on_new(self):
win = MainWindow()
win.show()
def on_close(self):
self.close()
def on_quit(self):
qApp.quit()
def on_exit(self):
qApp.exit(1)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = MainWindow()
win.show()
status = app.exec()
print(len(opened_windows), ' window(s) opened')
print('status = ', status)
sys.exit(status)
Currently I'm modifying on_close and on_exit like this:
def on_exit(self):
for w in opened_windows.copy():
w.on_close()
if len(opened_windows) == 0:
qApp.exit(1)
but I wonder if I'm missing a better way which would not force me to maintain a set of opened windows.
Cause
It is important to understand, that the app and the main window are related, but are not the same thing. So, when you want to close the program, don't bother closing the app. Close the main window instead. From the documentation of QCloseEvent :
Close events are sent to widgets that the user wants to close, usually by choosing "Close" from the window menu, or by clicking the X title bar button. They are also sent when you call QWidget::close() to close a widget programmatically.
Solution
Connect your exit-action's triggered signal to the close slot of your MainWindow. In your case, instead of:
self.create_action(self.on_exit, _file_menu, '&Exit')
write:
self.create_action(self.close, _file_menu, '&Exit').
Define in MainWindow a signal closed and emit it from your implementation of the closedEvent, e.g. in the place of opened_windows.remove(self)
In on_new connect win.closed to self.close
Example
Here is how I suggest you to change your code in order to implement the proposed solution:
import sys
from PyQt5.QtWidgets import *
class MainWindow(QMainWindow):
closed = pyqtSignal()
def __init__(self):
super().__init__()
self.create_actions()
def closeEvent(self, ev):
if QMessageBox.question(self, 'Closing', 'Really close?') == QMessageBox.Yes:
ev.accept()
self.closed.emit()
else:
ev.ignore()
def create_action(self, action_callback, menu, action_name):
action = QAction(action_name, self)
action.triggered.connect(action_callback)
menu.addAction(action)
def create_actions(self):
_file_menu = self.menuBar().addMenu('&File')
self.create_action(self.on_new, _file_menu, '&New')
_file_menu.addSeparator()
self.create_action(self.close, _file_menu, '&Exit')
def on_new(self):
win = MainWindow()
win.show()
win.closed.connect(self.close)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = MainWindow()
win.show()
status = app.exec()
print('status = ', status)
sys.exit(status)
Edit: I wonder how I missed it before. There is the QApplication::closeAllWindows slot which does exactly what I want and whose example is a binding to exit.
There is a way to propose to save modified data on quit and exit, the signal QCoreApplication::aboutToQuit.
Note that although the Qt documentation says that user interaction is not possible, at least with PyQt5 I could use a QMessageBox without apparent issues.

Issues when attaching and detaching external app from QDockWidget

Consider this little piece of code:
import subprocess
import win32gui
import win32con
import time
import sys
from PyQt5.Qt import * # noqa
class Mcve(QMainWindow):
def __init__(self, path_exe):
super().__init__()
menu = self.menuBar()
attach_action = QAction('Attach', self)
attach_action.triggered.connect(self.attach)
menu.addAction(attach_action)
detach_action = QAction('Detach', self)
detach_action.triggered.connect(self.detach)
menu.addAction(detach_action)
self.dock = QDockWidget("Attach window", self)
self.addDockWidget(Qt.RightDockWidgetArea, self.dock)
p = subprocess.Popen(path_exe)
time.sleep(0.5) # Give enough time so FindWindowEx won't return 0
self.hwnd = win32gui.FindWindowEx(0, 0, "CalcFrame", None)
if self.hwnd == 0:
raise Exception("Process not found")
def detach(self):
try:
self._window.setParent(None)
# win32gui.SetWindowLong(self.hwnd, win32con.GWL_EXSTYLE, self._style)
self._window.show()
self.dock.setWidget(None)
self._widget = None
self._window = None
except Exception as e:
import traceback
traceback.print_exc()
def attach(self):
# self._style = win32gui.GetWindowLong(self.hwnd, win32con.GWL_EXSTYLE)
self._window = QWindow.fromWinId(self.hwnd)
self._widget = self.createWindowContainer(self._window)
self.dock.setWidget(self._widget)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Mcve("C:\\Windows\\system32\\calc.exe")
w.show()
sys.exit(app.exec_())
The goal here is to fix the code so the window attaching/detaching into a QDockWidget will be made properly. Right now, the code has 2 important issues.
Issue1
Style of the original window is screwed up:
a) Before attaching (the calculator has a menu bar)
b) When attached (the calculator menu bar is gone)
c) When detached (the menu bar hasn't been restored properly)
I've already tried using flags/setFlags qt functions or getWindowLong/setWindowLong but I haven't had luck with all my attempts
Issue2
If you have attached and detached the calculator to the mainwindow, and then you decide to close the mainwindow, you definitely want everything (pyqt process) to be closed and cleaned properly. Right now, that won't be the case, why?
In fact, when you've attached/detached the calculator to the mainwindow, the python process will hold and you'll need to force the termination of the process manually (i.e. ctrl+break conemu, ctrl+c cmd prompt)... which indicates the code is not doing things correctly when parenting/deparenting
Additional notes:
http://doc.qt.io/qt-5/qwindow.html#fromWinId
http://doc.qt.io/qt-5/qwidget.html#createWindowContainer
In the above minimal code I'm spawning calc.exe as a child process but you can assume calc.exe is an existing non-child process spawned by let's say explorer.exe
I found part of the issue wrt to closing. So when you are creating the self._window in the attach function and you close the MainWindow, that other window (thread) is sitting around still. So if you add a self._window = None in the __init__ function and add a __del__ function as below, that part is fixed. Still not sure about the lack of menu. I'd also recommend holding onto the subprocess handle with self.__p instead of just letting that go. Include that in the __del__ as well.
def __del__(self):
self.__p.terminate()
if self._window:
print('terminating window')
self._window.close
Probably better yet would be to include a closeEvent
def closeEvent(self, event):
print('Closing time')
self.__p.terminate()
if self._window is not None:
print('terminating window')
self._window.close

Proper way to quit/exit a PyQt program

I have a script which has a login screen and if the cancel button is pressed, I want to exit the application altogether. I have tried 3 ways:
sys.exit()
QApplication.quit()
QCoreApplication.instance().quit()
Only number 1 works. The other two makes the dialog box white and it flashes then hangs and I cannot even switch to other applications. My code is below:
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtSql import *
from PyQt5.QtWidgets import *
import csv
import sys
from datetime import datetime, timedelta, time
import os
from ci_co_table import *
from login import *
class Ci_Co(QMainWindow):
"""Check in and check out module"""
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
class Login(QDialog):
"""User login """
def __init__(self):
QDialog.__init__(self)
self.ui = Ui_login_form()
self.ui.setupUi(self)
self.ui.buttonBox.accepted.connect(lambda: self.handle_login(servers=servers))
servers = {}
with open('servers.csv', newline='') as csvfile:
server_reader = csv.reader(csvfile)
for row in server_reader:
self.ui.cbo_db_name.addItem(row[1])
servers[row[1]] = (row[0],row[2])
def handle_login(self, servers=''):
global user
global pword
global database
global server
global bg_colour
user = self.ui.username.text()
pword = self.ui.password.text()
database = self.ui.cbo_db_name.currentText()
server = servers[database][0]
bg_colour = servers[database][1]
if __name__=="__main__":
app=QApplication(sys.argv)
global hotdate
global hotdate_string
global folio_num
global user
global pword
global dbase
global server
pword = ""
global database
global bg_colour
#Login
while True:
if Login().exec_() == QDialog.Accepted:
db = QSqlDatabase.addDatabase("QPSQL");
db.setHostName(server)
db.setDatabaseName(database);
db.setUserName(user);
db.setPassword(pword)
if (db.open()==False):
QMessageBox.critical(None, "Database Error", db.lastError().text())
else:
break
else:
#QApplication.quit()
QCoreApplication.instance().quit()
#sys.exit()
myapp = Ci_Co()
myapp.show()
sys.exit(app.exec_())
Calling QCoreApplication.quit() is the same as calling QCoreApplication.exit(0). To quote from the qt docs:
After this function has been called, the application leaves the main
event loop and returns from the call to exec(). The exec() function
returns returnCode. If the event loop is not running, this function
does nothing. [emphasis added]
So quit() or exit() are nothing like sys.exit(). The latter will terminate the program, but the former will merely terminate the event-loop (if it's running).
When the user cancels the login dialog, your example should just call sys.exit() to terminate the program. Otherwise, your program will just get stuck in the blocking while-loop.
Instead of using QApplication.quit(), since you defined app = QApplication(sys.argv), you could just write app.quit(), and that should work!
Something unrelated but might be helpful: I think it would be easier if you put the login check at the beginning of the __init__ function of your Ci_Co class. That way, you will start Ci_Co at the beginning, but it will first spawn the Login class. If the login fails, you can call app.quit(), and if it succeeds, it will automatically transition into Ci_Co. This saves you from a lot of the things you have to write in the if __name__ == "__main__" clause. Please comment if you have any more questions, I have a similar project with a login dialog box.
i try this 3 method to close my MainWindow() but it didn't work for my code.
sys.exit()
QApplication.quit()
qApp.quite()
So i use self.close() method which work Completely fine with my code.
here is my code
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.ui.QPushButton.clicked.connect(lambda: self.shutprocess())
def shutprocess(self):
reply = QMessageBox.question(self, 'Window Close', 'Are you sure you want to close the window?', QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
if reply == QMessageBox.Yes:
self.close()
print('Window closed')
else:
pass
add sys.exit(app.exec_()) To youer Code

How to hide a Gtk+ FileChooserDialog in Python 3.4?

I have a program set up so that it displays a FileChooserDialog all by itself (no main Gtk window, just the dialog).
The problem I'm having is that the dialog doesn't disappear, even after the user has selected the file and the program has seemingly continued executing.
Here's a snippet that showcases this issue:
from gi.repository import Gtk
class FileChooser():
def __init__(self):
global path
dia = Gtk.FileChooserDialog("Please choose a file", None,
Gtk.FileChooserAction.OPEN,
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
self.add_filters(dia)
response = dia.run()
if response == Gtk.ResponseType.OK:
print("Open clicked")
print("File selected: " + dia.get_filename())
path = dia.get_filename()
elif response == Gtk.ResponseType.CANCEL:
print("Cancel clicked")
dia.destroy()
def add_filters(self, dia):
filter_any = Gtk.FileFilter()
filter_any.set_name("Any files")
filter_any.add_pattern("*")
dia.add_filter(filter_any)
dialog = FileChooser()
print(path)
input()
quit()
The dialog only disappears when the program exits with the quit() function call.
I've also tried dia.hide(), but that doesn't work either - the dialog is still visible while code continues running.
What would the proper way to make the dialog disappear?
EDIT: I've since learned that it's discouraged to make a Gtk dialog without a parent window. However, I don't want to deal with having to have the user close a window that has nothing in it and simply stands as the parent for the dialog.
Is there a way to make an invisible parent window and then quit the Gtk main loop when the dialog disappears?
You can set up a window first by doing:
def __init__ (self):
[.. snip ..]
w = Gtk.Window ()
dia = Gtk.FileChooserDialog("Please choose a file", w,
Gtk.FileChooserAction.OPEN,
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
also, set a default value for path in case the user cancels:
path = ''
Then, at the end of your script:
print (path)
while Gtk.events_pending ():
Gtk.main_iteration ()
print ("done")
to collect and handle all events.

python GTK: Dialog with information that app is closing

I have a problem
My application on close has to logout from web application. It's take some time. I want to inform user about it with " logging out" information
class Belt(gtk.Window):
def __init__(self):
super(Belt, self).__init__()
self.connect("destroy", self.destroy)
def destroy(self, widget, data=None):
if self.isLogged:
md = gtk.MessageDialog(None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, ico, gtk.BUTTONS_NONE, txt)
md.showall()
self.send('users/logout.json', {}, False, False)
gtk.main_quit()
def main(self):
if self.iniError is False:
gtk.gdk.threads_init()
gtk.gdk.threads_enter()
gtk.main()
gtk.gdk.threads_leave()
if __name__ == "__main__":
app = Belt()
app.main()
When I try to show dialog in destroy method only window does appear, without icon and text.
I want to, that this dialog have no confirm button, just the information, and dialog have to be destroy with all app.
Any ideas?
Sorry for my poor English
Basically, GTK has to have the chance to work through the event queue all the time. If some other processing takes a long time and the event queue is not processed in the meantime, your application will become unresponsive. This is usually not what you want, because it may result in your windows not being updated, remaining grey, having strange artefacts, or other kinds of visible glitches. It may even cause your window system to grey the window out and offer to kill the presumably frozen application.
The solutution is to make sure the event queue is being processed. There are two primary ways to do this. If the part that takes long consists of many incremental steps, you can periodically process the queue yourself:
def this_takes_really_long():
for _ in range(10000):
do_some_more_work()
while gtk.events_pending():
gtk.main_iteration()
In the general case, you'll have to resort to some kind of asynchronous processing. The typical way is to put the blocking part into its own thread, and then signal back to the main thread (which sits in the main loop) via idle callbacks. In your code, it might look something like this:
from threading import Thread
import gtk, gobject
class Belt(gtk.Window):
def __init__(self):
super(Belt, self).__init__()
self.connect("destroy", self.destroy)
self.show_all()
self.isLogged = True
self.iniError = False
def destroy(self, widget, data=None):
if self.isLogged:
md = gtk.MessageDialog(None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, 0, gtk.BUTTONS_NONE, "Text")
md.show_all()
Thread(target=self._this_takes_very_long).start()
def main(self):
if self.iniError is False:
gtk.gdk.threads_init()
gtk.gdk.threads_enter()
gtk.main()
gtk.gdk.threads_leave()
def _this_takes_very_long(self):
self.send('users/logout.json', {}, False, False)
gobject.idle_add(gtk.main_quit)
if __name__ == "__main__":
app = Belt()
app.main()

Categories

Resources