wx.DirDialog not closing - python-3.x

Using wxPython version 4.0.1 (pheonix) with python 3.6.5
I use a wxPython DirDialog to allow my user to input a start directory. It correctly selects and populates my "working directory" variable (using GetPath()) but then doesn't ever close the directory dialog prompt box.
I read through the wxPython-user google pages and the only related question I found referred to this as being "intended behavior," implying it would happen later in execution (https://groups.google.com/forum/#!searchin/wxpython-users/close%7Csort:date/wxpython-users/ysEZK5PVBN4/ieLGEWc6AQAJ).
Mine, however, doesn't close until the entire script has completed running (which takes a fair amount of time), giving me the spinning wheel of death. I have tried a combination of calls to try to force the window to close.
app = wx.App()
openFileDialog = wx.DirDialog(None, "Select", curr, wx.DD_DIR_MUST_EXIST)
openFileDialog.ShowModal()
working_directory = openFileDialog.GetPath()
openFileDialog.EndModal(wx.CANCEL) #also wx.Close(True) and wx.Destroy()
openFileDialog.Destroy()
openFileDialog=None
I have also tried creating a window, passing it as the parent of the DirDialog, and then closing the window and it gives the same behavior.

You don't mention which operating system you are on or version of wx but in the partial code that you supplied there is no MainLoop, which is what was mentioned by Robin Dunn in his answer, in your link.
Try this and see if it works the way you would expect.
import wx
from os.path import expanduser
import time
class choose(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, -1, "Dialog")
panel = wx.Panel(self,-1)
text = wx.StaticText(panel,-1, "Place holder for chosen directory")
self.Bind(wx.EVT_CLOSE, self.OnClose)
self.Show()
curr = expanduser("~")
dlg = wx.DirDialog(None, message="Choose a directory", defaultPath = curr,
style=wx.DD_DEFAULT_STYLE|wx.DD_DIR_MUST_EXIST)
if dlg.ShowModal() == wx.ID_OK:
text.SetLabel(dlg.GetPath())
dlg.Destroy()
b = wx.BusyInfo("I'm busy counting",parent=None)
wx.Yield()
for i in range(30):
time.sleep(1)
del b
def OnClose(self, event):
self.Destroy()
if __name__ == '__main__':
my_app = wx.App()
choose(None)
my_app.MainLoop()

Related

How to create progressbar using python and glade for copying log files from one system to another system?

I am using this bash script for copying log files from one system to another system
#!/bin/bash s="/var/log"
d="/root/Directory" BACKUPFILE=scripts.backup.`date +%F`.tar.gz
scp -r root#$1:$s $2 rsync -chavzP --stats root#ipaddr
filename=ug-$(date +%-Y%-m%-d)-$(date +%-T).tgz
tar -czvf $2/$BACKUPFILE $s
tar --create --gzip --file=$d$filename $s
rm -rf /root/aaa/log
Also I have done progressbar code like this
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GObject
class ProgressBarWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="ProgressBar Demo")
self.set_border_width(10)
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
self.add(vbox)
self.progressbar = Gtk.ProgressBar()
vbox.pack_start(self.progressbar, True, True, 0)
button = Gtk.CheckButton("Show text")
button.connect("toggled", self.on_show_text_toggled)
vbox.pack_start(button, True, True, 0)
button = Gtk.CheckButton("Activity mode")
button.connect("toggled", self.on_activity_mode_toggled)
vbox.pack_start(button, True, True, 0)
button = Gtk.CheckButton("Right to Left")
button.connect("toggled", self.on_right_to_left_toggled)
vbox.pack_start(button, True, True, 0)
self.timeout_id = GObject.timeout_add(50, self.on_timeout, None)
self.activity_mode = False
def on_show_text_toggled(self, button):
show_text = button.get_active()
if show_text:
text = "some text"
else:
text = None
self.progressbar.set_text(text)
self.progressbar.set_show_text(show_text)
def on_activity_mode_toggled(self, button):
self.activity_mode = button.get_active()
if self.activity_mode:
self.progressbar.pulse()
else:
self.progressbar.set_fraction(0.0)
def on_right_to_left_toggled(self, button):
value = button.get_active()
self.progressbar.set_inverted(value)
def on_timeout(self, user_data):
"""
Update value on the progress bar
"""
if self.activity_mode:
self.progressbar.pulse()
else:
new_value = self.progressbar.get_fraction() + 0.01
if new_value > 1:
new_value = 0
self.progressbar.set_fraction(new_value)
# As this is a timeout function, return True so that it
# continues to get called
return True
win = ProgressBarWindow() win.connect("delete-event", Gtk.main_quit)
win.show_all() Gtk.main()
But I don't know how to embed my script with this progress bar code.
There are some approaches to this problem but main concern that you must care is that GUI and time consuming tasks (long tasks) are not good friends. Most GUI frameworks use their mainloop to take care of user input handling and draw the UI. This being said, you must separate those long tasks from the main UI and there are some ways to do it, be it threads, async methods, etc, it will all resume to how your language of choice deals with these problems.
Then there is the question of how to invoke the OS functions that you want to track. Probably the best way to do it would be by implementing them in your code, programmatically, but that would be a time consuming effort. So, using shell scripts will be your choice and doing so leads to the question: how to get the output of those commands? Well, there's popen and since you will be using python then you must spawn the shell scripts with popen calls. Best way to do it seems to be subprocess. An additional improvement would be a better tailored bash script that can get some previous analysis of command results (success or failure) and conditioned/formatted output.
Hope you can see where i'm going... You'll have to parse the data that normally goes to the console, interpret it and update the UI accordingly.
I give you a simple example of a Gtk Window with a progress bar that gets pulsed for every line of a shell command output (tree /):
import time
import threading
import subprocess
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import GLib, Gtk, GObject
def app_main():
win = Gtk.Window(default_height=50, default_width=300)
win.connect("delete-event", Gtk.main_quit)
win.connect("destroy", Gtk.main_quit)
progress = Gtk.ProgressBar(show_text=True)
win.add(progress)
def update_progress(i):
progress.pulse ()
#progress.set_fraction (i/100.0) # use this for percentage
#progress.set_text(str(i))
return False
def example_target():
i = 0 # can be used to pass percentage
proc = subprocess.Popen(['tree', '/'],stdout=subprocess.PIPE)
while True:
line = proc.stdout.readline()
if line != '':
time.sleep(0.1) # output is too quick, slow down a little :)
GLib.idle_add(update_progress, i)
else:
break
win.show_all()
thread = threading.Thread(target=example_target)
thread.daemon = True
thread.start()
if __name__ == "__main__":
app_main()
Gtk.main()
On the example above, we use threads. The shell command, that you can try on the console, dumps a tree of the folder structure on you disk. Can be anything but the goal was to have a long task. Since we can't track progress, the bar will be in activity mode and we pulse it for every line but if you can track progress you can use the set_fraction method instead.
Hope this will lead you on the right direction. GL

