I want to enable/disable a checkable item in a QTreeWidget, when a specific signal is sent.
The following code dows not work:
model = QStandardItemModel()
view = QTreeView()
view.setModel(model)
rootItem = QStandardItem()
rootItem = model.invisibleRootItem()
categoryItem = QStandardItem(item)
categoryItem.setCheckable(True)
rootItem.appendRow(categoryItem)
signalSource.availabilityChanged.connect(categoryItem.setEnabled)
It produces the error:
TypeError: unhashable type: 'PySide.QtGui.QStandardItem'
Is there a solution for changing the state or data of a QStandardItem via signal/slot?
This looks like a bug in PySide, as connect should accept any callable (the example code works correctly in PyQt4).
As a workaround, try wrapping QStandardItem methods in a lambda:
signalSource.availabilityChanged.connect(
lambda enable: categoryItem.setEnabled(enable))
EDIT
To connect the items in a loop, use a default argument, like this:
for button in buttonList:
item = QStandardItem("Test")
...
button.toggled.connect(
lambda enable, item=item: item.setEnabled(enable))
With the help of ekhumoros answer, I found a way to get my problem solved, but it seems to be an ugly workaround in PySide, using the sender to get the signal hooked up correctly.
import sys
import argparse
import signal
#import sip
#sip.setapi('QString', 2)
#from PyQt4.QtGui import QApplication, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QStandardItemModel, QStandardItem, QTreeView
from PySide.QtGui import QApplication, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QStandardItemModel, QStandardItem, QTreeView
class MainWindow(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
buttonList = []
for i in xrange(10):
button = QPushButton("1")
button.setCheckable(True)
buttonList.append(button)
model = QStandardItemModel()
view = QTreeView()
view.setModel(model)
layout = QVBoxLayout()
self.setLayout(layout)
buttonLayout = QHBoxLayout()
layout.addLayout(buttonLayout)
for button in buttonList:
buttonLayout.addWidget(button)
layout.addWidget(view)
rootItem = QStandardItem()
rootItem = model.invisibleRootItem()
self.itemList = {}
for button in buttonList:
item = QStandardItem("Test")
item.setCheckable(True)
rootItem.appendRow(item)
self.itemList[button] = item
# Works with PyQt4, but not with PySide
#button.toggled.connect(item.setEnabled)
# Workaround for PySide
button.toggled.connect(self.workaround)
for button in buttonList:
button.setChecked(True)
def workaround(self, enable):
self.itemList[self.sender()].setEnabled(enable)
def main(argv):
app = QApplication(argv)
w = MainWindow()
w.show()
retcode = app.exec_()
if __name__ == "__main__":
main(sys.argv)
Just using a lambda construct didn't work in the loop. It just connect all the signal with the last reference to iterate over.
Related
I am using QSqlTableModel and QTableView to represent my database. In the last column, called "Validated" I have a value either 1 or 0 (this will be a checkbox, but for simplicity leaving it like this). If the value is 1, the whole row should turn green, otherwise left as normal colour.
The model works fine without reimplementing the data method. However, when implementing the data method as follows:
def data(self,index,role):
if (role == Qt.BackgroundRole) and (self.record(index.row()).value("VALIDATED") == 1):
return QColor(0,128,0)
return super().data(index, role)
The table is updated with the changes and the colouring works, but the database is now never updated with the changes. I have tried adding a datachanged signal as well, but no luck.
I have followed the format given by previous questions such as PyQt - trouble with reimplementing data method of QSqlTableModel. And no such luck.
Any help would be greatly appreciated.
See below for the whole simplified code:
import sys, logging, os
from PySide2.QtCore import Qt
from PySide2.QtSql import QSqlDatabase
from PySide2.QtWidgets import QApplication, QTabWidget, QVBoxLayout, QMainWindow, QAction, QPushButton, QWidget, QAbstractScrollArea, QTableView, QGridLayout
from PySide2.QtGui import QKeySequence, QColor
from PySide2.QtSql import QSqlTableModel
log = logging.getLogger("test-logger")
logging.basicConfig(level=os.environ.get("LOGLEVEL", "INFO"))
class SQLTableModel(QSqlTableModel):
def __init__(self, db_table):
QSqlTableModel.__init__(self)
self.setEditStrategy(QSqlTableModel.OnRowChange)
self.setTable(db_table)
self.select()
log.info(self.database())
def data(self,index,role):
if (role == Qt.BackgroundRole) and (self.record(index.row()).value("VALIDATED") == 1):
return QColor(0,128,0)
return super().data(index, role)
class TableWidget(QWidget):
def __init__(self, db_table, statusBar):
QWidget.__init__(self)
# Getting the Model
self.tabledb = db_table
self.model = SQLTableModel(db_table)
self.statusBar = statusBar
self.CreateUI()
def CreateUI(self):
# Creating a QTableView
self.table_view = QTableView()
self.table_view.setModel(self.model)
self.table_view.setSizeAdjustPolicy(QAbstractScrollArea.AdjustToContents)
self.table_view.setColumnHidden(0, True)
self.main_layout = QGridLayout()
self.main_layout.addWidget(self.table_view)
self.setLayout(self.main_layout)
self.table_view.show()
class MainWindow(QMainWindow):
''' Sets up main window ui, with multiple tabed tables'''
def __init__(self):
QMainWindow.__init__(self)
self.setWindowTitle("test")
self.db = QSqlDatabase.addDatabase("QSQLITE")
self.db.setDatabaseName("test")
self.db.open()
self.CreateUI()
self.show()
def CreateUI(self):
''' Create UI elements '''
##Setup database sheets
sheet_table = TableWidget("Product_Structure",self.statusBar)
self.setCentralWidget(sheet_table)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
Hi everyone so I was doing some exam prep with PYQT5 and I created an application from an exercise in my workbook where we have to make it display a list of courses and when you click them it opens a message box with the course name also there is a button so that the user can add a course to the list. The add button is suppoused to open a QlineEdit on the last item in the listWidget, so the user can edit the field however I keep getting a TypeError message:
line 67, in onAddButton
self.mylistWidget.openPersistentEditor(self, modelItem)
TypeError: openPersistentEditor(self, QListWidgetItem): argument 1 has unexpected type 'UNISACourses'
import sys
from PyQt5.QtWidgets import (QListWidget, QLineEdit, QWidget, QMessageBox, QHBoxLayout, QAbstractItemView,
QApplication, QVBoxLayout, QPushButton, QButtonGroup)
from PyQt5.QtCore import pyqtSlot
from PyQt5 import Qt, QtGui
class MyListWidget(QListWidget, QLineEdit, QWidget):
"""
Subclassed QListWidget to allow for the closing of open editors and other modifications
"""
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setSelectionMode(QAbstractItemView.ExtendedSelection)
def keyPressEvent(self, event):
if event.key() == Qt.Key_Return:
print("Closing any persistent editor")
self.closePersistentEditor(self.model().index(self.count() - 1))
else:
super().keyPressEvent(event)
class UNISACourses(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
# Main Window Settings
self.setGeometry(300, 300, 350, 250)
self.setWindowTitle('Courses')
# Layout
self.main_layout = QVBoxLayout(self)
self.setLayout(self.main_layout)
# Main Widget
self.mylistWidget = MyListWidget()
self.mylistWidget.addItems(["COS1511", "COS1521", "COS1512", "MNB1512", "INF1505", "FAC1502"])
self.main_layout.addWidget(self.mylistWidget)
# Define a layout for the other buttons to exist in for flexibility with resizing
self.btn_add = QPushButton("Add", clicked=self.onAddButton)
self.btn_delete = QPushButton("Delete", clicked=self.onDeleteButton)
hbox = QHBoxLayout()
hbox.addWidget(self.btn_add)
hbox.addWidget(self.btn_delete)
self.main_layout.addLayout(hbox)
# Define any additional behavior of the list
self.mylistWidget.itemDoubleClicked.connect(self.onClicked)
def onClicked(self, item):
QMessageBox.information(self, "Info", item.text())
#pyqtSlot()
def onAddButton(self):
"""
Opens a QLineEdit editor on the last item in the listwidget, allowing the user to edit the field.
NOTE: The user must click outside of the editor field then press Enter in order to close the editor
"""
self.mylistWidget.addItem('')
modelItem = self.mylistWidget.model().index(self.mylistWidget.count() - 1)
self.mylistWidget.openPersistentEditor(self, modelItem)
#pyqtSlot()
def onDeleteButton(self):
for item in self.mylistWidget.selectedItems():
self.mylistWidget.takeItem(self.mylistWidget.row(item))
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = UNISACourses()
ex.show()
sys.exit(app.exec_())
You are passing two incorrect arguments (self and a QModelIndex) to QListWidget.openPersistentEditor which accepts one QListWidgetItem. Use the QListWidget.item method to get the item. You can also add QListWidget.setCurrentItem so it gets selected right away and ready to edit.
def onAddButton(self):
self.mylistWidget.addItem('')
modelItem = self.mylistWidget.item(self.mylistWidget.count() - 1)
self.mylistWidget.openPersistentEditor(modelItem)
self.mylistWidget.setCurrentItem(modelItem)
Same fix here:
def keyPressEvent(self, event):
if event.key() == Qt.Key_Return:
print("Closing any persistent editor")
self.closePersistentEditor(self.item(self.count() - 1))
else:
super().keyPressEvent(event)
Also the Qt Namespace class for Qt.Key_Return is inside the QtCore Module.
from PyQt5.QtCore import pyqtSlot, Qt
from PyQt5 import QtGui
I am trying to figure out how to select and edit newly created folder. Here is a bit of code demonstrating it:
import os
import sys
from PyQt5.QtWidgets import (QApplication,
QMainWindow,
QLabel,
QLineEdit,
QPushButton,
QShortcut,
QFileSystemModel,
QTreeView,
QWidget,
QVBoxLayout,
QHBoxLayout,
QLayout,
QMenu,
QPlainTextEdit,
QSizePolicy,
QMessageBox,
QAbstractItemView)
from PyQt5.QtCore import QSize, Qt, QRect
from PyQt5.QtGui import QKeySequence
class FSView(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setFixedSize(size.width()*1/4, size.height()*0.85)
self.model = QFileSystemModel()
self.model.setRootPath('')
self.model.setReadOnly(False)
self.tree = QTreeView()
self.tree.setContextMenuPolicy(Qt.CustomContextMenu)
self.tree.customContextMenuRequested.connect(self.openMenu)
self.tree.setModel(self.model)
self.tree.setAnimated(False)
self.tree.setIndentation(20)
self.tree.setDragDropMode(QAbstractItemView.InternalMove)
windowLayout = QVBoxLayout()
windowLayout.addWidget(self.tree)
self.setLayout(windowLayout)
def openMenu(self, position):
menu = QMenu()
menu.addAction('New folder', self.NewF)
menu.exec_(self.tree.viewport().mapToGlobal(position))
def NewF(self):
d = str(self.model.filePath(self.tree.currentIndex())) + '/New folder'
if not os.path.exists(d):
os.mkdir(d)
# correctIndex = self.tree.currentIndex() + 1 #not working
# self.tree.edit(correctIndex)
if __name__ == '__main__':
app = QApplication(sys.argv)
screen = app.primaryScreen()
size = screen.size()
ex = FSView()
ex.show()
sys.exit(app.exec_())
After creating the new folder, I would like it to be selected and in edit mode at same time (i.e.: self.tree.edit(correctIndex)).
I have checked some posts (here) but still have not managed to get the correct index.
Thanks for suggestions.
Using your code you must first obtain the QModelIndex using the index() method of QFileSystemModel passing it the path, and then call the setCurrentIndex() and edit() methods of QTreeView.
def NewF(self):
d = str(self.model.filePath(self.tree.currentIndex())) + '/New folder'
if not os.path.exists(d):
os.mkdir(d)
ix = self.model.index(d)
QTimer.singleShot(0, lambda ix=ix: self.tree.setCurrentIndex(ix))
QTimer.singleShot(0, lambda ix=ix: self.tree.edit(ix))
Or use the mkdir() method of QFileSystemModel as shown below:
def NewF(self):
ci = self.tree.currentIndex()
ix = self.model.mkdir(ci, "New folder")
QTimer.singleShot(0, lambda ix=ix : self.tree.setCurrentIndex(ix))
QTimer.singleShot(0, lambda ix=ix : self.tree.edit(ix))
There has been a small problem with a little project of mine using PyQt5. I tried to add a random QWidget (in this example a QPushbutton) to a custom QWidget. However, I don't understand the behavior of the "setParent" function. When I use it outside of the custom QWidget, the QPushButton is displayed. When I use it in a declared function of the custom Widget, the QPushButton is occluded and I have no chance of displaying it outside of adding a layout (which I don't want). Here an example of the source code:
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
class customWidget(QWidget):
def __init__(self):
super().__init__()
self.addButton()
def addButton(self):
button = QPushButton('not_showing')
button.setParent(self)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = QWidget()
button = QPushButton('showing')
button.setParent(w)
button.move(50,50)
w.resize(600,600)
w.move(1000,300)
w.setWindowTitle('Simple')
w.show()
sys.exit(app.exec_())
There is no change, when adding the parent during the initialization of the QPushButton.
When the function addButton exits, the button is removed.
If you want to see the button, try this:
class customWidget(QWidget):
def __init__(self):
super().__init__()
self.addButton()
self.button = None
def addButton(self):
if self.button is None:
self.button = QPushButton('not_showing')
self.button.setParent(self)
You do not have this problem in the main function because this function does not return until the application is stopped.
EDIT: The comment was right, but you also missed some arguments. This will work
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys
class customWidget(QWidget):
def __init__(self, parent=None):
super(customWidget, self).__init__(parent)
self.addButton()
def addButton(self):
button = QPushButton('not_showing')
button.setParent(self)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = customWidget()
button = QPushButton('showing')
button.setParent(w)
button.move(50,50)
w.resize(600,600)
w.move(1000,300)
w.setWindowTitle('Simple')
w.show()
sys.exit(app.exec_())
One day old in terms of experience with PyQT, I followed sample code HERE to do this, but I am clueless as to how I could separate the start download part from the start GUI part, so that I can instead start that when I press the OK (startBtn)button. Also, know the command I do doesn't do anything but give you an error, but I know that works.
Any help appreciated!
from PyQt5.QtWidgets import QApplication, QWidget, QMainWindow, QAction, qApp, QDesktopWidget, QPushButton, QHBoxLayout, QVBoxLayout, QTextEdit
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import QThread, QProcess
import sys
class GUI(QProcess):
def __init__(self):
super().__init__()
# Create an instance variable here (of type QTextEdit)
startBtn = QPushButton('OK')
stopBtn = QPushButton('Cancel')
#startBtn.clicked.connect()
stopBtn.clicked.connect(qApp.exit)
self.hbox = QHBoxLayout()
self.hbox.addStretch(1)
self.hbox.addWidget(startBtn)
self.hbox.addWidget(stopBtn)
self.edit = QTextEdit()
self.edit.setWindowTitle("QTextEdit Standard Output Redirection")
self.vbox = QVBoxLayout()
self.vbox.addStretch(1)
self.vbox.addWidget(self.edit)
self.vbox.addLayout(self.hbox)
#setLayout(self.vbox)
self.central=QWidget()
#self.vbox.addWidget(self.edit)
self.central.setLayout(self.vbox)
self.central.show()
def readStdOutput(self):
self.edit.append(str(self.readAllStandardOutput()))
def main():
app = QApplication(sys.argv)
qProcess = GUI()
qProcess.setProcessChannelMode(QProcess.MergedChannels);
qProcess.start("youtube-dl")
qProcess.readyReadStandardOutput.connect(qProcess.readStdOutput);
return app.exec_()
if __name__ == '__main__':
main()
2 notes:
If you also know how to disable the OK button when you press it, until the process is finished, then I'd love to know.
Not all imports are used, but I can clean that later. PyCharm show which is used and not. Cleanup is for later.
To do what you ask you have to have some considerations:
youtube-dl requires parameters, like the url, for this I have placed a QLineEdit.
To know when the process starts and ends, we use the signal: stateChanged(newState)
Complete code:
import sys
from PyQt5.QtCore import QProcess
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QHBoxLayout, QVBoxLayout, QTextEdit, QLabel, QLineEdit
class GUI(QProcess):
def __init__(self, parent=None):
super(GUI, self).__init__(parent=parent)
# Create an instance variable here (of type QTextEdit)
self.startBtn = QPushButton('OK')
self.stopBtn = QPushButton('Cancel')
self.hbox = QHBoxLayout()
self.hbox.addStretch(1)
self.hbox.addWidget(self.startBtn)
self.hbox.addWidget(self.stopBtn)
self.label = QLabel("Url: ")
self.lineEdit = QLineEdit()
self.lineEdit.textChanged.connect(self.EnableStart)
self.hbox2 = QHBoxLayout()
self.hbox2.addWidget(self.label)
self.hbox2.addWidget(self.lineEdit)
self.edit = QTextEdit()
self.edit.setWindowTitle("QTextEdit Standard Output Redirection")
self.vbox = QVBoxLayout()
self.vbox.addStretch(1)
self.vbox.addLayout(self.hbox2)
self.vbox.addWidget(self.edit)
self.vbox.addLayout(self.hbox)
self.central = QWidget()
self.central.setLayout(self.vbox)
self.central.show()
self.startBtn.clicked.connect(self.startDownload)
self.stopBtn.clicked.connect(self.kill)
self.stateChanged.connect(self.slotChanged)
self.EnableStart()
def slotChanged(self, newState):
if newState == QProcess.NotRunning:
self.startBtn.setDisabled(False)
elif newState == QProcess.Running:
self.startBtn.setDisabled(True)
def startDownload(self):
self.start("youtube-dl", [self.lineEdit.text()])
def readStdOutput(self):
self.edit.append(str(self.readAllStandardOutput()))
def EnableStart(self):
self.startBtn.setDisabled(self.lineEdit.text() == "")
def main():
app = QApplication(sys.argv)
qProcess = GUI()
qProcess.setProcessChannelMode(QProcess.MergedChannels)
qProcess.readyReadStandardOutput.connect(qProcess.readStdOutput)
return app.exec_()
if __name__ == '__main__':
main()
Screenshot: