Related
I have created a custom QGraphicsWidget with the ability to resize the widget in the scene. I can also add predefined widgets such as buttons, labels, etc. to my custom widget. I now have two problems.
The first being that the widget doesn't change the size (to re-adjust) upon inserting a new label or LineEdit widget as a result newly inserted widget stays out of the custom widget border.
The second problem is encountered when I try to change the setContentMargins of the QGraphicsLayout to something other than 0. For example QGraphicsLayout.setContentMargins(1, 1, 1, 20) will delay the cursor in the LineEdit widget.
Here is the image.
(Drag the grey triangle to change size)
import sys
from PyQt5 import QtWidgets, QtCore, QtGui, Qt
from PyQt5.QtCore import Qt, QRectF, QPointF
from PyQt5.QtGui import QBrush, QPainterPath, QPainter, QColor, QPen, QPixmap
from PyQt5.QtWidgets import QGraphicsRectItem, QApplication, QGraphicsView, QGraphicsScene, QGraphicsItem
class Container(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.layout = QtWidgets.QVBoxLayout(self)
self.layout.setContentsMargins(0, 0, 0, 0)
self.layout.setSpacing(0)
self.setStyleSheet('Container{background:transparent;}')
class GraphicsFrame(QtWidgets.QGraphicsWidget):
def __init__(self, *args, **kwargs):
super(GraphicsFrame, self).__init__()
x, y, h, w = args
rect = QRectF(x, y, h, w)
self.setGeometry(rect)
self.setMinimumSize(150, 150)
self.setMaximumSize(400, 800)
self.setAcceptHoverEvents(True)
self.setFlag(QGraphicsItem.ItemIsMovable, True)
self.setFlag(QGraphicsItem.ItemIsSelectable, True)
self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)
self.setFlag(QGraphicsItem.ItemIsFocusable, True)
self.mousePressPos = None
self.mousePressRect = None
self.handleSelected = None
self.polygon = QtGui.QPolygon([
QtCore.QPoint(int(self.rect().width()-10), int(self.rect().height()-20)),
QtCore.QPoint(int(self.rect().width()-10), int(self.rect().height()-10)),
QtCore.QPoint(int(self.rect().width()-20), int(self.rect().height()-10))
])
graphic_layout = QtWidgets.QGraphicsLinearLayout(Qt.Vertical, self)
graphic_layout.setContentsMargins(0, 0, 0, 20) # changing this will cause the second problem
self.container = Container()
proxyWidget = QtWidgets.QGraphicsProxyWidget(self)
proxyWidget.setWidget(self.container)
graphic_layout.addItem(proxyWidget)
self.contentLayout = QtWidgets.QFormLayout()
self.contentLayout.setContentsMargins(10, 10, 20, 20)
self.contentLayout.setSpacing(5)
self.container.layout.addLayout(self.contentLayout)
self.options = []
def addOption(self, color=Qt.white, lbl=None, widget=None):
self.insertOption(-1, lbl, widget, color)
def insertOption(self, index, lbl, widget, color=Qt.white):
if index < 0:
index = self.contentLayout.count()
self.contentLayout.addRow(lbl, widget)
self.options.insert(index, (widget, color))
def update_polygon(self):
self.polygon = QtGui.QPolygon([
QtCore.QPoint(int(self.rect().width() - 10), int(self.rect().height() - 20)),
QtCore.QPoint(int(self.rect().width() - 10), int(self.rect().height() - 10)),
QtCore.QPoint(int(self.rect().width() - 20), int(self.rect().height() - 10))
])
def hoverMoveEvent(self, event):
if self.polygon.containsPoint(event.pos().toPoint(), Qt.OddEvenFill):
self.setCursor(Qt.SizeFDiagCursor)
else:
self.unsetCursor()
super(GraphicsFrame, self).hoverMoveEvent(event)
def mousePressEvent(self, event):
self.handleSelected = self.polygon.containsPoint(event.pos().toPoint(), Qt.OddEvenFill)
if self.handleSelected:
self.mousePressPos = event.pos()
self.mousePressRect = self.boundingRect()
super(GraphicsFrame, self).mousePressEvent(event)
def mouseMoveEvent(self, event):
if self.handleSelected:
self.Resize(event.pos())
else:
super(GraphicsFrame, self).mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
super(GraphicsFrame, self).mouseReleaseEvent(event)
self.handleSelected = False
self.mousePressPos = None
self.mousePressRect = None
self.update()
def paint(self, painter, option, widget):
painter.save()
painter.setBrush(QBrush(QColor(37, 181, 247)))
pen = QPen(Qt.white)
pen.setWidth(2)
if self.isSelected():
pen.setColor(Qt.yellow)
painter.setPen(pen)
painter.drawRoundedRect(self.rect(), 4, 4)
painter.setPen(QtCore.Qt.white)
painter.setBrush(QtCore.Qt.gray)
painter.drawPolygon(self.polygon)
super().paint(painter, option, widget)
painter.restore()
def Resize(self, mousePos):
"""
Perform shape interactive resize.
"""
if self.handleSelected:
self.prepareGeometryChange()
width, height = self.geometry().width()+(mousePos.x()-self.mousePressPos.x()),\
self.geometry().height()+(mousePos.y()-self.mousePressPos.y())
self.setGeometry(QRectF(self.geometry().x(), self.geometry().y(), width, height))
self.contentLayout.setGeometry(QtCore.QRect(0, 30, width-10, height-20))
self.mousePressPos = mousePos
self.update_polygon()
self.updateGeometry()
def main():
app = QApplication(sys.argv)
grview = QGraphicsView()
scene = QGraphicsScene()
grview.setViewportUpdateMode(grview.FullViewportUpdate)
scene.addPixmap(QPixmap('01.png'))
grview.setScene(scene)
item = GraphicsFrame(0, 0, 300, 150)
scene.addItem(item)
item.addOption(Qt.green, lbl=QtWidgets.QLabel('I am a label'), widget=QtWidgets.QLineEdit())
item.addOption(lbl=QtWidgets.QLabel('why'), widget=QtWidgets.QLineEdit())
item.addOption(lbl=QtWidgets.QLabel('How'), widget=QtWidgets.QLineEdit())
item.addOption(lbl=QtWidgets.QLabel('Nooo.'), widget=QtWidgets.QLineEdit())
item.addOption(lbl=QtWidgets.QLabel('Nooo.'), widget=QtWidgets.QLineEdit())
item.addOption(lbl=QtWidgets.QLabel('Nooo.'), widget=QtWidgets.QLineEdit())
item2 = GraphicsFrame(50, 50, 300, 150)
scene.addItem(item2)
grview.fitInView(scene.sceneRect(), Qt.KeepAspectRatio)
grview.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
As already suggested to you more than once, using a QGraphicsWidget with a QGraphicsLayout is not a good idea if you are only using it to embed a QGraphicsProxyWidget, as you will certainly have to face unexpected behavior when changing geometries unless you really know what you're doing.
Then, prepareGeometryChange and updateGeometry are completely unnecessary for QGraphicsWidget, and resizing the widget using the item geometries is absolutely wrong for two reasons: first of all, it's up the graphics layout to manage the content size, then you're using scene coordinates, and since you're using scaling, those coordinates will not be correct as they should be transformed in widget's coordinate.
Since using a QSizeGrip is not doable due to the continuously changing scene rect (which, I have to say, is not always a good idea if done along with interactive resizing of contents), you can use a simple QGraphicsPathItem for it, and use that as a reference for the resizing, which is far more simple than continuously move the polygon and draw it.
class SizeGrip(QtWidgets.QGraphicsPathItem):
def __init__(self, parent):
super().__init__(parent)
path = QtGui.QPainterPath()
path.moveTo(0, 10)
path.lineTo(10, 10)
path.lineTo(10, 0)
path.closeSubpath()
self.setPath(path)
self.setPen(QtGui.QPen(Qt.white))
self.setBrush(QtGui.QBrush(Qt.white))
self.setCursor(Qt.SizeFDiagCursor)
class GraphicsFrame(QtWidgets.QGraphicsItem):
def __init__(self, *args, **kwargs):
super(GraphicsFrame, self).__init__()
x, y, w, h = args
self.setPos(x, y)
self.setAcceptHoverEvents(True)
self.setFlag(QGraphicsItem.ItemIsMovable, True)
self.setFlag(QGraphicsItem.ItemIsSelectable, True)
self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)
self.setFlag(QGraphicsItem.ItemIsFocusable, True)
self.container = Container()
self.proxy = QtWidgets.QGraphicsProxyWidget(self)
self.proxy.setWidget(self.container)
self.proxy.setMinimumSize(150, 150)
self.proxy.setMaximumSize(400, 800)
self.proxy.resize(w, h)
self.contentLayout = QtWidgets.QFormLayout()
self.contentLayout.setContentsMargins(10, 10, 20, 20)
self.contentLayout.setSpacing(5)
self.container.layout.addLayout(self.contentLayout)
self.options = []
self.sizeGrip = SizeGrip(self)
self.mousePressPos = None
self.proxy.geometryChanged.connect(self.resized)
self.resized()
def addOption(self, color=Qt.white, lbl=None, widget=None):
self.insertOption(-1, lbl, widget, color)
def insertOption(self, index, lbl, widget, color=Qt.white):
if index < 0:
index = self.contentLayout.count()
self.contentLayout.addRow(lbl, widget)
self.options.insert(index, (widget, color))
def mousePressEvent(self, event):
gripShape = self.sizeGrip.shape().translated(self.sizeGrip.pos())
if event.button() == Qt.LeftButton and gripShape.contains(event.pos()):
self.mousePressPos = event.pos()
else:
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
if self.mousePressPos:
delta = event.pos() - self.mousePressPos
geo = self.proxy.geometry()
bottomRight = geo.bottomRight()
geo.setBottomRight(bottomRight + delta)
self.proxy.setGeometry(geo)
diff = self.proxy.geometry().bottomRight() - bottomRight
if diff.x():
self.mousePressPos.setX(event.pos().x())
if diff.y():
self.mousePressPos.setY(event.pos().y())
else:
super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
self.mousePressPos = None
super().mouseReleaseEvent(event)
def resized(self):
rect = self.boundingRect()
self.sizeGrip.setPos(rect.bottomRight() + QtCore.QPointF(-20, -20))
def boundingRect(self):
return self.proxy.boundingRect().adjusted(-11, -11, 11, 11)
def paint(self, painter, option, widget):
painter.save()
painter.setBrush(QBrush(QColor(37, 181, 247)))
painter.drawRoundedRect(self.boundingRect().adjusted(0, 0, -.5, -.5), 4, 4)
painter.restore()
Do note that using fitInView() before showing the view is not a good idea, especially if using proxy widgets and layouts.
I have a QGraphicsScene where I add QRectF objects anchored to QWidget objects in order to move them. I'd need to capture events or signals from the QRectF but the mousePressEvent method never runs.
These objects have a sort of balance and it would be hard to replace the QRectF with a QRect or a QGraphicsRectItem, because drawing the base rect in the scene only that class is accepted.
I also tried to implement the mousePressEvent method is GraphicBlock class (which is a QWidget) but nothing happens.
This is my QRectF
class BlockRect(QRectF):
def __init__(self, x, y, dim1, dim2, block_type):
super(QRectF, self).__init__(x, y, dim1, dim2)
self.block_type = block_type
def contains(self, point):
if self.x() + self.width() \
> point.x() > self.x() - self.width()/2:
if self.y() + self.height() \
> point.y() > self.y() - self.height()/2:
return True
return False
# Never called
def mousePressEvent(self, event):
print("click!")
dialog = MyDialog(self.block_type)
dialog.exec()
super(BlockRect, self).mouseDoubleClickEvent(event)
And this is the method where I draw it:
def draw_block(self, block_type):
"""Drawing a graphic clock with its properties"""
# Setting the visible scene
viewport_rect = QRect(0, 0, self.view.viewport().width(),
self.view.viewport().height())
viewport = self.view.mapToScene(viewport_rect).boundingRect()
start_x = viewport.x()
start_y = viewport.y()
# The initial point of each block is translated of 20px in order not to
# overlap them (always in the visible area)
point = QPoint(start_x + 20*(self.numBlocks % 20) + 20,
start_y + 20*(self.numBlocks % 20) + 20)
transparent = QColor(0, 0, 0, 0)
# Creation of the graphic block
block = GraphicBlock(self.numBlocks, block_type, 0, self.scene)
# Positioning the block
proxy = self.scene.addWidget(block)
proxy.setPos(point.x(), point.y())
# Creation of the rect that will be parent of the QWidget GraphicBlock
# in order to move it in the QGraphicsScene
rect = BlockRect(point.x() + 10, point.y() + 10,
block.width() - 20, block.height() - 20,
block_type)
# The rect is added to the scene and becomes the block's parent
proxy_control = self.scene.addRect(rect, QPen(transparent), QBrush(transparent))
proxy_control.setFlag(QGraphicsItem.ItemIsMovable, True)
proxy_control.setFlag(QGraphicsItem.ItemIsSelectable, True)
proxy.setParentItem(proxy_control)
block.set_rect(rect)
self.blocks[self.numBlocks] = block
self.numBlocks += 1
self.update()
I really don't know or understand how i could capture events in some way.
Here it is my QWidget class, i.e. GraphicBlock, which do have event methods but doesn't execute them. I think I should control events from the QGraphicsScene object.
class GraphicBlock(QWidget):
"""QWidget that carries both graphical and logical information about the
layer node
"""
def __init__(self, block_id, block_type, block_data, scene):
super(GraphicBlock, self).__init__()
self.block_id = block_id
self.block_type = block_type
self.block_data = block_data # Just to try
self.scene = scene
self.rect = None
# Setting style and transparent background for the rounded corners
self.setAttribute(Qt.WA_TranslucentBackground)
self.setStyleSheet(GRAPHIC_BLOCK_STYLE)
# Block title label
type_label = QLabel(block_type.name)
type_label.setStyleSheet(BLOCK_TITLE_STYLE)
# Main vertical layout: it contains the label title and grid
layout = QVBoxLayout()
layout.setSpacing(0)
layout.addWidget(type_label)
self.setLayout(layout)
if block_type.parameters:
# Creating the grid for parameters
grid = QWidget()
grid_layout = QGridLayout()
grid.setLayout(grid_layout)
layout.addWidget(grid)
# Iterating and displaying parameters
par_labels = dict()
count = 1
for par in block_type.parameters:
par_labels[par] = QLabel(par)
par_labels[par].setAlignment(Qt.AlignLeft)
par_labels[par].setStyleSheet(PAR_BLOCK_STYLE)
dim = QLabel("<dim>")
dim.setAlignment(Qt.AlignRight)
dim.setStyleSheet(DIM_BLOCK_STYLE)
grid_layout.addWidget(par_labels[par], count, 1)
grid_layout.addWidget(dim, count, 0)
count += 1
else:
type_label.setStyleSheet(ZERO_PARS_BLOCK_TITLE)
def set_rect(self, rect):
self.rect = rect
Can somebody please provide an explanation, or better yet a short example, of how to use the RubberBandDrag enum value in QGraphicsView? PyQt5 would be great, but I can translate from the C++ version if that is preferred for whomever can provide a helpful exmaple.
NoDrag and ScrollHandDrag are relatively easy to understand (NoDrag makes the mouse a pointer and you can capture clicks at certain locations, ScrollHandDrag makes the mouse a hand and you can implement click and drag to scroll around), but I'm unclear on what RubberBandDrag can be used for.
Before somebody says "go read the docs", here is the information provided
https://doc.qt.io/qt-5/qgraphicsview.html
enum QGraphicsView::DragMode
QGraphicsView::RubberBandDrag
A rubber band will appear. Dragging the mouse will set the rubber band
geometry, and all items covered by the rubber band are selected. This
mode is disabled for non-interactive views.
This is clear but I'm not sure how I could actually use RubberBandDrag. Is there a way to use this to drag points of a QPolygon around after initial placement? That would be really useful.
The QGraphicsView::RubberBandDrag flag only serves to activate the internal QRubberBand:
And the QRubberBand in general only aims to visualize a selected area and in the case of QGraphicsView select the items below that area if they are selectable(enable QGraphicsItem::ItemIsSelectable flag).
According to your last question: Is there a way to use this to drag points of a QPolygon around after initial placement? That would be really useful, it seems to me that you have an XY problem since it seems that the use of drag in the terminology makes you think that it serves to drag elements, because no, that drag refers to the way of creating the rubber band.
In a few moments I will show how to implement the drag of the vertices to modify the QPolygon.
The following shows how to modify the position of the vertices by dragging the mouse:
import math
from PyQt5 import QtCore, QtGui, QtWidgets
class GripItem(QtWidgets.QGraphicsPathItem):
circle = QtGui.QPainterPath()
circle.addEllipse(QtCore.QRectF(-10, -10, 20, 20))
square = QtGui.QPainterPath()
square.addRect(QtCore.QRectF(-15, -15, 30, 30))
def __init__(self, annotation_item, index):
super(GripItem, self).__init__()
self.m_annotation_item = annotation_item
self.m_index = index
self.setPath(GripItem.circle)
self.setBrush(QtGui.QColor("green"))
self.setPen(QtGui.QPen(QtGui.QColor("green"), 2))
self.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable, True)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
self.setFlag(QtWidgets.QGraphicsItem.ItemSendsGeometryChanges, True)
self.setAcceptHoverEvents(True)
self.setZValue(11)
self.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
def hoverEnterEvent(self, event):
self.setPath(GripItem.square)
self.setBrush(QtGui.QColor("red"))
super(GripItem, self).hoverEnterEvent(event)
def hoverLeaveEvent(self, event):
self.setPath(GripItem.circle)
self.setBrush(QtGui.QColor("green"))
super(GripItem, self).hoverLeaveEvent(event)
def mouseReleaseEvent(self, event):
self.setSelected(False)
super(GripItem, self).mouseReleaseEvent(event)
def itemChange(self, change, value):
if change == QtWidgets.QGraphicsItem.ItemPositionChange and self.isEnabled():
self.m_annotation_item.movePoint(self.m_index, value)
return super(GripItem, self).itemChange(change, value)
class PolygonAnnotation(QtWidgets.QGraphicsPolygonItem):
def __init__(self, parent=None):
super(PolygonAnnotation, self).__init__(parent)
self.m_points = []
self.setZValue(10)
self.setPen(QtGui.QPen(QtGui.QColor("green"), 2))
self.setAcceptHoverEvents(True)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable, True)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
self.setFlag(QtWidgets.QGraphicsItem.ItemSendsGeometryChanges, True)
self.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
self.m_items = []
def addPoint(self, p):
self.m_points.append(p)
self.setPolygon(QtGui.QPolygonF(self.m_points))
item = GripItem(self, len(self.m_points) - 1)
self.scene().addItem(item)
self.m_items.append(item)
item.setPos(p)
def movePoint(self, i, p):
if 0 <= i < len(self.m_points):
self.m_points[i] = self.mapFromScene(p)
self.setPolygon(QtGui.QPolygonF(self.m_points))
def move_item(self, index, pos):
if 0 <= index < len(self.m_items):
item = self.m_items[index]
item.setEnabled(False)
item.setPos(pos)
item.setEnabled(True)
def itemChange(self, change, value):
if change == QtWidgets.QGraphicsItem.ItemPositionHasChanged:
for i, point in enumerate(self.m_points):
self.move_item(i, self.mapToScene(point))
return super(PolygonAnnotation, self).itemChange(change, value)
def hoverEnterEvent(self, event):
self.setBrush(QtGui.QColor(255, 0, 0, 100))
super(PolygonAnnotation, self).hoverEnterEvent(event)
def hoverLeaveEvent(self, event):
self.setBrush(QtGui.QBrush(QtCore.Qt.NoBrush))
super(PolygonAnnotation, self).hoverLeaveEvent(event)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
scene = QtWidgets.QGraphicsScene()
w = QtWidgets.QGraphicsView(scene)
polygon_item = PolygonAnnotation()
scene.addItem(polygon_item)
r = 100
sides = 10
for i in range(sides):
angle = 2 * math.pi * i / sides
x = r * math.cos(angle)
y = r * math.sin(angle)
p = QtCore.QPointF(x, y) + QtCore.QPointF(200, 200)
polygon_item.addPoint(p)
w.resize(640, 480)
w.show()
sys.exit(app.exec_())
I'm trying to make a custom text widget that is double buffered (In order to avoid flicker).
However, I'd like to be able to do a few things. Yet, I'm unsure of the exact methods I should use.
The first two are easy I simply want to change the background and foreground color.
So more or less I want to be able to change the text color for self.Text in self.Draw().
Snippet:
self.Text = mdc.DrawText(self.TextString, 10, 0)
As sell as the Background (fill) color for self.MemoryDC.
Next, does anyone know how I could center self.Text? Finally, how do I configure self.Text after it has been created?
The widget thus far:
class DynamicText (wx.Panel):
def __init__(self, par):
self.Par = par
wx.Panel.__init__(self, self.Par)
self.Time = Time(self, func=self.SetTime)
self.Dim = self.Par.GetClientSize()
self.SetSize(self.Dim)
self.Bind(wx.EVT_SIZE, self.Resize)
self.Bind(wx.EVT_ERASE_BACKGROUND, self.Erase)
self.Bind(wx.EVT_PAINT, self.Paint)
def Set (self, text) :
self.TextString = text
def SetTime (self, time) :
self.Set(str(time))
self.Resize(None)
def Resize(self, event):
self.Width, self.Height = self.GetSize()
bitmap = wx.EmptyBitmap(self.Width, self.Height)
self.MemoryDC = wx.MemoryDC(bitmap)
''' Redraws **self.MemoryDC** '''
mdc = self.MemoryDC
''' Deletes everything from widget. '''
mdc.Clear()
fs = 11
font = wx.Font( fs, wx.DEFAULT, wx.NORMAL, wx.NORMAL)
mdc.SetFont(font)
self.Draw()
self.Refresh()
def Draw (self) :
mdc = self.MemoryDC
self.Text = mdc.DrawText(self.TextString, 10, 0)
def Erase(self, event):
''' Does nothing, as to avoid flicker. '''
pass
def Paint(self, event):
pdc = wx.PaintDC(self)
w, h = self.MemoryDC.GetSize()
pdc.Blit(0, 0, w, h, self.MemoryDC, 0, 0)
I don't understand what you mean by configuring self.Text after it was created. If you want to change the text after you've drawn it - you can't. Once you've drawn it to the DC it's there, and the only way to change it would be to clear the DC and repaint it. In your case, it seems all you need to do when the text is updated is to call Resize() again, forcing a redraw. Note that DrawText() retruns nothing, so the value of your self.Text would be None. You definitely can't use that to refer to the drawn text. :D
As for the rest, here's an example of a Draw() method that centers the text and paints it blue:
def Draw(self) :
mdc = self.MemoryDC
dc_width, dc_height = mdc.GetSizeTuple()
text_width, text_height, descent, externalLeading = mdc.GetFullTextExtent(self.TextString)
x = (dc_width - text_width) / 2
y = (dc_height - text_height) / 2
mdc.SetTextForeground('Blue')
mdc.DrawText(self.TextString, x, y)
I am creating a small drawing application from a python book, "wxPython in Action", and it uses self.GetClientSize() to get the size of a window. For some reason this is return (0, 0) for me instead of the expected value (800, 600).
The program crashes when wx.EmptyBitmap is called with 0, 0 as its parameters. If I put
wx.EmptyBitmap(800, 600) the entire program runs fine, minus resizing.
Here is the relevant method
def InitBuffer(self):
size = self.GetClientSizeTuple()
print size
sys.exit(1)
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
And this is the complete code
#!/usr/bin/arch -i386 /usr/bin/python2.6 -tt
import sys
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_SIZE, self.OnSize)
self.Bind(wx.EVT_MOTION, self.OnMotion)
self.Bind(wx.EVT_IDLE, self.OnIdle)
self.Bind(wx.EVT_PAINT, self.OnPaint)
def InitBuffer(self):
size = self.GetClientSizeTuple()
print size
sys.exit(1)
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 coord in line:
dc.DrawLine(*coord)
def SetColor(self, color):
self.color = color
self.pen = wx.Pen(self.color, self.thickness, wx.SOLID)
def GetColor(self):
return self.color
def SetThickness(self, thickness):
self.thickness = thickness
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)
def main():
app = wx.PySimpleApp()
frame = SketchFrame(None)
frame.Show(True)
app.MainLoop()
if __name__ == '__main__':
main()
It's because you're calling GetSize in the __init__() method - the window isn't fully created until this method has completed. Thus, it hasn't has its width and height set properly.
You could use wx.CallAfter/CallLater to postpone the calling of this function until window creation has fully completed.
I don't know if there is a better solution, but the problem is that when the object was being initialized it didn't have a parent yet, so it didn't know what size it should be. Thus it was width 0 and height 0. However, it needed to initialize the buffer. What I did to fix this was
if size == (0, 0):
size.width = 1
size.height = 1
Once it is added to the frame it gets a new size and the buffer is resized. So I guess that works!
I suppose another solution would be to pass a size parameter to the init method, but i'd prefer not to have to do that if it is not required.
Please post other solutions if you have them =)