wxPython for image and buttons (resizable) - python

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?

Related

QGraphicsItem position not updated (PyQt4)

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.

Transparent panel doesn't updates its background after being moved/dragged in wxPython

I am working with python v2.7 and wxPython v3.0 on Windows 8 OS.
The code provided below simply creates a transparent panel named as myPanel that contains a button. The transparent panel is created on a mainPanel which contains an image as a background.
The transparent panel can be dragged around in the frame.
Problem: After dragging the transparent panel I observed that the background of the transparent panel is not updated automatically. How to update it automatically? How ever if I minimize the gui window and restore it again, the background of the transparent panel is updated automatically! I don't understand the reason of this affect?
I tried using Refresh(), Update() etc. in MouseUp(self, e) method, but unfortunately nothing helped.
Here are the screenshots of the app. The initial state is shown in the image below when the app starts:
After dragging the transparent panel, the background is not updated as shown in the image below:
After minimizing the app window and then restoring it, you'll notice that the background of the transparent panel is updated automatically as shown in the image below:
Code: The image used in the code can be downloaded from here. globe.jpg
import wx
class gui(wx.Frame):
def __init__(self, parent, id, title):
self.d = d = {}
wx.Frame.__init__(self, None, id, title, size=(260,260), style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER)
statusbar = self.CreateStatusBar()
self.mainPanel = mainPanel = wx.Panel(self)
self.mainSizer = mainSizer = wx.BoxSizer(wx.VERTICAL)
self.myPanel = myPanel = wx.Panel(mainPanel, -1, style=wx.TRANSPARENT_WINDOW, size=(80,80))
button1 = wx.Button(myPanel, -1, size=(30,30), pos=(10,10))
button1.SetBackgroundColour('#fff111')
mainSizer.Add(myPanel, 0, wx.ALL, 0)
myPanel.Bind(wx.EVT_LEFT_DOWN, self.MouseDown)
myPanel.Bind(wx.EVT_MOTION, self.MouseMove)
myPanel.Bind(wx.EVT_LEFT_UP, self.MouseUp)
image_file = 'globe.jpg'
bmp1 = wx.Image(image_file, wx.BITMAP_TYPE_ANY).ConvertToBitmap()
wx.StaticBitmap(mainPanel, -1, bmp1, (0, 0))
mainPanel.Bind(wx.EVT_MOTION, self.MouseMove)
mainPanel.Bind(wx.EVT_LEFT_UP, self.MouseUp)
mainPanel.SetSizer(mainSizer)
mainPanel.Layout()
def MouseDown(self, e):
o = e.GetEventObject()
sx,sy = self.mainPanel.ScreenToClient(o.GetPositionTuple())
dx,dy = self.mainPanel.ScreenToClient(wx.GetMousePosition())
o._x,o._y = (sx-dx, sy-dy)
self.d['d'] = o
def MouseMove(self, e):
try:
if 'd' in self.d:
o = self.d['d']
x, y = wx.GetMousePosition()
o.SetPosition(wx.Point(x+o._x,y+o._y))
except: pass
def MouseUp(self, e):
try:
if 'd' in self.d: del self.d['d']
except: pass
if __name__=='__main__':
app = wx.App()
frame = gui(parent=None, id=-1, title="Test")
frame.Show()
app.MainLoop()
Thank you for your time!
You can create a custom panel and then draw a portion of the globe on that panel based on where it's located on top of the parent frame. This method "fakes" the transparency. I've included an example below.
import wx
class CustomPanel(wx.Panel):
def __init__(self,parent):
wx.Panel.__init__(self,parent,-1,size=(80,80))
self.Bind(wx.EVT_PAINT, self.OnPaint)
def OnPaint(self, evt):
parentw,parenth = self.GetParent().GetSize()
image = wx.Image('globe.jpg', wx.BITMAP_TYPE_ANY)
x,y = self.GetPosition()
mywidth,myheight = self.GetSize()
if x + mywidth >= parentw:
mywidth = parentw - x
if y + myheight >= parenth:
myheight = parenth - y
drawx = 0
drawy = 0
if x < 0:
drawx = abs(x)
x = 0
if y < 0:
drawy = abs(y)
y = 0
r = wx.Rect(x,y,mywidth,myheight)
try:
image = image.GetSubImage(r)
except:
# rectangle is out of parent
print 'rect ',r ,' is out of parent frame'
return
bitmap = image.ConvertToBitmap()
pdc = wx.PaintDC(self)
pdc.DrawBitmap(bitmap, drawx, drawy)
class gui(wx.Frame):
def __init__(self, parent, id, title):
self.d = d = {}
wx.Frame.__init__(self, None, id, title, size=(260,260), style=wx.DEFAULT_FRAME_STYLE | wx.RESIZE_BORDER | wx.CLIP_CHILDREN)
statusbar = self.CreateStatusBar()
self.mainPanel = mainPanel = wx.Panel(self)
self.mainSizer = mainSizer = wx.BoxSizer(wx.VERTICAL)
#self.myPanel = myPanel = wx.Panel(mainPanel, -1, style=wx.TRANSPARENT_WINDOW, size=(80,80))
self.myPanel = myPanel = CustomPanel(mainPanel)
button1 = wx.Button(myPanel, -1, size=(30,30), pos=(10,10))
button1.SetBackgroundColour('#fff111')
button2 = wx.Button(myPanel, -1, size=(30,30), pos=(40,40))
button2.SetBackgroundColour('#fff111')
mainSizer.Add(myPanel, 0, wx.ALL, 0)
myPanel.Bind(wx.EVT_LEFT_DOWN, self.MouseDown)
myPanel.Bind(wx.EVT_MOTION, self.MouseMove)
myPanel.Bind(wx.EVT_LEFT_UP, self.MouseUp)
image_file = 'globe.jpg'
bmp1 = wx.Image(image_file, wx.BITMAP_TYPE_ANY).ConvertToBitmap()
wx.StaticBitmap(mainPanel, -1, bmp1, (0, 0))
mainPanel.Bind(wx.EVT_MOTION, self.MouseMove)
mainPanel.Bind(wx.EVT_LEFT_UP, self.MouseUp)
mainPanel.SetSizer(mainSizer)
mainPanel.Layout()
def MouseDown(self, e):
o = e.GetEventObject()
sx,sy = self.mainPanel.ScreenToClient(o.GetPositionTuple())
dx,dy = self.mainPanel.ScreenToClient(wx.GetMousePosition())
o._x,o._y = (sx-dx, sy-dy)
self.d['d'] = o
def MouseMove(self, e):
try:
if 'd' in self.d:
o = self.d['d']
x, y = wx.GetMousePosition()
o.SetPosition(wx.Point(x+o._x,y+o._y))
self.myPanel.Refresh()
except: pass
def MouseUp(self, e):
try:
if 'd' in self.d: del self.d['d']
except: pass
if __name__=='__main__':
app = wx.App()
frame = gui(parent=None, id=-1, title="Test")
frame.Show()
app.MainLoop()