OSX: Dialog.Destroy() hangs program in wxPython 3.0

We are working on bringing our application up to date with wxPython 3.0.2, however this is one of two major bug that is still around.
Background: on program start, we spawn a custom dialog telling the user that some things are loading. This dialog has an animation, so it's important to keep the main thread with the GUI clear while we load data in the background thread. When that is done, we sent a command to a callback that Destroy()s the dialog, and the program is able to function as normal.
This works well in 2.8, but it seems to hang our app in 3.0. The dialog message disappears, but we cannot close the program or interact with the GUI, almost as if the GUI was still locked under Modal.
Here's a test script that demonstrates, being as close to the original program as possible in the logic path it takes:
import wxversion
wxversion.select('3.0')
import wx
import time
import threading
class OpenThread(threading.Thread):
def __init__(self, callback):
threading.Thread.__init__(self)
self.callback = callback
self.start()
def run(self):
time.sleep(0.5) # Give GUI some time to finish drawing
for i in xrange(5):
print i
time.sleep(.3)
print "ALL DONE"
wx.CallAfter(self.callback)
class WaitDialog(wx.Dialog):
def __init__(self, parent, title = "Processing"):
wx.Dialog.__init__ (self, parent, id=wx.ID_ANY, title = title, size=(300,30),
style=wx.NO_BORDER)
mainSizer = wx.BoxSizer( wx.HORIZONTAL )
self.SetBackgroundColour(wx.WHITE)
txt = wx.StaticText(self, wx.ID_ANY, u"Waiting...", wx.DefaultPosition, wx.DefaultSize, 0)
mainSizer.Add( txt, 1, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 0 )
self.SetSizer( mainSizer )
self.Layout()
self.Bind(wx.EVT_CLOSE, self.OnClose)
self.CenterOnParent()
def OnClose(self, event):
pass
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Test")
self.waitDialog = None
mainSizer = wx.BoxSizer( wx.HORIZONTAL )
choice = wx.Choice(self, wx.ID_ANY, style=0)
choice.Append("No Selection", 0)
choice.Append("Selection 1", 1)
choice.Append("Selection 2", 2)
mainSizer.Add( choice , 1, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 0 )
self.SetSizer( mainSizer )
self.Show()
self.doThing()
def doThing(self):
self.waitDialog = WaitDialog(self, title="Opening previous fits")
OpenThread(self.closeWaitDialog)
self.waitDialog.ShowModal()
def closeWaitDialog(self):
self.waitDialog.Destroy()
test = wx.App(False)
MainFrame()
test.MainLoop()
You can comment out the self.waitDialog bits and see that it is the dialogs giving trouble. There are other places in the program that this happens in, always after we close out of a Dialog. Is there something I'm missing? Is there a workaround? We also have a few more dialogs that we utilize, so a workaround would ideally be a small fix rather than a huge refactoring
wx.CallAfter basically just puts the event into a queue, so I wonder if the event queue is getting blocked somehow such that it isn't processing the event that would call your event handler to destroy the dialog.
I found the following that might help:
wxPython wx.CallAfter - how do I get it to execute immediately?
Basically you could use wx.WakeUpIdle() or wx.GetApp().ProcessIdle() or maybe even ProcessPendingEvents.
This might also be helpful:
http://wxpython.org/Phoenix/docs/html/window_deletion_overview.html
I also found this useful StackOverflow answer to a similar problem in that you may just need to call the dialog's EndModal method before Destroying it.

