refreshing QTreeView / QSortFilterProxyModel - python

Good Day to All,
Been writing code for years, but still a bit green when it comes to PyQt,
so please forgive my syntactically lacking question(s) ;-)
I'm hacking a derivative of the (famous?) packaged example that
comes with PyQt4 (and Qt), namely "basicsortfiltermodel.pyw"
from "../examples/itemviews" in PyQt4...
I've added a little popup menu (let's call this B.py) that one can launch
from the BasicSort-derivative (let's call this A.py).
I believe I'm correcting adding new data (a new record) to the
QSortFilterProxyModel(). (I think this because I'm not getting
any errors now, after some effort) But I seem to be unable to get
the QTreeView to refresh. I've scoured the Qt class docs and
Google'd the heck out of it (seems like a common question from
the looks of it, lol)..
Now I know this is an ugly hack, but just to try to get it to work
(elegance can come later is my theory)...
At the bottom of A.py, I declared a global "wX",
global wX;
[...]
window = Window()
wX = Window()
window.setSourceModel(createMailModel(window))
so that when I hit a button later, I could more easily get a hold
of the "parent" value found in the runtime "createMailModel".
From which I get the "model" handle..
model = QtGui.QStandardItemModel(0, 17, WinX)
addMail(model, "image",
"tabl00",
etc
etc)
Anyways,..I think this is working....
But after adding a new record via addMail(), I can't seem to get
self.proxyModel to refresh itself..
I'm pretty sure this a stupid newbie issue, lol....but could anyone
help shed some light on how to make this work?
Many Thanks,

I believe you have to add new items into your original model not the proxy one. Once items is added proxy model and view will updates themselves accordingly. See if an example below would work for you:
import sys
from PyQt4 import QtGui
class MainForm(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainForm, self).__init__(parent)
self.setMinimumSize(300, 400)
self.model = QtGui.QStandardItemModel()
self.sortModel = QtGui.QSortFilterProxyModel()
self.sortModel.setSourceModel(self.model)
parentItem = self.model.invisibleRootItem()
parentItem.appendRow(QtGui.QStandardItem("3"))
parentItem.appendRow(QtGui.QStandardItem("1"))
parentItem.appendRow(QtGui.QStandardItem("4"))
parentItem.appendRow(QtGui.QStandardItem("2"))
self.view = QtGui.QListView(self)
self.view.setModel(self.sortModel)
self.view.setGeometry(0, 0, 200, 400)
self.button = QtGui.QPushButton("add items", self)
self.button.move(200, 0)
self.button.clicked.connect(self.on_button_clicked)
self.layout = QtGui.QVBoxLayout(self.centralWidget())
self.layout.addWidget(self.view)
self.layout.addWidget(self.button)
self.sortModel.sort(0)
def on_button_clicked(self):
parentItem = self.model.invisibleRootItem()
parentItem.appendRow(QtGui.QStandardItem("222"))
parentItem.appendRow(QtGui.QStandardItem("333"))
parentItem.appendRow(QtGui.QStandardItem("444"))
def main():
app = QtGui.QApplication(sys.argv)
form = MainForm()
form.show()
app.exec_()
if __name__ == '__main__':
main()
hope this helps, regards

Related

Why is QMessageBox.exec() not blocking my thread

