If mouse button event draw rectangle PyQt5

Hello all. I am new to this forum and to Python. I am writing a simple code to make a prototype demonstrator running on a Raspberry and need to draw a rectangle when the left button of the mouse is pressed.

I am using PyQt5 as I thought it would be easy to handle as the library has all I need, but I am sure I am being foolish.

Here is the code I “wrote”:
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QAction, QLabel, QMainWindow
from PyQt5.QtGui import QPainter, QPixmap, QPen, QBrush, QImage, QIcon
from PyQt5 import QtCore, QtGui, QtWidgets, uic
from PyQt5.QtCore import Qt, QPoint
import time

image_path = r"/home/pi/Pictures/SCREEN2.jpg"

class Window(QMainWindow):
def init(self):
super().init()
self.image = QPixmap(image_path)
self.showFullScreen()

def mousePressEvent(self, event):
    if event.button() == Qt.LeftButton:
                
        pen = QtGui.QPen()
        pen.setWidth(3)
        pen.setColor(QtGui.QColor(255, 0, 0))
    
        brush = QtGui.QBrush()
        brush.setColor(QtGui.QColor(255, 0, 0))
        brush.setStyle(Qt.SolidPattern)
    
        painter.setBrush(brush)
        painter.setPen(pen)
        painter.drawRect(207, 152, 409, 222)
        painter.end()

if name == “main”:
app=QApplication(sys.argv)
window = Window()
window.show()
app.exec()

Is there anybody with a shiny soul that can help me with this?

If this message is not correctly placed in the forum, please let me know and I will move it ASAP.

Thank you all for watching.

Joan.

Welcome! :smile:

You’ve made a good start but there are some things missing. Let’s take a look.

import sys
from PyQt5.QtWidgets import QApplication, QWidget, QAction, QLabel, QMainWindow
from PyQt5.QtGui import QPainter, QPixmap, QPen, QBrush, QImage, QIcon
from PyQt5 import QtCore, QtGui, QtWidgets, uic
from PyQt5.QtCore import Qt, QPoint
import time

image_path = r"/home/pi/Pictures/SCREEN2.jpg"

class Window(QMainWindow):

    def __init__(self):

        super().__init__()
        self.image = QPixmap(image_path)
        self.showFullScreen()

You refer to an image but never use it. I guess you want to draw a rectangle on top of this image. Is that correct?

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:

            pen = QtGui.QPen()
            pen.setWidth(3)
            pen.setColor(QtGui.QColor(255, 0, 0))

            brush = QtGui.QBrush()
            brush.setColor(QtGui.QColor(255, 0, 0))
            brush.setStyle(Qt.SolidPattern)

            painter.setBrush(brush)
            painter.setPen(pen)
            painter.drawRect(207, 152, 409, 222)
            painter.end()

The pen and brush creation is fine, but you can’t really paint on the widget outside the paintEvent method, which you didn’t define. I’m guessing that you meant to create a QPainter instance and assign it to painter. You should do this in the paintEvent method. You should use the mousePressEvent method to record where the user pressed on the widget.

if name == "__main__":
    app=QApplication(sys.argv)
    window = Window()
    window.show()
    app.exec()

You use show() to show the window but also use showFullScreen() in the widget’s __init__ method. I suggest just using one of these methods to avoid confusion.

This should help you get started. :smile: Feel free to ask again if you want more hints.

Hi David!

Thanks a lot for your reply. I did some more digging to display the image as you suggested, came across with a different class, don’t really know if I am messing everything up here…

Now the image displays, but I still dont understand the dynamics of making a rectangle display when I press a left or right mouse button. Answering your question, yes, I am trying to display a rectangle onto the background image.

I have tried making a trigger variable, when it is equal to 1 then it should paint a rectangle using paintEvent function. But obviously this is not working:

import sys
from PyQt5.QtWidgets import QApplication, QWidget, QAction, QLabel
from PyQt5.QtGui import QPainter, QPixmap, QPen, QBrush, QImage, QIcon
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt, QPoint
from PIL import Image, ImageDraw
import time

image_path = r"/home/pi/Pictures/SCREEN2.jpg"

class App(QWidget):
def init(self):
super().init()
self.width = 1280
self.height = 800
self.initUI()
self.showFullScreen()