PyQt : Manage two MainWindow

i'm trying to manage two MainWindow (MainWindow & AccessWindow) with PyQt for my RFID ACCESS CONTROL Project.
I want to show the first MainWindow all time (Endless Loop).
Then, i want to hide it and show the second MainWindow when the RFID Reader (who's working on "auto-reading mode") read an RFID Tag.
so in the main python program i have a pseudo "do while" loop (while True: and break with a condition) to read on serial port the data provided by the reader. Then i check a DB.. It's not important. So the trigger event is "when the reader read something).
I got some help from another forum and now i have this:
# -*- coding: utf-8 -*-
from PyQt4 import QtCore, QtGui
import sys, pyodbc, serial
import os
import time
#Variables
Code_Zone = "d"
class MainWindow(QtGui.QWidget):
def __init__(self, main):
super(MainWindow, self).__init__()
self.main = main
self.grid = QtGui.QGridLayout(self)
self.welcome = QtGui.QLabel("WELCOME", self)
self.grid.addWidget(self.welcome, 2, 2, 1, 5)
class AccessWindow(QtGui.QWidget):
def __init__(self):
super(AccessWindow, self).__init__()
self.setMinimumSize(150, 50)
self.grid = QtGui.QGridLayout(self)
self.label = QtGui.QLabel(self)
self.grid.addWidget(self.label, 1, 1, 1, 1)
class Main(object):
def __init__(self):
self.accueil = MainWindow(self)
self.accueil.show()
self.access = AccessWindow()
def wait(self):
# RFID READER ENDLESS LOOP
while 1:
global EPC_Code
ser = serial.Serial(port='COM6', baudrate=115200)
a = ser.read(19).encode('hex')
if (len(a)==38):
EPC_Code = a[14:]
print ('EPC is : ' + EPC_Code)
break
else:
continue
ser.close()
self.on_event(EPC_Code)
def on_event(self, data):
def refresh():
self.toggle_widget(False)
self.wait()
# vérification des données
EPC_Code = data
sql_command = "[Get_Access_RFID] #Code_RFID = '"+EPC_Code+"', #Code_Zone = '"+Code_Zone+"'" # STORED PROCEDURE
db_cursor.execute(sql_command)
rows = db_cursor.fetchone()
result= str(rows[0])
print ("result = " + str(result))
if result == "True":
# si OK
self.access.label.setText('ACCESS GRANTED')
else:
# si pas OK
self.access.label.setText('ACCESS DENIED')
self.toggle_widget(True)
QtCore.QTimer.singleShot(2000, refresh)
def toggle_widget(self, b):
self.accueil.setVisible(not b)
self.access.setVisible(b)
if __name__=='__main__':
cnxn = """DRIVER={SQL Server};SERVER=***;PORT=***;UID=***;PWD=***;DATABASE=***"""
db_connection = pyodbc.connect(cnxn)
db_cursor = db_connection.cursor()
print ('Connected TO DB & READY')
app = QtGui.QApplication(sys.argv)
main = Main()
main.wait()
sys.exit(app.exec_())
and now my problem is that the text of the first window doesn't appear when i run the program but the text of the second window appear when i keep my badge near the RFID Reader.
Instead of two MainWindow, create one. As content, create two classes which extend QtGui.QWidget called MainView and AccessView. Instead of replacing the window, just put the correct view into the window. That way, you can swap views without opening/closing windows.
If you use a layout, then the window will resize to fit the view.
The next problem is that you block the UI thread which means Qt can't handle events (like the "paint UI" event). To fix this, you must move the RFID handling code in a background thread. You can emit signals from this background thread to update the UI.
Note: You must not call UI code from a thread!! Just emit signals. PyQt's main loop will see them and process them.
Related:
https://joplaete.wordpress.com/2010/07/21/threading-with-pyqt4/
Updating GUI elements in MultiThreaded PyQT, especially the second example using signals. The first example is broken (calling addItem() from a thread) is not allowed)

QFileDialog: no signals emitted, wrong starting directory

I'm trying to show a QFileDialog using the following piece of code:
import os, sys
from PyQt4.QtGui import *
class MainWindow(QWidget):
def __init__(self):
QWidget.__init__(self)
self._button = QPushButton('Test button')
self._button.clicked.connect(self._onButtonClicked)
self._layout = QHBoxLayout()
self._layout.addWidget(self._button)
self.setLayout(self._layout)
def _onButtonClicked(self):
self._dialog = QFileDialog(self, 'Select directory')
self._dialog.setDirectory(os.getenv('HOME'))
self._dialog.setFileMode(QFileDialog.Directory)
self._dialog.directoryEntered.connect(self._onDirEntered)
self._dialog.exec_()
def _onDirEntered(self, directory):
print("Entered directory: %s" % (directory))
if __name__ == "__main__":
app = QApplication(sys.argv)
mw = MainWindow()
mw.show()
app.exec_()
Two problems here:
The directoryEntered signal is never emitted, at least I don't get any output from the script (except for some KDE warnings about Samba support, etc.); actually no signal from the QFileDialog class I tried to connect to gets emitted in the example. What am I doing wrong?
In this example I set the starting directory to $HOME, but the dialog will start in /home instead and have my home directory selected in the listing instead of starting directly in my home directory. Can I change this behaviour somehow?
I'm using Python 3.4.0 with PyQt 4.10.4-2.
Both problems do not occur when using a non-native dialog as suggested by #ekhumoro.