please check below snippet from my application:
requestDialogSignal = QtCore.pyqtSignal()
class MainWindow(QMainWindow):
def __init__(self):
...
self.requestDialogSignal.connect(self.slotRequestDialog, Qt.QueuedConnection)
def slotRequestDialog():
mbox = QtWidgets.QMessageBox(QtWidgets.QMessageBox.NoIcon, "title", "message")
mbox.setStandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
result = mbox.exec()
def CreateDialogs(self):
self.requestDialogSignal.emit()
time.sleep(1)
self.requestDialogSignal.emit()
app = QApplication(sys.argv)
window = MainWindow()
window.show()
t = threading.Thread(target=window.CreateDialogs)
t.start()
app.exec()
Essentially, what I was trying to achieve is 2nd QMessageBox to appear only after first one gets answered. I was expecting the first QMessageBox to block the receiver thread on exec(), which due to Qt.QueuedConnection should not allow for the slot to be called second time. If I change the connection to Qt.BlockingQueuedConnection it behaves as expected, although this blocks the sending thread, which is not what I want.
Try something like this. I have not tested it, as I do not have PyQt nor PySide on my machine. There may be some little issues but the logic is clear and I hope understandable.
requestDialogSignal = QtCore.pyqtSignal()
class MainWindow(QMainWindow):
def __init__(self):
self.state = 0 # a state variable which can be used to define the dialog opening logic
...
def openDialog(self):
mbox = QtWidgets.QMessageBox(QtWidgets.QMessageBox.NoIcon, "title", "message", self) # use self to give the dialog a parent; not strictly needed but it is a good practice
mbox.setStandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
mbox.finished.connect(self.onDialogFinished) # or you can connect to accepted or rejected signals to finetune the logic
mbox.open() # open() makes the dialog window-modal but does not block the code
def onDialogFinished(self):
self.state += 1
if self.state == 1: # makes sure we open te dialog only twice, but you can change the logic as you like with the state variable
self.openDialog()
app = QApplication(sys.argv)
window = MainWindow()
window.show()
window.openDialog()
app.exec()
I believe that the following solution should also work but it is ugly for three reasons. 1) Calling dialog's exec() before application's exec(). 2) Using too much exec(), which is not a good practice. exec() should be avoided if possible. 3) Containing an unnecessary recursion... None of these three objections are I believe wrong, they are just ugly and I do not like them.
requestDialogSignal = QtCore.pyqtSignal()
class MainWindow(QMainWindow):
def __init__(self):
self.state = 0 # a state variable which can be used to define the dialog opening logic
...
def execDialog(self):
mbox = QtWidgets.QMessageBox(QtWidgets.QMessageBox.NoIcon, "title", "message", self)
mbox.setStandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
mbox.exec() # blocks the code until closed
self.state += 1
if self.state == 1: # makes sure we open te dialog only twice, but you can change the logic as you like with the state variable
self.execDialog()
app = QApplication(sys.argv)
window = MainWindow()
window.show()
window.execDialog()
app.exec()
What I find strange about you code is that you show the same dialog twice. Is this really what you want? I guess you actually want to show two different dialogs... and in that case the code would look different. But anyway, I have shown you how to avoid threads and how to properly use (or avoid) exec(). I would much vote for using the first of my examples rather than the second.

PyQt WebEngineView interferes with MainMenu

I'm trying to create an application that contains a web browser within it, but when I add the web browser my menu bar visually disappears but functionally remains in place. The following are two images, one showing the "self.centralWidget(self.web_widget)" commented out, and the other allows that line to run. If you run the example code, you will also see that while visually the entire web page appears as if the menu bar wasn't present, you have to click slightly below each entry field and button in order to activate it, behaving as if the menu bar was in fact present.
Web Widget Commented Out
Web Widget Active
Example Code
import os
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtWebEngineWidgets import *
class WebPage(QWebEngineView):
def __init__(self, parent=None):
QWebEngineView.__init__(self)
self.current_url = ''
self.load(QUrl("https://facebook.com"))
self.loadFinished.connect(self._on_load_finished)
def _on_load_finished(self):
print("Url Loaded")
class MainWindow(QMainWindow):
def __init__(self, parent=None):
# Initialize the Main Window
super(MainWindow, self).__init__(parent)
self.create_menu()
self.add_web_widet()
self.show()
def create_menu(self):
''' Creates the Main Menu '''
self.main_menu = self.menuBar()
self.main_menu_actions = {}
self.file_menu = self.main_menu.addMenu("Example File Menu")
self.file_menu.addAction(QAction("Testing Testing", self))
def add_web_widet(self):
self.web_widget = WebPage(self)
self.setCentralWidget(self.web_widget)
if __name__ == "__main__":
app = QApplication(sys.argv)
main_window = MainWindow()
main_window.showMaximized()
sys.exit(app.exec_()) # only need one app, one running event loop
Development Environment
Windows 10, PyQt5, pyqt5-5.9
EDIT
The problem doesn't seem to be directly related to the menu bar. Even removing the menu bar the issue still occurs. That said, changing from showMaximized() to showFullScreen() does seem to solve the problem.
I no longer believe this is an issue with PyQt5 specifically but rather a problem with the graphics driver. Specifically, if you look at Atlassian's HipChat application it has a similar problem which is documented here:
https://jira.atlassian.com/browse/HCPUB-3177
Some individuals were able to solve the problem by running the application from the command prompt with the addendum "--disable-gpu" but that didn't work for my python application. On the other hand, rolling back the Intel(R) HD Graphics Driver did solve my problem. Version 21.20.16.4627 is the one that seems to be causing problems.

In PyQt, how does one get a shared menu and toolbar to talk to the currently active subwindow?

I have an application which has a main window, which can have multiple subwindows. I would like to have one set of QActions in the main window that interact with the currently selected window. For example, the application might be a text editor, and clicking file->save should save the text file the user is currently working on. Additionally, some QActions are checkable, so their checked state should reflect the state of the currently active window.
Here is a minimum working example that has the basic functionality I want, but I suspect there is a better way to do it (further discussion below the code).
import sys
import PyQt4.QtGui as QtGui
class DisplayWindow(QtGui.QWidget):
def __init__(self, parent=None, name="Main Window"):
# run the initializer of the class inherited from
super(DisplayWindow, self).__init__()
self.myLayout = QtGui.QFormLayout()
self.FooLabel = QtGui.QLabel(self)
self.FooLabel.setText(name)
self.myLayout.addWidget(self.FooLabel)
self.setLayout(self.myLayout)
self.is_foo = False
def toggle_foo(self):
self.is_foo = not self.is_foo
if self.is_foo:
self.FooLabel.setText('foo')
else:
self.FooLabel.setText('bar')
class WindowActionMain(QtGui.QMainWindow):
def __init__(self):
super(WindowActionMain, self).__init__()
self.fooAction = QtGui.QAction('Foo', self)
self.fooAction.triggered.connect(self.set_foo)
self.fooAction.setCheckable(True)
menubar = self.menuBar()
fileMenu = menubar.addMenu('&File')
fileMenu.addAction(self.fooAction)
self.toolbar = self.addToolBar('File')
self.toolbar.addAction(self.fooAction)
self.centralZone = QtGui.QMdiArea()
self.centralZone.subWindowActivated.connect(
self.update_current_window)
self.setCentralWidget(self.centralZone)
self.create_dw("Window 1")
self.create_dw("Window 2")
def create_dw(self, name):
dw = DisplayWindow(name=name)
self.centralZone.addSubWindow(dw)
dw.show()
def update_current_window(self):
""" redirect future actions to affect the newly selected window,
and update checked statuses to reflect state of selected window"""
current_window = self.centralZone.activeSubWindow()
if current_window:
self.current_dw = self.centralZone.activeSubWindow().widget()
self.fooAction.setChecked(self.current_dw.is_foo)
def set_foo(self):
self.current_dw.toggle_foo()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
ex = WindowActionMain()
ex.show()
sys.exit(app.exec_())
My actual version of DisplayWindow could be useful in many different projects, and I want to package it up so that you don't have to add a lot of code to the main window to use it. Therefore, DisplayWindow, all of its functionality and a list of available actions should be in one module, which would be imported in WindowActionMain's module. I should then be able to add more actions for DisplayWindow without changing any code in WindowActionMain. In particular, I don't want to have to write a little function like WindowActionMain.set_foo(self) just to redirect each action to the right place.
Yes, this is possible by handling the QMenu's aboutToShow signal
and considering the QGuiApplication's focusWindow (or however you get that in Qt4).
Example below shows a generic 'Window' menu acting on the frontmost window.
http://doc.qt.io/qt-4.8/qmenu.html#aboutToShow
http://doc.qt.io/qt-5/qguiapplication.html#focusWindow
def on_windowMenu_aboutToShow(self):
self.windowMenu.clear()
self.newWindowAction = QtWidgets.QAction(self)
self.newWindowAction.setShortcut("Ctrl+n")
self.newWindowAction.triggered.connect(self.on_newWindowAction)
self.newWindowAction.setText("New Window")
self.windowMenu.addAction(self.newWindowAction)
self.windowMenu.addSeparator()
playerWindows = [w for w in self.topLevelWindows() if w.type()==QtCore.Qt.Window and w.isVisible()]
for i, w in enumerate(playerWindows):
def action(i,w):
a = QtWidgets.QAction(self)
a.setText("Show Window {num} - {title}".format(num=i+1, title=w.title()))
a.triggered.connect(lambda : w.requestActivate())
a.triggered.connect(lambda : w.raise_())
self.windowMenu.addAction(a)
action(i,w)
self.windowMenu.addSeparator()
self.closeWindowAction = QtWidgets.QAction(self)
self.closeWindowAction.setShortcut("Ctrl+w")
self.closeWindowAction.triggered.connect(lambda : self.focusWindow().close())
self.closeWindowAction.setText("Close")
self.windowMenu.addAction(self.closeWindowAction)

