I want to create a bar with minimum control features when minimizing the main application window, similar to the bar in the image below but in PyQt6.
Currently, I am creating a new small screen on the minimize action but I could not remove the bar which has the close, minimize and maximize buttons from it.
I eventually got it and I was able to write the below code to produce it
class FloatingDialogBar(QWidget):
def __init__(self, parent=None, project: str = None, task: str = None):
super(FloatingDialogBar, self).__init__(parent)
self.project = project if project else 'N/A'
self.task = task if task else 'N/A'
self.__init_ui()
self.__init_layouts()
def greetings(self):
# Do Something
pass
def mousePressEvent(self, event):
if event.button() == Qt.MouseButton.LeftButton:
self.offset = event.pos()
else:
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
if self.offset is not None and event.buttons() == Qt.MouseButton.LeftButton:
self.move(self.pos() + event.pos() - self.offset)
else:
super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
self.offset = None
super().mouseReleaseEvent(event)
def __init_ui(self):
self.setWindowFlags(
self.windowFlags() |
Qt.WindowType.FramelessWindowHint |
Qt.WindowType.WindowStaysOnTopHint
)
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
left = 100
top = 100
self.width = 320
height = 60
self.setGeometry(
left,
top,
self.width,
height,
)
self.setMaximumWidth(self.width)
self.setMinimumWidth(self.width)
self.setMinimumHeight(height)
self.setMaximumHeight(height)
pass
def __init_layouts(self):
self.frame = QFrame(self)
self.frame.setStyleSheet(consts.FLOATING_BAR_STYLESHEET)
# Create widgets
layout = QHBoxLayout()
self.lbl_project = QLabel(self.project)
self.lbl_project.setStyleSheet(consts.FLOATING_BAR_QLabel_STYLESHEET)
layout.addWidget(self.lbl_project)
self.lbl_task = QLabel(self.task)
self.lbl_task.setStyleSheet(consts.FLOATING_BAR_QLabel_STYLESHEET)
layout.addWidget(self.lbl_task)
layout.setContentsMargins(0, 0, 0, 0)
self.frame.setLayout(layout)
self.frame.setMaximumWidth(self.width)
self.frame.setMinimumWidth(self.width)
self.frame.setMinimumHeight(60)
self.frame.setMaximumHeight(60)
pass
Related
I have built a custom widget that is placed inside a custom scene. The custom scene rect resizes when the itemBoundingRect crosses the scene rect. In the beginning, the scene rectangle is set to (0, 0, 2000, 2000). I am able to resize my widgets properly inside this rectangle. The problem arises when I try to move the item against the top (i,e when the item moves in negative y-axis), The item altogether decreases the size on the y-axis when I try to increase it.
Here is the demonstration of the problem:
(Note: the scene resizes when the item is placed at any of the edges by a factor of 500. eg:- after first resize the scene rect would be (-500, -500, 2500, 2500) )
Here is the code:
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QGraphicsItem, QStyle, QGraphicsView
# ---------class size grip use to increase widget size----------#
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 resizeEvent(self, event):
super(Container, self).resizeEvent(event)
# ------------------ Creating custom item to place in scene--------------------#
class GraphicsFrame(QtWidgets.QGraphicsWidget):
def __init__(self):
super().__init__()
graphic_layout = QtWidgets.QGraphicsLinearLayout(Qt.Vertical, self)
self.container = Container()
proxyWidget = QtWidgets.QGraphicsProxyWidget()
proxyWidget.setWidget(self.container)
graphic_layout.addItem(proxyWidget)
self.pen = QtGui.QPen()
self.pen.setColor(Qt.red)
self.container.setMinimumSize(150, 150)
self.container.setMaximumSize(400, 800)
self.setFlag(QGraphicsItem.ItemIsSelectable, True)
self.setFlag(QGraphicsItem.ItemIsMovable, True)
self.container.resizeEvent = lambda _: self.resize()
self.container.startPos = None
def addWidget(self, widget):
self.container.layout().addWidget(widget)
def paint(self, qp, opt, widget):
qp.save()
self.pen.setWidth(3)
p = QtGui.QPainterPath()
p.addRoundedRect(self.boundingRect().adjusted(0, 0, -.5, -.5), 4, 4)
if self.isSelected():
self.pen.setColor(Qt.yellow)
qp.setBrush(Qt.transparent)
qp.setPen(self.pen)
qp.drawPath(p)
qp.setClipPath(p)
opt.state &= ~QStyle.State_Selected
super().paint(qp, opt, widget)
qp.restore()
def resize(self):
width = self.container.size().width()
height = self.container.size().height()
rect = QtCore.QRectF(self.pos().x(), self.pos().y(), width + 22, height + 22)
self.setGeometry(rect)
# -------------------- Custom view to hold the items -----------------#
class View(QtWidgets.QGraphicsView):
context_menu_signal = QtCore.pyqtSignal()
def __init__(self, bg_color=Qt.white):
super().__init__()
self.scene = Scene()
self.setRenderHints(QtGui.QPainter.Antialiasing)
self.setDragMode(self.RubberBandDrag)
self._isPanning = False
self._mousePressed = False
self.setCacheMode(self.CacheBackground)
self.setMouseTracking(True)
self.setScene(self.scene)
self.scene.selectionChanged.connect(self.selection_changed)
self._current_selection = []
texture = QtGui.QImage(30, 30, QtGui.QImage.Format_ARGB32)
qp = QtGui.QPainter(texture)
qp.setBrush(bg_color)
qp.setPen(QtGui.QPen(QtGui.QColor(189, 190, 191), 2))
qp.drawRect(texture.rect())
qp.end()
self.scene.setBackgroundBrush(QtGui.QBrush(texture))
self.setViewportUpdateMode(self.FullViewportUpdate) # This will avoid rendering artifacts
testFrame = GraphicsFrame()
newFrame = GraphicsFrame()
testFrame.addWidget(QtWidgets.QLineEdit())
newFrame.addWidget(QtWidgets.QLabel('Bruh'))
self.scene.addItem(testFrame)
self.scene.addItem(newFrame)
def wheelEvent(self, event):
# Save the scene pos
oldPos = self.mapToScene(event.pos())
if event.modifiers() == Qt.ControlModifier:
delta = event.angleDelta().y()
if delta > 0:
self.on_zoom_in()
elif delta < 0:
self.on_zoom_out()
# Get the new position
newPos = self.mapToScene(event.pos())
# Move scene to old position
delta = newPos - oldPos
self.translate(delta.x(), delta.y())
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self._mousePressed = True
if self._isPanning:
self.viewport().setCursor(Qt.ClosedHandCursor)
self._dragPos = event.pos()
event.accept()
else:
super().mousePressEvent(event)
elif event.button() == Qt.MidButton:
self._mousePressed = True
self._isPanning = True
self.viewport().setCursor(Qt.ClosedHandCursor)
self._dragPos = event.pos()
event.accept()
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
if self._mousePressed and self._isPanning:
newPos = event.pos()
diff = newPos - self._dragPos
self._dragPos = newPos
self.horizontalScrollBar().setValue(
self.horizontalScrollBar().value() - diff.x()
)
self.verticalScrollBar().setValue(
self.verticalScrollBar().value() - diff.y()
)
event.accept()
else:
super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
if event.button() == Qt.LeftButton:
if self._isPanning:
self.viewport().setCursor(Qt.OpenHandCursor)
else:
self._isPanning = False
self.viewport().unsetCursor()
self._mousePressed = False
zoomed = False if self.transform().m11() == 1.0 else True
self.scene.adjust(zoomed) # adjust the item scene rectangle
elif event.button() == Qt.MiddleButton:
self._isPanning = False
self.viewport().unsetCursor()
self._mousePressed = False
super().mouseReleaseEvent(event)
def select_items(self, items, on):
pen = QtGui.QPen(
QtGui.QColor(245, 228, 0) if on else Qt.white,
0.5,
Qt.SolidLine,
Qt.RoundCap,
Qt.RoundJoin,
)
for item in items:
item.pen = pen
def selection_changed(self):
try:
self.select_items(self._current_selection, False)
self._current_selection = self.scene.selectedItems()
self.select_items(self._current_selection, True)
except RuntimeError:
pass
def on_zoom_in(self):
if self.transform().m11() < 2.25:
self.scale(1.5, 1.5)
def on_zoom_out(self):
if self.transform().m11() > 0.7:
self.scale(1.0 / 1.5, 1.0 / 1.5)
def resizeEvent(self, event):
super().resizeEvent(event)
# ------------Custom scene which resizes the scene rect on when the item boundary hits the scene rect---------#
class Scene(QtWidgets.QGraphicsScene):
def __init__(self):
super(Scene, self).__init__()
self.setSceneRect(0, 0, 2000, 2000)
self.sceneRect().adjust(-20, -20, 20, 20)
self.old_rect = self.itemsBoundingRect()
def adjust(self, zoomed):
w = self.sceneRect().width()
h = self.sceneRect().height()
x = self.sceneRect().x()
y = self.sceneRect().y()
adjust_factor = 500
adjust_factor2 = 200
smaller = self.is_smaller()
self.old_rect = self.itemsBoundingRect()
if not self.sceneRect().contains(self.old_rect):
self.setSceneRect(-adjust_factor + x, -adjust_factor + y, adjust_factor + w, adjust_factor + h)
print(f'sceneRect: {self.sceneRect()}')
if not zoomed and smaller:
print('yes')
self.setSceneRect(adjust_factor2 + x, adjust_factor2 + y, abs(adjust_factor2 - w),
abs(adjust_factor2 - h))
def is_smaller(self):
x = self.old_rect.x()
y = self.old_rect.y()
h = self.old_rect.height()
w = self.old_rect.width()
if ((x <= self.itemsBoundingRect().x()) and (y <= self.itemsBoundingRect().y())
and (h > self.itemsBoundingRect().height()) and (w > self.itemsBoundingRect().width())):
return True
return False
# -----------main---------#
import sys
app = QtWidgets.QApplication(sys.argv)
w = View()
w.show()
sys.exit(app.exec_())
I know the code is lengthy but any help is appreciated.
The problem is a (partial) recursion caused by a wrong implementation of the object structure.
When the widget is resized using the size grip, it receives a resizeEvent that in your code is overridden by the GraphicsFrame.resize(): if you use setGeometry(), it will cause the graphics layout to resize its children, which in turn will call again a resize on the proxy widget. It's just a partial recursion because layout adjustments often require more than an event loop "cycle" (and with proxy widgets it might be even more).
The solution obviously is to avoid this recursion, which could be done by simply adding the proxy widget to the scene. Since the multiple item selection is required, this cannot be possible, as QGraphicsProxyWidgets are also panels (see the ItemIsPanel flag): only one panel graphics item can be active at once, so the only actual solution is to create a QGraphicsItem as a parent for the proxy. From that point, the geometry of the parent is based on the proxy, and the painting can be easily implemented.
class GraphicsFrame(QtWidgets.QGraphicsItem):
def __init__(self):
super().__init__()
self.container = Container()
self.container.setMinimumSize(150, 150)
self.container.setMaximumSize(400, 800)
self.proxy = QtWidgets.QGraphicsProxyWidget(self)
self.proxy.setWidget(self.container)
self.setFlags(self.ItemIsSelectable | self.ItemIsMovable)
def addWidget(self, widget):
self.container.layout().addWidget(widget)
def boundingRect(self):
# use the proxy for the bounding rect, adding the preferred margin
return self.proxy.boundingRect().adjusted(-11, -11, 11, 11)
def paint(self, qp, opt, widget):
qp.save()
qp.setPen(QtGui.QPen(Qt.yellow if self.isSelected() else Qt.red, 3))
qp.drawRoundedRect(self.boundingRect().adjusted(0, 0, -.5, -.5), 4, 4)
qp.restore()
My code is drawing lines on a QImage using mousePressEvent and mouseReleaseEvent. It works fine but I would like a dynamic preview line to appear when I'm drawing the said line (ie on MouseMoveEvent). Right now the line just appears when I release the left mouse button and I can't see what I'm drawing.
I want the preview of the line to appear and update as I move my mouse, and only "fixate" when I release the left mouse button. Exactly like the MS Paint Line tool : https://youtu.be/YIw9ybdoM6o?t=207
Here is my code (it is derived from the Scribble Example):
from PyQt5.QtCore import QPoint, QRect, QSize, Qt
from PyQt5.QtGui import QImage, QPainter, QPen, QColor, qRgb
from PyQt5.QtWidgets import QApplication, QWidget, QMainWindow
import sys
class DrawingArea(QWidget):
def __init__(self, parent=None):
super(DrawingArea, self).__init__(parent)
self.setAttribute(Qt.WA_StaticContents)
self.scribbling = False
self.myPenWidth = 1
self.myPenColor = QColor('#000000')
self.image = QImage()
self.startPoint = QPoint()
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self.startPoint = event.pos()
self.scribbling = True
def mouseReleaseEvent(self, event):
if event.button() == Qt.LeftButton and self.scribbling:
self.drawLineTo(event.pos())
self.scribbling = False
def paintEvent(self, event):
painter = QPainter(self)
dirtyRect = event.rect()
painter.drawImage(dirtyRect, self.image, dirtyRect)
def resizeEvent(self, event):
if self.width() > self.image.width() or self.height() > self.image.height():
newWidth = max(self.width() + 128, self.image.width())
newHeight = max(self.height() + 128, self.image.height())
self.resizeImage(self.image, QSize(newWidth, newHeight))
self.update()
super(DrawingArea, self).resizeEvent(event)
def drawLineTo(self, endPoint):
painter = QPainter(self.image)
painter.setPen(QPen(self.myPenColor, self.myPenWidth, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
painter.drawLine(self.startPoint, endPoint)
rad = self.myPenWidth / 2 + 2
self.update(QRect(self.startPoint, endPoint).normalized().adjusted(-rad, -rad, +rad, +rad))
def resizeImage(self, image, newSize):
if image.size() == newSize:
return
newImage = QImage(newSize, QImage.Format_RGB32)
newImage.fill(qRgb(255, 255, 255))
painter = QPainter(newImage)
painter.drawImage(QPoint(0, 0), image)
self.image = newImage
class MainWindow(QMainWindow):
def __init__(self, parent=None):
QMainWindow.__init__(self, parent)
self.setCentralWidget(DrawingArea())
self.show()
def main():
app = QApplication(sys.argv)
ex = MainWindow()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
I can't figure out how to show the preview of the line I'm drawing and I haven't found a suitable answer yet. How can I go about doing this ?
You can draw the lines just within the paintEvent() method instead than directly on the image, then paint on the image when the mouse is actually released.
class DrawingArea(QWidget):
def __init__(self, parent=None):
super(DrawingArea, self).__init__(parent)
self.setAttribute(Qt.WA_StaticContents)
self.scribbling = False
self.myPenWidth = 1
self.myPenColor = QColor('#000000')
self.image = QImage()
self.startPoint = self.endPoint = None
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self.startPoint = event.pos()
def mouseMoveEvent(self, event):
if self.startPoint:
self.endPoint = event.pos()
self.update()
def mouseReleaseEvent(self, event):
if self.startPoint and self.endPoint:
self.updateImage()
def paintEvent(self, event):
painter = QPainter(self)
dirtyRect = event.rect()
painter.drawImage(dirtyRect, self.image, dirtyRect)
if self.startPoint and self.endPoint:
painter.drawLine(self.startPoint, self.endPoint)
def updateImage(self):
if self.startPoint and self.endPoint:
painter = QPainter(self.image)
painter.setPen(QPen(self.myPenColor, self.myPenWidth, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
painter.drawLine(self.startPoint, self.endPoint)
painter.end()
self.startPoint = self.endPoint = None
self.update()
Note that you don't need to call update() within the resize event, as it's automatically called.
I also removed the unnecessary update rect calls, as it's almost useless in this case: specifying a rectangle in which the update should happen is usually done when very complex widgets are drawn (especially when lots of computations are executed to correctly draw everything and only a small part of the widget actually needs updates). In your case, it's almost more time consuming to compute the actual update rectangle than painting all the contents of the widget.
I think this page shows some really nice solutions for the problem of yours. For example, it shows how to implement a custom class which actually gives you a "drawing board":
class Canvas(QLabel):
def __init__(self):
super().__init__()
pixmap = QtGui.QPixmap(600, 300)
self.setPixmap(pixmap)
self.last_x, self.last_y = None, None
self.pen_color = QtGui.QColor('#000000')
def set_pen_color(self, c):
self.pen_color = QtGui.QColor(c)
def mouseMoveEvent(self, e):
if self.last_x is None: # First event.
self.last_x = e.x()
self.last_y = e.y()
return # Ignore the first time.
painter = QtGui.QPainter(self.pixmap())
p = painter.pen()
p.setWidth(1)
p.setColor(self.pen_color)
painter.setPen(p)
painter.drawLine(self.last_x, self.last_y, e.x(), e.y())
painter.end()
self.update()
# Update the origin for next time.
self.last_x = e.x()
self.last_y = e.y()
def mouseReleaseEvent(self, e):
self.last_x = None
self.last_y = None
You can use this Canvas class (or whatever name you would give it) everywhere you need. For example in the MainWindow:
class MainWindow(QMainWindow):
def __init__(self, parent=None):
QMainWindow.__init__(self, parent)
self.canvas = Canvas()
self.canvas.set_pen_color('#fffee5') # set the colour you want
self.setCentralWidget(self.canvas)
self.show()
Hope this could help! Happy coding! :)
User defined button that will hold an image and is moveable causes screen smearing when moved to the left and causes a screen smear to the right of the widget. Any ideas?
Image of smearing to the right of the playing card - vertical grey lines.
As i stated this only happens when moving the button to the left.
#!/usr/bin/python3
# -*- coding: utf-8 -*-
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from functools import partial
import datetime,psutil,sys
class playingCard(QPushButton):
def __init__(self, Text = '', parent = None):
super(playingCard, self).__init__()
self.ftop = 10
self.fleft = 10
self.fwidth = 87
self.fheight = 124
self.initUI()
def initUI(self):
self.setGeometry(self.fleft, self.ftop, self.fwidth+2, self.fheight+2)
self.setText('')
pixmap = QPixmap('clubs1.png')
pixmap = pixmap.scaled(self.fwidth,self.fheight, Qt.KeepAspectRatio, Qt.FastTransformation)
buttonicon = QIcon(pixmap)
self.setIcon(buttonicon)
self.setIconSize( QSize(self.fwidth,self.fheight))
self.setFixedSize( QSize(self.fwidth+2,self.fheight+2))
def mousePressEvent(self, event):
self.__mousePressPos = None
self.__mouseMovePos = None
if event.button() == Qt.LeftButton:
self.__mousePressPos = event.globalPos()
self.__mouseMovePos = event.globalPos()
super(playingCard, self).mousePressEvent(event)
def mouseMoveEvent(self, event):
if event.buttons() == Qt.LeftButton:
# adjust offset from clicked point to origin of widget
currPos = self.mapToGlobal(self.pos())
globalPos = event.globalPos()
diff = globalPos - self.__mouseMovePos
newPos = self.mapFromGlobal(currPos + diff)
self.move(newPos)
self.__mouseMovePos = globalPos
super(playingCard, self).mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
if self.__mousePressPos is not None:
moved = event.globalPos() - self.__mousePressPos
if moved.manhattanLength() > 3:
event.ignore()
return
super(playingCard, self).mouseReleaseEvent(event)
class Example(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def addAction(self,name,shortcut,status):
self.actions[name] = QAction(name, self)
self.actions[name].setShortcut(shortcut)
self.actions[name].setStatusTip(status)
if name == 'Exit':
self.actions[name].triggered.connect( self.close )
else:
self.actions[name].triggered.connect( partial(self.viewEvent,name) )
def hastab(self,tabname):
return self.tabWidget.findChild(QWidget, tabname) != None
def ontab(self,tabname):
currentIndex = self.tabWidget.currentIndex()
currentTitle = self.tabWidget.tabText(currentIndex)
return tabname == currentTitle
def gettab(self,tabname):
page = self.tabWidget.findChild(QWidget, tabname)
return self.tabWidget.indexOf(page)
def initUI(self):
self.actions = dict() # holds the QActions
self.tabs = dict()
self.tabWidget = QTabWidget()
self.tabWidget.setTabsClosable(True)
self.tabWidget.tabCloseRequested.connect(self.closeTab)
self.setCentralWidget(self.tabWidget)
self.addAction('Exit', 'Ctrl+Q','Exit application')
self.addAction('Game','Ctrl+G','Game')
self.statusBar()
menubar = self.menuBar()
fileMenu = menubar.addMenu('&File')
fileMenu.addAction( self.actions['Game'] )
fileMenu.addAction( self.actions['Exit'] )
self.setWindowTitle('Main window')
self.showMaximized()
def closeTab (self, currentIndex):
currentQWidget = self.tabWidget.widget(currentIndex)
title=self.tabWidget.tabText(currentIndex)
currentQWidget.deleteLater()
self.tabWidget.removeTab(currentIndex)
del self.tabs[title]
del self.tables[title]
self.timers[title].stop()
del self.timers[title]
def keyPressEvent(self, e):
currentIndex=self.tabWidget.currentIndex()
title = None
if currentIndex != -1:
title=self.tabWidget.tabText(currentIndex)
if e.key() == Qt.Key_F11:
if self.isMaximized():
self.showNormal()
else:
self.showMaximized()
def viewEvent(self, name):
if name in self.tabs:
return
self.tabs[name] = QWidget()
vbox = QVBoxLayout()
vbox.addWidget( playingCard() )
# Add box layout, add table to box layout and add box layout to widget
self.tabs[name].layout = vbox
self.tabs[name].setLayout(self.tabs[name].layout)
self.tabWidget.addTab(self.tabs[name],name)
def closeEvent(self, event):
reply = QMessageBox.question(self, 'Message',
"Are you sure to quit?", QMessageBox.Yes |
QMessageBox.No, QMessageBox.No)
if reply == QMessageBox.Yes:
event.accept()
else:
event.ignore()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
I have written a code to draw a rectangle on an image in a QGraphicsView but if I resize the image, for example full screen, the position of the rectangle becomes misplaced. Is there any way to fix this?
I think one possible solution is to align the image every time in top left corner but I cannot give 2 arguments in setAlignment().
Here is the code:(the buttons are just for show atm)
class MyWidget(QWidget):
def __init__(self):
super().__init__()
self.b1 = QPushButton('Next Image')
self.b2 = QPushButton('Crop')
self.b3 = QPushButton('Clear')
self.view = GraphicsView()
h_box = QHBoxLayout()
v_box = QVBoxLayout()
v_box.addWidget(self.b1)
v_box.addWidget(self.b2)
v_box.addWidget(self.b3)
h_box.addWidget(self.view)
h_box.addLayout(v_box)
self.setLayout(h_box)
#self.resize(800, 800)
self.setWindowTitle("Super Duper Cropper")
self.show()
class GraphicsView(QGraphicsView):
def __init__(self):
super().__init__()
self.setScene(QGraphicsScene())
self.item = QGraphicsPixmapItem(QPixmap('test.jpg'))
self.scene().addItem(self.item)
def mousePressEvent(self, event):
self.xi = event.x()
self.yi = event.y()
def mouseMoveEvent(self, event):
self.xf = event.x()
self.yf = event.y()
self.draw_rect()
def mouseReleaseEvent(self, event):
self.xf = event.x()
self.yf = event.y()
self.draw_rect()
def draw_rect(self):
self.scene().removeItem(self.item)
self.scene().addItem(self.item)
self.scene().addRect(self.xi, self.yi, self.xf-self.xi, self.yf-self.yi, pen=QPen(QColor(51, 153, 255), 2,
Qt.SolidLine), brush=QBrush(QColor(0, 255, 0, 40)))
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MyWidget()
window.show()
app.aboutToQuit.connect(app.deleteLater)
sys.exit(app.exec_())
You have the following errors:
The coordinates of the scene are different from the coordinates of the view, so you must use the mapToScene() method if you want to establish the right position of the rectangle.
Why do you add and remove the items? the best thing is to reuse
You want the position of the rectangle to be relative to the QGraphicsPixmapItem, so the rectangle has to be a child of the QGraphicsPixmapItem.
Using the above we obtain the following:
class GraphicsView(QGraphicsView):
def __init__(self):
super().__init__()
self.setScene(QGraphicsScene())
self.item = QGraphicsPixmapItem(QPixmap('test.jpg'))
self.scene().addItem(self.item)
self.rect_item = QGraphicsRectItem(QRectF(), self.item)
self.rect_item.setPen(QPen(QColor(51, 153, 255), 2, Qt.SolidLine))
self.rect_item.setBrush(QBrush(QColor(0, 255, 0, 40)))
def mousePressEvent(self, event):
self.pi = self.mapToScene(event.pos())
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
pf = self.mapToScene(event.pos())
if (self.pi - pf).manhattanLength() > QApplication.startDragDistance():
self.pf = pf
self.draw_rect()
super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
pf = self.mapToScene(event.pos())
if (self.pi - pf).manhattanLength() > QApplication.startDragDistance():
self.pf = pf
self.draw_rect()
super().mouseReleaseEvent(event)
def draw_rect(self):
r = QRectF(self.pi, self.pf).normalized()
r = self.rect_item.mapFromScene(r).boundingRect()
self.rect_item.setRect(r)
thanks in advance for the replies.... I have started to create a network topology program which works in the same way as cisco packet tracer or GNS3... I have used Python for a number of projects but using the wxPython library for the first time is proving to be a huge handful when it comes to understanding what functions to use.
The user has a variety of different buttons such as a router button, switch, host computers and server button etc when they click this button I would like the image on the button to appear in a editable area. (somthing similar to paint) The user can then move it within this area using the wxPython drag and drop function. I cant get my head around what to use for this area.... I have tryed searching in google for 'wxPython how to create a paint program' etc but still no luck.
Please could someone suggest how they would make this program?
Karl
Here is the excerpt from the wxpython (wxpython in action by Robin Dunn) book which shows how to use paint. Hope this helps:
import wx
class SketchWindow(wx.Window):
def __init__(self, parent, ID):
wx.Window.__init__(self, parent, ID)
self.SetBackgroundColour("White")
self.color = "Black"
self.thickness = 1
self.pen = wx.Pen(self.color, self.thickness, wx.SOLID)
self.lines = []
self.curLine = []
self.pos = (0, 0)
self.InitBuffer()
self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
self.Bind(wx.EVT_MOTION, self.OnMotion) self.Bind(wx.EVT_SIZE,
self.OnSize) self.Bind(wx.EVT_IDLE, self.OnIdle)
self.Bind(wx.EVT_PAINT, self.OnPaint)
def InitBuffer(self):
size = self.GetClientSize()
self.buffer = wx.EmptyBitmap(size.width, size.height)
dc = wx.BufferedDC(None, self.buffer)
dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
dc.Clear()
self.DrawLines(dc)
self.reInitBuffer = False
def GetLinesData(self):
return self.lines[:]
def SetLinesData(self, lines):
self.lines = lines[:]
self.InitBuffer()
self.Refresh()
def OnLeftDown(self, event):
self.curLine = []
self.pos = event.GetPositionTuple()
self.CaptureMouse()
def OnLeftUp(self, event):
if self.HasCapture():
self.lines.append((self.color,
self.thickness,
self.curLine))
self.curLine = []
self.ReleaseMouse()
def OnMotion(self, event):
if event.Dragging() and event.LeftIsDown():
dc = wx.BufferedDC(wx.ClientDC(self), self.buffer)
self.drawMotion(dc, event)
event.Skip()
def drawMotion(self, dc, event):
dc.SetPen(self.pen)
newPos = event.GetPositionTuple()
coords = self.pos + newPos
self.curLine.append(coords)
dc.DrawLine(*coords)
self.pos = newPos
def OnSize(self, event):
self.reInitBuffer = True
def OnIdle(self, event):
if self.reInitBuffer:
self.InitBuffer()
self.Refresh(False)
def OnPaint(self, event):
dc = wx.BufferedPaintDC(self, self.buffer)
def DrawLines(self, dc):
for colour, thickness, line in self.lines:
pen = wx.Pen(colour, thickness, wx.SOLID)
dc.SetPen(pen)
for coords in line:
dc.DrawLine(*coords)
def SetColor(self, color):
self.color = color
self.pen = wx.Pen(self.color, self.thickness, wx.SOLID)
def SetThickness(self, num):
self.thickness = num
self.pen = wx.Pen(self.color, self.thickness, wx.SOLID)
class SketchFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, -1, "Sketch Frame",
size=(800,600))
self.sketch = SketchWindow(self, -1)
if __name__ == '__main__':
app = wx.PySimpleApp()
frame = SketchFrame(None)
frame.Show(True)
app.MainLoop()