I'm using a custom QFileDialog because I want to select multiple directories.
But the exec_ function is very slow, and I can't figure out why. I'm using the newest version of PyQt.
Code Snippet:
from PyQt4 import QtGui, QtCore, QtNetwork, uic
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
uic.loadUi('gui.ui', self)
self.connect(self.multiPackerAddDirsBtn,
QtCore.SIGNAL('clicked()'), self.multiPackerAddDirs)
def multiPackerAddDirs(self):
dialog = QtGui.QFileDialog(self)
dialog.setFileMode(QtGui.QFileDialog.Directory)
dialog.setOption(QtGui.QFileDialog.ShowDirsOnly, True)
dialogTreeView = dialog.findChild(QtGui.QTreeView)
dialogTreeView.setSelectionMode(
QtGui.QAbstractItemView.ExtendedSelection)
if dialog.exec_():
for dirname in dialog.selectedFiles():
self.multiPackerDirList.addItem(str(dirname))
print(str(dirname))
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
The QFileDialog constructor creates a Qt dialog, whereas the static functions (like getSaveFileName) will create a native one (unless the DontUseNativeDialog option is set to True).
The native dialogs may be faster or slower than Qt's, depending on the platform in use.
For some plaforms, though, it appears the problem may be more acute. See this longstanding bug, which affects Windows XP and Windows 7 (amongst others) with Qt 4.7 / 4.8.
UPDATE
Just to be clear:
On Windows, the static function QFileDialog.getExistingDirectory opens the native "Browse For Folder" dialog, which only allows selecting a single directory. So Qt cannot provide a native dialog for selecting multiple directories, because Windows doesn't provide one.
The other main alternative is to use Qt's own, non-native file-dialog and monkey-patch it as suggested in this faq. However, as you've already discovered, this currently has the significant downside of being annoyingly slow due to bugs in the underlying implementation.
The only remaining alternatives are to either write your own directory-lister dialog, or try to think of another way of solving your immediate problem (i.e. without using a file-dialog).
I had very very slow performance from the default Qt file browser dialog. Listing a directory took ~5s and selecting a file took ~3s. Adding the "DontUseNativeDialog" option fixed my problem completely.
file_path = QtGui.QFileDialog.getSaveFileName( self, 'Title', path, "", "", QtGui.QFileDialog.DontUseNativeDialog )
print file_path
Related
I created UI using Qt Designer. Then I converted the ui file to a .py file (pyuic -x) - it works fine if launched directly. Then I tried to subclass my ui in a separate file to implement additional logic. And this is where things start to go wrong. Inheriting from QMainWindow and my Qt Designer file works OK with no issues, as expected. However, the moment I set any WindowFlag for my QMainWindow (any flag - I tried these: StaysOnTop, FramelessWindowHint) and run the file, the window appears and instantly disappears. The program continues to run in a console window, or as a PyCharm process, but the window is gone. It looks to me like it is getting out of scope - but why setting a simple flag would make any difference to the garbage collector? Could someone explain this behaviour?
Minimum code required to reproduce this phenomenon:
from ui import Ui_MainWindow
from PyQt5 import QtCore, QtWidgets, QtGui
import sys
class Logic(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
self.setupUi(self)
self.show()
# self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
# self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
self.setAttribute(QtCore.Qt.WA_NoSystemBackground, True)
self.setAttribute(QtCore.Qt.WA_TranslucentBackground, True)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = Logic()
sys.exit(app.exec_())
The window should appear and stay on the screen until one (or more) of the flags are uncommented. I use Python 3.8 (32-bit) with PyQt5. Run environment provided by PyCharm. Windows 10.
From the documentation of setWindowFlags():
Note: This function calls setParent() when changing the flags for a window, causing the widget to be hidden. You must call show() to make the widget visible again..
So, just move self.show() after setting the flags, or call it from outside the __init__ (after the instance is created), which is the most common and suggested way to do so, as it's considered good practice to show a widget only after it has been instanciated.
I'm attempting to create a dialog which contains two child widgets: on the left side a QFileDialog instance so users can select files, and on the right side a separate widget which will be used to show a preview of the selected file if it is of a certain type.
The problem is that the dialog opens up and I can see the "preview" widget just fine, but the QFileDialog is not showing up at all.
This short example demonstrates my problem:
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
app = QApplication([])
main_dialog = QDialog()
main_dialog.setWindowTitle('My Dialog')
layout = QHBoxLayout(main_dialog)
file_dialog = QFileDialog(main_dialog, Qt.Widget)
file_dialog.setOption(QFileDialog.DontUseNativeDialog)
layout.addWidget(file_dialog)
preview = QLabel('Preview', main_dialog)
layout.addWidget(preview)
main_dialog.show()
app.exec_()
Some things that I've tried:
Add file_dialog.show() before/after main_dialog.show(): this shows up the QFileDialog, but in a different window; I want the file dialog to appear inside main_dialog, not as a separate window;
Do not pass Qt.Widget to the QFileDialog constructor, to no effect;
Do not pass main_dialog as parent to QFileDialog, again no effect;
Change main_dialog to a QWidget just to see if it changed anything, it did not;
I've searched the docs but did not find a suitable solution.
Any hints? Also, suggestions on how to accomplish the task of allowing the user to select a file and display a preview of the file in the same window are welcome.
Context: this is a port of an old application written for Qt3. Qt3's QFileSystem dialog had this "preview" functionality built-in; I'm trying to reproduce the same functionality in Qt5.
Versions
Python 2.7
PyQt 5.5.1
I've also tried with Python 3.6 (from conda-forge) but obtained the same behavior.
You need to turn off the Qt.Dialog flag in the file dialog's windowFlags...
file_dialog.setWindowFlags(file_dialog.windowFlags() & ~Qt.Dialog)
Otherwise the QFileDialog will always be created as a top level window. Works for me anyway.
I have created a GUI with Qt Designer and accessed it via
def loadUiWidget(uifilename, parent=None):
loader = QtUiTools.QUiLoader()
uifile = QtCore.QFile(uifilename)
uifile.open(QtCore.QFile.ReadOnly)
ui = loader.load(uifile, parent)
uifile.close()
return ui
MainWindow = loadUiWidget("form.ui")
MainWindow.show()
children = MainWindow.children()
button1 = MainWindow.QPushButton1
"children" does already contain the widgets "QPushButton1", "QTextBrowser1" created in the UI but shouldn't the be accessed by the recoursive findChildren() method?
What is an elegant way to access the widgets of the .ui File?
References:
Find correct instance,
Load .ui file
Since widget names in Qt Designer must be unique, the hierarchy (at least for getting references to the widgets) is flattened (with no risk of conflict), and so the best way is just to access them via:
loader = QtUiTools.QUiLoader()
ui = loader.load('filename.ui', parent)
my_widget = ui.my_widget_name
This would place a reference to the widget called 'my_widget_name' in Qt Designer in the variable my_widget.
I would say the above is the most pythonic way of accessing the widgets created when you load the .ui file.
There are two disadvantages of loading UI at run time:
overhead each time the program is run (actually, each time the loader is used)
lack of support of code completion and checking, since IDE doesn't know the code behind ui until the uifile has been loaded.
An alternative, assuming you are using the modern version of PySide called "Qt for Python", is to "compile" the .ui file to a Python class (see docs). For this, after saving filename.ui, execute
pyside2-uic filename.ui -o ui_mainwindow.py
while within your virtual environment, if any. The new class will be called Ui_MainWindow. Assuming you have a text_box widget in your UI, you can now access its properties and methods. Here is a full working example:
import sys
from PySide2.QtWidgets import QApplication, QMainWindow
from ui_mainwindow import Ui_MainWindow
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.ui.text_box.setPlainText('test') # here we are addressing widget
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
Notes:
pyside2-uic should be called after every change of the .ui file. This is a disadvantage of this approach in comparison to OP. It also means that you should either version-control both .ui and .py files for your UI, or somehow call uic during deployment.
The big advantage is that IDE like PyCharm has access to all widget methods and properties for autocompletion and code checking.
As of today, pyside2-uic creates non-PEP8 compliant code. However, as long as you give your widgets PEP8-compliant names, your own code will be OK.
I have a QT4 and python 2.4 based GUI. The user starts using it by opening a file. In addition to explicit browsing, I would like to allow the user to specify a file to open as a command line argument. I am looking for some event in the QMainWindow (or wherever) that would allow me to detect when the application completed its initialization and is ready for user interaction at which point I could automatically open the file and populate the widgets. So far I could not find anything better than overriding showEvent which is not exactly it because the main window is still not visible at this point. It might be okay but I am looking for a proper way to do this. In some other UI toolkits that I have used in the past that would be something like “main form layout completed” event that would signal that the UI is safe to deal with. Is there something similar in QT4? I am running this on Linux if that matters.
You insights are greatly appreciated.
You don't need an event here. It is guaranteed that everything is loaded after __init__() and show() have run, so you can just put your code for the file opening after that.
import sys
from PyQt4 import QtGui
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.setupUI()
self.show()
# normal __init__ done
if len(sys.argv) > 1:
with open(sys.argv[1]) as f:
# do stuff with file
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
I'll start with the question and then try to explain:
Is there a way for an imported module to call a function in the module that imports it?
I am just learning to use Qt and am starting with Qt Designer to get some fundamentals worked out.
I have figured out how to create more than one ".ui" file in order to get the code for multiple windows and have managed to work out how to call the multiple windows from a main application by importing the code for the two windows.
For example, starting with win1.ui and win2.ui I create win1.py and win2.py - from my main application I import win1 and win2...
Note - I got this far by following this simple tutorial : http://www.youtube.com/watch?v=bHsC6WJsK-U&list=PLF4575388795F2531&index=10&feature=plpp_video
OK - now the question. If I have a button in win2, I know how to link that button to a function in the win2.py code. What I don't know how to do is link the button in win2 to a function in my main application.
My only thought would be to add a function as an argument to the class that sets up the second window but if I do that then any changes to win2.ui will wreck the code that I have changed.
Thus, Is there a way for an imported module to call a function in the module that imports it?
I hope this is clear without adding a bunch of code that isn't really relevant...
Qt is based on event-driven programming. Generally when you start building up your widgets, what you are going to be wanting to do is providing information to receiver widgets via signals that are then processed. You don't want to explicitly have a child widget know or require to call methods on a parent widget (this is not always the case, but it is good to avoid when possible).
I'm gonna post some examples that don't have UI files for ease here, but just assume you can build the same widget with designer and have it work the same way...
testwidget.py
from PyQt4 import QtGui, QtCore
class TestWidget(QtGui.QWidget):
textSaved = QtCore.pyqtSignal(str)
def __init__( self, parent = None ):
super(TestWidget, self).__init__(parent)
# create the ui (or load it)
self.__edit = QtGui.QTextEdit(self)
self.__button = QtGui.QPushButton(self)
self.__button.setText('Save')
layout = QtGui.QVBoxLayout()
layout.addWidget(self.__edit)
layout.addWidget(self.__button)
self.setLayout(layout)
# create connections
self.__button.clicked.connect(self.emitTextSaved)
def emitTextSaved( self ):
# allow Qt's blocking of signals paradigm to control flow
if ( not self.signalsBlocked() ):
self.textSaved.emit(self.__edit.toPlainText())
testwindow.py
from PyQt4 import QtGui, QtCore
import testwidget
class TestWindow(QtGui.QMainWindow):
def __init__( self, parent == None ):
super(TestWindow, self).__init__(parent)
# create the ui (or load it)
self.__editor = testwidget.TestWidget(self)
self.setCentralWidget(self.__editor)
# create connections
self.__editor.textSaved.connect(self.showMessage)
def showMessage( self, message ):
QtGui.QMessageBox.information(self, 'Message', message)
So, here you can see that instead of thinking about it like - "when I click the button in TestWidget, I want to show a message in TestWindow" and explicitly link the two methods, you expose a signal that the TestWidget will emit out when the user performs an action, then connect that signal to the showMessage slot of the TestWindow. This way, your smaller widgets become more independent, and its more a matter of how you connect to each event that drives your application.
I could have done something like self.parent().showMessage(self.__edit.toPlainText()) within the TestWidget's emitTextSaved method to call the method directly - but this is not a good design.