Cairo introspection errors in Eclipse with PyDev `TypeError: Couldn't find conversion for foreign struct 'cairo.Context'`

I am working on adding a printer interface to some home-brewed Python3 code with a Gtk3 UI, using (mostly) Eclipse Indigo with the PyDev plugin.
While developing the PrintOperation callbacks I found a problem where apparently the gi-introspection fails to find the right underlying library struct for the Cairo Context. The error reported in the console is:
Traceback (most recent call last):
File "/home/bob/Projects/MovieList/src/MovieList/MovieListIO.py", line 203, in on_printDialog_draw_page
cr = context.get_cairo_context()
File "/usr/lib/python3/dist-packages/gi/types.py", line 43, in function
return info.invoke(*args, **kwargs)
TypeError: Couldn't find conversion for foreign struct 'cairo.Context'
At first I thought this was something to do with Eclipse and/or PyDev, because I could run the program within Idle without any error messages. But then I found that when the program was packaged for deployment with the built-in command-line Python tools, the installed version also gave the error. So, I wrote a couple of test scripts abstracting the printer functionality to try to isolate what was going on. In both cases, the key line is in the on_printOperation_draw_page() callback (marked with comments).
Here is the first test script (Script 1, printTestPdf.py), which loads a pdf file using Poppler, and prints it using the system print dialog:
#!/usr/bin/env python3
import os
from gi.repository import Gtk, Poppler
testFile = 'file://' + os.path.join(os.getcwd(), 'printTestPdf.pdf')
pdfDocument = Poppler.Document.new_from_file(testFile, None)
class Example(Gtk.Window):
def __init__(self):
super(Example, self).__init__()
self.init_ui()
def init_ui(self):
self.set_title("Print Pdf Test")
self.resize(230, 150)
self.set_position(Gtk.WindowPosition.CENTER)
self.connect("delete-event", Gtk.main_quit)
printButton = Gtk.Button('Press Me')
self.add(printButton)
printButton.connect('clicked', self.on_printButton_clicked)
self.show_all()
def on_printButton_clicked(self, widget):
"""
Handler for the button click.
"""
printOperation = Gtk.PrintOperation()
printOperation.connect('draw-page', self.on_printOperation_draw_page)
printOperation.set_job_name('Print Pdf Test')
printOperation.set_n_pages(pdfDocument.get_n_pages())
printOperation.run(Gtk.PrintOperationAction.PRINT_DIALOG,
parent=self)
def on_printOperation_draw_page(self, printOperation, context, pageNo):
"""
Handler for the draw-page signal from the printOperation.
"""
cr = context.get_cairo_context() # <-- THIS IS THE LINE
page = pdfDocument.get_page(pageNo)
page.render_for_printing(cr)
def main():
app = Example()
Gtk.main()
if __name__ == "__main__":
main()
This is the second script (Script 2, printTestHtml.py), which is almost identical, except it loads an HTML file for printing using weasyprint:
#!/usr/bin/env python3
import os
from gi.repository import Gtk
from weasyprint import HTML
testFile = os.path.join(os.getcwd(), 'printTestHtml.html')
pdfDocument = HTML(filename=testFile).render()
class Example(Gtk.Window):
def __init__(self):
super(Example, self).__init__()
self.init_ui()
def init_ui(self):
self.set_title("Print Html Test")
self.resize(230, 150)
self.set_position(Gtk.WindowPosition.CENTER)
self.connect("delete-event", Gtk.main_quit)
printButton = Gtk.Button('Press Me')
self.add(printButton)
printButton.connect('clicked', self.on_printButton_clicked)
self.show_all()
def on_printButton_clicked(self, widget):
"""
Handler for the button click.
"""
printOperation = Gtk.PrintOperation()
printOperation.connect('begin-print', self.on_printOperation_begin_print)
printOperation.connect('draw-page', self.on_printOperation_draw_page)
printOperation.set_job_name('Print HTML Test')
printOperation.set_n_pages(len(pdfDocument.pages))
printOperation.run(Gtk.PrintOperationAction.PRINT_DIALOG,
parent=self)
def on_printOperation_draw_page(self, printOperation, context, pageNo):
"""
Handler for the draw-page signal from the printOperation.
"""
cr = context.get_cairo_context() # <-- THIS IS THE LINE
page = pdfDocument.pages[pageNo]
page.paint(cr) # <-- there is a separate issue here
def main():
app = Example()
Gtk.main()
if __name__ == "__main__":
main()
Both scripts generate an internal pdf document, which is used to render each page on request via the PrintOperation draw_page callback.
Now, whether and how the scripts succeed or fail depends on the context in which they are run. Script 1 always works, except if it is run after a failure of Script 2 in Idle. Script 2 always generates the error message as reported above when run in Eclipse. In Idle, Script 2's behaviour is complex. Sometimes it fails due to a second problem (marked), and does not exhibit the first failure. However, for reasons I have yet to establish, every so often it generates the original error, and when it does, it keeps doing it and Script 1 show the error too, until Idle is re-booted. Running directly from the command line matches the behaviour in Eclipse. I have tried to summarise this behaviour below:
* Eclipse
- Script 1: Always OK
- Script 2: Always Fails
* Command line
- Script 1: Always OK
- Script 2: Always Fails
* Idle
- Script 1: OK, except after failure of Script 2
- Script 2: Intermittent Fail. Knock-on to future runs (up to next error)
This pattern of failure may help determine what the root problem is, but it is beyond me to understand it.
Ignoring the bizarre behaviour in Idle, it is possible the difference between Script 1 and Script 2 holds a clue to my original problem. Why does Script 1 run successfully, while Script 2 generates the introspection error?
If you can offer any suggestions as to what is going wrong I would be most grateful. If you can come up with a solution I will be delighted!
In view of the lack of response I have come up with the following workaround, which uses WebKit instead of weasyprint to do the parsing, rendering and administration of the printing from html:
#!/usr/bin/env python3
import os
from gi.repository import Gtk, WebKit
testFile = 'file://' + os.path.join(os.getcwd(), 'printTestHtml.html')
class Example(Gtk.Window):
def __init__(self):
super(Example, self).__init__()
self.init_ui()
def init_ui(self):
self.set_title("Print Html WebKit Test")
self.resize(230, 150)
self.set_position(Gtk.WindowPosition.CENTER)
self.connect("delete-event", Gtk.main_quit)
printButton = Gtk.Button('Press Me')
self.add(printButton)
printButton.connect('clicked', self.on_printButton_clicked)
self.show_all()
def on_printButton_clicked(self, widget):
webView = WebKit.WebView()
webView.load_uri(testFile)
webFrame = webView.get_main_frame()
webFrame.print_full(Gtk.PrintOperation(),
Gtk.PrintOperationAction.PRINT_DIALOG)
def main():
app = Example()
Gtk.main()
if __name__ == "__main__":
main()
What I think is going on is that somehow weasyprint interferes with the introspection process. I have raised this as a bug on the weasyprint home page on github.
Just in case it helps I was looking for a solution to the cairo error.
Had this cairo error happen on my RPi3 with Pandas and Matplotlib. The plot window was showing up blank.
I had to run sudo apt-get install python-gobject-cairo
Based on this: https://github.com/rbgirshick/py-faster-rcnn/issues/221

Resources