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'm trying to guess how to update the position of the edge when the nodes are moved or why it's not been automatically updated. I have found that the position of the nodes is not updated if I remove the self.update() from the mouseReleaseEvent but I don't know which is the mecanism to get the same on the edge class. Can anybody help me with this?
EDIT: With "position" I mean the value got by getPos() or getScenePos() for the QEdgeGraphicItem. It's printed on the output for the nodes and the Edge.
#!/usr/bin/env python
import math
from PyQt4 import QtCore, QtGui
from PyQt4.QtGui import QGraphicsView, QGraphicsTextItem
class QEdgeGraphicItem(QtGui.QGraphicsItem):
Type = QtGui.QGraphicsItem.UserType + 2
def __init__(self, sourceNode, destNode, label=None):
super(QEdgeGraphicItem, self).__init__()
self.sourcePoint = QtCore.QPointF()
self.destPoint = QtCore.QPointF()
self.setAcceptedMouseButtons(QtCore.Qt.NoButton)
self.setFlag(QtGui.QGraphicsItem.ItemIsMovable)
self.setFlag(QtGui.QGraphicsItem.ItemSendsGeometryChanges)
# self.setCacheMode(QtGui.QGraphicsItem.DeviceCoordinateCache)
self.source = sourceNode
self.dest = destNode
self.label = QGraphicsTextItem("WHY IN THE HELL IS IT IN 0,0", self)
self.label.setParentItem(self)
self.label.setDefaultTextColor(QtCore.Qt.black)
self.source.addEdge(self)
self.dest.addEdge(self)
self.adjust()
def adjust(self):
if not self.source or not self.dest:
return
line = QtCore.QLineF(self.mapFromItem(self.source, 0, 0),
self.mapFromItem(self.dest, 0, 0))
self.prepareGeometryChange()
self.sourcePoint = line.p1()
self.destPoint = line.p2()
def boundingRect(self):
if not self.source or not self.dest:
return QtCore.QRectF()
extra = 2
return QtCore.QRectF(self.sourcePoint,
QtCore.QSizeF(self.destPoint.x() - self.sourcePoint.x(),
self.destPoint.y() - self.sourcePoint.y())).normalized().adjusted(-extra,
-extra,
extra,
extra)
def paint(self, painter, option, widget):
if not self.source or not self.dest:
return
# Draw the line itself.
line = QtCore.QLineF(self.sourcePoint, self.destPoint)
if line.length() == 0.0:
return
painter.setPen(QtGui.QPen(QtCore.Qt.black, 1, QtCore.Qt.SolidLine,
QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
painter.drawLine(line)
painter.setBrush(QtCore.Qt.NoBrush)
painter.setPen(QtCore.Qt.red)
painter.drawRect(self.boundingRect())
print "Edge:"+str(self.scenePos().x()) + " " + str(self.scenePos().y())
class QNodeGraphicItem(QtGui.QGraphicsItem):
Type = QtGui.QGraphicsItem.UserType + 1
def __init__(self, label):
super(QNodeGraphicItem, self).__init__()
# self.graph = graphWidget
self.edgeList = []
self.newPos = QtCore.QPointF()
self.setFlag(QtGui.QGraphicsItem.ItemIsMovable)
self.setFlag(QtGui.QGraphicsItem.ItemSendsGeometryChanges)
# self.setCacheMode(QtGui.QGraphicsItem.DeviceCoordinateCache)
# self.setZValue(1)
self.size = 40
self.border_width = 4
def type(self):
return QNodeGraphicItem.Type
def addEdge(self, edge):
self.edgeList.append(edge)
edge.adjust()
def boundingRect(self):
x_coord = y_coord = (-1*(self.size/2)) - self.border_width
width = height = self.size+23+self.border_width
return QtCore.QRectF(x_coord, y_coord , width,
height)
def paint(self, painter, option, widget):
x_coord = y_coord = -(self.size / 2)
width = height = self.size
painter.save()
painter.setBrush(QtGui.QBrush(QtGui.QColor(100, 0, 200, 127)))
painter.setPen(QtCore.Qt.black)
painter.drawEllipse(x_coord, y_coord, width, height)
painter.restore()
print "Node: " + str(self.scenePos().x()) + " " + str(self.scenePos().y())
def itemChange(self, change, value):
if change == QtGui.QGraphicsItem.ItemPositionHasChanged:
for edge in self.edgeList:
edge.adjust()
return super(QNodeGraphicItem, self).itemChange(change, value)
def mousePressEvent(self, event):
self.update()
super(QNodeGraphicItem, self).mousePressEvent(event)
def mouseReleaseEvent(self, event):
self.update()
super(QNodeGraphicItem, self).mouseReleaseEvent(event)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
QtCore.qsrand(QtCore.QTime(0, 0, 0).secsTo(QtCore.QTime.currentTime()))
node1 = QNodeGraphicItem("Node1")
node2 = QNodeGraphicItem("Node2")
edge = QEdgeGraphicItem(node1,node2)
view = QGraphicsView()
view.setCacheMode(QtGui.QGraphicsView.CacheBackground)
view.setViewportUpdateMode(QtGui.QGraphicsView.BoundingRectViewportUpdate)
view.setRenderHint(QtGui.QPainter.Antialiasing)
view.setTransformationAnchor(QtGui.QGraphicsView.AnchorUnderMouse)
view.setResizeAnchor(QtGui.QGraphicsView.AnchorViewCenter)
view.scale(0.8, 0.8)
view.setMinimumSize(400, 400)
view.setWindowTitle("Example")
scene = QtGui.QGraphicsScene(view)
scene.setItemIndexMethod(QtGui.QGraphicsScene.NoIndex)
scene.setSceneRect(-400, -400, 800, 800)
view.setScene(scene)
scene.addItem(node1)
scene.addItem(node2)
scene.addItem(edge)
view.show()
sys.exit(app.exec_())
Thank you.
So I have a main window. I use setCentralWidget to set it to a class I made called mainWindowWidget to handle the UI.
I am now trying to add a graphics view widget I made to that but cannot seem to get anything to display. If I set the graphics view as the central widget I can see it and all my behaviour is working.
Do I need to do anything to get the graphics view to display within another widget?
Below are the sections of code I think are relevant to the question followed by the entire application. I am really new to PyQt and any guidance would be appreciated.
class mainWindowWidget(QtGui.QWidget):
grid = None
scene = None
def __init__(self):
self.initScene()
QtGui.QWidget.__init__(self)
def initScene(self):
self.grid = QtGui.QGridLayout()
'''Node Interface'''
self.scene = Scene(0, 0, 1280, 720, self)
self.view = QtGui.QGraphicsView()
self.view.setScene(self.scene)
self.grid.addWidget(self.view)
'''AttributeWindow'''
class MainWindowUi(QtGui.QMainWindow):
def __init__(self):
mainDataGraber = ind.dataGraber()
QtGui.QMainWindow.__init__(self)
self.setWindowTitle('RIS RIB Generator')
mainwindowwidget = mainWindowWidget()
self.setCentralWidget(mainwindowwidget)
This is the main GUI file for the application
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
This is the base py file for the GUI
Todo list
-----------------
- Pop up menu for adding new Nodes
- node connectivity
- create data structure for storing
"""
#import code mods
import sys
import uuid
import gtk, pygtk
from PyQt4 import QtGui, QtCore
from array import *
#import StyleMod
import RRG_NodeInterfaceGUIStyle as ns
import RRG_importNodeData as ind
"""
Base class for a node. Contains all the inilization, drawing, and containing inputs and outputs
"""
class node(QtGui.QGraphicsRectItem):
nid = 0
width = ns.nodeWidth
height = ns.nodeHeight
color = ns.nodeBackgroundColor
alpha = ns.nodeBackgroundAlpha
x = 90
y = 60
inputs=[]
outputs=[]
viewObj = None
isNode = True
scene = None
def __init__(self, n_x, n_y, n_width,n_height, n_scene):
QtGui.QGraphicsRectItem.__init__(self, n_x, n_y, n_width, n_height)
self.width = n_width
self.height = n_height
self.x = n_x
self.y = n_y
self.scene = n_scene
self.nid = uuid.uuid4()
print(self.nid)
self.setFlag(QtGui.QGraphicsItem.ItemIsMovable, True)
self.iniNodeData()
def mousePressEvent(self, e):
print("Square got mouse press event")
print("Event came to us accepted: %s"%(e.isAccepted(),))
a = []
self.scene.selectedNodes = a
self.scene.selectedNodes.append(self)
self.scene.selectedNodeID = self.nid
QtGui.QGraphicsRectItem.mousePressEvent(self, e)
def mouseReleaseEvent(self, e):
#print("Square got mouse release event")
#print("Event came to us accepted: %s"%(e.isAccepted(),))
QtGui.QGraphicsRectItem.mouseReleaseEvent(self, e)
"""
This is where inputs and outputs will be created based on node type
"""
def iniNodeData(self):
print('making node data')
for j in range(5):
this = self
x = input(this,0, 0+(j*10),self.scene)
self.inputs.append(x)
for k in range(5):
this = self
x = output(this, self.width-10, 0+(k*10),self.scene)
self.outputs.append(x)
def mouseMoveEvent(self, event):
self.scene.updateConnections()
QtGui.QGraphicsRectItem.mouseMoveEvent(self, event)
def nid(self):
return self.nid
"""
Nodes will evaluate from the last node to the first node, therefore inputs are evaluted
"""
class input(QtGui.QGraphicsRectItem):
currentConnectedNode = None
currentConnectedOutput = None
parentNode = None
width = 10
height = 10
x = 1
y = 1
color = 1
drawItem = None
isOutput = False
isNode = False
scene = None
points = []
line = None
def __init__(self, pnode, posX, posY, n_scene):
self.parentNode = pnode
self.x = posX
self.y = posY
self.color = 1
self.scene = n_scene
QtGui.QGraphicsRectItem.__init__(self, self.x+self.parentNode.x, self.y+self.parentNode.y, self.width, self.height, self.parentNode)
self.setFlag(QtGui.QGraphicsItem.ItemIsSelectable, True)
'''
This will handel connections. It determins if a connection is allowed aswell as creates them
'''
def mousePressEvent(self, e):
#print("Square got mouse press event")
#print("Event came to us accepted: %s"%(e.isAccepted(),))
emptyArray = []
if len(self.scene.selectedNodes) > 0 or len(self.scene.selectedNodes) > 1:
a = self.scene.selectedNodes[0]
if a.isNode == False:
if a.parentNode != self.parentNode:
if a.isOutput == True and self.isOutput == False:
print('Output and Input! line test runnin....')
currentConnectedOutput = a
currentConnectedNode = a.parentNode
if self.line != None:
self.line = None
self.scene.addConnection(self, a)
elif a.isOutput == True and self.isOutput == True:
print('Output and Output')
elif a.isOutput == False and self.isOutput == False:
print('Input and Input')
self.scene.selectedNodes = emptyArray
else:
self.scene.selectedNodes = emptyArray
else:
self.scene.selectedNodes = emptyArray
self.scene.selectedNodes.append(self)
else:
self.scene.selectedNodes.append(self)
QtGui.QGraphicsRectItem.mousePressEvent(self, e)
'''
Output value from a node
'''
class output(QtGui.QGraphicsRectItem):
parentNode = None
width = 10
height = 10
x = 0
y = 0
isOutput = True
isNode = False
scene = None
def __init__(self, pnode, posX, posY, n_scene):
self.parentNode = pnode
self.x = posX
self.y = posY
self.color = 1
self.scene = n_scene
QtGui.QGraphicsRectItem.__init__(self, self.x+self.parentNode.x, self.y+self.parentNode.y, self.width, self.height, self.parentNode)
self.setFlag(QtGui.QGraphicsItem.ItemIsSelectable, True)
'''
This will handel connections. It determins if a connection is allowed aswell as creates them
'''
def mousePressEvent(self, e):
#print("Square got mouse press event")
#print("Event came to us accepted: %s"%(e.isAccepted(),))
emptyArray = []
if len(self.scene.selectedNodes) > 0 or len(self.scene.selectedNodes) > 1:
a = self.scene.selectedNodes[0]
if a.isNode == False:
if a.parentNode != self.parentNode:
if a.isOutput == False and self.isOutput == True:
print('Input and Output!')
a.currentConnectedOutput = self
a.currentConnectedNode = self.parentNode
elif a.isOutput == True and self.isOutput == False:
print('Output and Input!')
elif a.isOutput == True and self.isOutput == True:
print('Output and Output')
elif a.isOutput == False and self.isOutput == False:
print('Input and Input')
self.scene.selectedNodes = emptyArray
else:
self.scene.selectedNodes = emptyArray
else:
self.scene.selectedNodes = emptyArray
self.scene.selectedNodes.append(self)
else:
self.scene.selectedNodes.append(self)
QtGui.QGraphicsRectItem.mousePressEvent(self, e)
class connection(QtGui.QGraphicsLineItem):
usedNodeIDs = []
inputItem = None
outputItem = None
x1 = 0.0
y1 = 0.0
x2 = 0.0
y2 = 0.0
nid = None
scene = None
def __init__(self, n_inputItem, n_outputItemm, n_scene):
self.inputItem = n_inputItem
self.outputItem = n_outputItemm
self.usedNodeIDs.append(self.inputItem.parentNode.nid)
self.usedNodeIDs.append(self.outputItem.parentNode.nid)
self.updatePos()
QtGui.QGraphicsLineItem.__init__(self, self.x1, self.y1, self.x2, self.y2)
self.setFlag(QtGui.QGraphicsItem.ItemIsSelectable, True)
self.scene = n_scene
self.nid = uuid.uuid4()
def update(self):
self.updatePos()
self.setLine(self.x1, self.y1, self.x2, self.y2)
def updatePos(self):
scenePosInput = QtGui.QGraphicsItem.pos(self.inputItem)
scenePosOutput = QtGui.QGraphicsItem.pos(self.outputItem)
scenePosInputNode = QtGui.QGraphicsItem.pos(self.inputItem.parentNode)
scenePosOutputNode = QtGui.QGraphicsItem.pos(self.outputItem.parentNode)
self.x1 = (scenePosInputNode.x()+self.inputItem.parentNode.x)+(scenePosInput.x()+self.inputItem.x) + ns.inputWidth/2
self.y1 = (scenePosInputNode.y()+self.inputItem.parentNode.y)+(scenePosInput.y()+self.inputItem.y) + ns.inputHeight/2
self.x2 = (scenePosOutputNode.x()+self.outputItem.parentNode.x)+(scenePosOutput.x()+self.outputItem.x) + ns.outputWidth/2
self.y2 = (scenePosOutputNode.y()+self.outputItem.parentNode.y)+(scenePosOutput.y()+self.outputItem.y) + ns.outputHeight/2
def mousePressEvent(self, e):
self.scene.selectedNodeID = self.nid
QtGui.QGraphicsLineItem.mousePressEvent(self, e)
'''
Check Click events on the scene Object
Also Stores the node data
'''
class Scene(QtGui.QGraphicsScene):
nodes = []
connections = []
selectedNodeID = None
def __init__(self, x, y, w, h, p):
super(Scene, self).__init__()
self.width = w
self.height = h
def mousePressEvent(self, e):
#print("Scene got mouse press event")
#print("Event came to us accepted: %s"%(e.isAccepted(),))
QtGui.QGraphicsScene.mousePressEvent(self, e)
def mouseReleaseEvent(self, e):
#print("Scene got mouse release event")
#print("Event came to us accepted: %s"%(e.isAccepted(),))
QtGui.QGraphicsScene.mouseReleaseEvent(self, e)
def dragMoveEvent(self, e):
QtGui.QGraphicsScene.dragMoveEvent(self, e)
def updateConnections(self):
for connect in self.connections:
connect.update()
def addNode(self):
newNode = node(250,250,100,150, self)
self.addItem(newNode)
self.nodes.append(newNode)
def addPatternNode(self):
newNode = node(250,250,100,150, self)
self.addItem(newNode)
self.nodes.append(newNode)
def addConnection(self, n_inputItem, n_outputItem):
newConnection = connection(n_inputItem, n_outputItem, self)
self.addItem(newConnection)
self.connections.append(newConnection)
def keyPressEvent(self, e):
#Delete a node after it have been clicked on
#Use the node ID as the unique ID of the node to delete
if e.key() == QtCore.Qt.Key_Delete or e.key() == QtCore.Qt.Key_Backspace:
#If nothing selected
if self.selectedNodeID != None:
isConnection = False
for j in range(len(self.connections)):
if self.connections[j].nid == self.selectedNodeID:
isConnection = True
self.removeItem(self.connections[j])
self.connections.remove(self.connections[j])
if isConnection != True:
#first remove connections
rmItem = False
connectionsToRemove = []
for connect in self.connections:
rmItem = False
for nid in connect.usedNodeIDs:
if nid == self.selectedNodeID:
if rmItem == False:
connectionsToRemove.append(connect)
rmItem = True
for removeThis in connectionsToRemove:
self.connections.remove(removeThis)
self.removeItem(removeThis)
#now remove the nodes
for j in range(len(self.nodes)):
print(self.nodes[j].nid)
#figure out which node in our master node list must be deleted
if self.nodes[j].nid == self.selectedNodeID:
self.removeItem(self.nodes[j])
self.nodes.remove(self.nodes[j])
self.selectedNodeID = None
class mainWindowWidget(QtGui.QWidget):
grid = None
scene = None
def __init__(self):
self.initScene()
QtGui.QWidget.__init__(self)
def initScene(self):
self.grid = QtGui.QGridLayout()
'''Node Interface'''
self.scene = Scene(0, 0, 1280, 720, self)
self.view = QtGui.QGraphicsView()
self.view.setScene(self.scene)
self.grid.addWidget(self.view)
'''AttributeWindow'''
class MainWindowUi(QtGui.QMainWindow):
def __init__(self):
mainDataGraber = ind.dataGraber()
QtGui.QMainWindow.__init__(self)
self.setWindowTitle('RIS RIB Generator')
mainwindowwidget = mainWindowWidget()
self.setCentralWidget(mainwindowwidget)
exitAction = QtGui.QAction(QtGui.QIcon('exit24.png'), 'Exit', self)
exitAction.setShortcut('Ctrl+Q')
exitAction.setStatusTip('Exit application')
exitAction.triggered.connect(self.close)
newNodeAction = QtGui.QAction(QtGui.QIcon('exit24.png'), 'New Node', self)
newNodeAction.setStatusTip('Add a blank node')
newNodeAction.triggered.connect(mainwindowwidget.scene.addPatternNode)
nodeCreationActions = []
for nodeType in mainDataGraber.abstractNodeObjects:
nodeName = nodeType.nName
nodeType = nodeType.nType
#nodeStatusTip = nodeType.nhelp
newNodeAction = QtGui.QAction(QtGui.QIcon('exit24.png'), nodeName, self)
newNodeAction.setStatusTip('nodeType.nhelp')
if nodeType == 'pattern':
newNodeAction.triggered.connect(mainwindowwidget.scene.addPatternNode)
nodeCreationActions.append(newNodeAction)
newNodeAction = QtGui.QAction(QtGui.QIcon('exit24.png'), 'New Node', self)
newNodeAction.setStatusTip('Add a blank node')
newNodeAction.triggered.connect(mainwindowwidget.scene.addPatternNode)
self.statusBar()
menubar = self.menuBar()
fileMenu = menubar.addMenu('&File')
fileMenu.addAction(newNodeAction)
nodeMenu = menubar.addMenu('&Nodes')
for action in nodeCreationActions:
nodeMenu.addAction(action)
fileMenu.addAction(exitAction)
'''
Start Point
'''
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
win = MainWindowUi()
win.show()
sys.exit(app.exec_())
Your issue is that you don't specify a parent for the QGridLayout (in the mainWindowWidget class), so it isn't attached to a widget. This results in the layout (and all widgets contained within it) not being visible. Adding a parent to the layout reveals a second problem in that you try and do things with the QWidget before calling __init__.
The corrected code is thus:
class mainWindowWidget(QtGui.QWidget):
grid = None
scene = None
def __init__(self):
QtGui.QWidget.__init__(self)
self.initScene()
def initScene(self):
self.grid = QtGui.QGridLayout(self)
'''Node Interface'''
self.scene = Scene(0, 0, 1280, 720, self)
self.view = QtGui.QGraphicsView()
self.view.setScene(self.scene)
self.grid.addWidget(self.view)
Note: For future questions requiring debugging help, please make a minimilistic example that is runnable. Don't just dump 90% of your code in a stack overflow post. It's not fun trying to dig through random code trying to cut out the missing imports so that it still reproduces the problem (fortunately it wasn't too difficult in this case). See How to create a Minimal, Complete, and Verifiable example.
Note 2: Why are you importing pygtk into a qt app?
I want to put such an image in a wx.Panel :
The animals should be "buttons" so that if I click on them, their image changes, and if I reclick, the image returns to normal (thus the animals can be considered as simple BitmapToggleButtons, as suggested by another question here on SO)
This panel should be resized/rescaled (all all the children images / togglebuttons too!) keeping the aspect ratio, if the parent wx.Panel is resized to something smaller for example (like would do the standard Windows Photo Viewer : http://res1.windows.microsoft.com/resbox/en/windows%207/main/7eaf462a-86dd-42d2-a789-7413f5472dae_63.jpg)
I am still a bit lost on : how to implement such a clickable (with toggle buttons) and rescalable Canvas?
Edit : I started with something fruitful here Rescale image when parent is resized in wxPython, but now I'm totally stuck about how to continue (detect clicks, update buttons with direct DC Painting ?), that's why the bounty.
You will have to implement your own hit testing, i.e. be able to determine where each animal is -- this is the difficult part and there is really nothing in wxWidgets to help you with this. The rest is relatively simple, you might even be able to use the existing wxMouseEventsManager to avoid writing the boilerplate code yourself (but if you can't, you can at least look at its implementation, which is done entirely in wxWidgets itself, to see what you need to do).
Depending on how much of this is already written, you may want to take a look at FloatCanvas (it's in the wxPython library).
If you have most of the code-base done, you can use a hit test, which is rather simple. Just make a dictionary with the [x][y] coordinates as keys which has the the BitmapTogglebutton as its value.
Here's some code that does something similar (it's been a while since I've used wxPython so it may not be 100%):
def onLeftDown( event ):
x,y = event.GetX(), event.GetY()
hitmap_x = hitmap.get(x,None)
if hitmap_x:
btn = hitmap_x.get(y, None)
...stuff with btn like toggles
I made some code for practice recently. It may somehow match with your requirement.
Code is ugly and mess because I'm a newcomer to python.
Support:
image on the backgroud draggable
image animating with double-click
background and image on it resizeable
Notice:
you need have pygame to run the code
you can load real image by replacing PyGamePseudoImage()
image coordinate adjustment is not smooth enough while zoom in/out
Code:
import wx
import pygame
BLACK = ( 0, 0, 0)
WHITE = (255, 255, 255)
BLUE = ( 0, 0, 255)
GREEN = ( 0, 255, 0)
RED = (255, 0, 0)
pygame.font.init()
try:
regular_font_file = os.path.join(os.path.dirname(__file__), "Vera.ttf")
bold_font_file = os.path.join(os.path.dirname(__file__), "VeraBd.ttf")
# Check for cx_Freeze
#
if "frozen" in sys.__dict__.keys() and sys.frozen:
regular_font_file = os.path.join(sys.path[1], "Vera.ttf")
bold_font_file = os.path.join(sys.path[1], "VeraBd.ttf")
BIG_FONT = pygame.font.Font(regular_font_file, 30)
SMALL_FONT = pygame.font.Font(regular_font_file, 12)
BOLD_FONT = pygame.font.Font(bold_font_file, 12)
except:
# TODO: log used font: pygame.font.get_default_font()
#print("Could not load {0}".format(os.path.join(os.path.dirname(__file__), "Vera.ttf")))
BIG_FONT = pygame.font.Font(None, 40)
SMALL_FONT = BOLD_FONT = pygame.font.Font(None, 20)
class PyGamePseudoImage():
def __init__(self, size, color):
self.screen = pygame.Surface(size, 0, 32)
self.screen.fill(color)
def getImage(self):
return self.screen
class __MouseMixin:
def onLeftUp(self, event):
pass
def onLeftDown(self, event):
pass
def onLeftDClick(self, event):
pass
def onRightUp(self, event):
pass
def onRightDown(self, event):
pass
def onDragging(self, event):
pass
def onMouseEnter(self, event):
pass
def OnMouseHandler(self, event):
event.Skip()
if event.LeftUp():
self.onLeftUp(event)
elif event.LeftDown():
self.onLeftDown(event)
elif event.LeftDClick():
self.onLeftDClick(event)
elif event.RightUp():
self.onRightUp(event)
elif event.RightDown():
self.onRightDown(event)
elif event.Dragging() and event.LeftIsDown():
self.onDragging(event)
pass
class DragSprite(__MouseMixin, pygame.sprite.Sprite):
SPRITE_BUTTON, SPRITE_TRANSPORTER = range(2)
def __init__(self, parent=None):
pygame.sprite.Sprite.__init__(self)
self.is_select = 0
self.lastPos = 0
self.lastUpdate = 0
self.parent = parent
def setLastPos(self, pos):
self.lastPos = pos
def move(self, pos):
dx = pos[0] - self.lastPos[0]
dy = pos[1] - self.lastPos[1]
self.lastPos = pos
center = (self.rect.center[0] + dx, self.rect.center[1] + dy)
self.rect.center = center
return
def isSelected(self):
return self.is_select
def setSelect(self, is_select):
self.is_select = is_select
return
def update(self, current_time):
return
def drawBoader(image, rect):
W,H = (rect.width, rect.height)
yellow = (255, 255, 0)
pygame.draw.rect(image, yellow, (0,0,W-2,H-2), 2)
class ButtonSprite(DragSprite):
def __init__(self, parent=None, initPos=(0,0), width=50, height=50, dicts=None):
DragSprite.__init__(self, parent)
self.type = DragSprite.SPRITE_BUTTON
self.resourceCfgDict = dicts
self.imageResource = {}
self.status = 0
self.index = 0
self.parent = parent
self.initPos = (initPos[0], initPos[1])
self.width = width
self.height = height
self.rectOnLoad = pygame.Rect(initPos, (width, height))
self.rect = self.rectOnLoad.copy()
self.operationOn = None
self.operationOff = None
self.operationDic = {"on": self.getOperationOnItem, "off": self.getOperationOffItem}
self.guiCfg = None
for dic in dicts:
self.loadImgResource(dic)
self.setCurrentResource("off")
def getOperationOnItem(self):
return self.operationOn
def getOperationOffItem(self):
return self.operationOff
def loadImgResource(self, dict):
"""
load image with pygame lib
"""
key = dict[0]
file_name = dict[1]
#image_file = pygame.image.load(file_name) #use this to load real image
image_file = PyGamePseudoImage((500,500), file_name).getImage()
imagedata = pygame.image.tostring(image_file, "RGBA")
imagesize = image_file.get_size()
imageSurface = pygame.image.fromstring(imagedata, imagesize , "RGBA")
self.imageResource[key] = (file_name, imageSurface)
def resizeResource(self, src, size):
return pygame.transform.smoothscale(src, size)
def setCurrentResource(self, status):
self.currentStatus = status
self.imageOnLoad = self.resizeResource(self.imageResource[status][1], (self.width, self.height))
self.image = pygame.transform.scale(self.imageOnLoad, (self.rect.width, self.rect.height))
def switchResource(self, index):
self.setCurrentResource(index)
def onZoomUpdate(self, zoomRatio):
parentRect = pygame.Rect(self.parent.GetRect())
dx = self.rectOnLoad.centerx - parentRect.centerx
dy = self.rectOnLoad.centery - parentRect.centery
self.rect.centerx = parentRect.centerx + dx*zoomRatio
self.rect.centery = parentRect.centery + dy*zoomRatio
self.rect.height = self.imageOnLoad.get_rect().height * zoomRatio
self.rect.width = self.imageOnLoad.get_rect().width * zoomRatio
self.image = pygame.transform.scale(self.imageOnLoad, (self.rect.width, self.rect.height))
def update(self, current_time, ratio):
if self.isSelected():
drawBoader(self.image, self.image.get_rect())
else:
pass
#self.image = self.imageOnLoad.copy()
def onRightUp(self, event):
print "onRightUp"
event.Skip(False)
pass
def onLeftDClick(self, event):
if self.currentStatus == "on":
self.setCurrentResource("off")
elif self.currentStatus == "off":
self.setCurrentResource("on")
return
def move(self, pos):
DragSprite.move(self, pos)
parentRect = pygame.Rect(self.parent.GetRect())
centerDx = self.rect.centerx - parentRect.centerx
centerDy = self.rect.centery - parentRect.centery
self.rectOnLoad.centerx = parentRect.centerx + centerDx/self.parent.zoomRatio
self.rectOnLoad.centery = parentRect.centery + centerDy/self.parent.zoomRatio
class MyHmiPanel(wx.Panel):
def __init__(self, parent, ID):
wx.Window.__init__(self, parent, ID)
self.parent = parent
self.hwnd = self.GetHandle()
self.size = self.GetSizeTuple()
self.size_dirty = True
self.rootSpriteGroup = pygame.sprite.LayeredUpdates()
self.timer = wx.Timer(self)
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Bind(wx.EVT_TIMER, self.Update, self.timer)
self.Bind(wx.EVT_SIZE, self.OnSize)
self.fps = 60.0
self.timespacing = 1000.0 / self.fps
self.timer.Start(self.timespacing, False)
self.previous_time = 0
self.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouse)
self.selectedSprite = None
self.zoomRatio = 1
self.background = None
self.bgRect = None
self.backgroundOnUpdate = None
self.bgRetOnUpdate = None
self.loadBackground()
self.addTestSprite()
def loadBackground(self):
#self.background = pygame.image.load(image_file) #use this to load real image
self.background = PyGamePseudoImage((500,500), (255, 0, 0)).getImage()
self.bgRect = self.background.get_rect()
self.backgroundOnUpdate = self.background.copy()
self.bgRetOnUpdate = self.bgRect.copy()
def resizeUpdateBackground(self):
self.bgRect.center = self.screen.get_rect().center
self.bgRetOnUpdate = self.bgRect.copy()
def zoomUpdateBackground(self, zoomRatio):
self.bgRetOnUpdate.width = self.bgRect.width * zoomRatio
self.bgRetOnUpdate.height = self.bgRect.height * zoomRatio
self.bgRetOnUpdate.width = self.bgRect.width * zoomRatio
self.bgRetOnUpdate.center = self.screen.get_rect().center
self.backgroundOnUpdate = pygame.transform.scale(self.background, (self.bgRetOnUpdate.width, self.bgRetOnUpdate.height))
def drawBackground(self, screen):
screen.blit(self.backgroundOnUpdate, self.bgRetOnUpdate)
def addTestSprite(self):
#self.rootSpriteGroup.add(ButtonSprite(self, initPos=(100, 100), width=100, height=100, dicts= [('on', btn_red_on), ('off', btn_red_off)]))
#self.rootSpriteGroup.add(ButtonSprite(self, initPos=(200, 200), width=100, height=100, dicts= [('on', btn_red_on), ('off', btn_red_off)]))
self.rootSpriteGroup.add(ButtonSprite(self, initPos=(100, 100), width=100, height=100, dicts= [('on', GREEN), ('off', BLUE)]))
self.rootSpriteGroup.add(ButtonSprite(self, initPos=(200, 200), width=100, height=100, dicts= [('on', GREEN), ('off', BLUE)]))
def Update(self, event):
self.Redraw()
return
def Redraw(self):
if self.size[0] == 0 or self.size[1] == 0:
return
if self.size_dirty:
self.screen = pygame.Surface(self.size, 0, 32)
self.resizeUpdateBackground()
self.size_dirty = False
self.screen.fill((0,0,0))
self.drawBackground(self.screen)
w, h = self.screen.get_size()
current_time = pygame.time.get_ticks()
self.previous_time = current_time
self.rootSpriteGroup.update(current_time, self.zoomRatio)
self.rootSpriteGroup.draw(self.screen)
s = pygame.image.tostring(self.screen, 'RGB') # Convert the surface to an RGB string
#img = wx.ImageFromData(self.size[0], self.size[1], s) # Load this string into a wx image
img = wx.ImageFromData(w, h, s) # Load this string into a wx image
#if img.IsOk() is not True:
# return
bmp = wx.BitmapFromImage(img) # Get the image in bitmap form
dc = wx.ClientDC(self) # Device context for drawing the bitmap
dc = wx.BufferedDC( dc)
dc.DrawBitmap(bmp, 0, 0, 1) # Blit the bitmap image to the display
def checkCollide(self, event):
x , y = (event.GetX(),event.GetY())
mousePoint = pygame.sprite.Sprite()
mousePoint.rect = pygame.Rect(x, y, 1, 1)
copoint = pygame.sprite.spritecollide(mousePoint, self.rootSpriteGroup, None)
if copoint:
copoint = copoint[-1]
return copoint
def removeSelectedSprite(self):
if self.selectedSprite:
self.selectedSprite.setSelect(0)
self.selectedSprite = None
def setNewSelectedSprite(self, sprite):
self.removeSelectedSprite()
sprite.setSelect(1)
self.selectedSprite = sprite
def onSelectSprite(self, event, onMouseObj):
if onMouseObj:
if self.selectedSprite:
if onMouseObj != self.selectedSprite:
self.setNewSelectedSprite(onMouseObj)
else:
self.setNewSelectedSprite(onMouseObj)
self.selectedSprite.setLastPos((event.GetX(),event.GetY()))
else:
self.removeSelectedSprite()
def OnMouse(self, event):
onMouseObj = self.checkCollide(event)
event.Skip()
if onMouseObj:
onMouseObj.OnMouseHandler(event)
if not event.GetSkipped():
print "event dropped "
return
if event.LeftDown():
self.onSelectSprite(event, onMouseObj)
elif event.LeftUp():
pass
elif event.RightUp():
self.onSelectSprite(event, onMouseObj)
elif event.RightDown():
self.onSelectSprite(event, onMouseObj)
elif event.Dragging() and event.LeftIsDown():
if self.selectedSprite:
self.selectedSprite.move((event.GetX(),event.GetY()))
def OnPaint(self, event):
self.Redraw()
event.Skip() # Make sure the parent frame gets told to redraw as well
def OnSize(self, event):
self.size = self.GetSizeTuple()
self.size_dirty = True
def Kill(self, event):
self.Unbind(event=wx.EVT_PAINT, handler=self.OnPaint)
self.Unbind(event=wx.EVT_TIMER, handler=self.Update, source=self.timer)
def onZoomIn(self):
self.zoomRatio += 0.2
self.onZoomUpdate()
def onZoomReset(self):
self.zoomRatio = 1
self.onZoomUpdate()
def onZoomOut(self):
if self.zoomRatio > 0.2:
self.zoomRatio -= 0.2
self.onZoomUpdate()
def onZoomUpdate(self):
self.zoomUpdateBackground(self.zoomRatio)
for s in self.rootSpriteGroup.sprites():
s.onZoomUpdate(self.zoomRatio)
class TestFrame ( wx.Frame ):
def __init__( self, parent, fSize ):
wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = wx.EmptyString, pos = wx.DefaultPosition, size = fSize, style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL )
self.SetSizeHintsSz( wx.DefaultSize, wx.DefaultSize )
fgSizer1 = wx.FlexGridSizer( 2, 1, 0, 0 )
fgSizer1.AddGrowableCol( 0 )
fgSizer1.AddGrowableRow( 0 )
fgSizer1.SetFlexibleDirection( wx.VERTICAL )
fgSizer1.SetNonFlexibleGrowMode( wx.FLEX_GROWMODE_ALL )
self.panelMain = MyHmiPanel(self, -1)
fgSizer1.Add( self.panelMain, 1, wx.EXPAND |wx.ALL, 5 )
self.m_panel4 = wx.Panel( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL )
bSizer3 = wx.BoxSizer( wx.HORIZONTAL )
self.bZoomIn = wx.Button( self.m_panel4, wx.ID_ANY, u"Zoom In", wx.DefaultPosition, wx.DefaultSize, 0 )
bSizer3.Add( self.bZoomIn, 0, wx.ALL, 5 )
self.bReset = wx.Button( self.m_panel4, wx.ID_ANY, u"Reset", wx.DefaultPosition, wx.DefaultSize, 0 )
bSizer3.Add( self.bReset, 0, wx.ALL, 5 )
self.bZoomOut = wx.Button( self.m_panel4, wx.ID_ANY, u"Zoom Out", wx.DefaultPosition, wx.DefaultSize, 0 )
bSizer3.Add( self.bZoomOut, 0, wx.ALL, 5 )
self.m_panel4.SetSizer( bSizer3 )
self.m_panel4.Layout()
bSizer3.Fit( self.m_panel4 )
fgSizer1.Add( self.m_panel4, 1, wx.EXPAND |wx.ALL, 5 )
self.SetSizer( fgSizer1 )
self.Layout()
self.Centre( wx.BOTH )
self.bZoomIn.Bind( wx.EVT_BUTTON, self.onZoomIn )
self.bReset.Bind( wx.EVT_BUTTON, self.onZoomReset )
self.bZoomOut.Bind( wx.EVT_BUTTON, self.onZoomOut )
def __del__( self ):
pass
def onZoomIn( self, event ):
self.panelMain.onZoomIn()
event.Skip()
def onZoomReset( self, event ):
self.panelMain.onZoomReset()
event.Skip()
def onZoomOut( self, event ):
self.panelMain.onZoomOut()
event.Skip()
if __name__=='__main__':
app = wx.App(redirect=False)
frame = TestFrame(None, (800, 600))
frame.SetPosition((100, 100))
frame.Show()
app.MainLoop()
I solved the problem with :
import wx
from floatcanvas import FloatCanvas
class MyPanel(wx.Panel):
def __init__(self, parent):
super(MyPanel, self).__init__(parent)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(self.sizer)
# add a canvas
self.Canvas = FloatCanvas.FloatCanvas(self, BackgroundColor = "LIGHT GREY")
self.Canvas.Bind(wx.EVT_SIZE, self.OnSize)
self.sizer.Add(self.Canvas, -1, flag=wx.EXPAND)
# add a toggle button
image_dis = wx.Image('file_disabled.png')
image_ena = wx.Image('file_enabled.png')
img_dis = self.Canvas.AddScaledBitmap(image_dis, (x,-y), Height=image_dis.GetHeight(), Position = 'tl')
img_ena = self.Canvas.AddScaledBitmap(image_ena, (x,-y), Height=image_ena.GetHeight(), Position = 'tl')
img_dis.other = img_ena
img_ena.other = img_dis
img_ena.Visible = False
# bind the toggle button event
img_dis.Bind(FloatCanvas.EVT_FC_LEFT_UP, self.OnToggle)
img_ena.Bind(FloatCanvas.EVT_FC_LEFT_UP, self.OnToggle)
def OnToggle(self, button):
button.other.Visible = True
button.Visible = False
self.Canvas.Draw(True)
def OnSize(self, event):
event.Skip()
wx.CallLater(1, self.Canvas.ZoomToBB)
I can't answer the scaling issue, but an old trick I recall for doing the arbitrary image target hit-checking (no buttons required) goes like this:
1) Create a blank invisible image the same size as the visible one.
2) As you draw targets on the main image, draw an identically shaped "shadow" to the invisible with all the same value pixel (but a unique value for every target). A "handle", if you will.
3) When you get a mouse click on the main image, use the coordinates to get the same pixel from your invisible shadow image. The value will be the handle for the target.
Simple once you hear it, isn't it?
I'd like to create a simple shaped window in wxPython. More or less I want to do the wx equivalent to Tkinter's self.overrideredirect(1) (It get's rid of the default OS boarder), then round the corners on the window.
There's a shaped frame demo in the wxPython demos. I apologize for the indirect source. They originally came as a windows installer here:
source code
You'll want to look at shaped_frame_mobile.py or shaped_frame.py, which both call images.py from that listing for the sample window bitmap. It's not the exact equivalent to overrideredirect since you will have to provide an image to be drawn for the frame, but it could still help you accomplish something similar.
The important parts are the functions that set the window shape based on the bitmap and handle the wx.EVT_PAINT event:
def SetWindowShape(self, evt=None):
r = wx.RegionFromBitmap(self.bmp)
self.hasShape = self.SetShape(r)
def OnPaint(self, evt):
dc = wx.PaintDC(self)
dc.DrawBitmap(self.bmp, 0,0, True)
Edit - Here's an altered shaped_frame_mobile.py that loads the .png image specified in the IMAGE_PATH variable. Change that to point to your image:
import wx
# Create a .png image with something drawn on a white background
# and put the path to it here.
IMAGE_PATH = '/python26/projects/shapedwin/image.png'
class ShapedFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, "Shaped Window",
style = wx.FRAME_SHAPED | wx.SIMPLE_BORDER )
self.hasShape = False
self.delta = wx.Point(0,0)
# Load the image
image = wx.Image(IMAGE_PATH, wx.BITMAP_TYPE_PNG)
image.SetMaskColour(255,255,255)
image.SetMask(True)
self.bmp = wx.BitmapFromImage(image)
self.SetClientSize((self.bmp.GetWidth(), self.bmp.GetHeight()))
dc = wx.ClientDC(self)
dc.DrawBitmap(self.bmp, 0,0, True)
self.SetWindowShape()
self.Bind(wx.EVT_LEFT_DCLICK, self.OnDoubleClick)
self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
self.Bind(wx.EVT_MOTION, self.OnMouseMove)
self.Bind(wx.EVT_RIGHT_UP, self.OnExit)
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Bind(wx.EVT_WINDOW_CREATE, self.SetWindowShape)
def SetWindowShape(self, evt=None):
r = wx.RegionFromBitmap(self.bmp)
self.hasShape = self.SetShape(r)
def OnDoubleClick(self, evt):
if self.hasShape:
self.SetShape(wx.Region())
self.hasShape = False
else:
self.SetWindowShape()
def OnPaint(self, evt):
dc = wx.PaintDC(self)
dc.DrawBitmap(self.bmp, 0,0, True)
def OnExit(self, evt):
self.Close()
def OnLeftDown(self, evt):
self.CaptureMouse()
pos = self.ClientToScreen(evt.GetPosition())
origin = self.GetPosition()
self.delta = wx.Point(pos.x - origin.x, pos.y - origin.y)
def OnMouseMove(self, evt):
if evt.Dragging() and evt.LeftIsDown():
pos = self.ClientToScreen(evt.GetPosition())
newPos = (pos.x - self.delta.x, pos.y - self.delta.y)
self.Move(newPos)
def OnLeftUp(self, evt):
if self.HasCapture():
self.ReleaseMouse()
if __name__ == '__main__':
app = wx.PySimpleApp()
ShapedFrame().Show()
app.MainLoop()
Guys I know there is a accepted answer, I used that answer in ubuntu with python 2.x, however when I tried to use it on windows python 3.x it didn't work. So I fixed it after small research (wx.Region needs a transparent color, see below code). And changed several depreciated methods:
import wx
IMAGE_PATH = ".\Images\myImageFile.png"
class ShapedFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, "Shaped Window",
style = wx.FRAME_SHAPED | wx.SIMPLE_BORDER )
self.hasShape = False
self.delta = wx.Point(0,0)
# Load the image
self.bmp = wx.Image(IMAGE_PATH, wx.BITMAP_TYPE_ANY).ConvertToBitmap()
#self.bmp = wx.Bitmap(image)
self.transparentColour = wx.Colour(255, 255, 255, alpha=wx.ALPHA_OPAQUE)
self.SetClientSize((self.bmp.GetWidth(), self.bmp.GetHeight()))
dc = wx.ClientDC(self)
dc.DrawBitmap(self.bmp, 0,0, True)
self.SetWindowShape()
self.Bind(wx.EVT_LEFT_DCLICK, self.OnDoubleClick)
self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
self.Bind(wx.EVT_MOTION, self.OnMouseMove)
self.Bind(wx.EVT_RIGHT_UP, self.OnExit)
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Bind(wx.EVT_WINDOW_CREATE, self.SetWindowShape)
def SetWindowShape(self, evt=None):
r = wx.Region(self.bmp , self.transparentColour)
self.hasShape = self.SetShape(r)
def OnDoubleClick(self, evt):
if self.hasShape:
self.SetShape(wx.Region())
self.hasShape = False
else:
self.SetWindowShape()
def OnPaint(self, evt):
dc = wx.PaintDC(self)
dc.DrawBitmap(self.bmp, 0, 0, True)
def OnExit(self, evt):
self.Close()
def OnLeftDown(self, evt):
self.CaptureMouse()
pos = self.ClientToScreen(evt.GetPosition())
origin = self.GetPosition()
self.delta = wx.Point(pos.x - origin.x, pos.y - origin.y)
def OnMouseMove(self, evt):
if evt.Dragging() and evt.LeftIsDown():
pos = self.ClientToScreen(evt.GetPosition())
newPos = (pos.x - self.delta.x, pos.y - self.delta.y)
self.Move(newPos)
def OnLeftUp(self, evt):
if self.HasCapture():
self.ReleaseMouse()
if __name__ == '__main__':
app = wx.App()
ShapedFrame().Show()
app.MainLoop()