I am trying to embed a button per row inside a tableview. My botton are drawing correctly but are not reacting to any clicks.. Should I be setting flags for this column? so far I have something like:
if index.column() == 14:
flags |= QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsUserCheckable | Qt.ItemIsEnabled
return flags
And this is the delegate:
class AButton(QtGui.QStyledItemDelegate):
mouse_isPressed = False
def __init__(self, parent = None):
QtGui.QStyledItemDelegate.__init__(self, parent)
def boundingRect(self):
return QtCore.QRectF(0, 0, 40, 40)
def paint(self, painter, option, widget = 0):
opt = QtGui.QStyleOptionButton()
opt.state = ((QtGui.QStyle.State_Sunken if self.mouse_isPressed else QtGui.QStyle.State_Raised) | QtGui.QStyle.State_Enabled)
opt.text = self.text()
opt.icon = self.icon()
opt.rect = option.rect
opt.palette = option.palette
QtGui.QApplication.style().drawControl(QtGui.QStyle.CE_PushButton, opt, painter)
def text(self):
return QtCore.QString("hi")
def icon(self):
return QtGui.QIcon()
def mousePressEvent(self, event):
self.mouse_isPressed = True
print "HELLO"
self.update()
def mouseReleaseEvent(self, event):
self.mouse_isPressed = False
self.update()
Is there any example out there I could look at?
thanks in advance,
Cris
Qt delegates doesn't provide specific events handlers (like mousePressEvent, mouseReleaseEvent, ...) as QWidget does.
If you want to react to user actions you should reimplement the editorEvent method.
By the way, there is no such 'update' method defined in QStyledItemDelegate
Related
I have a scene with QGraphicsPixmapItem, and show a QGraphicScene object like a rectangle.
def setupUI(self):
self.pixmap01 = QPixmap.fromImage(qImg)
self.mainpic = QGraphicsPixmapItem(self.pixmap01)
self.scene1.addItem(self.mainpic)
self.graphicsViewScene1.setScene(self.scene1)
self.scene1.update()
def DrawRectangle(self):
scene = RectangleScene()
scene.addItem(self.mainpic)
self.graphicsViewScene1.setScene(scene)
Now i a draw rectangle and top of a scene(this part of code is fine) but after right click on rect and choose replace pic item(option menu) i want to replace this selected area of QPixmap with another QPixmap(for this action i use Qpainter but nothing happens)
class RectangleScene(QGraphicsScene):
def __init__(self, *args, **kwargs):
super(RectangleScene, self).__init__(*args, **kwargs)
def mousePressEvent(self, event):
self.clean_scene()
self.start_point = event.scenePos()
self.end_point =self.start_point
self.graphics_line = QGraphicsRectItem(QRectF(self.start_point, self.end_point))
self.update_path()
def mouseMoveEvent(self, event):
self.end_point = event.scenePos()
self.update_path()
def mouseReleaseEvent(self, event):
self.end_point = event.scenePos()
self.update_path()
def update_path(self):
self.graphics_line.setRect(QRectF(self.start_point, self.end_point))
self.addItem(self.graphics_line)
def contextMenuEvent(self, event):
menu = QtWidgets.QMenu()
f1 = menu.addAction("repalce pic")
if action == f1:
tmp_rect = QRectF(self.start_point, self.end_point)
new_pix_map = QPixmap.fromImage(qImg)
painter = QPainter(self.imageProcessing.pixmap01)
painter.drawPixmap(tmp_rect,new_pix_map ,tmp_rect)
in another part of code after select this area, get a ndarray of this area and add some filters then i want to add top of this rect
I have a custom QGraphicsItem on a QgraphicsScene. And I'm trying to draw on QGraphicsItem following mouse events. Here is what I implemented:
The custom QGraphicsItem in implemented in GraphicsItem_custom.py
from PySide2.QtWidgets import QGraphicsItem
from PySide2.QtCore import QPointF, QRectF, Qt, QRect, QPoint
from PySide2.QtGui import QPen, QPainter, QPixmap
class GraphicsItem_custom(QGraphicsItem):
def __init__(self,*args, **kwargs):
super().__init__(*args, **kwargs)
self.start, self.end = QPoint(), QPoint()
self.rectangles = []
def boundingRect(self):
return QRectF(0, 0, 500, 300)
def paint(self, painter, option, widget):
painter.setPen(QPen(Qt.red, 6, Qt.SolidLine))
painter.drawText(QPointF(0, 10), "Hiya")
painter.drawRect(self.boundingRect())
for rectangle in self.rectangles:
painter.drawRect(rectangle)
if not self.start.isNull() and not self.end.isNull() and self.start != self.end:
rect = QRect(self.start, self.end)
print("paintEvent start: " + str(self.start) + ", end: " + str(self.end))
print("")
painter.drawRect(rect.normalized())
def mousePressEvent(self, event):
super().mousePressEvent(event)
print("Pressed")
if event.buttons() and Qt.LeftButton:
self.start = event.pos()
self.end = self.start
self.update()
print("left button")
def mouseMoveEvent(self, event):
print("Move")
super().mouseMoveEvent(event)
if event.buttons() and Qt.LeftButton:
self.end = event.pos()
self.update()
def mouseReleaseEvent(self, event):
super().mouseReleaseEvent(event)
print("Released")
if event.button() and Qt.LeftButton:
if self.start != self.end:
r = QRect(self.start, self.end).normalized()
self.rectangles.append(r)
self.start = self.end = QPoint()
self.update()
Then, in main.py I create the scene and view, and add the item onto the scene:
class MyApp(QMainWindow):
def __init__(self):
super().__init__()
self.window_width, self.window_height = 1200, 800
self.setMinimumSize(self.window_width, self.window_height)
self.scene = QGraphicsScene(self)
self.view = QGraphicsView(self.scene, self)
self.view.setGeometry(0,0,1000, 700)
self.item = GraphicsItem_custom()
self.scene.addItem(self.item)
if __name__ == '__main__':
# don't auto scale when drag app to a different monitor.
# QApplication.setAttribute(Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)
app = QApplication(sys.argv)
app.setStyleSheet('''
QWidget {
font-size: 30px;
}
''')
myApp = MyApp()
myApp.show()
try:
sys.exit(app.exec_())
except SystemExit:
print('Closing Window...')
When I run this code, the mousePressEvent() is triggered when I press, however, mouseReleaseEvent() is not when triggered when I release. Can anyone tell me what I did wrong?
The documentation of mousePressEvent() explains:
If you do reimplement this function, event will by default be accepted (see QEvent::accept()), and this item is then the mouse grabber. This allows the item to receive future move, release and doubleclick events.
If you call the base implementation like this:
super().mousePressEvent(event)
the result will the same as not reimplementing the function (you're calling the default behavior), so the event will not accepted, meaning that the item will not receive move, release and doubleclick events, unless you've set specific flags that trigger those event handlers due to their nature (like Qt.ItemIsMovable).
You have to carefully decide if you actually need to call the base implementation or not (usually depending on the flags), and eventually add this at some point in the mousePressEvent() override:
event.setAccepted(True)
I'm developing a GUI where you can connect nodes between them except in a few special cases. This implementation works perfectly fine most of the time, but after some testing i found that, when I connect one QGraphicsPixmapItem with another through a QGraphicsLineItem, and the user opens the contextual menu before completing the link, the line get stuck, and it cannot be deleted.
The process to link two elements is to first press the element, then keep pressing while moving the line and releasing when the pointer is over the other element. This is achieved using mousePressEvent, mouseMoveEvent and mouseReleaseEvent, respectively.
This code is an example:
#!/usr/bin/env python3
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import sys
class Ellipse(QGraphicsEllipseItem):
def __init__(self, x, y):
super(Ellipse, self).__init__(x, y, 30, 30)
self.setBrush(QBrush(Qt.darkBlue))
self.setFlag(QGraphicsItem.ItemIsMovable)
self.setZValue(100)
def contextMenuEvent(self, event):
menu = QMenu()
first_action = QAction("First action")
second_action = QAction("Second action")
menu.addAction(first_action)
menu.addAction(second_action)
action = menu.exec(event.screenPos())
class Link(QGraphicsLineItem):
def __init__(self, x, y):
super(Link, self).__init__(x, y, x, y)
self.pen_ = QPen()
self.pen_.setWidth(2)
self.pen_.setColor(Qt.red)
self.setPen(self.pen_)
def updateEndPoint(self, x2, y2):
line = self.line()
self.setLine(line.x1(), line.y1(), x2, y2)
class Scene(QGraphicsScene):
def __init__(self):
super(Scene, self).__init__()
self.link = None
self.link_original_node = None
self.addItem(Ellipse(200, 400))
self.addItem(Ellipse(400, 400))
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
item = self.itemAt(event.scenePos(), QTransform())
if item is not None:
self.link_original_node = item
offset = item.boundingRect().center()
self.link = Link(item.scenePos().x() + offset.x(), item.scenePos().y() + offset.y())
self.addItem(self.link)
def mouseMoveEvent(self, event):
super().mouseMoveEvent(event)
if self.link is not None:
self.link.updateEndPoint(event.scenePos().x(), event.scenePos().y())
def mouseReleaseEvent(self, event):
super().mouseReleaseEvent(event)
if self.link is not None:
item = self.itemAt(event.scenePos(), QTransform())
if isinstance(item, (Ellipse, Link)):
self.removeItem(self.link)
self.link_original_node = None
self.link = None
class MainWindow(QMainWindow):
def __init__(self):
super(QMainWindow, self).__init__()
self.scene = Scene()
self.canvas = QGraphicsView()
self.canvas.setScene(self.scene)
self.setCentralWidget(self.canvas)
self.setGeometry(500, 200, 1000, 600)
self.setContextMenuPolicy(Qt.NoContextMenu)
app = QApplication(sys.argv)
win = MainWindow()
win.show()
sys.exit(app.exec())
How can I get rid off the line before/after the context menu event? I tried to stop them, but I do not know how.
Assuming that the menu is only triggered from a mouse button press, the solution is to remove any existing link item in the mouseButtonPress too.
def mousePressEvent(self, event):
if self.link is not None:
self.removeItem(self.link)
self.link_original_node = None
self.link = None
# ...
Note that itemAt for very small items is not always reliable, as the item's shape() might be slightly off the mapped mouse position. Since the link would be removed in any case, just do the same in the mouseReleaseEvent():
def mouseReleaseEvent(self, event):
super().mouseReleaseEvent(event)
if self.link is not None:
item = self.itemAt(event.scenePos(), QTransform())
if isinstance(item, Ellipse):
# do what you need with the linked ellipses
# note the indentation level
self.removeItem(self.link)
self.link_original_node = None
self.link = None
As you can see, I create a window with left buttons, but I don't know how to use QStackedWidget to show different views after pressed every button. I searched several similar questions, but they were not useful for me. I am new to PyQt5. I add my code below, please have a look.
Here is my code:
class StackedWidget(QStackedWidget):
def __init__(self, parent = None):
QStackedWidget.__init__(self, parent)
def setCurrentIndex(self, index):
QStackedWidget.setCurrentIndex(self, index)
def setSend(self):
self.setCurrentIndex(0)
def setHome(self):
self.setCurrentIndex(1)
class Homewindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowFlags(Qt.FramelessWindowHint)
self.style = """
QPushButton{
background-color:rgba(0,0,0,20);
}
QPushButton:hover {
background-color: rgba(0,0,0,40);
color: white;
}
"""
self.setStyleSheet(self.style)
self.initUI()
def initUI(self):
self.setGeometry(300,100,804,634)
self.setWindowTitle('Home')
self.stack = StackedWidget()
page1 = QLabel("Page1")
self.stack.addWidget(page1)
email = QLabel("Page2")
self.stack.addWidget(email)
titleBarLbl = QLabel(self)
titleBarLbl.setStyleSheet("background-color:blue")
titleBarLbl.resize(805,53)
self.sendBtn = QPushButton(self)
self.sendBtn.setStyleSheet("background-color:red")
self.sendBtn.clicked.connect(self.stack.setSend)
self.sendBtn.setGeometry(0,53,48,48)
self.homeBtn = QPushButton(self)
self.homeBtn.setStyleSheet("background-color:green")
self.homeBtn.clicked.connect(self.stack.setHome)
self.homeBtn.setGeometry(0,101,48,48)
self.show()
def mousePressEvent(self,event):
if event.button() == Qt.LeftButton:
self.moving = True;
self.offset = event.pos()
def mouseMoveEvent(self,event):
if self.moving:
self.move(event.globalPos()-self.offset)
The code in your question seems to work okay, except that you did not give the stack-widget a parent, or set its geometry. So just do something like this:
def initUI(self):
...
self.stack = StackedWidget(self)
self.stack.setGeometry(55, 0, 750, 600)
A generic way to change the pages in a stack-widget using buttons is to use a QButtonGroup. Each button can be linked to the index of a given page in the stack-widget. Then the buttonClicked signal of the button-group can be used to change the page:
self.group = QtWidgets.QButtonGroup(self)
self.group.addButton(self.buttonA, 0)
self.group.addButton(self.buttonB, 1)
self.group.addButton(self.buttonC, 2)
# etc
self.group.buttonClicked[int].connect(self.stack.setCurrentIndex)
If necessary, the index of the page in the stack-widget can be obtained like this:
index = self.stack.indexOf(self.pageA)
I need to populate a listview with widgets, then have a custom proxyfilter work with it.
Without the filter it works great, when active it seems to delete the widgets attach to the model.
It shows up fine showing all items, filtering works but when erasing the filter, when hidden widgets should be shown again following error gets thrown:
custom_widget.setGeometry(option.rect)
RuntimeError: underlying C/C++ object has been deleted
Tried not using QVariant and going the internalPointer route but breaks at the same spot.
Thanks for having a look!
Setup:
def __init__(self, *args):
QtGui.QWidget.__init__(self, *args)
# create temp data
self.list_data = []
for x in xrange(500):
widget = ListItemWidget(text=str(x), parent=self)
self.list_data.append((str(x), widget)) # testing to put in inmut tuple
# create listviewmodel
self.lm = ListViewModel(parent=self)
# create listview widget
self.lv = QtGui.QListView()
# create filter proxy
self.proxy_model = ListViewFilterProxyModel()
self.proxy_model.setFilterPattern('')
self.proxy_model.setSourceModel(self.lm)
# set model of listview to filter proxy
self.lv.setModel(self.proxy_model)
# set delegate for column 0
self.lv.setItemDelegateForColumn(0, CustomWidgetDelegate(self.lv))
self.lm.updateData(self.list_data)
self.proxy_model.invalidate()
self.connect(self.filter_edit, QtCore.SIGNAL("textChanged(QString)"), self.update_filter)
def update_filter(self, pattern):
self.proxy_model.setFilterPattern(pattern)
self.proxy_model.invalidate()
Custom widget
class ListItemWidget(QtGui.QWidget):
def __init__(self, text=None, parent=None):
QtGui.QWidget.__init__(self)
self.text = text
#QtCore.pyqtProperty(QtCore.QString)
def text(self):
return self.__text
#text.setter
def text(self, value):
self.__text = value
Delegate for painting the view
class CustomWidgetDelegate(QtGui.QItemDelegate):
def __init__(self, parent=None):
super(CustomWidgetDelegate, self).__init__(parent)
def paint(self, painter, option, index):
custom_widget = index.model().data(index, QtCore.Qt.DisplayRole).toPyObject()[1]
>>>>>> custom_widget.setGeometry(option.rect)
if not self.parent().indexWidget(index):
self.parent().setIndexWidget(index, custom_widget)
List view model:
class ListViewModel(QtCore.QAbstractListModel):
def __init__(self, parent=None, *args):
QtCore.QAbstractListModel.__init__(self, parent, *args)
self.listdata = []
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self.listdata)
def data(self, index, role):
if role == QtCore.Qt.SizeHintRole:
return QtCore.QSize(80, 80)
if index.isValid() and role == QtCore.Qt.DisplayRole:
return QtCore.QVariant(self.listdata[index.row()]).toPyObject()
return QtCore.QVariant()
def updateData(self, listdata):
self.listdata = listdata
index = len(self.listdata)
return True
Finally the filter proxy model:
class ListViewFilterProxyModel(QtGui.QSortFilterProxyModel):
def __init__(self, parent=None):
self.filter_str = None
QtGui.QSortFilterProxyModel.__init__(self, parent)
def setFilterPattern(self, pattern):
self.filter_str = QtCore.QString(pattern)
def filterAcceptsRow(self, sourceRow, sourceParent):
if self.filter_str is None:
return True
index = self.sourceModel().index(sourceRow, 0, sourceParent)
# just testing on the str here...
text = index.data().toPyObject()[0]
if not str(self.filter_str) in text:
return False
return True