QDialog - Prevent Closing in Python and PyQt

I have a login screen dialog written using pyqt and python and it shows a dialog pup up when it runs and you can type in a certin username and password to unlock it basicly. It's just something simple I made in learning pyqt. I'm trying to take and use it somewhere else but need to know if there is a way to prevent someone from using the x button and closing it i would like to also have it stay on top of all windows so it cant be moved out of the way? Is this possible? I did some research and couldn't find anything that could help me.
Edit:
as requested here is the code:
from PyQt4 import QtGui
class Test(QtGui.QDialog):
def __init__(self):
QtGui.QDialog.__init__(self)
self.textUsername = QtGui.QLineEdit(self)
self.textPassword = QtGui.QLineEdit(self)
self.loginbuton = QtGui.QPushButton('Test Login', self)
self.loginbuton.clicked.connect(self.Login)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.textUsername)
layout.addWidget(self.textPassword)
layout.addWidget(self.loginbuton)
def Login(self):
if (self.textUsername.text() == 'Test' and
self.textPassword.text() == 'Password'):
self.accept()
else:
QtGui.QMessageBox.warning(
self, 'Wrong', 'Incorrect user or password')
class Window(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
if Test().exec_() == QtGui.QDialog.Accepted:
window = Window()
window.show()
sys.exit(app.exec_())
Bad news first, it is not possible to remove the close button from the window, based on the Riverbank mailing system
You can't remove/disable close button because its handled by the
window manager, Qt can't do anything there.
Good news, you can override and ignore, so that when the user sends the event, you can ignore or put a message or something.
Read this article for ignoring the QCloseEvent
Also, take a look at this question, How do I catch a pyqt closeEvent and minimize the dialog instead of exiting?
Which uses this:
class MyDialog(QtGui.QDialog):
# ...
def __init__(self, parent=None):
super(MyDialog, self).__init__(parent)
# when you want to destroy the dialog set this to True
self._want_to_close = False
def closeEvent(self, evnt):
if self._want_to_close:
super(MyDialog, self).closeEvent(evnt)
else:
evnt.ignore()
self.setWindowState(QtCore.Qt.WindowMinimized)
You can disable the window buttons in PyQt5.
The key is to combine it with "CustomizeWindowHint",
and exclude the ones you want to be disabled.
Example:
#exclude "QtCore.Qt.WindowCloseButtonHint" or any other window button
self.setWindowFlags(
QtCore.Qt.Window |
QtCore.Qt.CustomizeWindowHint |
QtCore.Qt.WindowTitleHint |
QtCore.Qt.WindowMinimizeButtonHint
)
Result with QDialog:
Reference: https://doc.qt.io/qt-5/qt.html#WindowType-enum
Tip: if you want to change flags of the current window, use window.show()
after window.setWindowFlags,
because it needs to refresh it, so it calls window.hide().
Tested with QtWidgets.QDialog on:
Windows 10 x32,
Python 3.7.9,
PyQt5 5.15.1
.
I don't know if you want to do this but you can also make your window frameless. To make window frameless you can set the window flag equal to QtCore.Qt.FramelessWindowHint

My QFileSystemModel doesn't work as expected in PyQt

EDIT2: model.hasChildren(parentIndex) returns True, but model.rowCount(parentIndex) returns 0. Is QFileSystemModel just fubar in PyQt?
EDIT: With a bit of adaptation this all works exactly as it should if I use QDirModel. This is deprecated, but maybe QFileSystemModel hasn't been fully implemented in PyQt?
I'm learning the Qt Model/View architecture at the moment, and I've found something that doesn't work as I'd expect it to. I've got the following code (adapted from Qt Model Classes):
from PyQt4 import QtCore, QtGui
model = QtGui.QFileSystemModel()
parentIndex = model.index(QtCore.QDir.currentPath())
print model.isDir(parentIndex) #prints True
print model.data(parentIndex).toString() #prints name of current directory
rows = model.rowCount(parentIndex)
print rows #prints 0 (even though the current directory has directory and file children)
The question:
Is this a problem with PyQt, have I just done something wrong, or am I completely misunderstanding QFileSystemModel? According to the documentation, model.rowCount(parentIndex) should return the number of children in the current directory. (I'm running this under Ubuntu with Python 2.6)
The QFileSystemModel docs say that it needs an instance of a Gui application, so I've also placed the above code in a QWidget as follows, but with the same result:
import sys
from PyQt4 import QtCore, QtGui
class Widget(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
model = QtGui.QFileSystemModel()
parentIndex = model.index(QtCore.QDir.currentPath())
print model.isDir(parentIndex)
print model.data(parentIndex).toString()
rows = model.rowCount(parentIndex)
print rows
def main():
app = QtGui.QApplication(sys.argv)
widget = Widget()
widget.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
I've solved it.
The reason to use QFileSystemModel as opposed to QDirModel is because QFileSystemModel loads the data from the filesystem in a separate thread. The problem with that is that if you try to print the number of children just after it's been constructed is that it won't have loaded the children yet. The way to fix the above code is to add the following:
self.timer = QtCore.QTimer(self)
self.timer.singleShot(1, self.printRowCount)
to the end of the constructor, and add a printRowCount method which will print the correct number of children. Phew.
Since you've already figured it out, just a couple of extra thoughts on what was going on with your model: QFileSystemModel::rowCount returns rows from the visibleChildren collection; I guess you're correctly identified the problem: at the time when you're checking row count it was not yet populated. I've changed your example without using timers; pls, check if it works for you:
class Widget(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.model = QtGui.QFileSystemModel()
self.model.setRootPath(QtCore.QDir.currentPath())
def checkParent(self):
parentIndex = self.model.index(QtCore.QDir.currentPath())
print self.model.isDir(parentIndex)
print self.model.data(parentIndex).toString()
rows = self.model.rowCount(parentIndex)
print "row count:", rows
def main():
app = QtGui.QApplication(sys.argv)
widget = Widget()
widget.show()
app.processEvents(QtCore.QEventLoop.AllEvents)
widget.checkParent()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
I believe your code should work correctly on any UI event after widget constructed is shown on the screen
regards

Categories

Resources