Related
I am new to PyQt5. I need to draw a rectangle on an image. I found something that can draw on a mainwindow but I need to draw on an image with QLabel. QLabel is an widget. I am don't know how to draw rectangle on a widget.
class MyWidget(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.setGeometry(30,30,600,400)
self.picture = QtWidgets.QLabel(self)
self.picture.setGeometry(QtCore.QRect(10, 60, 1111, 541))
self.picture.setMinimumSize(QtCore.QSize(0, 0))
self.picture.setStyleSheet("background-color: rgb(188, 188, 188);")
self.picture.setText("")
self.picture.setPixmap(QtGui.QPixmap("G:/JOB files/JoBfiles/300/5.jpg"))
self.image =QtGui.QPixmap("G:/JOB files/JoBfiles/300/5.jpg")
self.picture.setScaledContents(True)
self.picture.setObjectName("picture")
self.begin = QtCore.QPoint()
self.end = QtCore.QPoint()
self.show()
def paintEvent(self, event):
qp = QtGui.QPainter( self)
br = QtGui.QBrush(QtGui.QColor(100, 10, 10, 40))
#qp.drawPixmap(QtCore.QRect(self.begin, self.end), self.picture)
qp.setBrush(br)
# qp.drawRect(QtCore.QRect(self.begin, self.end))
qp.drawRect(QtCore.QRect(self.begin, self.end) )
def mousePressEvent(self, event):
self.begin = event.pos()
self.end = event.pos()
self.update()
def mouseMoveEvent(self, event):
self.end = event.pos()
self.update()
def mouseReleaseEvent(self, event):
self.begin = event.pos()
self.end = event.pos()
self.update()
app=QApplication(sys.argv)
ui=MyWidget()
mainwindow=QtWidgets.QStackedWidget()
mainwindow.addWidget(ui)
screen = app.primaryScreen()
size = screen.size()
mainwindow.setFixedWidth(size.width())
mainwindow.setFixedHeight(size.height())
mainwindow.showMaximized()
mainwindow.show()
app.exec_()
this is what I got
I have created a custom Frame for a Qgraphicswidget. I have come across two problems, First, one being that clicks are not being detected under MouseMoveEvent, that is event.button() always returns 0 even if there is a mouse click. Second, is that my setCursor() doesn't change the cursor. Here is my code under the custom frame class.
from PyQt5 import QtGui, QtCore
from PyQt5.QtCore import Qt, QRectF, QEvent, QPoint
from PyQt5.QtGui import QPen, QColor, QPainter, QBrush, qRgb, QPolygon
from PyQt5.QtWidgets import *
import sys
class Frame(QFrame):
def __init__(self, parent=None, option=[], margin=0):
super(Frame, self).__init__()
self.parent = parent
self._triangle = QPolygon()
self.options = option
self._margin = margin
self.start_pos = None
# self.parent.setViewport(parent)
self.setStyleSheet('background-color: lightblue')
self.setMouseTracking(True)
self.installEventFilter(self)
self.show()
def update_option(self, option):
self.options = option
def paintEvent(self, event):
super().paintEvent(event)
qp = QPainter(self)
qp.setPen(Qt.white)
qp.setBrush(Qt.gray)
qp.drawPolygon(self._triangle)
def _recalculate_triangle(self):
p = QPoint(self.width() - 20, self.height() - 10)
q = QPoint(self.width() - 10, self.height() - 20)
r = QPoint(self.width() - 10, self.height() - 10)
self._triangle = QPolygon([p, q, r])
self.update()
def resizeEvent(self, event):
self._recalculate_triangle()
super().resizeEvent(event)
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
if event.button() == Qt.LeftButton and self._triangle.containsPoint(
event.pos(), Qt.OddEvenFill
):
self.parent.viewport().setCursor(Qt.SizeFDiagCursor)
self.start_pos = event.pos()
# print(self.start_pos)
else:
self.parent.viewport().unsetCursor()
self.start_pos = None
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
if self._triangle.containsPoint(event.pos(), Qt.OddEvenFill):
self.parent.viewport().setCursor(Qt.SizeFDiagCursor)
else:
self.parent.viewport().unsetCursor()
self.start_pos = None
if event.button() == Qt.LeftButton:
if event.button() == QtCore.Qt.LeftButton and self.start_pos is not None:
self.parent.viewport().setCursor(Qt.SizeFDiagCursor)
delta = event.pos() - self.start_pos
self.n_resize(self.width()+delta.x(), self.height()+delta.y())
self.start_pos = event.pos()
elif not self._triangle.containsPoint(event.pos(), Qt.OddEvenFill):
self.parent.viewport().unsetCursor()
self.start_pos = None
super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
self.parent.viewport().unsetCursor()
self.start_pos = None
super().mouseReleaseEvent(event)
def n_resize(self, width, height):
self.resize(width, height)
if __name__ == '__main__':
q = QApplication(sys.argv)
a = Frame()
sys.exit(q.exec())
I have also tried using eventfilter but of no use.
EDIT:
from PyQt5 import QtGui, QtCore
from PyQt5.QtCore import Qt, QPoint
from PyQt5.QtGui import QPen, QColor, QPainter, QBrush, QPolygon
from PyQt5.QtWidgets import *
import sys
class graphLayout(QGraphicsView):
def __init__(self, parent=None):
super().__init__(parent)
self.scene = QGraphicsScene()
self.lines = []
self.draw_grid()
self.set_opacity(0.3)
widget = QGraphicsProxyWidget()
t = stack(self)
t.setFlag(QGraphicsItem.ItemIsMovable)
t.resize(340, 330)
self.scene.addItem(t)
self.setScene(self.scene)
self.show()
def create_texture(self):
image = QtGui.QImage(QtCore.QSize(30, 30), QtGui.QImage.Format_RGBA64)
pen = QPen()
pen.setColor(QColor(189, 190, 191))
pen.setWidth(2)
painter = QtGui.QPainter(image)
painter.setPen(pen)
painter.drawRect(image.rect())
painter.end()
return image
def draw_grid(self):
texture = self.create_texture()
brush = QBrush()
# brush.setColor(QColor('#999'))
brush.setTextureImage(texture) # Grid pattern.
self.scene.setBackgroundBrush(brush)
borderColor = Qt.black
fillColor = QColor('#DDD')
def set_visible(self, visible=True):
for line in self.lines:
line.setVisible(visible)
def delete_grid(self):
for line in self.lines:
self.scene.removeItem(line)
del self.lines[:]
def set_opacity(self, opacity):
for line in self.lines:
line.setOpacity(opacity)
def wheelEvent(self, event):
if event.modifiers() == Qt.ControlModifier:
delta = event.angleDelta().y()
if delta > 0:
self.on_zoom_in()
elif delta < 0:
self.on_zoom_out()
super(graphLayout, self).wheelEvent(event)
def mousePressEvent(self, event):
if event.button() == Qt.MidButton:
self.setCursor(Qt.OpenHandCursor)
self.mousepos = event.localPos()
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
# This helps to pan the area
if event.buttons() == Qt.MidButton:
delta = event.localPos() - self.mousepos
h = self.horizontalScrollBar().value()
v = self.verticalScrollBar().value()
self.horizontalScrollBar().setValue(int(h - delta.x()))
self.verticalScrollBar().setValue(int(v - delta.y()))
self.mousepos = event.localPos()
super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
self.unsetCursor()
self.mousepos = event.localPos()
super().mouseReleaseEvent(event)
def on_zoom_in(self):
if self.transform().m11() < 3.375:
self.setTransformationAnchor(self.AnchorUnderMouse)
self.scale(1.5, 1.5)
def on_zoom_out(self):
if self.transform().m11() > 0.7:
self.setTransformationAnchor(self.AnchorUnderMouse)
self.scale(1.0 / 1.5, 1.0 / 1.5)
class stack(QGraphicsWidget):
_margin = 0
def __init__(self, parent=None):
super().__init__()
self.options = []
self.gridlayout = parent
graphic_layout = QGraphicsLinearLayout(Qt.Vertical, self)
self.width, self.height = 10, 10
self.outer_container = Frame(parent, self.options, self._margin)
self.outer_container.setContentsMargins(0, 0, 0, 0)
self.setParent(self.outer_container)
layout = QVBoxLayout()
self.headerLayout = QGridLayout()
self.headerLayout.setContentsMargins(2, 0, 0, 0)
self.top_bar = QFrame()
self.top_bar.setFrameShape(QFrame.StyledPanel)
self.top_bar.setLayout(self.headerLayout)
self.top_bar.setContentsMargins(0, 0, 0, 0)
self.top_bar.setMaximumHeight(30)
# self.contentLayout = QVBoxLayout()
self.contentLayout = QFormLayout()
self.contentLayout.setContentsMargins(10, 10, 10, 10)
self.contentLayout.setSpacing(5)
layout.addWidget(self.top_bar)
layout.addLayout(self.contentLayout)
layout.setSpacing(0)
layout.setContentsMargins(0, 0, 0, 0)
self.outer_container.setContentsMargins(0, 0, 0, 0)
self.setContentsMargins(0, 0, 0, 0)
self.setMaximumSize(400, 800)
self.outer_container.setLayout(layout)
widget = QGraphicsProxyWidget()
widget.setWidget(self.outer_container)
# todo: figure out a way to add top_bar widget
graphic_layout.addItem(widget)
graphic_layout.setSpacing(0)
graphic_layout.setContentsMargins(0, 0, 0, 0)
# widget move and resize note: don't touch any of these
self.__mouseMovePos = None
self._triangle = QPolygon()
self.start_pos = None
def addHeaderWidget(self, widget=None, column=0, bg_color='green'):
self.top_bar.setStyleSheet(f'background-color:{bg_color};')
self.headerLayout.addWidget(widget, 0, column)
class Frame(QFrame):
def __init__(self, parent=None, option=[], margin=0):
super(Frame, self).__init__()
self.parent = parent
self._triangle = QPolygon()
self.options = option
self._margin = margin
self.start_pos = None
# self.parent.setViewport(parent)
self.setStyleSheet('background-color: lightblue')
self.setMouseTracking(True)
self.installEventFilter(self)
self.show()
def update_option(self, option):
self.options = option
def paintEvent(self, event):
super().paintEvent(event)
qp = QPainter(self)
qp.setPen(Qt.white)
qp.setBrush(Qt.gray)
qp.drawPolygon(self._triangle)
def _recalculate_triangle(self):
p = QPoint(self.width() - 20, self.height() - 10)
q = QPoint(self.width() - 10, self.height() - 20)
r = QPoint(self.width() - 10, self.height() - 10)
self._triangle = QPolygon([p, q, r])
self.update()
def resizeEvent(self, event):
self._recalculate_triangle()
super().resizeEvent(event)
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
if event.button() == Qt.LeftButton and self._triangle.containsPoint(
event.pos(), Qt.OddEvenFill
):
self.parent.viewport().setCursor(Qt.SizeFDiagCursor)
self.start_pos = event.pos()
# print(self.start_pos)
else:
self.parent.viewport().unsetCursor()
self.start_pos = None
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
if self._triangle.containsPoint(event.pos(), Qt.OddEvenFill):
self.parent.viewport().setCursor(Qt.SizeFDiagCursor)
else:
self.parent.viewport().unsetCursor()
self.start_pos = None
if event.button() == Qt.LeftButton:
if event.button() == QtCore.Qt.LeftButton and self.start_pos is not None:
self.parent.viewport().setCursor(Qt.SizeFDiagCursor)
delta = event.pos() - self.start_pos
self.n_resize(self.width()+delta.x(), self.height()+delta.y())
self.start_pos = event.pos()
elif not self._triangle.containsPoint(event.pos(), Qt.OddEvenFill):
self.parent.viewport().unsetCursor()
self.start_pos = None
super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
self.parent.viewport().unsetCursor()
self.start_pos = None
super().mouseReleaseEvent(event)
def n_resize(self, width, height):
self.resize(width, height)
if __name__ == '__main__':
q = QApplication(sys.argv)
a = graphLayout()
sys.exit(q.exec())
There are various issues on your code, but the base problems are:
mouse button state cannot be retrieved by event.button() in a MouseMove event, and event.buttons() should be used instead: the difference is clear: button() shows the buttons that generate the event (and a mouse move event is obviously not generated by any button), buttons() shows the button state when the event is generated;
events that are not explicitly managed by an object are always propagated to its parent(s), which means that your mouse movements are also possibly processed by the parent widget, then the graphics proxy, the scene, the viewport, the view, etc, and that up to the top level window, until one of the previous objects actually returns True from event() or an event filter; in your case it results in moving the graphics item, since you enabled the ItemIsMovable flag.
I don't know why the cursor is not actually set, but frankly your code is so convoluted that I really cannot find the reason.
Since what you're actually looking for is a way to resize the widget, I suggest you another solution.
While implementing a resizing with custom painting is certainly feasible, in most cases it's well enough to use a QSizeGrip (as already suggested to you in another post), which is a widget that allows resizing top-level windows and is automatically able to understand which "corner" use for the resizing based on its position. Remember that the parent of the QSizeGrip is very important, because it uses it to understand which is its top level window, and, in this case, the "container frame", even if it's in a QGraphicsScene.
Note that QSizeGrip should not be added to a layout, and it should always be manually moved according to its corner position and the size of its parent (unless it's placed on the top left corner), and since you already need custom painting, it's better to subclass it.
from PyQt5 import QtCore, QtGui, QtWidgets
class SizeGrip(QtWidgets.QSizeGrip):
def __init__(self, parent):
super().__init__(parent)
parent.installEventFilter(self)
self.setFixedSize(30, 30)
self.polygon = QtGui.QPolygon([
QtCore.QPoint(10, 20),
QtCore.QPoint(20, 10),
QtCore.QPoint(20, 20),
])
def eventFilter(self, source, event):
if event.type() == QtCore.QEvent.Resize:
geo = self.rect()
geo.moveBottomRight(source.rect().bottomRight())
self.setGeometry(geo)
return super().eventFilter(source, event)
def paintEvent(self, event):
qp = QtGui.QPainter(self)
qp.setPen(QtCore.Qt.white)
qp.setBrush(QtCore.Qt.gray)
qp.drawPolygon(self.polygon)
class Container(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.sizeGrip = SizeGrip(self)
self.startPos = None
layout = QtWidgets.QVBoxLayout(self)
layout.setContentsMargins(6, 6, 6, 30)
self.setStyleSheet('''
Container {
background: lightblue;
border: 0px;
border-radius: 4px;
}
''')
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.LeftButton:
self.startPos = event.pos()
def mouseMoveEvent(self, event):
if self.startPos:
self.move(self.pos() + (event.pos() - self.startPos))
def mouseReleaseEvent(self, event):
self.startPos = None
class GraphicsRoundedFrame(QtWidgets.QGraphicsProxyWidget):
def __init__(self):
super().__init__()
self.container = Container()
self.setWidget(self.container)
def addWidget(self, widget):
self.container.layout().addWidget(widget)
def paint(self, qp, opt, widget):
qp.save()
p = QtGui.QPainterPath()
p.addRoundedRect(self.boundingRect().adjusted(0, 0, -.5, -.5), 4, 4)
qp.setClipPath(p)
super().paint(qp, opt, widget)
qp.restore()
class View(QtWidgets.QGraphicsView):
def __init__(self):
super().__init__()
scene = QtWidgets.QGraphicsScene()
self.setScene(scene)
self.setRenderHints(QtGui.QPainter.Antialiasing)
scene.setSceneRect(0, 0, 1024, 768)
texture = QtGui.QImage(30, 30, QtGui.QImage.Format_ARGB32)
qp = QtGui.QPainter(texture)
qp.setBrush(QtCore.Qt.white)
qp.setPen(QtGui.QPen(QtGui.QColor(189, 190, 191), 2))
qp.drawRect(texture.rect())
qp.end()
scene.setBackgroundBrush(QtGui.QBrush(texture))
testFrame = GraphicsRoundedFrame()
scene.addItem(testFrame)
testFrame.addWidget(QtWidgets.QLabel('I am a label'))
testFrame.addWidget(QtWidgets.QPushButton('I am a button'))
import sys
app = QtWidgets.QApplication(sys.argv)
w = View()
w.show()
sys.exit(app.exec_())
I got this code that helps me draw a rectangle from another SO answer, I'd like to be able to drag the left and right sides of the rectangle to adjust the width of the rectangle, make the rectangle behave in a way similar to how you crop an image on most photo editing software, where you draw the initial area but you have the possibility to adjust the width afterwards to get the crop you want.
the code I have so far:
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class MyWidget(QWidget):
def __init__(self):
super().__init__()
self.setGeometry(30,30,600,400)
self.begin = QPoint()
self.end = QPoint()
self.show()
def paintEvent(self, event):
qp = QPainter(self)
br = QBrush(QColor(100, 10, 10, 40))
qp.setBrush(br)
qp.drawRect(QRect(self.begin, self.end))
def mousePressEvent(self, event):
self.begin = event.pos()
self.end = event.pos()
# print(f"press begin {self.begin}")
# print(f"press end {self.end}")
self.update()
def mouseMoveEvent(self, event):
self.end = event.pos()
self.update()
def mouseReleaseEvent(self, event):
#self.begin = event.pos()
self.end = event.pos()
#self.update()
print(f"begin {self.begin}")
print(f"end {self.end}")
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MyWidget()
window.show()
app.aboutToQuit.connect(app.deleteLater)
sys.exit(app.exec_())
I found the answer to this on a PyQt forum, courtesy of a man by the name of Salem Bream, he answered my question, I thought I'd share it with the SO community.
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
FREE_STATE = 1
BUILDING_SQUARE = 2
BEGIN_SIDE_EDIT = 3
END_SIDE_EDIT = 4
class MyWidget(QWidget):
def __init__(self):
super().__init__()
self.setGeometry(30, 30, 600, 400)
self.begin = QPoint()
self.end = QPoint()
self.state = FREE_STATE
def paintEvent(self, event):
qp = QPainter(self)
br = QBrush(QColor(100, 10, 10, 40))
qp.setBrush(br)
qp.drawRect(QRect(self.begin, self.end))
def mousePressEvent(self, event):
if not self.begin.isNull() and not self.end.isNull():
p = event.pos()
y1, y2 = sorted([self.begin.y(), self.end.y()])
if y1 <= p.y() <= y2:
# 3 resolution, more easy to pick than 1px
if abs(self.begin.x() - p.x()) <= 3:
self.state = BEGIN_SIDE_EDIT
return
elif abs(self.end.x() - p.x()) <= 3:
self.state = END_SIDE_EDIT
return
self.state = BUILDING_SQUARE
self.begin = event.pos()
self.end = event.pos()
self.update()
def applye_event(self, event):
if self.state == BUILDING_SQUARE:
self.end = event.pos()
elif self.state == BEGIN_SIDE_EDIT:
self.begin.setX(event.x())
elif self.state == END_SIDE_EDIT:
self.end.setX(event.x())
def mouseMoveEvent(self, event):
self.applye_event(event)
self.update()
def mouseReleaseEvent(self, event):
self.applye_event(event)
self.state = FREE_STATE
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MyWidget()
window.show()
app.aboutToQuit.connect(app.deleteLater)
sys.exit(app.exec_())
Following image showing an app with one input widget and one paintwidget.
and there are two paint classes in the code. The idea is when a button is clicked, calling first paint class and show it in Paintwidget, well when another paint class is called, wants to show in same Paintwidget. Not really overlap each other, rather than showing in same widget.
How could that be done?
When round button clicks this codeline changes from
self.mainSplitter.addWidget(self.paint1)
to
self.mainSplitter.addWidget(self.paint2)
And vice versa
Visualization:
The code:
from PyQt5 import QtCore, QtGui, QtWidgets
class Foo(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(Foo, self).__init__(parent)
self.setGeometry(QtCore.QRect(200, 100, 800, 650))
self.button = Button()
self.paint1 = Paintwidget1()
self.paint2 = Paintwidget2()
self.button.valuesChanged.connect(self.paint1.set_size_squares)
self.button.valueChanged.connect(self.paint2.set_size_round)
self.mainSplitter = QtWidgets.QSplitter(QtCore.Qt.Horizontal)
self.mainSplitter.addWidget(self.button)
self.mainSplitter.addWidget(self.paint1)
self.setCentralWidget(self.mainSplitter)
class Paintwidget1(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.sizeHint()
self.setBackgroundRole(QtGui.QPalette.Base)
self.setAutoFillBackground(True)
self._size = QtCore.QSizeF()
self._path = QtGui.QPainterPath()
self._rect = QtCore.QRectF()
self._type = QtGui.QRegion.Rectangle
self._factor = 1.0
self._pos = QtCore.QPointF()
self._initial_flag = False
fnt = self.font()
fnt.setPointSize(20)
self.setFont(fnt)
def showEvent(self, event):
if not self._initial_flag:
self._pos = self.rect().center()
self._initial_flag = True
#QtCore.pyqtSlot(int, int)
def set_size_squares(self, w, h):
self._path = QtGui.QPainterPath()
self._size = QtCore.QSizeF(w, h)
self._type = QtGui.QRegion.Rectangle
self.updatePath()
def paintEvent(self, event):
pen = QtGui.QPen()
brush = QtGui.QBrush(QtCore.Qt.black)
painter = QtGui.QPainter(self)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
painter.setPen(pen)
painter.setBrush(brush)
painter.translate(self.rect().center())
painter.scale(self._factor, self._factor)
painter.translate(-self.rect().center())
painter.translate(self._pos)
painter.drawPath(self._path)
if self._type == QtGui.QRegion.Rectangle:
painter.fillRect(self._rect, QtGui.QBrush(QtCore.Qt.gray, QtCore.Qt.Dense7Pattern))
painter.setBrush(QtGui.QBrush(QtCore.Qt.NoBrush))
painter.drawRect(self._rect)
def mousePressEvent(self, event):
QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.OpenHandCursor))
self._initial_pos = event.pos()
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
delta = event.pos() - self._initial_pos
self._path.translate(delta)
self._rect.translate(delta)
self.update()
self._initial_pos = event.pos()
super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
QtWidgets.QApplication.restoreOverrideCursor()
super().mouseReleaseEvent(event)
def updatePath(self):
r = QtCore.QRectF(QtCore.QPointF(), self._size)
r.moveCenter(QtCore.QPointF())
self._rect = QtCore.QRectF(r)
self.update()
def wheelEvent(self, event):
self._factor *= 1.01**(event.angleDelta().y()/15.0)
self.update()
super().wheelEvent(event)
class Paintwidget2(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.sizeHint()
self.setBackgroundRole(QtGui.QPalette.Base)
self.setAutoFillBackground(True)
self._size = QtCore.QSizeF()
self._path = QtGui.QPainterPath()
self._rect = QtCore.QRectF()
self._type = QtGui.QRegion.Rectangle
self._factor = 1.0
self._pos = QtCore.QPointF()
self._initial_flag = False
fnt = self.font()
fnt.setPointSize(20)
self.setFont(fnt)
def showEvent(self, event):
if not self._initial_flag:
self._pos = self.rect().center()
self._initial_flag = True
#QtCore.pyqtSlot(int)
def set_size_round(self, v):
self._path = QtGui.QPainterPath()
self._size = QtCore.QSizeF(v, 0.8*v)
self._type = QtGui.QRegion.Ellipse
self.updatePath()
def paintEvent(self, event):
pen = QtGui.QPen()
brush = QtGui.QBrush(QtCore.Qt.black)
painter = QtGui.QPainter(self)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
painter.setPen(pen)
painter.setBrush(brush)
painter.translate(self.rect().center())
painter.scale(self._factor, self._factor)
painter.translate(-self.rect().center())
painter.translate(self._pos)
painter.drawPath(self._path)
if self._type == QtGui.QRegion.Ellipse:
painter.setBrush(QtGui.QBrush(QtCore.Qt.gray, QtCore.Qt.Dense7Pattern))
painter.drawEllipse(self._rect)
def mousePressEvent(self, event):
QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.OpenHandCursor))
self._initial_pos = event.pos()
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
delta = event.pos() - self._initial_pos
self._path.translate(delta)
self._rect.translate(delta)
self.update()
self._initial_pos = event.pos()
super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
QtWidgets.QApplication.restoreOverrideCursor()
super().mouseReleaseEvent(event)
def updatePath(self):
r = QtCore.QRectF(QtCore.QPointF(), self._size)
r.moveCenter(QtCore.QPointF())
self._rect = QtCore.QRectF(r)
self.update()
def wheelEvent(self, event):
self._factor *= 1.01**(event.angleDelta().y()/15.0)
self.update()
super().wheelEvent(event)
class Button(QtWidgets.QWidget):
valueChanged = QtCore.pyqtSignal(int)
valuesChanged = QtCore.pyqtSignal(int,int)
def __init__(self, parent=None):
super(Button, self).__init__(parent)
roundbutton = QtWidgets.QPushButton('Round')
squarebutton = QtWidgets.QPushButton('Square')
Alay = QtWidgets.QVBoxLayout(self)
Alay.addWidget(roundbutton)
Alay.addWidget(squarebutton)
self.value = QtWidgets.QLabel()
roundbutton.clicked.connect(self.getbuttonfunc)
squarebutton.clicked.connect(self.sqaurebuttonfunc)
#QtCore.pyqtSlot()
def getbuttonfunc(self):
number, ok = QtWidgets.QInputDialog.getInt(self, self.tr("Set Number"),
self.tr("Input:"), 1, 1)
if ok:
self.valueChanged.emit(number)
#QtCore.pyqtSlot()
def sqaurebuttonfunc(self):
number, ok = QtWidgets.QInputDialog.getInt(self, self.tr("Set Number"),
self.tr("Input:"), 1, 1)
if ok:
self.valuesChanged.emit(number, number)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = Foo()
w.show()
sys.exit(app.exec_())
You have to use a QStackedWidget to exchange widget, also set the sizeHint() of Paintwidget1 and Paintwidget2:
class Foo(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(Foo, self).__init__(parent)
self.setGeometry(QtCore.QRect(200, 100, 800, 650))
self.button = Button()
self.paint1 = Paintwidget1()
self.paint2 = Paintwidget2()
self.button.valuesChanged.connect(self.on_paint1)
self.button.valueChanged.connect(self.on_paint2)
self.mainSplitter = QtWidgets.QSplitter(QtCore.Qt.Horizontal)
self._stacked_widget = QtWidgets.QStackedWidget()
self.mainSplitter.addWidget(self.button)
self.mainSplitter.addWidget(self._stacked_widget)
self.setCentralWidget(self.mainSplitter)
self._stacked_widget.addWidget(self.paint1)
self._stacked_widget.addWidget(self.paint2)
#QtCore.pyqtSlot(int, int)
def on_paint1(self, w, h):
self._stacked_widget.setCurrentIndex(0)
self.paint1.set_size_squares(w, h)
#QtCore.pyqtSlot(int)
def on_paint2(self, v):
self._stacked_widget.setCurrentIndex(1)
self.paint2.set_size_round(v)
class Paintwidget1(QtWidgets.QWidget):
# ...
def sizeHint(self):
return QtCore.QSize(640, 480)
class Paintwidget2(QtWidgets.QWidget):
# ...
def sizeHint(self):
return QtCore.QSize(640, 480)
In trying to Implement paint, I decided to begin with shapes
My init:
def __init__(self, parent=None):
self.modified = False
self.rectangle = False
self.ellipse = False
self.begin = QPoint()
self.end = QPoint()
self.myPenWidth = 1
self.myFigureColor = Qt.black
self.image = QImage()
I have an event in which there are functions for drawing shapes
def mousePressEvent(self, event):
if (event.button() == Qt.LeftButton) and self.rectangle:
self.draw_rectandle(event)
if (event.button() == Qt.LeftButton) and self.ellipse:
self.draw_ellipse(event)
Here we use the function in which is located a drawing figures:
I'll post them below
def mouseMoveEvent(self, event):
if (event.buttons() & Qt.LeftButton) and self.rectangle:
self.end = event.pos()
self.update()
if (event.buttons() & Qt.LeftButton) and self.ellipse:
self.end = event.pos()
self.update()
And
def mouseReleaseEvent(self, event):
if (event.buttons() & Qt.LeftButton) and self.rectangle:
self.begin = event.pos()
self.end = event.pos()
if (event.buttons() & Qt.LeftButton) and self.ellipse:
self.begin = event.pos()
self.end = event.pos()
And another function paintEvent:
def paintEvent(self, event):
painter = QPainter(self)
dirtyRect = event.rect()
painter.drawImage(dirtyRect, self.image, dirtyRect)
if self.rectangle == True:
painter.setPen(QPen(self.myFigureColor, self.myPenWidth,
Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
painter.drawRect(QRect(self.begin, self.end))
if self.ellipse == True:
painter.setPen(QPen(self.myFigureColor, self.myPenWidth,
Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
painter.drawEllipse(QRect(self.begin, self.end))
The functions of figures themselves:
def draw_rectandle(self, event):
painter = QPainter(self.image)
painter.setPen(QPen(self.myFigureColor, self.myPenWidth,
Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
painter.drawRect(QRect(self.begin, self.end))
self.begin = event.pos()
self.end = event.pos()
self.modified = True
self.update()
def draw_ellipse(self, event):
painter = QPainter(self.image)
painter.setPen(QPen(self.myFigureColor, self.myPenWidth,
Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
painter.drawEllipse(QRect(self.begin, self.end))
self.begin = event.pos()
self.end = event.pos()
self.modified = True
self.update()
Also "pseudo binds"
def rectangleOn(self):
self.ellipse = False
self.rectangle = True
def ellipseOn(self):
self.rectangle = False
self.ellipse = True
They are used to change the function flags when the shape button is pressed.
The buttons themselves look like if you need to:
Actions:
self.rectangle = QAction(QIcon('Image/rectangle.png'), 'Rectangle', self)
self.rectangle.triggered.connect(self.scribbleArea.rectangleOn)
self.ellipse = QAction(QIcon('Image/Ellipse.png'), 'Ellipse', self)
self.ellipse.triggered.connect(self.scribbleArea.ellipseOn)
Toolbar with button:
toolbar = self.addToolBar('Tools')
toolbar.addAction(self.rectangle)
toolbar.addAction(self.ellipse)
I think that there is no need for a function to change the color and size. Therefore, while I will not post.
Now I'll tell you what the problem is.
In paintEvent, I have a drawing showing a shape. That is, we pull the edge, and see how the shape changes. But then from there it is not saved anywhere.
And in mousePressEvent already drawing the rectangle itself without this drag animation
It seems that all is well, but here there is a bug
As you can see, the drawing of the rectangle goes to the mousePressEvent. And this means that the rectangle appears only when I start drawing the next one. This is already a mistake. BUT! If I draw a few rectangles and start drawing an ellipse, the last rectangle becomes an ellipse.
And also with other my shapes.
If I start drawing a line, and then switch to an ellipse and start drawing ellipse in another place, the line drawn by me will immediately become a very narrow ellipse.
Therefore, I need to somehow pull out this function from the mousePressEvent, so that the desired shape is drawn, with such switching between tools.
If you need more details, write с:
P.S. I apologize for my English, if something is wrong с:
I can not reproduce because you show your code to pieces, I recommend you always show a united code so we can easily execute those that we want to help.
As you have noticed, you must save the data in the image if you want them to remain and the temporary data to paint them directly in the widget. The logic of my solution is that from mousePressEvent until an instant before mouseReleaseEvent is painted directly in the widget, and then saved in the image, for this I created a class called AbstractScribbleArea that implements that general logic, and if you want to implement the logic for other shapes you should only overwrite the draw() method.
from PyQt5 import QtCore, QtGui, QtWidgets
class AbstractScribbleArea(QtWidgets.QWidget):
def __init__(self, parent=None):
super(AbstractScribbleArea, self).__init__(parent)
self._start = QtCore.QPoint()
self._end = QtCore.QPoint()
self._pixmap = QtGui.QPixmap()
self._shape = ""
self._color = QtGui.QColor("black")
self._pen_width = 1
self._pen = QtGui.QPen(self._color, self._pen_width,
QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin)
def mousePressEvent(self, event):
if event.buttons() & QtCore.Qt.LeftButton:
self._start = event.pos()
self._end = event.pos()
self.update()
super(AbstractScribbleArea, self).mousePressEvent(event)
def mouseMoveEvent(self, event):
if event.buttons() & QtCore.Qt.LeftButton:
self._end = event.pos()
self.update()
super(AbstractScribbleArea, self).mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
# draw on image
painter = QtGui.QPainter(self._pixmap)
painter.setPen(self._pen)
self.draw(painter, self._start, self._end, True)
self._start = QtCore.QPoint()
self._end = QtCore.QPoint()
self.update()
super(AbstractScribbleArea, self).mouseReleaseEvent(event)
def paintEvent(self, event):
painter = QtGui.QPainter(self)
painter.drawPixmap(self.rect(), self._pixmap, self.rect())
painter.setPen(self._pen)
self.draw(painter, self._start, self._end, False)
def resizeEvent(self, event):
w = self.width() if self.width() > self._pixmap.width() else self._pixmap.width()
h = self.height() if self.height() > self._pixmap.height() else self._pixmap.height()
s = QtCore.QSize(w, h)
if s != self._pixmap.size():
pixmap = QtGui.QPixmap(self._pixmap)
self._pixmap = QtGui.QPixmap(s)
painter = QtGui.QPainter(self._pixmap)
painter.fillRect(QtCore.QRect(0, 0, w, h), QtCore.Qt.white)
painter.drawPixmap(pixmap.rect(), pixmap)
super(AbstractScribbleArea, self).resizeEvent(event)
def draw(self, painter, start, end, is_released):
raise NotImplementedError
def sizeHint(self):
return QtCore.QSize(640, 480)
def set_pen_width(self, width):
self._pen.setWidth(width)
def set_pen_color(self, color):
self._pen.setColor(QtGui.QColor(color))
def set_shape(self, shape):
self._shape =shape
def shape(self):
return self._shape
class ScribbleArea(AbstractScribbleArea):
def draw(self, painter, start, end, is_released):
if start.isNull() or end.isNull():
return
if self.shape() == "rectangle":
self.draw_rectangle(painter, start, end)
elif self.shape() == "ellipse":
self.draw_ellipse(painter, start, end)
def draw_rectangle(self, painter, start, end):
rect = QtCore.QRect(start, end)
painter.drawRect(rect)
def draw_ellipse(self, painter, start, end):
rect = QtCore.QRect(start, end)
painter.drawEllipse(rect)
#QtCore.pyqtSlot()
def rectangleOn(self):
self.set_shape("rectangle")
#QtCore.pyqtSlot()
def ellipseOn(self):
self.set_shape("ellipse")
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.scribbleArea = ScribbleArea()
self.scribbleArea.set_pen_width(10)
self.scribbleArea.set_pen_color("red")
self.setCentralWidget(self.scribbleArea)
self.rectangle = QtWidgets.QAction(QtGui.QIcon('Image/rectangle.png'), 'Rectangle', self)
self.rectangle.triggered.connect(self.scribbleArea.rectangleOn)
self.ellipse = QtWidgets.QAction(QtGui.QIcon('Image/Ellipse.png'), 'Ellipse', self)
self.ellipse.triggered.connect(self.scribbleArea.ellipseOn)
toolbar = self.addToolBar('Tools')
toolbar.addAction(self.rectangle)
toolbar.addAction(self.ellipse)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())