def initUI(self):
    self.setGeometry(0, 0, 1280, 800)
    label = QLabel(self)
    pixmap = QPixmap("/home/pi/Pictures/SCREEN2.jpg")
    label.setPixmap(pixmap)
    self.resize(pixmap.width(), pixmap.height())
    

    
def mousePressEvent(self, event):
    if event.button() == Qt.LeftButton:
        print("left button pressed")
        x = 1
        print (x)
        
    if event.button() == Qt.RightButton:
        print("right button pressed")
        x = 2
        print (x)
  
def paintEvent(self):
    if x == 1:
        painter = QPainter(self)
        painter.setPen(QPen(Qt.red, 1, Qt.Solidine))
        painter.setBrush(QBrush(Qt.red, Qt.SolidPattern))
        painter.drawRect(409, 222, 207, 152)
        painter.show()

if name == “main”:
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())

I hope I am closer now!

Thanks again for your help!!

Just a quick hint: if you include your code using the </> icon in the editor, it should display nicely in your message. Otherwise, include it between lines with only ``` on them.

I think it’s probably a good idea to simplify this. First, let’s only import the modules and widgets we need for now:

import sys
from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5.QtGui import QBrush, QImage, QPainter, QPen
from PyQt5.QtCore import Qt

Let’s simplify the __init__ method – we don’t need to have separate attributes for width and height, or set the geometry before resizing the widget. We just need to load the image make sure we show the widget with the correct size:

class App(QWidget):

    def __init__(self):
        super().__init__()
        self.click = 0
        self.image = QImage("/home/pi/Pictures/SCREEN2.jpg")
        self.resize(self.image.width(), self.image.height())
        self.show()

I included a click attribute to use in the other methods. You can use a global variable for this, but it is cleaner to use an attribute.

I changed the mousePressEvent method to use this attribute and also added update() calls to make sure that the widget is redrawn when the user clicks on it:

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            print("left button pressed")
            self.click = 1
            self.update()

        if event.button() == Qt.RightButton:
            print("right button pressed")
            self.click = 2
            self.update()

I also updated the paintEvent method to include the event parameter. The previous implementation didn’t include this – this meant that it was never called! It can be difficult to catch problems like that and maybe that explains why there were one a couple of errors in the method.

For example, you previously used painter.show() instead of painter.end() and perhaps that was because it wasn’t clear why it wasn’t painting anything.

    def paintEvent(self, event):
        if self.click == 1:
            painter = QPainter(self)
            painter.drawImage(0, 0, self.image)
            painter.setPen(QPen(Qt.red, 1, Qt.SolidLine))
            painter.setBrush(QBrush(Qt.red, Qt.SolidPattern))
            painter.drawRect(409, 222, 207, 152)
            painter.end()

I also included a call to the painter.drawImage method to draw the image instead of using a QLabel like you did – which is fine, but I wanted to show how you can paint it yourself.

if __name__ == "__main__":
    app = QApplication(sys.argv)
    ex = App()
    sys.exit(app.exec_())

All of these parts should fit together to produce a working program.

You will need to change the mousePressEvent method to record the points that the user pressed by calling event.pos() and using the result in paintEvent when you draw the rectangle.

Also, in paintEvent, you probably want to always draw the image and only draw the rectangle when you have two points.

Wow David! I don’t know how to thank you for your huge help! I will try this straight away. I understand a lot more now, and see where I was doing things wrong.

I will let you know how this ends up.

Joan.

Hi again David,

I have run through the code and it is a lot better now! However for some reason the image does not render at the beginning, but when the mousePressEvent is triggered, probably because there is the update.

I am surely being harsh but I have created another paint event with if self.click = 0 to paint the image. There might be a right way to solve it…

On the other hand, I would like to make the rectangle disappear after 1 second of drawing it, should I use the time library and paint the image after 1 second?

I can’t thank enough your help.

Regards,

Joan.

Yes, that’s partially correct. In your code, you check for self.click == 1 which will only do something after you have clicked with the left mouse button. The widget will be painted when it is created, but self.click is zero at that point in time.

Checking for self.click == 0 is one way to solve the problem. I would do this:

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.drawImage(0, 0, self.image)
        painter.setPen(QPen(Qt.red, 1, Qt.SolidLine))
        painter.setBrush(QBrush(Qt.red, Qt.SolidPattern))
        if self.click != 0:
            painter.drawRect(409, 222, 207, 152)
        painter.end()

Just be aware that the rectangle will appear after you press either the left or right button.

The time library will stop the user interface from responding. It’s easiest to use QTimer to do this, but you need to learn about signals and slots first.

No problem. I think you might benefit from taking a look at some of the tutorials for PyQt that can be found via the Python Wiki:
https://wiki.python.org/moin/PyQt/Tutorials

This one seems relevant, but doesn’t seem to deal with all the features you might want:

Ultimately, you need to decide what you want the widget to show, how it should behave, and what the user should do when they interact with it.

Hi David,

I have finished the code as you suggested and it works flawlessly!! Thank you so much for your help. :smile:

Joan.

Excellent! :smiley:

If you want to experiment with more complex ways to interact with the widget, such as drawing the rectangle, you can try an approach like this:

class Window(QMainWindow):

    def __init__(self):

        super().__init__()
        self.image = QImage(image_path)
        #self.showFullScreen()
        self.startPos = None
        self.rect = QRect()
        self.drawing = False

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton and not self.drawing:
            self.startPos = event.pos()
            self.rect = QRect(self.startPos, self.startPos)
            self.drawing = True
            self.update()

    def mouseMoveEvent(self, event):
        if self.drawing == True:
            self.rect = QRect(self.startPos, event.pos())
            self.update()

    def mouseReleaseEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.drawing = False

    def paintEvent(self, event):

        pen = QtGui.QPen()
        pen.setWidth(3)
        pen.setColor(QtGui.QColor(255, 0, 0))

        brush = QtGui.QBrush()
        brush.setColor(QtGui.QColor(255, 0, 0))
        brush.setStyle(Qt.SolidPattern)

        painter = QPainter(self)
        painter.drawImage(0, 0, self.image)
        painter.setBrush(brush)
        painter.setPen(pen)
        if not self.rect.isNull():
            painter.drawRect(self.rect)
        painter.end()

This involves a mouse press, move, then release.

That is great, I will be playing with it for sure!

What I pretended to do with this thread is learn how to paint shapes using PyQt5 and you helped so much. In fact what I am planning to do is to use this knowledge to paint the rectangles with the input of some coordinates that come from an Arduino connected through Serial on the USB port.

The problem I am finding now is that eventhough I found a way to read the Serial port with a little code below, I don’t have really an idea on how to insert this into the script you helped me build using PyQt5. This here is the code that I have been using to test the connection.

import serial

if __name__ == '__main__':
ser = serial.Serial('/dev/ttyUSB0', 9600, timeout=1)
ser.flush()
while True:
if ser.in_waiting > 0:
line = ser.readline().decode('utf-8').rstrip()
print(line)

(I think I learned how to insert code correctly in the post :slight_smile: )

I believe there is a function in Qt for Serial communication, but I have found it to be very high level for my poor knowledge.

The format I am sending the data from arduino is x;y being x and y integer positive number from 1 to 1500 that represent a coordinate set. I need to read those numbers, put them into a variable and use it with the Paint Event if the coordinates fall into the determined area in the function.

I don’t want to abuse, and maybe this should even be in a different thread…

Again, I thank you so much for the help!

Joan.

Sorry, what I mean is that I wanted to learn to paint a shape triggered by an event. I started with a simple action (mouse press) rather than starting with a communication solving topic…

Joan.

No problem. It’s best to start with something simple to understand the basics then approach the problem you really want to solve.

Yes. :smile: It takes some getting used to.

The main issue with writing code using GUI frameworks like Qt is that it conflicts with the “normal” way that people expect programs to run. In “normal” programs, you write code to do things directly. With frameworks like Qt, you write code to respond to events in the framework, then pass control to it – the app.exec() call to start the event loop in almost every application. Generally speaking, once you do that, your code is only run when the framework calls your methods.

This inversion of control means that a lot of code that is written with the “normal” programming model in mind won’t work with frameworks that assume that they have control. This is why using time.sleep to delay things in a Qt application won’t work the way most people expect it to.

The QtSerialPort module is probably the easiest way to read the serial port if you want to integrate that into a Qt application. You can use the serial module instead, but that doesn’t know anything about Qt’s event loop, so extra code needs to be written to make them work together nicely. Since the QSerialPort class is a subclass of QIODevice, it can be integrated into the application using the signals and slots mechanism.

Perhaps that would be best, as it would make it easier to refer back to it later.

Perfect! Will close it here. Thanks for all your support :relaxed: