How to fix PyQgis layout resizing problem when making window bigger? - python

I'm working on creating python plugin that implements image viewer and use layout to plased elements in correct plases. I want to resize all elements in dialog window of this plugin using resizeEvent of QDialog, but I have one strange problem: when I want to make dialog window smaller - all works good like on image1, but, if I want to make window bigger I have seen a problem like on image2:
Here is my resizeEvent method for my dialog:
def resizeEvent(self, a0: QResizeEvent):
geom = list(self.geometry().getCoords())
x_shift = geom[2] - geom[0] #transform x coord from monitor coords to dialog window coords
y_shift = geom[3] - geom[1] #transform y coord from monitor coords to dialog window coords
self.verticalLayout.setGeometry(QRect(0, 0, x_shift, y_shift))
And here is my code how I connect my ui with python code using uic:
import os
from qgis.PyQt import uic
from qgis.PyQt import QtWidgets
FORM_CLASS, _ = uic.loadUiType(os.path.join(
os.path.dirname(__file__), 'name_of_ui'))
class nameDialog(QtWidgets.QDialog, FORM_CLASS):
def __init__(self, parent=None):
super(status_checkerDialog, self).__init__(parent)
self.setupUi(self)
In this script, I create the resize function. Also, I would like to add that I use to build the skeleton of the module named Plugin Builder. Maybe this information will help you point out exactly where I am making a mistake. I also add the code skeleton that implements all functions of the module:
class status_checker:
def __init__(self, iface):
"Constructor"
def add_action(
self,
icon_path,
text,
callback,
enabled_flag=True,
add_to_menu=True,
add_to_toolbar=True,
status_tip=None,
whats_this=None,
parent=None):
"""Add a toolbar icon to the toolbar"""
def initGui(self):
"""Create the menu entries and toolbar icons inside the QGIS GUI"""
def unload(self):
"""Removes the plugin menu item and icon from QGIS GUI."""
def run(self):
"""Run method that performs all the real work"""
# Create the dialog with elements (after translation) and keep reference
# Only create GUI ONCE in callback, so that it will only load when the plugin is started
if self.first_start == True:
self.first_start = False #self.first_start is a variable that is created in the initialization function and initially set to true
self.dlg = status_checkerDialog()

Related

PyQt5 Place QImage in a widget

I created a small GUI that allows me to draw a number. That number is supposed to be classified with a CNN. The CNN is not connected to this GUI yet. Will do that later on. I am still very new to PyQt5 and used some code that i found online for the drawing with QImage. It works, but at the moment i can draw all over the GUI. Is it possible to place that in a widget? So that I can only draw inside a specific frame and not all over the GUI?
So basically how can i get the self.image = QImage(...) iside a widget or something that i created earlier on my GUI? Is that possible somehow or would you even suggest to solve it in totaly different way?
import sys
from PyQt5 import QtWidgets
from PyQt5.QtGui import QIcon, QImage, QPainter, QPen, QBrush
from PyQt5.QtCore import Qt, QPoint
from UI.mainwindow_2 import Ui_MainWindow
import matplotlib.pyplot as plt
import numpy as np
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.image = QImage(self.size(), QImage.Format_RGB32)
self.image.fill(Qt.white)
self.drawing = False
self.brushSize = 28
self.brushColor = Qt.black
self.lastPoint = QPoint()
self.ui.Clear.clicked.connect(self.clear)
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self.drawing = True
self.lastPoint = event.pos()
def mouseMoveEvent(self, event):
if(event.buttons() & Qt.LeftButton) & self.drawing:
painter = QPainter(self.image)
painter.setPen(QPen(self.brushColor, self.brushSize, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
painter.drawLine(self.lastPoint, event.pos())
self.lastPoint = event.pos()
self.update()
def mouseReleaseEvent(self, event):
if event.button() == Qt.LeftButton:
self.drawing = False
def paintEvent(self, event):
canvasPainter = QPainter(self)
canvasPainter.drawImage(self.rect(),self.image, self.image.rect())
def clear(self):
self.image.fill(Qt.white)
self.update()
def main():
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()
First of all, the following aspects must be considered:
paint events are received by any (visible) QWidget subclass
painting is always constricted to the geometry of the widget
painting always happens from the bottom to the top, so whenever a widget has some children, whatever has been painted on that parent widget will be potentially covered by those children
So, the first thing to do is to implement the paintEvent only on the actual widget for which you want to paint. Since you're using Designer, this makes things a bit more complex, as there is no way to subclass a widget that already exists in an ui.
Luckily, Qt has a concept called "promoted widgets": it allows to "expand" a certain widget by specifying the custom subclass that will be actually used when the ui will be finally generated in the program.
choose which widget in your ui will be used for painting; I suggest you to start from a basic QWidget, I'll explain more about this later;
right click on it, and select "Promote to..." from the context menu;
in the "Promoted class name" type the exact class name you are going to use (let's say "Canvas");
in the "Header file" field, type the file name that will contain that class, as it would appear in an import statement (meaning that it should not have the py extension!); assuming you want to do everything in a single script and your script is named "mycanvas.py", type "mycanvas" in that field;
ensure that the "Base class name" combo is set to the exact class type of the widget you've chosen (QWidget, in this case, which is usually automatically selected)
click "Add" and then "Promote";
save the ui and generate the file with pyuic;
Now, the implementation of your mycanvas.py file is simple:
class Canvas(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.image = QImage(self.size(), QImage.Format_RGB32)
self.image.fill(Qt.white)
self.drawing = False
self.brushSize = 28
self.brushColor = Qt.black
self.lastPoint = QPoint()
# ... all the painting related methods, as you did in your code
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.ui.Clear.clicked.connect(self.ui.canvas.clear)
def main():
# ...
Two considerations:
a QFrame usually has some borders, and since painting can always happen within the whole widget size, you might end up painting on the borders too. If you want a QFrame and paint inside that widget, then add a QWidget as a child to that frame and ensure that a layout is set for the frame;
setting the QImage size within the init is not a good approach, as the widget might change its size or it could be initialized with a size smaller than it will eventually have; you either set a fixed size in the __init__ before creating the QImage, or you keep track of the points/lines in an internal container and continuously draw the contents in the paintEvent;

Setting button signals for widget created via wrapper function

I am trying to create a GUI (see attached screenshot) where it has about 6 sets of widgets where each set comprises of QLabel, QLineEdit and QPushButton.
Instead of writing the widgets and the layout again and again, I thought of writing up a wrapper function such that I will only need to write up 6 lines for the creation.
However, while this works efficiently for me in terms of the widgets creation, I realized that I am not able to set the Signal for the button(s) as I do not know the widget's names.
What will be the best way to set the signals if I decided to use this wrapper methodology?
The following is a screenshot of what I am trying to achieve as well as my code:
class MyDevTool(QtGui.QWidget):
def __init__(self, parent=None):
super(MyDevTool, self).__init__(parent)
self._create_ui()
def _create_hbox_layout(self, label):
h_layout = QtGui.QHBoxLayout()
label = QtGui.QLabel(label)
label.setFixedWidth(80)
line_edit = QtGui.QLineEdit()
push_btn = QtGui.QPushButton("<<<")
h_layout.addWidget(label)
h_layout.addWidget(line_edit)
h_layout.addWidget(push_btn)
return h_layout
def _create_ui(self):
main_layout = QtGui.QVBoxLayout()
main_layout.addLayout(
self._create_hbox_layout("Input Directory")
)
main_layout.addLayout(
self._create_hbox_layout("Output Directory")
)
self.setLayout(main_layout)

how to create a new window automatically in PyQt5?

It's weird that a function to create a new window works when I use a button to call it and didn't work when something satisfy some condition to call it.
from PyQt5 import QtWidgets, QtCore
from interface import Ui_Form as uiInterface
from chooseLauncherFile import Ui_Form as uiChooseLauncherFile
class MyInterface(uiInterface):
def __init__(self):
self.window = QtWidgets.QWidget()
self.setupUi(self.window)
self.threads = QtCore.QThread()
self.threads.run = self.init
self.threads.start()
self.window.show()
def init(self):
self.util = Util(self)
self.util.detectLauncher()
self.syncToServer() #this function should run after main window show
def chooseLauncherFile(self):
self.chooseLauncherWindow = QtWidgets.QWidget()
self.chooseLauncherUi=MyChooseLauncherFile()
self.chooseLauncherUi.setupUi(self.chooseLauncherWindow)
self.chooseLauncherWindow.show()
class MyChooseLauncherFile(uiChooseLauncherFile):
def confirm(self, item):
EXEC_FILE = item.text()
class Util():
def __init__(self, interface):
self.interface = interface
def detectLauncher(self):
if True: #has been simplified here
self.interface.chooseLauncherFile()
these code will make the new child window non-response, but it will be ok when I change the code like following
def init(self):
self.syncToServer() #this function should run after main window show
self.pushButton.pressed.connect(self.chooseLauncherFile)
#this line come from another file
#to use a button to call function
It's highly appreciate if you could help me make code of top works, in another world, create a new window automatically not using button.

treatment of mouse events opencv gui vs pyqt

I was working with OpenCV gui functions for a while, and the possibilities are a little restricting for python users. Today I started with Pyqt and come across the following conclusion: qt is really confusing.
Now the question concerning mouse events:
In OpenCV I just do the following:
import cv2
cv2.namedWindow('Window',1)
def CallBackFunc(event,x,y,flags,param):
global xc,yc,evt,flg
xc,yc,evt,flg=x,y,event,flags
cv2.setMouseCallback('Window', CallBackFunc)
This opens a seperate thread, which constantly refreshes the global variables xc,yc,evt,flg, and I can access them anywhere, at anytime I want. If I want to stop the refreshing, I just do a cv2.setMouseCallback('Window',nothing), whereby nothing is
def nothing():
pass
It may not be the most beautiful way of dealing with mouse events, but I am fine with it. How can I achieve such freedom with PyQt?
EDIT:
For example, the following script is displaying a white circle, and constantly drawing a text into it.
import sys
from PySide import QtGui
import numpy as np
import cv2
class QCustomLabel (QtGui.QLabel):
def __init__ (self, parent = None):
super(QCustomLabel, self).__init__(parent)
self.setMouseTracking(True)
def mouseMoveEvent (self, eventQMouseEvent):
self.x,self.y=eventQMouseEvent.x(),eventQMouseEvent.y()
cvImg=np.zeros((900,900),dtype=np.uint8)
cv2.circle(cvImg,(449,449),100,255,-1)
cv2.putText(cvImg,"x at {}, y at {}".format(self.x,self.y),(375,455), cv2.FONT_HERSHEY_SIMPLEX,0.5,(0,0,0),1,cv2.LINE_AA)
height, width= cvImg.shape
bytearr=cvImg.data
qImg = QtGui.QImage(bytearr, width, height, QtGui.QImage.Format_Indexed8)
self.setPixmap(QtGui.QPixmap.fromImage(qImg))
def mousePressEvent (self, eventQMouseEvent):
self.evt=eventQMouseEvent.button()
class QCustomWidget (QtGui.QWidget):
def __init__ (self, parent = None):
super(QCustomWidget, self).__init__(parent)
self.setWindowOpacity(1)
# Init QLabel
self.positionQLabel = QCustomLabel(self)
# Init QLayout
layoutQHBoxLayout = QtGui.QHBoxLayout()
layoutQHBoxLayout.addWidget(self.positionQLabel)
self.setLayout(layoutQHBoxLayout)
self.show()
if QtGui.QApplication.instance() is not None:
myQApplication=QtGui.QApplication.instance()
else:
myQApplication = QtGui.QApplication(sys.argv)
myQTestWidget = QCustomWidget()
myQTestWidget.show()
myQApplication.exec_()
The problem here is, that this is all executed inside the QCustomLabel Class, and inside the MouseMoveEvent function. But I want a seperate function, lets call it drawCircle, outside of that class, which has access to the mouse position and events. With opencv this would be no problem at all. And it would take only a fraction of the writing effort, which is needed for a pyqt implementation.
I think the right question is: Why dont I like pyqt yet?
You can use an event-filter to avoid having to subclass the QLabel:
class QCustomWidget (QtGui.QWidget):
def __init__ (self, parent = None):
super(QCustomWidget, self).__init__(parent)
self.setWindowOpacity(1)
# Init QLabel
self.positionQLabel = QtGui.QLabel(self)
self.positionQLabel.setMouseTracking(True)
self.positionQLabel.installEventFilter(self)
# Init QLayout
layoutQHBoxLayout = QtGui.QHBoxLayout()
layoutQHBoxLayout.addWidget(self.positionQLabel)
self.setLayout(layoutQHBoxLayout)
self.show()
def eventFilter(self, source, event):
if event.type() == QtCore.QEvent.MouseMove:
self.drawCircle(event.x(), event.y())
return super(QCustomWidget, self).eventFilter(source, event)
def drawCircle(self, x, y):
# whatever

How to reference currently open dialog box?

I'm creating an app using Eric4 and PyQt4.
I have two dialog boxes, one runs as a thread and the other is a standard dialog box with a label inside, which I want to change to an image.
Every time the main window thread runs I want it to change the current image displayed in the dialog box to a new image. Everything works fine except every time the thread runs it creates a new dialog box with the new image inside - I want it to change the image in the dialog box that's currently open.
Dialog box with image inside:
class SubWindow(QDialog, Ui_subWindow):
def __init__(self, parent = None):
QDialog.__init__(self, parent)
self.setupUi(self)
self.show()
def main(self, img):
pic = self.imgView
pic.setPixmap(QtGui.QPixmap(os.getcwd() + img))
Thread which changes the image:
class MainWindow(QDialog, Ui_MainWindow, threading.Thread):
def __init__(self, parent = None):
threading.Thread.__init__(self)
QDialog.__init__(self, parent)
self.setupUi(self)
self.show()
#some code here which does some stuff then calls changeImg()
def changeImg(self):
img = SubWindow()
img.main(img)
I've not included all my code, only the relevant bits.
It looks like the problem is that you are creating a new SubWindow every time you wish to change the image. I would recommend creating the SubWindow as an attribute to MainWindow in the MainWindiw.__init__ function:
class MainWindow(QDialog, Ui_MainWindow, threading.Thread):
def __init__(self, parent = None):
threading.Thread.__init__(self)
QDialog.__init__(self, parent)
self.setupUi(self)
self.show()
self.img = SubWindow() # Create SubWindow once here
def changeImg(self):
self.img.main(self.img) # Only change the image, no new SubWindow

Categories

Resources