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)
Related
I want to replace the built-in method closeEvent of QMainWindow class instance that handles the form close event.
CODE #1
import sys
from PySide2.QtUiTools import QUiLoader
from PySide2.QtWidgets import QApplication
from PySide2.QtCore import QFile, QIODevice
app = QApplication(sys.argv)
ui_file_name = "ui\Main.ui"
ui_file = QFile(ui_file_name)
if not ui_file.open(QIODevice.ReadOnly):
print("Cannot open {}: {}".format(ui_file_name, ui_file.errorString()))
sys.exit(-1)
loader = QUiLoader()
window = loader.load(ui_file)
ui_file.close()
if not window:
print(loader.errorString())
sys.exit(-1)
def MainFormCloseEvent(event):
print(event)
event.ignore()
print(window.closeEvent)
window.closeEvent=MainFormCloseEvent
print(window.closeEvent)
window.show()
sys.exit(app.exec_())
This code does not cause the MainFormCloseEvent function to be called when the form closes.
This code print the following information:
<built-in method closeEvent of PySide2.QtWidgets.QMainWindow object at 0x000000000573BF80>
<function MainFormCloseEvent at 0x0000000002C37430>
But this code works well
CODE #2
import sys
from PySide2.QtUiTools import QUiLoader
from PySide2.QtWidgets import QApplication
from PySide2.QtCore import QFile, QIODevice
from PySide2.QtWidgets import QMainWindow
def MainFormCloseEvent(event):
print(event)
event.ignore()
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
def closeEvent(self, event):
print('Original class method')
app = QApplication(sys.argv)
window = MainWindow()
print(window.closeEvent)
window.closeEvent=MainFormCloseEvent
print(window.closeEvent)
window.show()
sys.exit(app.exec_())
This code print the following information:
<bound method MainWindow.closeEvent of <main.MainWindow(0x51522b0) at 0x0000000004E8AF40>>
<function MainFormCloseEvent at 0x0000000002C37430>
<PySide2.QtGui.QCloseEvent object at 0x0000000004E8F340>
I can't understand the fundamental difference between these codes. I replace the class instance method in the same way, but in the first case it does not work, but in the second it works.
I only noticed the difference that in the first code, the closeEvent method is built-in and in the second code, the closeEvent method is bound. But I did not find in Google what it means and how to make the first code work.
No, you're not replacing the method in the same way.
While creating a method as class attribute and overwriting an instance method at runtime usually has similar results, at the lower level it's not the same, which is extremely important when using complex modules like python binding to libraries written in other languages.
In general, attribute overwriting for methods should be done with extreme care only when it's safe to do it (and you do know what you're doing). It's also important to note that doing it for event handlers is risky, it makes debugging confusing and it also makes calling the default implementation more complex and awkward (you cannot call super()).
Unfortunately, PySide doesn't directly supports setting the UI on an existing widget instance, which is what you would do with PyQt and using a proper class, like in your second example), but there is a possible workaround, as explained in this related post.
class UiLoader(QtUiTools.QUiLoader):
_baseinstance = None
def createWidget(self, classname, parent=None, name=''):
if parent is None and self._baseinstance is not None:
widget = self._baseinstance
else:
widget = super().createWidget(classname, parent, name)
if self._baseinstance is not None:
setattr(self._baseinstance, name, widget)
return widget
def loadUi(self, uifile, baseinstance=None):
self._baseinstance = baseinstance
widget = self.load(uifile)
QtCore.QMetaObject.connectSlotsByName(baseinstance)
return widget
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
ui_file_name = "ui\Main.ui"
ui_file = QFile(ui_file_name)
ui_file.open(QIODevice.ReadOnly)
loader = UiLoader()
loader.loadUi(ui_file, self)
def closeEvent(self, event):
print('Original class method')
Still, you shouldn't overwrite the closeEvent with a basic function, instead you should probably opt for signals, further subclassing, or implement alternate ways to change the behavior (ie, using instance attributes).
I tried it.
from PySide2 import QtWidgets
from PySide2 import QtGui
from PySide2 import QtCore
from PySide2.QtUiTools import QUiLoader
from maya.app.general.mayaMixin import MayaQWidgetBaseMixin
import shiboken2 as shiboken
import os
UIFILEPATH = 'D:/MAYA/pyside_pick/ui/PicsTest5.ui'
class MainWindow(MayaQWidgetBaseMixin,QtWidgets.QMainWindow):
def __init__(self,parent=None):
super(MainWindow,self).__init__(parent)
self.UI = QUiLoader().load(UIFILEPATH)
self.setWindowTitle(self.UI.windowTitle())
self.setCentralWidget(self.UI)
#image
img = QtGui.QPixmap('D:/MAYA/pyside_pick/images/imgKohaku.png')
self.scene = QtWidgets.QGraphicsScene(self)
item = QtWidgets.QGraphicsPixmapItem(img)
self.scene.addItem(item)
self.UI.graphicsView_char_1.setScene(self.scene)
#filter
self._filter = Filter()
self.installEventFilter(self._filter)
self.UI.pSphere1.installEventFilter(self._filter)
#primary
self.UI.label.setStyleSheet("QLabel {color : white;}")
self.UI.label.setText("A")
def labelTest(self):
self.UI.label.setStyleSheet("QLabel {color : red;}")
self.UI.label.setText("B")
print('D')
return False
class Filter(QtCore.QObject):
def eventFilter(self, widget, event):
win = MainWindow()
if event.type() == QtCore.QEvent.MouseButtonPress:
print(widget.objectName())
cmds.select(widget.objectName())
win.labelTest()
return False
def main():
win = MainWindow()
win.show()
if __name__ == '__main__':
main()
I clicked the button that 'pSphere1', but
self.UI.label.setStyleSheet("QLabel {color : red;}") self.UI.label.setText("B")
were look like it's not working.
I can change it inside define with UI loaded, but can't I do setText from outside?
How can I change the label of an imported UI file?
I find this, but I really do not understand. I couldn't find any mention of them beyond this page.
Change comboBox values in Qt .ui file with PySide2
If you know, I also want you to tell me where to put them.
Your issue is within the eventFilter(), and specifically the first line:
win = MainWindow()
This will create a new main window instance, which clearly doesn't make sense, since you obviously want to interact with the existing one.
While you could add the instance as an argument in the filter constructor in order to get a reference to the instance and directly call the function, that wouldn't be very good from the OOP point of view, as objects should never directly access attributes of their "parents".
A better and more correct approach would be to use a custom signal instead, and connect it from the main window instance:
class Filter(QtCore.QObject):
testSignal = QtCore.Signal()
def eventFilter(self, widget, event):
if event.type() == QtCore.QEvent.MouseButtonPress:
print(widget.objectName())
cmds.select(widget.objectName())
self.testSignal.emit()
return False
class MainWindow(MayaQWidgetBaseMixin, QtWidgets.QMainWindow):
def __init__(self, parent=None):
# ...
self._filter.testSignal.connect(self.labelTest)
Note that widgets could accept events and prevent propagation (for instance, buttons or graphics views that have selectable or movable items), so you might not receive the event in the filter in those cases.
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.
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.
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.