drag and drop image with wx.EVT_MOVE

I want write script with drag and drop image. I uploaded 2 images (.jpg ) and tryed to drag image on panel.
When I drag firstImage on Panel - everything looks good.
But when I drag secondImage on Panel (especially when secondImage contact with firstImage) occurring one mistake. Content of secondImage located under content of firstImage. How can I do, that when I drag secondImage, its content located over content of firstImage? Thanks.
script.py
import wx
import wx.lib.buttons as buttons
class Main(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, id=-1, title=title, size=(300, 300))
self.panel = wx.Panel(self, wx.ID_ANY, wx.DefaultPosition, size=(300, 300))
firstImage = ButtonImage(self, self.panel, r'.\image_wtf\1.jpg', (20,20))
secondImage = ButtonImage(self, self.panel, r'.\image_wtf\2.jpg', (20, 135))
self.Layout()
class ButtonImage():
def __init__(self, parent, panel, nameImage, pos):
self.panel = panel
self.bmp = wx.Bitmap(nameImage, wx.BITMAP_TYPE_ANY)
### search for a piece of the maximum position (limit boundary)
self.maxPiecePositionX = self.panel.GetSize()[0] - self.bmp.GetSize()[0]
self.maxPiecePositionY = self.panel.GetSize()[1] - self.bmp.GetSize()[1]
self.bmapBtn = wx.BitmapButton(self.panel, id=wx.ID_ANY, bitmap=self.bmp, style=wx.NO_BORDER, pos=pos)
#self.bmapBtn.Bind(wx.EVT_ENTER_WINDOW, self.EnterButton, self.bmapBtn)
#self.bmapBtn.Bind(wx.EVT_LEAVE_WINDOW, self.LeaveButton, self.bmapBtn)
self.bmapBtn.Bind(wx.EVT_LEFT_DOWN, self.OnClickDown, self.bmapBtn)
self.bmapBtn.Bind(wx.EVT_LEFT_UP, self.OnClickUp, self.bmapBtn)
self.bmapBtn.Bind(wx.EVT_MOTION, self.MoveButton, self.bmapBtn)
#self.bmapBtn.Bind(wx.EVT_MOVE, self.MoveButtonTest, self.bmapBtn)
self.hold = 0
self.holdPosition = (0, 0)
def EnterButton(self, event):
pass
def LeaveButton(self, event):
self.hold = 0
def OnClickDown(self, event):
obj = event.GetEventObject()
self.hold = 1
self.holdPosition = (event.GetX(), event.GetY())
def OnClickUp(self, event):
self.hold = 0
def MoveButton(self, event):
deltaX, deltaY = 0, 0
if self.hold:
deltaX = event.GetPosition()[0] - self.holdPosition[0]
deltaY = event.GetPosition()[1] - self.holdPosition[1]
newPositionX = self.bmapBtn.GetPosition()[0] + deltaX
newPositionY = self.bmapBtn.GetPosition()[1] + deltaY
if (0 < newPositionX < self.maxPiecePositionX) and (0 < newPositionY < self.maxPiecePositionY):
self.bmapBtn.SetPosition((newPositionX, newPositionY))
else:
self.holdPosition = self.holdPosition[0] + deltaX, self.holdPosition[1] + deltaY
self.bmapBtn.Refresh()
app = wx.PySimpleApp()
frame = Main(None, u"Game")
frame.Show()
app.MainLoop()
You can use Raise() to raises the window to the top of the window hierarchy.
def MoveButton():
.................
.................
self.bmapBtn.Raise()
self.bmapBtn.Refresh()

What is the simplest way to create a shaped window in wxPython?

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()

wxPython GetClientSize() returning (0, 0)

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 =)

Categories