I have QSpinBox's Signal valueChanged connected to a QWidget's function like:
class MyWidget(QtGui.QWidget):
def __init__(self, *args):
QtGui.QWidget.__init__(self, *args)
#just an example
mySpinBox = QtGui.QSpinBox()
mySpinBox.valueChanged.connect(self.foo)
def foo(self):
if value was changed by Mouse Wheel:
#do this
else:
#do nothing
Derive QSpinBox and override the wheelevent
http://pyqt.sourceforge.net/Docs/PyQt4/qwidget.html#wheelEvent
You can define your own signal which will be emitted by the wheelevent to obtain the behavior you want to have.
For an example / tutorial, see:
http://pythoncentral.io/pysidepyqt-tutorial-creating-your-own-signals-and-slots/
Based on Gombat's answer and this question (because how it is explained in provided tutorial it wil raise an error) i did this in the widget
class MyWidget(QtGui.QWidget):
mySignal1 = QtCore.pyqtSignal() #these have to be here or...
mySignal2 = QtCore.pyqtSignal() #...there will be an attribute error
def __init__(self, *args):
QtGui.QWidget.__init__(self, *args)
#just an example
self.mySpinBox1 = QtGui.QSpinBox()
self.mySpinBox2 = QtGui.QSpinBox()
self.mySpinBox1.installEventFilter(self)
self.mySpinBox2.installEventFilter(self)
self.mySignal1.connect(self.foo)
self.mySignal2.connect(self.bar)
def eventFilter(self, source, event):
if source is self.spinBox1:
if event.type() == QtCore.QEvent.Wheel:
self.spinBox1.wheelEvent(event)
self.mySignal1.emit()
return True
else:
return False
elif source is self.spinBox2:
if event.type() == QtCore.QEvent.Wheel:
self.spinBox2.wheelEvent(event)
self.mySignal2.emit()
return True
else:
return False
else:
return QtGui.QWidget.eventFilter(self, source, event)
def foo(self):
#right here, when a mouse wheel event occured in spinbox1
def bar(self):
#right here, when a mouse wheel event occured in spinbox2
hope this helps. thanks (untested, because it is just an example, there might be errors)
Related
I want to display a pandas.DataFrame content as a table using QTableView . I have a view-model which sub-classes QAbstractTableModel. This works fine.
I face problem when user updates the DataFrame contents (The application lets user to script and user can access the DataFrame directly and update it). I want to show the updated values in the GUI. I can do this by emitting the dataChanged signal. But there are so many methods to update an DataFrame that I am wondering why not have the GUI always display the contents of the DataFrame.
Is there any switch to do this? Or any other widget ?
I can register a QTimer and emit dataChanged every 1 second - Is there any better way to do this.
I think the equivalent model view classes in Android do similar to what I think.
Here is a minimal code. Here pressing the button updates the data-model,
but the view will continue to show the cached value. If I emit data-changed signal after the update, it would show the updated value. But I wan to avoid this if possible and want the view to show the data always from the DataFrame :
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWidgets import QTableView
from PyQt5.QtWidgets import QVBoxLayout
from PyQt5.QtWidgets import QPushButton
from PyQt5.QtWidgets import QWidget
import pandas as pd
from PyQt5.QtCore import QAbstractTableModel
from PyQt5.QtCore import Qt
class DFTableModel(QAbstractTableModel):
def __init__(self, df):
super().__init__()
self.df = df
def rowCount(self, parent):
return len(self.df)
def columnCount(self, parent):
return self.df.columns.size + 1
def data(self, index, role):
if index.isValid():
if role == Qt.DisplayRole:
if index.column() == 0:
return self.df.index[index.row()]
return str(self.df.values[index.row()][index.column() - 1])
def headerData(self, col, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
if col == 0:
return 'index'
return self.df.columns[col - 1]
return None
def setData(self, index, value, role=Qt.EditRole):
k = self.df.columns[index.column() - 1]
self.df.set_value(index.row(), k, value)
return True
def flags(self, index):
if index.column() > 0:
return Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable
return Qt.ItemIsEnabled | Qt.ItemIsSelectable
def main():
app = QApplication([])
table_view = QTableView()
df = pd.DataFrame({'a': [1, 2, 3]})
model = DFTableModel(df)
table_view.setModel(model)
table_view.show()
def on_button_click():
print("Updated data")
df.set_value(0, 'a', 33)
button = QPushButton('Update data')
button.clicked.connect(on_button_click)
vbox = QVBoxLayout()
vbox.addWidget(table_view)
vbox.addWidget(button)
main = QWidget()
main.setLayout(vbox)
main.show()
app.exec()
if __name__ == '__main__':
main()
I want to add custom rows in QFileSystemModel under QTreeview. The row is only added when the directory contains files with a certain extension. Basically, After starting up the directory listing, the user will click through the folders. As soon as the folder the user clicked contains the target file, I would like to hide these files (which I know how to do), then use custom rows to represent a summary of these files.
For example, if the folder contains files like the following
A.01.dat
A.02.dat
A.03.dat
...
B.01.dat
B.02.dat
B.03.dat
I would like to create custom rows:
A
B
However, if the folder clicked does not contain these .dat files, then no custom rows should be created.
I have also tried to insert rows directly into QFileSystemModel
self.treeivew.model = QtGui.QFileSystemModel()
...
for n, s in enumerate(self.sequence):
self.treeview.model.beginInsertRows(index, 0, 0)
result = self.treeview.model.insertRow(1, index)
print(result)
self.treeview.model.setData(index, QString(s['Name']),role=QtCore.Qt.DisplayRole)
self.treeview.model.endInsertRows()
But the insertion failed.
If reimplementation is necessary, as I have seen many places have suggested, can anyone provide a concrete example on how the reimplementation should be done to allow such conditional custom row insertion?
Thanks in advance.
I would implement an item model with dynamic child insertion. This is just a standard QAbstractItemModel with a few extra methods -
rowCount - you would normally implement this for a tree model anyway. Just make sure that it returns 0 if the node has children that have not been loaded yet.
hasChildren - override to return True for nodes that have children that haven't been loaded yet and return whatever the base class returns in all other cases.
canFetchMore - return True if the node has children that haven't been loaded yet, False otherwise.
fetchMore - this is where you perform whatever logic you need to decide what nodes to create and insert them into the model.
Here's the basic idea - for nodes that you know have children that haven't been loaded, return 0 from rowCount and True from canFetchMore and hasChildren. This tells Qt to show a node with an expander next to it even though it currently has no children. When the expander is clicked, fetchMore is called and you populate the children from the given parent.
One thing to note - you must call beginInsertRows and endInsertRows in the fetchMore method. What's more, you musn't change the underlying datastore before calling beginInsertRows or after endInsertRows. Unfortunately, you need to know how many rows you are inserting when you call beginInsertRows - so you are probably going to want to generate a list of nodes to add, then make the call to beginInsertRows. If you do it this way though, you can't set the new nodes' parent, as it would change the underlying datastore.
You can see in the code below, that I set the parent node in the Node.insert_child method which is called between the beginInsertRows and endInsertRows calls.
The code doesn't do exactly what you are after - it's a basic file system model illustrating dynamic loading, you'll need to insert you custom logic to generate the category nodes you want in the fetchMore call. It also only shows the filename and lacks icons.
If you want the modified date and size to be shown, you'll need to store these in the relevant nodes and set the model columnCount method to return the correct number of columns.
For icons, extend the model data method to check for the Qt.DecorationRole and return the relevant QIcon.
There might be some superfluous stuff in the code as it's a cut down and repurposed model from something else.
import sys
import os
import sip
sip.setapi('QVariant', 2)
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class Node(object):
def __init__(self, name, path=None, parent=None):
super(Node, self).__init__()
self.name = name
self.children = []
self.parent = parent
self.is_dir = False
self.path = path
self.is_traversed = False
if parent is not None:
parent.add_child(self)
def add_child(self, child):
self.children.append(child)
child.parent = self
def insert_child(self, position, child):
if position < 0 or position > self.child_count():
return False
self.children.insert(position, child)
child.parent = self
return True
def child(self, row):
return self.children[row]
def child_count(self):
return len(self.children)
def row(self):
if self.parent is not None:
return self.parent.children.index(self)
return 0
class FileSystemTreeModel(QAbstractItemModel):
FLAG_DEFAULT = Qt.ItemIsEnabled | Qt.ItemIsSelectable
def __init__(self, root, path='c:/', parent=None):
super(FileSystemTreeModel, self).__init__()
self.root = root
self.parent = parent
self.path = path
for file in os.listdir(path):
file_path = os.path.join(path, file)
node = Node(file, file_path, parent=self.root)
if os.path.isdir(file_path):
node.is_dir = True
def getNode(self, index):
if index.isValid():
return index.internalPointer()
else:
return self.root
## - dynamic row insertion starts here
def canFetchMore(self, index):
node = self.getNode(index)
if node.is_dir and not node.is_traversed:
return True
return False
## this is where you put custom logic for handling your special nodes
def fetchMore(self, index):
parent = self.getNode(index)
nodes = []
for file in os.listdir(parent.path):
file_path = os.path.join(parent.path, file)
node = Node(file, file_path)
if os.path.isdir(file_path):
node.is_dir = True
nodes.append(node)
self.insertNodes(0, nodes, index)
parent.is_traversed = True
def hasChildren(self, index):
node = self.getNode(index)
if node.is_dir:
return True
return super(FileSystemTreeModel, self).hasChildren(index)
def rowCount(self, parent):
node = self.getNode(parent)
return node.child_count()
## dynamic row insert ends here
def columnCount(self, parent):
return 1
def flags(self, index):
return FileSystemTreeModel.FLAG_DEFAULT
def parent(self, index):
node = self.getNode(index)
parent = node.parent
if parent == self.root:
return QModelIndex()
return self.createIndex(parent.row(), 0, parent)
def index(self, row, column, parent):
node = self.getNode(parent)
child = node.child(row)
if not child:
return QModelIndex()
return self.createIndex(row, column, child)
def headerData(self, section, orientation, role):
return self.root.name
def data(self, index, role):
if not index.isValid():
return None
node = index.internalPointer()
if role == Qt.DisplayRole:
return node.name
else:
return None
def insertNodes(self, position, nodes, parent=QModelIndex()):
node = self.getNode(parent)
self.beginInsertRows(parent, position, position + len(nodes) - 1)
for child in nodes:
success = node.insert_child(position, child)
self.endInsertRows()
return success
app = QApplication(sys.argv)
model = FileSystemTreeModel(Node('Filename'), path='c:/')
tree = QTreeView()
tree.setModel(model)
tree.show()
sys.exit(app.exec_())
I subclassed a QListWidget (named List) which has a pyqtSignal named dataChanged that is emitted in another baseclass DataWidget. Actually everything works, but
if the methods setForeground or setTooltip of an item (QListWidgetItem) in the List are called, I get this
TypeError: native Qt signal is not callable
message. Another pyqtSignal named itemLeft of the List class if emitted not in a baseclass but in the List class itself and does not have the problem.
So, what I'd like to know is:
Why does this message appear when calling a method of a listitem?
How come calling a method is related to that signal?!
What is wrong with the code? / What do I need to change?
Here is a MWE to reproduce it.
from __future__ import print_function
import sys
from PyQt4.QtGui import (QMainWindow, QApplication, QFormLayout, QListWidget,
QListWidgetItem, QColor)
from PyQt4.QtCore import pyqtSignal
class DataWidget(object):
""" A widget to drop data """
def dropEvent(self, event):
""" Emits the dataChanged signal """
# actions to be taken on drop
self.dataChanged.emit()
class List(QListWidget, DataWidget):
""" List widget used for, e. g. features """
# This signal makes no problems
itemLeft = pyqtSignal()
# but this one does
dataChanged = pyqtSignal()
def __init__(self, parent):
super(List, self).__init__(parent)
self.setAcceptDrops(True)
self.setMouseTracking(True)
self.setSortingEnabled(True)
def leaveEvent(self, event):
self.itemLeft.emit()
class ApplicationWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
# The ListWidget
self.list = List(self)
self.setCentralWidget(self.list)
self.list.itemLeft.connect(self.doOnItemLeft)
self.list.dataChanged.connect(self.doOnDrop)
# Adding an item to self.list
item = QListWidgetItem('List Item Name', self.list)
textcolor, tooltip = QColor(), None
textcolor.setRgb(50, 50, 115)
tooltip = 'Tool tip'
# Calling following methods gives causes
# TypeError: native Qt signal is not callable
print('calling: QListWidgetItem.setForeground() for', str(item.text()))
item.setForeground(textcolor)
print('calling: QListWidgetItem.setToolTip() for', str(item.text()))
item.setToolTip(tooltip)
def doOnItemLeft(self):
# make gui adaptions...
print('Widget left')
def doOnDrop(self):
# get data from widget and so on...
pass
app = QApplication(sys.argv)
win = ApplicationWindow()
win.show()
sys.exit(app.exec_())
Version
>>> PyQt4.pyqtconfig.Configuration().pyqt_version_str
'4.9.6'
>>> sys.version_info
sys.version_info(major=2, minor=7, micro=5, releaselevel='final', serial=0)
As Vahancho and Osterfeld identified, the issue is that QListWidget inherits from QAbstractItemView, which defines a protected slot called "dataChanged": the slot is called when items are changed in the model, thereby allowing the derived class (QListWidget in this case) to take action.
Hence creating a signal of the same name does not make sense. Calling methods setForeground or setTooltip on the list item causes the slot to be called, only the slot has been overridden by a signal, leading to the observed error.
I want to perform a sort of rows in QTableView, so that the underlying TableModel would have its data sorted, too:
If I'm not mistaken, built-in sorts in QTableView don't affect the order of rows in underlying TableModel, so I had to write a custom QTableView and custom QAbstractTableModel implementation of internal drag and drop.
To test, if it works at all, I respond to any drag of cell by reordering first and second rows:
import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class Model(QAbstractTableModel):
def __init__(self):
QAbstractTableModel.__init__(self, parent=None)
self.data = [("elem1", "ACDC"), ("elem2", "GUNSNROSES"), ("elem3", "UFO")]
self.setSupportedDragActions(Qt.MoveAction)
def flags(self, index):
if index.isValid():
return Qt.ItemIsDragEnabled | Qt.ItemIsDropEnabled | Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable
else:
return Qt.ItemIsDropEnabled | Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable
def rowCount(self, parent=QModelIndex()):
return len(self.data)
def columnCount(self, parent=QModelIndex()):
return 1
def data(self, index, role):
if role == Qt.DisplayRole:
print "row = %s" % int(index.row())
return QVariant(self.data[int(index.row())][1])
return QVariant()
def headerData(self, index, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return QVariant(str(index))
elif orientation == Qt.Vertical and role == Qt.DisplayRole:
return self.data[index][0]
def dragMoveEvent(self, event):
event.setDropAction(QtCore.Qt.MoveAction)
event.accept()
def moveRows(self, parent, source_first, source_last, parent2, dest):
print "moveRows called, self.data = %s" % self.data
self.beginMoveRows(parent, source_first, source_last, parent2, dest)
self.data = self.data[1] + self.data[0] + self.data[2]
self.endMoveRows()
print "moveRows finished, self.data = %s" % self.data
class View(QTableView):
def __init__(self, parent=None):
QTableView.__init__(self, parent=None)
self.setSelectionMode(self.ExtendedSelection)
self.setDragEnabled(True)
self.acceptDrops()
self.setDragDropMode(self.InternalMove)
self.setDropIndicatorShown(True)
def dragEnterEvent(self, event):
event.accept()
def dragMoveEvent(self, event):
event.accept()
def dropEvent(self, event):
print "dropEvent called"
point = event.pos()
self.model().moveRows(QModelIndex(), 0, 0, QModelIndex(), 1)
event.accept()
def mousePressEvent(self, event):
print "mousePressEvent called"
self.startDrag(event)
def startDrag(self, event):
print "startDrag called"
index = self.indexAt(event.pos())
if not index.isValid():
return
self.moved_data = self.model().data[index.row()]
drag = QDrag(self)
mimeData = QMimeData()
mimeData.setData("application/blabla", "")
drag.setMimeData(mimeData)
pixmap = QPixmap()
pixmap = pixmap.grabWidget(self, self.visualRect(index))
drag.setPixmap(pixmap)
result = drag.start(Qt.MoveAction)
class Application(object):
def __init__(self):
app = QApplication(sys.argv)
self.window = QWidget()
self.window.show()
layout = QVBoxLayout(self.window)
self.view = View()
self.view.setModel(Model())
layout.addWidget(self.view)
sys.exit(app.exec_())
For some reason, this code doesn't work. It successfully starts the drag (well, almost successfully, cause it shows the previous row, instead of the current one as the drag icon), invokes mousePressEvent, startDrag, dropEvent and moveRows function, but then dies within moveRows with message:
Qt has caught an exception thrown from an event handler. Throwing
exceptions from an event handler is not supported in Qt. You must
reimplement QApplication::notify() and catch all exceptions there.
Qt has caught an exception thrown from an event handler. Throwing
exceptions from an event handler is not supported in Qt. You must
reimplement QApplication::notify() and catch all exceptions there.
terminate called after throwing an instance of 'std::bad_alloc'
what(): std::bad_alloc
Aborted
(Duplication of paragraph in error message is intentional - that's what it outputs verbatim).
How do I debug this error? (inserting try-except in moveRows doesn't help)
Do you have a better recipe for performing internal drag-and-drops, affecting the model in tableviews?
You have several problems in your code I will address just two here:
You are calling Model.moveRows with the wrong arguments:
change self.model().moveRows(QModelIndex(), 0, 0, QModelIndex(), 1)
by self.model().moveRows(QModelIndex(), 1, 1, QModelIndex(), 0)
You are changing your data in the wrong way:
change self.data = self.data[1] + self.data[0] + self.data[2]
by self.data = [self.data[1], self.data[0] , self.data[2]]
Note: problem 1 is the one who is provoking the exception on your code. Also note that is a bad idea naming an instance variable and a function the same (Model.data)
I have a QListWidget with few elements. Moving through the list is done with the arrow keys.
How to make the "infinite loop", meaning that whan the last item is reached and you go down, the selection jumps to the first item, and reverse from first to last if you want to go up?
Here is part of the code for creating list widget:
self.listWidget = QtGui.QListWidget(Form)
self.listWidget.setFont(font)
self.listWidget.setFocusPolicy(QtCore.Qt.StrongFocus)
self.listWidget.setAutoFillBackground(True)
self.listWidget.setAlternatingRowColors(True)
self.listWidget.setWordWrap(True)
self.listWidget.setSelectionRectVisible(True)
self.listWidget.setObjectName("listWidget")
self.listWidget.hasFocus()
self.listWidget.itemActivated.connect(self.klik)
self.gridLayout.addWidget(self.listWidget, 0, 0, 1, 1)
In order to do such a circular list, you can subclass QListWidget, override its keyPressEvent() method and check whether you are in the first/last row before moving via setCurrentRow() if needed.
class CircularListWidget(QtGui.QListWidget):
"""
Circular ListWidget.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_Down:
if self.currentRow() == self.count()-1:
self.setCurrentRow(0)
return
elif event.key() == QtCore.Qt.Key_Up:
if self.currentRow() == 0:
self.setCurrentRow(self.count()-1)
return
# Otherwise, parent behavior
super().keyPressEvent(event)
Just replace your QListWidget by this CircularListWidget and it should work. I did not find any built-in method to do what you ask for, hence this workaround.