How to read a csv file after browsing it in PyQt5 [duplicate] - python-3.x

I have a block of code that opens a QFileDialog using Python3 and PyQt5:
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QFileDialog
import sys
class MCVE(QWidget):
def __init__(self):
super().__init__()
self.initialize()
def initialize(self):
self.setWindowTitle('MCVE')
self.setGeometry(50, 50, 400, 200)
btn = QPushButton('Example', self)
btn.clicked.connect(self.clicked)
self.show()
def clicked(self):
filename = QFileDialog.getOpenFileName(
self, "Open Template", "c:\\",
"Templates (*.xml);;All Files (*.*)")
print(filename)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = MCVE()
sys.exit(app.exec_())
In Python 2 using PyQt4 the print(filename) statement, after pressing the cancel button, outputs as an empty string. When I run the same code in Python 3 using PyQt5 I get:
('', '')
NOTE: The quotes are Single Quotes
Can someone explain what is going on? I couldn't find anything under the documentation between PyQt4 and PyQt5. I know that strings changed between Python 2 and Python 3, but I'm not sure those changes would cause an issue like this. Thanks!

The getOpenFileName function in PyQt4 returns a string that is the name of the selected file, and if none is selected then it returns an empty string.
filename = QFileDialog.getOpenFileName(self, "Open Template", "c:\\", "Templates (*.xml);;All Files (*.*)")
However in PyQt5 this returns a tuple of 2 elements where the first one is a string that has the same behavior as in PyQt4, and the second element is the filter used.
filename, filters = QFileDialog.getOpenFileName(self, "Open Template", "c:\\", "Templates (*.xml);;All Files (*.*)")
Note: The majority of documentation of PyQt5 is in Qt5, since in general the names of the methods, the inputs and the result are similar.

Related

Change WM_CLASS of Dialog (tkinter)

Is there a way to change WM_CLASS of showinfo (and other dialogs)? className, class_ parameters do the job for Tk, Toplevel.
import tkinter as tk
from tkinter.messagebox import showinfo
if __name__ == '__main__':
root = tk.Tk(className='ymail')
mail_client = tk.Toplevel(root, class_='ymail')
new_message = tk.Toplevel(root, class_='ymail')
showinfo(title="Cancel sending", parent=new_message, message="""
Send is cancelled due to empty message""")
root.mainloop()
For showinfo dialog
$ xprop WM_CLASS
gives
WM_CLASS(STRING) = "__tk__messagebox", "Dialog"
I think it is convenient to cycle tkinter windows with Alt-~ (Tilde), for which their WM_CLASS shall be the same.
I did the search ("tkinter change WM_CLASS showinfo"). Some of the hits are not applicable, some don't work (xdotool), and some I'd rather use as a last resort (converting C program to python).
Using
Debian 10
python 3.7.3
GNOME 3.30.1
EDIT
Added workaround (using xdotool)
import threading
import subprocess
import time
import tkinter as tk
from tkinter.messagebox import showinfo
def change_dialog_class(from_="Dialog", to_="ymail"):
cmd = f"xdotool search --class {from_} set_window --class {to_}"
time.sleep(1)
subprocess.run(cmd.split())
if __name__ == '__main__':
root = tk.Tk(className='ymail')
mail_client = tk.Toplevel(root, class_='ymail')
new_message = tk.Toplevel(root, class_='ymail')
tk.Frame.class_ = 'ymail'
threading.Thread(target=change_dialog_class, args=("Dialog", "ymail"),
daemon=True).start()
showinfo(title="Cancel sending", parent=new_message,
message="""Send is cancelled due to empty message""")
root.mainloop()
along with ymail.desktop it works
$ cat ~/.local/share/applications/ymail.desktop
[Desktop Entry]
Type=Application
Terminal=false
Name=ymail
Icon=python
StartupWMClass=ymail
yet, the python solution would be better
Since I'm not a XSystem user it took me some time to follow up. It
seems like that you are looking for wm_group and unfortunately it isnt
possible without subclassing it, which results in pretty much the same
as writing your own class with tk.Toplevel. Anyway I hope
toplevel.wm_group(root) ease things out and works for you.
After I noticed that the SimpleDialog may has some functionality that you want to keep and can be hard to code for yourself, I decided to write an answer that you may want to use. It also provides the class_ option in case wm_group dosent work for you.
Here is the code:
import tkinter as tk
import tkinter.simpledialog as simpledialog
class MessageBox(simpledialog.SimpleDialog):
def __init__(self, master,**kwargs):
simpledialog.SimpleDialog.__init__(self,master,**kwargs)
#root.tk.call('wm', 'group', self.root._w, master)
def done(self,num):
print(num)
self.root.destroy()
root = tk.Tk()
MessageBox(root,title='Cancel',text='Im telling you!',class_='ymail',
buttons=['Got it!','Nah'], default=None, cancel=None)
root.mainloop()
and here is the source:
https://github.com/python/cpython/blob/main/Lib/tkinter/simpledialog.py#L31

QThread closes whenever QFileDialog Called After Migrating from PyQt5 to Pyside2

First of all, I'm currently migrating my source code from PyQt5 to PySide2 which requires me to change some of the syntaxes. As this site said that it only needs 3 things to do migrate from PyQt to Pyside2.
1.app.exec_. exec_ was used as exec is a Python2 keyword. Under Python3, PyQt5 allows the use of exec but not PySide2.
2.Under PyQt5 it’s QtCore.pyqtSignal and QtCore.pyqtSlot and under PySide2 it’s QtCore.Signal and QtCore.Slot .
3.loading Ui files.
But anyway later on when I tried to run my code it gave me this following error:
QThread: Destroyed while thread is still running
I had more than 2000 lines of code and I cannot determine which is the cause of this other than my last action which is trying to call QFileDialog which shouldn't be a problem (I've tested this with PyQt import and there's no problem and no warning at all). But in PySide2 it definitely might be the cause of it. I look up into this, he doesn't have the same problem as mine exactly. I'm not trying to call QFileDialog from different thread.
this is the minimal reproducible example of my working code in PyQt5:
import sys
import os
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QMainWindow, QFileDialog, QMessageBox, QWidget, QDialog
import random
class MyWidget(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
self.path = os.path.abspath(os.path.dirname(sys.argv[0]))
self.button = QtWidgets.QPushButton("Open File")
self.labelFile = QtWidgets.QLabel("empty")
self.labelData = QtWidgets.QLabel("None")
self.layout = QtWidgets.QVBoxLayout()
self.layout.addWidget(self.button)
self.layout.addWidget(self.labelFile)
self.layout.addWidget(self.labelData)
self.setLayout(self.layout)
self.button.clicked.connect(self.open_file)
timer = QtCore.QTimer(self)
timer.timeout.connect(self.update_data_value)
timer.start(1000)
def open_file(self):
x = QFileDialog.getOpenFileName(self,"Pilih File CSV yang Ingin Diproses",self.path,"CSV Files (*.csv)")
self.labelFile.setText(x[0])
def update_data_value(self):
self.DataProcess = DataProcess()
self.DataProcess.progress.connect(self.update_data_label)
self.DataProcess.start()
def update_data_label(self,x):
self.labelData.setText(str(x[0]))
class DataProcess(QtCore.QThread):
progress = QtCore.pyqtSignal(object)
def __init__(self):
QtCore.QThread.__init__(self)
def run(self):
x = random.randint(1,100)
self.progress.emit([str(x)+ " from thread"])
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
widget = MyWidget()
widget.show()
sys.exit(app.exec_())
and this is the non-working one in PySide2 after renaming import accordingly to PySide2 also renaming 'pyqtsignal' to 'Signal'
import sys
import os
from PySide2 import QtCore, QtGui, QtWidgets
from PySide2.QtWidgets import QMainWindow, QFileDialog, QMessageBox, QWidget, QDialog
import random
class MyWidget(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
self.path = os.path.abspath(os.path.dirname(sys.argv[0]))
self.button = QtWidgets.QPushButton("Open File")
self.labelFile = QtWidgets.QLabel("empty")
self.labelData = QtWidgets.QLabel("None")
self.layout = QtWidgets.QVBoxLayout()
self.layout.addWidget(self.button)
self.layout.addWidget(self.labelFile)
self.layout.addWidget(self.labelData)
self.setLayout(self.layout)
self.button.clicked.connect(self.open_file)
timer = QtCore.QTimer(self)
timer.timeout.connect(self.update_data_value)
timer.start(1000)
def open_file(self):
x = QFileDialog.getOpenFileName(self,"Pilih File CSV yang Ingin Diproses",self.path,"CSV Files (*.csv)")
self.labelFile.setText(x[0])
def update_data_value(self):
self.DataProcess = DataProcess()
self.DataProcess.progress.connect(self.update_data_label)
self.DataProcess.start()
def update_data_label(self,x):
self.labelData.setText(str(x[0]))
class DataProcess(QtCore.QThread):
progress = QtCore.Signal(object)
def __init__(self):
QtCore.QThread.__init__(self)
def run(self):
x = random.randint(1,100)
self.progress.emit([str(x)+ " from thread"])
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
widget = MyWidget()
widget.show()
sys.exit(app.exec_())
so after creating this minimal example, I realized that PySide QFileDialog makes the QThread stop while PyQt QFileDialog doesn't freeze the main thread. Is there anything I could do to handle this in similar syntax architecture? (e.g not using "movetothread" or "QObject")
The problem is that you're overwriting self.DataProcess every time a new thread is created, which may cause the previous object to be garbage-collected by Python before Qt has a chance to delete it. This can result in a core-dump if Qt tries to delete an object which is no longer there. Problems of this kind are quite common in both PyQt and PySide, and are usually caused by not keeping proper references to dependant objects. The normal solution is to ensure that the affected objects are given a parent and, if necessary, explicitly delete them at an appropriate time.
Here is one way to fix your example:
class MyWidget(QtWidgets.QWidget):
...
def update_data_value(self):
# ensure the thread object has a parent
process = DataProcess(self)
process.progress.connect(self.update_data_label)
process.start()
def update_data_label(self,x):
self.labelData.setText(str(x[0]))
class DataProcess(QtCore.QThread):
progress = QtCore.Signal(object)
def __init__(self, parent):
# ensure the thread object has a parent
QtCore.QThread.__init__(self, parent)
def run(self):
x = random.randint(1,100)
self.progress.emit([str(x)+ " from thread"])
# explicitly schedule for deletion
self.deleteLater()
It's hard to say exactly why PySide behaves differently to PyQt in this particular case. It usually just comes down to low-level differences between the two implementations. Probably there are equivalent cases that affect PyQt but not PySide. However, if you manage object references and cleanup carefully, such differences can usually be eliminated.

wx.DirDialog not closing

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

How to avoid parent widget warning when using QFileDialog class?

I'm studying Python and PyQt5 in Microsoft Windows 7. My IDE is PyCharm 4.5 CE.
I am trying to make the file dialog to users can select the files or directories easily.
My code is...
# coding: utf-8
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.init_gui()
def init_gui(self):
file_names = QFileDialog.getOpenFileNames(self, "Select one or more files to open", "C:/Windows", "")
print(file_names)
self.setGeometry(100, 100, 500, 300)
self.show()
if __name__ == "__main__":
app = QApplication(sys.argv)
mw = MainWindow()
sys.exit(app.exec_())
This code works properly. But the only thing that annoying me is this.
There are many buttons in parent main window and one of the button shows file dialog.
What is the proper parent in this situation?
From PyQt5 documentation the method signature is:
QStringList getOpenFileNames (QWidget parent=None, QString caption=QString(), QString directory=QString(), QString filter=QString(), QString selectedFilter=None, Options options=0)
The parent must be an instance of QWidget or of some class that inherits from QWidget, and that's exactly what QMainWindow is (and this explains why everything works as expected).
Now, to understand why PyCharm displays a warning: if you look in the QFileDialog.py file, which is automatically generated by PyCharm from PyQt5\QtWidgets.pyd you will see that the method getOpenFileNames is not declared as staticmethod nor as classmethod:
def getOpenFileNames(self, QWidget_parent=None, str_caption='', str_directory='', str_filter='', str_initialFilter='', QFileDialog_Options_options=0): # real signature unknown; restored from __doc__
""" QFileDialog.getOpenFileNames(QWidget parent=None, str caption='', str directory='', str filter='', str initialFilter='', QFileDialog.Options options=0) -> (list-of-str, str) """
pass
so PyCharm expects (wrongly) the method to be called on an instance of QFileDialog, but here you have no instance of QFileDialog (as the method docstring suggests the real method signature is unknown), hence it expects the first argument of the method (self) to be an instance of QFileDialog and thus it throws a warning.
You can turn off such warning by disabling the inspection just for the desired statement:
# noinspection PyTypeChecker,PyCallByClass
file_names = QFileDialog.getOpenFileNames(self, "Select one or more files to open", "C:/Windows", "")
print(file_names)

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.

Resources