I created a wxPython application which shows some messages on a dialog window. The dialog window is needed to be force-destroyed by the application before I click the dialog OK button. I used wx.lib.delayedresult to make the destroy call.
My code is:
import wx
dlg=wx.MessageDialog(somewindow,'somemessage')
from wx.lib.delayedresult import startWorker
def _c(d):
dlg.EndModal(0)
dlg.Destroy()
def _w():
import time
time.sleep(1.0)
startWorker(_c,_w)
dlg.ShowModal()
This can do what I desire to do while I got a error message below:
(python:15150): Gtk-CRITICAL **: gtk_widget_destroy: assertion `GTK_IS_WIDGET (widget)' failed
How do I "safely" destroy a dialog without clicking the dialog button?
It has been a while since I have used wxWidgets but I think your dlg.Destroy() may be in the wrong place. Try moving it into the main thread.
import wx
dlg=wx.MessageDialog(somewindow,'somemessage')
from wx.lib.delayedresult import startWorker
def _c(d):
dlg.EndModal(0)
def _w():
import time
time.sleep(1.0)
startWorker(_c,_w)
dlg.ShowModal()
dlg.Destroy()
I would use a wx.Timer()
import wx
########################################################################
class MyDialog(wx.Dialog):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Dialog.__init__(self, None, title="Test")
timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.onTimer, timer)
timer.Start(5000)
self.ShowModal()
#----------------------------------------------------------------------
def onTimer(self, event):
""""""
print "in onTimer"
self.Destroy()
if __name__ == "__main__":
app = wx.App(False)
dlg = MyDialog()
app.MainLoop()
See also http://www.blog.pythonlibrary.org/2009/08/25/wxpython-using-wx-timers/
My problem with dlg.Destroy() is that it is not exiting the prompt.
I have done following to exit the prompt:
def OnCloseWindow(self, e):
dial = wx.MessageDialog(None, 'Are you sure to quit?', 'Question',
wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
ret = dial.ShowModal()
if ret == wx.ID_YES:
self.Destroy()
sys.exit(0)
sys.exit(0) will exit the prompt and move to next line.
Related
when i click 'X' on my application, and press "No" from MessageBox, the program will not be closed. But when i hide the program and click "Quit" from the Menu at system tray and click "No" from the MessageBox, the program will still be closed successfully....
my code is something like this:
exitAction = menu.addAction("Quit")
exitAction.triggered.connect(self.close)
then my closeEvent() code is:
def closeEvent(self, event):
reply = QMessageBox.question(self, 'Quit', 'Are You Sure to Quit?', QMessageBox.No | QMessageBox.Yes)
if reply == QMessageBox.Yes:
event.accept()
else:
event.ignore()
EDIT:
I realised whenever the menu pops up an QMessageBox, no matter which option i choose, the whole program will still be closed, i think it might be this problem?:
os._exit(app2.exec_())
i added self.show() before the messagebox and it works, is there any way to make it works without self.show()? Because i am allowing user to quit the program from system tray when the program is hidden
def closeEvent(self, event):
self.show() << I added this and it works
reply = QMessageBox.question(self, 'Quit', 'Are You Sure to Quit?', QMessageBox.No | QMessageBox.Yes)
if reply == QMessageBox.Yes:
event.accept()
else:
event.ignore()
Reproducible Code:
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUi()
def initUi(self):
self.resize(350, 150)
self.setWindowTitle("Test")
pb_min = QPushButton("Minimise the Program", self)
pb_min.clicked.connect(self.pbMin)
h_box = QHBoxLayout()
h_box.addStretch()
h_box.addWidget(pb_min)
h_box.addStretch()
self.setLayout(h_box)
def pbMin(self):
menu = QMenu()
showAction = menu.addAction('Show')
showAction.triggered.connect(self.showGUI)
exitAction = menu.addAction("Quit")
exitAction.triggered.connect(self.close)
self.hide()
self.mSysTrayIcon = QSystemTrayIcon(self)
icon = QIcon("test.png")
self.mSysTrayIcon.setIcon(icon)
self.mSysTrayIcon.setContextMenu(menu)
self.mSysTrayIcon.setToolTip("Show Test")
self.mSysTrayIcon.activated.connect(self.onActivated)
self.mSysTrayIcon.show()
def showGUI(self):
self.show()
self.mSysTrayIcon.hide()
def onActivated(self, reason):
if reason == self.mSysTrayIcon.Trigger:
self.show()
self.mSysTrayIcon.hide()
def closeEvent(self, event):
reply = QMessageBox.question(self, 'Quit', 'Are You Sure to Quit?', QMessageBox.Yes | QMessageBox.No)
if reply == QMessageBox.Yes:
event.accept()
else:
event.ignore()
if __name__ == "__main__":
app = QApplication(sys.argv)
ex = Example()
ex.show()
os._exit(app.exec_())
There are some issues with the code you provided.
First of all, you shouldn't create a new system tray icon everytime you minimize the window; while it might make some sense, it can be a problem as in some platforms (notably, Windows) the icon might not always be "deleted" (as in "hidden") when you create a new one, unless you explicitly delete it (usually with deleteLater()).
Then, while you say that "the program will still be closed successfully", it's possible that it won't. That usually depends on the platform (different OSes and OS versions), but that's not the point.
Also, since you need a unified way to ensure that the user really wants to quit, you should provide a respective method to do that, and therefore react to its return value, since closeEvent and triggered action connections respond in different ways.
I've adapted your code, unifying a "closeRequest" method that should better react to user interaction.
A couple of notes.
Whenever you're using "non standard" QWidgets (such as QSystemTrayIcon) you will need better control on how/when your program actually quits, and special care is required in setting QApplication.setQuitOnLastWindowClosed(bool).
Using os._exit is not the same as sys.exit: "os._exit() should normally only be used in the child process after a fork()", which is the (rarely) case of concurrent event loops, such as using PyQt event loop along with a PyGame one (see official docs).
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUi()
def initUi(self):
self.resize(350, 150)
self.setWindowTitle("Test")
pb_min = QPushButton("Minimise the Program", self)
pb_min.clicked.connect(self.pbMin)
h_box = QHBoxLayout()
h_box.addStretch()
h_box.addWidget(pb_min)
h_box.addStretch()
self.setLayout(h_box)
menu = QMenu()
showAction = menu.addAction('Show')
showAction.triggered.connect(self.showGUI)
exitAction = menu.addAction("Quit")
exitAction.triggered.connect(self.quitRequest)
self.mSysTrayIcon = QSystemTrayIcon(self)
icon = QIcon("test.png")
self.mSysTrayIcon.setIcon(icon)
self.mSysTrayIcon.setContextMenu(menu)
self.mSysTrayIcon.setToolTip("Show Test")
self.mSysTrayIcon.activated.connect(self.onActivated)
def pbMin(self):
self.hide()
self.mSysTrayIcon.show()
def showGUI(self):
self.show()
self.mSysTrayIcon.hide()
def onActivated(self, reason):
if reason == self.mSysTrayIcon.Trigger:
self.show()
self.mSysTrayIcon.hide()
def quitRequest(self):
if self.closeRequest():
QApplication.quit()
def closeRequest(self):
reply = QMessageBox.question(self, 'Quit', 'Are You Sure to Quit?', QMessageBox.Yes | QMessageBox.No)
return reply == QMessageBox.Yes
def closeEvent(self, event):
if self.closeRequest():
event.accept()
QApplication.quit()
else:
event.ignore()
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
app.setQuitOnLastWindowClosed(False)
ex = Example()
ex.show()
sys.exit(app.exec_())
Found my own solution, whenever the main program is hidden, messagebox shown through system tray once is closed, the whole program will shut down as well,
so add app.setQuitOnLastWindowClosed(False) to avoid the closing of MessageBox leading to the whole program to shut down.
Lastly, add quit() in the closeEvent()
I'm using Python and wxPython to interact between my user and an USB device. The USB device is somewhat slow in processing commands. Therefor, after sending the command, I'm showing a dialog notifying the user about the command and giving the device enough time to process the command. The code:
def ActionOnButtonClick( self, event ):
# Send command to USB device;
device.Send("command")
# Notify user + allowing device to process command;
dlg = wx.MessageDialog(parent=None, message="Info", caption="Info", style=wx.OK)
dlg.ShowModal()
dlg.Destroy()
# Start timer;
self.RunTimer.Start(500)
When I run the code like this the "RunTimer" will run only once. After some testing I noticed that when I remove the messagedialog, the RunTimer will run continuously without any problems.
I can't figure out what I'm doing wrong. Any thoughts/ideas?
Thank you in advance for your answer!
Best regards,
Peter
#soep Can you run this test code. If in doubt start with the basics.
import wx
class Frame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, title="Timer Dialog Test")
self.button_1 = wx.Button(self, 1, label="Start Timer")
self.button_1.Bind(wx.EVT_BUTTON, self.OnButton, id=1)
sizer_1 = wx.BoxSizer(wx.VERTICAL)
sizer_1.Add(self.button_1, 0, wx.ALL, 5)
self.SetSizer(sizer_1)
sizer_1.Fit(self)
self.Layout()
self.timer = wx.Timer(self)
self.breaktimer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer)
def OnTimer(self, evt):
print "timer"
def OnButton(self,evt):
dlg = wx.MessageDialog(parent=None, message="Starting Timer", caption="Timer Info", style=wx.OK)
dlg.ShowModal()
dlg.Destroy()
self.timer.Start(1000)
if __name__ == "__main__":
app = wx.App()
frame = Frame(None)
frame.Show()
app.MainLoop()
This is a follow-up to my previous question, wxPython popup from calling imported function.
I was able to figure out a way to create a wxPython dialog window to determine if a thread my GUI called should continue its execution. What I did was simply create the dialog window in the thread itself. However, once I made the thread exit by clicking "no" in this popup, the close button in my GUI became unresponsive, and I couldn't close the GUI itself. Once again, your help is much appreciated!
GUI code:
import sys
import os
import re
import subprocess
import threading
import wx
import errno, os, stat, shutil
import extern_func
#this object redirects the external function output to the text box
class RedirectText(object):
def __init__(self,aWxTextCtrl):
self.out=aWxTextCtrl
def write(self,string):
self.out.WriteText(string)
#GUI code here
class progFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, title="functionGUI", size=(800, 600), style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER)
panel = wx.Panel(self)
#more things....
self.closeButton = wx.Button(panel, wx.ID_OK, "Run", pos=(250, 300))
self.runButton = wx.Button(panel, wx.ID_OK, "Run", pos=(200, 300))
self.out=wx.TextCtrl(panel, style=wx.TE_MULTILINE|wx.VSCROLL|wx.TE_READONLY, pos = (300, 50), size=(500, 200))
#Run button event
self.Bind(wx.EVT_BUTTON, self.OnRun, self.runButton)
#close button event
self.Bind(wx.EVT_BUTTON, self.OnClose, self.closeButton)
#command prompt output to frame
redir=RedirectText(self.out)
sys.stdout=redir
self.Show()
def OnRun(self, event):
t=threading.Thread(target=self.__run)
t.start()
def OnClose(self, event):
self.Destroy()
#external function call
def __run(self):
externFunc()
if __name__ == '__main__':
app = wx.App(False)
progFrame(None)
app.MainLoop()
External function code:
import sys
def externFunc():
print "Starting execution..."
#a bunch of code...
#this is the code for the Yes/No prompt and what introduced the buggy behavior
if(os.path.isdir(mirror_source_path) or os.path.isdir(mirror_dest_path)):
app = wx.App(False)
dlg = wx.MessageDialog(None, "Something bad happened. Continue?","Warning",wx.YES_NO | wx.ICON_QUESTION)
retCode = dlg.ShowModal()
if (retCode == wx.ID_YES):
print "Continuing."
else:
print "Aborted."
return None
sys.exit(0)
dlg.Destroy()
#more function code...
print "Success!"
You cannot have 2 wxPython main loops running at the same time. That will cause some pretty screwy behavior. Personally I think I would split this code into 2 threads. When the first one finishes, it sends a message using wx.CallAfter and pubsub or wx.PostEvent and at that time you can do your if statement in a wxPython handler.
If you continue, then you spin up a second thread with the rest of the function code.
Ok, so I am playing around and trying to get an understanding of how to make GUI's with buttons, I figured i would start simple and make one with two buttons that displays a different message depending on which one is clicked. I made the first button and tested it... worked fine, made the second button and when i tested it i get the message for the second button when i click the first button, and nothing when i click the second button. I tried searching around but it doesn't seem anyone else had this issue so I am obviously doing something wrong.
#!/usr/bin/env python
import os
import wx
class Frame(wx.Frame):
def OnOpen(self,e):
self.dirname=''
dlg=wx.FileDialog(self,'Choose a File',self.dirname,'','*.*',wx.OPEN)
if dlg.ShowModal()==wx.OK:
self.filename=dlg.GetFileName()
self.dirname=dlg.GetDirectory()
f=open(os.path.join(self.dirname,self.filename),'r')
self.Control.SetValue(f.read())
f.close()
dlg.Destroy()
def OnAbout(self,e):
dlg=wx.MessageDialog(self,'Aoxx','Author',wx.OK)
dlg.ShowModal()
dlg.Destroy()
def OnExit(self,e):
dlg=wx.MessageDialog(self,'Exit','Terminate',wx.OK)
dlg.ShowModal()
self.Close(True)
dlg.Destroy()
def __init__(self,parent,id):
wx.Frame.__init__(self,parent,id,'Frame works',size=(450,600))
panel=wx.Panel(self)
self.CreateStatusBar()
filemenu=wx.Menu()
self.filemenu=wx.Menu()
menubar=wx.MenuBar()
menubar.Append(filemenu,'&File')
#menubar.Append(filemenu,'&Help')
self.SetMenuBar(menubar)
MenuOpen=filemenu.Append(wx.ID_OPEN,'&Open','File Dir')
MenuExit=filemenu.Append(wx.ID_ANY,'E&xit','Term')
MenuAbout=filemenu.Append(wx.ID_ABOUT,'&About','Info')
self.Bind(wx.EVT_MENU,self.OnOpen,MenuOpen)
self.Bind(wx.EVT_MENU,self.OnExit,MenuExit)
self.Bind(wx.EVT_MENU,self.OnAbout,MenuAbout)
pic1=wx.Image('C:\Users\******\Pictures\Tri.bmp', wx.BITMAP_TYPE_BMP).ConvertToBitmap()
self.button=wx.BitmapButton(panel,-1, pic1,pos=(10,10))
self.Bind(wx.EVT_BUTTON,self.ClickTri,self.button)
self.button.SetDefault()
pic2=wx.Image('C:\Users\******\Pictures\ClickWin.bmp', wx.BITMAP_TYPE_BMP).ConvertToBitmap()
self.buton=wx.BitmapButton(panel,-1,pic2,pos=(220,10))
self.Bind(wx.EVT_BUTTON,self.ClickWin,self.button)
def ClickTri(self,event):
dlg=wx.MessageDialog(self,'No touching the TriForce Rook!','HEY!!!',wx.OK)
dlg.ShowModal()
dlg.Destroy()
def ClickWin(self,event):
dlg=wx.MessageDialog(self,'You would.....','REALLY?',wx.OK)
dlg.ShowModal()
dlg.Destroy()
self.Show(True)
if __name__=='__main__':
app=wx.PySimpleApp()
frame=Frame(None,id=-1)
frame.Show()
app.MainLoop()
you cant have 2 self.button make the second one self.button2 or something
I'm assuming this is possible with a multiline text box, but not sure how to do it. What I'm looking to do is make a log box in my wxPython program, where I can write messages to it when certain actions happen. Also, i need to write the messages not only when an event happens, but certain times in the code. How would i get it to redraw the window so the messages appear at that instant?
I wrote an article on this sort of thing a couple years ago:
http://www.blog.pythonlibrary.org/2009/01/01/wxpython-redirecting-stdout-stderr/
If you would like just a log dialog in wxpython, use wx.LogWindow:
import wx
class MainWindow(wx.Frame):
def __init__(self, parent=None):
wx.Frame.__init__(self, parent, wx.NewId(), 'Logging')
self.log_window = wx.LogWindow(self, 'Log Window', bShow=True)
box_sizer = wx.BoxSizer(orient=wx.VERTICAL)
show_log_button = wx.Button(self, wx.NewId(), 'Show Log')
show_log_button.Bind(wx.EVT_BUTTON, self._show_log)
log_message_button = wx.Button(self, wx.NewId(), 'Log Message')
log_message_button.Bind(wx.EVT_BUTTON, self._log_message)
box_sizer.AddMany((show_log_button, log_message_button))
self.SetSizer(box_sizer)
self.Fit()
self.Bind(wx.EVT_CLOSE, self._on_close)
def _show_log(self, event):
self.log_window.Show()
def _log_message(self, event):
wx.LogError('New error message')
def _on_close(self, event):
self.log_window.this.disown()
wx.Log.SetActiveTarget(None)
event.Skip()
if __name__ == '__main__':
app = wx.PySimpleApp()
dlg = MainWindow()
dlg.Show()
app.MainLoop()
Where bShow in wx.LogWindow is if it's initially shown or not. This will log nicely all your wx.LogX messages that you can trigger, and it still passes it on to any other handlers.
Another method you could use would be to log with python and then, upon opening a frame/window with a text control in it, use LoadFile to open the log file:
import logging
LOG_FILENAME = 'example.log'
logging.basicConfig(filename=LOG_FILENAME,level=logging.DEBUG)
logging.debug('This message should go to the log file')
Then, when creating a wx.TextCtrl somewhere:
log_control = wx.TextCtrl(self, wx.NewId(), style=wx.TE_MULTILINE|wx.TE_READONLY)
log_control.LoadFile('example.log')
EDIT:
This now works with the _on_close event! Thanks Fenikso