Related
I've created a panel thats derived from wx.lib.scrolledpanel. I could scroll on it fine with my mousewheel until I put a grid into the panel. Now when the mouse cursor is on top of the grid, the scroll stopped working, and would start working again if i moved the cursor outside of the grid.
I figured the easiest solution after searching and searching was to just manually capture the mousewheel event and scroll the panel manually. I bound this handler to wx.EVT_MOUSEWHEEL inside my wx.App object
class Wx_app(wx.App):
def __init__(self):
super().__init__(clearSigInt=True)
self.frame = MyFrame(None, pos=(0,0), size=(1900, 1100))
self.Bind(wx.EVT_MOUSEWHEEL, self.on_mouse_wheel)
def on_mouse_wheel(self, e):
# get the current scroll pos, is tuple with x as first val, y as second val
pos = self.frame.panel.CalcUnscrolledPosition(0, 0)
y_pos = pos[1]
# detemrine if user is scrolling up or down
if e.GetWheelRotation() > 0:
# user is scrolling up
print("UP")
self.frame.panel.Scroll(0, y_pos + 10)
else:
# user is scrolling down
print("DOWN")
self.frame.panel.Scroll(0, y_pos - 10)
This code works when i try to scroll down, but when i try to scroll back up with the the mousewheel nothing happens, even though "UP" registers in my terminal. Also i would think up should be y_pos - 10 and not y_pos + 10, but then the wheel scrolls in the opposite direction you would expect. What am I doing wrong? Perhaps i'm not getting the correct existing position in the first place, but CalcUnscrolledPosition is the only thing I could find that could maybe do that. I'm new to Python please explain it like I'm a 5 year old. thanks
Your immediate issue is that Up would be Y - 10 and Down would be Y + 10.
You need to go further up (less y) or further down (more y).
You also may well be comparing Apples with Oranges, when relying on the position as returned from the mouse event and how that relates to the position within the scrolled window.
The crux of this is that both the scrolledpanel and the grid are scrollable widgets.
Your issue seems to be that your grid is not of sufficent size to show the grid's scrollbars, thus causing confusion.
You can force the scrollbars on the grid and size it so that it is obvious that there are two sets of scrollbars and hopefully your users will work it out.
Here is a sample grid in a scrolled panel to play with (ignore the bound functions, they were there because initially I misunderstood your issue, thinking that you wanted manual scrolling within the grid)
import wx
import wx.lib.scrolledpanel
import wx.grid
class MyPanel(wx.Panel):
def __init__(self, *args, **kwargs):
wx.Panel.__init__(self, *args, **kwargs)
self.sizer = wx.BoxSizer(wx.HORIZONTAL)
self.fileFormPanel = FileFormPanel(self)
self.sizer.Add(self.fileFormPanel, 1, wx.EXPAND)
self.SetSizer(self.sizer)
class FileFormPanel(wx.lib.scrolledpanel.ScrolledPanel):
def __init__(self, parent):
wx.lib.scrolledpanel.ScrolledPanel.__init__(self, parent)
sizer = wx.BoxSizer(wx.VERTICAL)
# Create a wxGrid object
self.grid = wx.grid.Grid(self, -1, size=(300,480))
self.grid.CreateGrid(5, 10)
self.grid.SetRowLabelSize(1)
self.grid.SetDefaultColSize(120)
self.grid.SetColSize(0, 50)
self.grid.ShowScrollbars(True, True)
for j in range(5):
for k in range(10):
self.grid.SetCellValue(j, k, str(k))
text = wx.TextCtrl(self, wx.ID_ANY, value="Some text")
sizer.Add(self.grid)
sizer.Add(text)
self.SetSizer(sizer)
self.SetupScrolling()
#self.grid.Bind(wx.EVT_MOUSEWHEEL, self.OnGrid)
#self.Bind(wx.EVT_MOUSEWHEEL, self.OnScroll)
#self.grid.Bind(wx.grid.EVT_GRID_SELECT_CELL, self.OnSelect)
# def OnGrid(self, event):
# obj = event.GetEventObject()
# if event.WheelRotation > 0:
# self.grid.MoveCursorUp(False)
# else:
# self.grid.MoveCursorDown(False)
#
# def OnSelect(self, event):
# obj = event.GetEventObject()
# r = event.GetRow()
# c = event.GetCol()
# self.grid.MakeCellVisible(r,c)
# event.Skip()
#
# def OnScroll(self, event):
# print("window scroll")
# event.Skip()
class DemoFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, parent = None, size = (400, 400))
MyPanel(self)
class App(wx.App):
def OnInit(self):
self.frame = DemoFrame()
self.frame.Show()
self.SetTopWindow(self.frame)
return True
app = App()
app.MainLoop()
Here I wrote this code but did not work:
import sys
from PyQt4 import QtGui, QtCore
class CricleImage(QtCore.QObject):
def __init__(self):
super(CricleImage, self).__init__()
self.pix = QtGui.QGraphicsPixmapItem(QtGui.QPixmap("bird(01).jpg"))
#drawRoundCircle
rect = self.pix.boundingRect()
self.gri = QtGui.QGraphicsRectItem(rect)
self.gri.setPen(QtGui.QColor('red'))
if __name__ == '__main__':
myQApplication = QtGui.QApplication(sys.argv)
IMG = CricleImage()
#scene
scene = QtGui.QGraphicsScene(0, 0, 400, 300)
scene.addItem(IMG.pix)
#view
view = QtGui.QGraphicsView(scene)
view.show()
sys.exit(myQApplication.exec_())
One possible solution is to overwrite the paint() method of the QGraphicsPixmapItem and use setClipPath to restrict the painting region:
from PyQt4 import QtCore, QtGui
class CirclePixmapItem(QtGui.QGraphicsPixmapItem):
#property
def radius(self):
if not hasattr(self, "_radius"):
self._radius = 0
return self._radius
#radius.setter
def radius(self, value):
if value >= 0:
self._radius = value
self.update()
def paint(self, painter, option, widget=None):
painter.save()
rect = QtCore.QRectF(QtCore.QPointF(), 2 * self.radius * QtCore.QSizeF(1, 1))
rect.moveCenter(self.boundingRect().center())
path = QtGui.QPainterPath()
path.addEllipse(rect)
painter.setClipPath(path)
super().paint(painter, option, widget)
painter.restore()
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
pixmap = QtGui.QPixmap("logo.jpg")
scene = QtGui.QGraphicsScene()
view = QtGui.QGraphicsView(scene)
view.setRenderHints(
QtGui.QPainter.Antialiasing | QtGui.QPainter.SmoothPixmapTransform
)
it = CirclePixmapItem(pixmap)
scene.addItem(it)
it.radius = pixmap.width() / 2
view.show()
sys.exit(app.exec_())
Update:
# ...
view = QtGui.QGraphicsView(
scene, alignment=QtCore.Qt.AlignTop | QtCore.Qt.AlignLeft
)
# ...
view.show()
it.setPos(80, 80)
sys.exit(app.exec_())
Second possible solution:
import sys
#from PyQt4 import QtCore, QtGui
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class Label(QLabel):
def __init__(self, *args, antialiasing=True, **kwargs):
super(Label, self).__init__(*args, **kwargs)
self.Antialiasing = antialiasing
self.setMaximumSize(200, 200)
self.setMinimumSize(200, 200)
self.radius = 100
self.target = QPixmap(self.size())
self.target.fill(Qt.transparent) # Fill the background with transparent
# Upload image and zoom to control level
p = QPixmap("head2.jpg").scaled(
200, 200, Qt.KeepAspectRatioByExpanding, Qt.SmoothTransformation)
painter = QPainter(self.target)
if self.Antialiasing:
# antialiasing
painter.setRenderHint(QPainter.Antialiasing, True)
painter.setRenderHint(QPainter.HighQualityAntialiasing, True)
painter.setRenderHint(QPainter.SmoothPixmapTransform, True)
path = QPainterPath()
path.addRoundedRect(
0, 0, self.width(), self.height(), self.radius, self.radius)
# pruning
painter.setClipPath(path)
painter.drawPixmap(0, 0, p)
self.setPixmap(self.target)
class Window(QWidget):
def __init__(self, *args, **kwargs):
super(Window, self).__init__(*args, **kwargs)
layout = QHBoxLayout(self)
layout.addWidget(Label(self))
self.setStyleSheet("background: green;")
if __name__ == "__main__":
app = QApplication(sys.argv)
w = Window()
w.show()
sys.exit(app.exec_())
Another approach, slightly different from the one provided by eyllanesc. While this might seem much more complicated than that, I believe that it offers a better implementation and interface, with the addition of better performance.
In this case, instead of overriding the paint method (that is run everytime the item is painted, which happens very often), I'm using the shape() function along with the QGraphicsItem.ItemClipsToShape flag, that allows to limit the painting only within the boundaries of the path shape.
What shape() does is to return a QPainterPath that includes only the "opaque" portions of an item that will react to mouse events and collision detection (with the scene boundaries and its other items). In the case of a QGraphicsPixmapItem this also considers the possible mask (for example, a PNG based pixmap with transparent areas, or an SVG image). By setting the ItemClipsToShape we can ensure that the painting will only cover the parts of the image that are within that shape.
The main advantage of this approach is that mouse interaction and collision detection with other items honors the actual circle shape of the item.
This means that if you click outside the circle (but still within the rectangle area of the full image), the item will not receive the event. Also, if the image supports masking (a PNG with transparent areas) which by default would not be part of the shape, this method will take that into account.
Also, by "caching" the shape we are also speeding up the painting process a bit (since Qt will take care of it, without any processing done using python).
class CircleClipPixmapItem(QtGui.QGraphicsPixmapItem):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setFlag(self.ItemClipsToShape)
self.updateRect()
def updateRect(self):
baseRect = super().boundingRect()
minSize = min(baseRect.width(), baseRect.height())
self._boundingRect = QtCore.QRectF(0, 0, minSize, minSize)
self._boundingRect.moveCenter(baseRect.center())
self._shape = QtGui.QPainterPath()
self._shape.addEllipse(self._boundingRect)
# the shape might include transparent areas, using the & operator
# I'm ensuring that _shape only includes the areas that intersect
# the shape provided by the base implementation
self._shape &= super().shape()
def setPixmap(self, pm):
super().setPixmap(pm)
# update the shape to reflect the new image size
self.updateRect()
def setShapeMode(self, mode):
super().setShapeMode(mode)
# update the shape with the new mode
self.updateRect()
def boundingRect(self):
return self._boundingRect
def shape(self):
return self._shape
Keep in mind that there's a catch about both methods: if the aspect ratio of the image differs very much from 1:1, you'll always end up with some positioning issues. With my image, for example, it will always be shown 60 pixel right from the actual item position. If you want to avoid that, the updateRect function will be slightly different and, unfortunately, you'll have to override the paint() function (while still keeping it a bit faster than other options):
def updateRect(self):
baseRect = super().boundingRect()
minSize = min(baseRect.width(), baseRect.height())
self._boundingRect = QtCore.QRectF(0, 0, minSize, minSize)
# the _boundingRect is *not* centered anymore, but a new rect is created
# as a reference for both shape intersection and painting
refRect= QtCore.QRectF(self._boundingRect)
refRect.moveCenter(baseRect.center())
# note the minus sign!
self._reference = -refRect.topLeft()
self._shape = QtGui.QPainterPath()
self._shape.addEllipse(self._boundingRect)
self._shape &= super().shape().translated(self._reference)
# ...
def paint(self, painter, option, widget):
# we are going to translate the painter to the "reference" position,
# let's save its state before that
painter.save()
painter.translate(self._reference)
super().paint(painter, option, widget)
painter.restore()
This will make the boundingRect (and resulting internal shape) position the whole item at the top-left of the item position.
The following image shows the differences between the two approaches; I've used a PNG with transparent areas to better explain the whole concept.
On the top there is the source image, in the middle the paint() override approach, and finally the shape() implementation at the bottom.
While there seems to be no difference between the two methods, as shown on the examples on the left, on the right I've highlighted the actual boundaries of each item, by showing their boundingRect (in blue), shape (in red), which will be used for mouse events, collision detection and paint clipping; the green circle shows the overall circle used for both shape and painting.
The examples in the middle show the positioning based on the original image size, while on the right you can see the absolute positioning based on the effective circle size as explained above.
Drawing a circle around the image
Unfortunately, the ItemClipsToShape flag doesn't support antialiasing for clipping: if we just draw a circle after painting the image the result will be ugly. On the left you can see that the circle is very pixellated and does not overlap perfectly on the image. On the right the correct painting.
To support that, the flag must not be set, and the paint function will be a bit different.
class CircleClipPixmapItem(QtGui.QGraphicsPixmapItem):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# we don't need this anymore:
# self.setFlag(self.ItemClipsToShape)
# always set the shapeMode to the bounding rect without any masking:
# if the image has transparent areas they will be clickable anyway
self.setShapeMode(self.BoundingRectShape)
self.updateRect()
self.pen = QtGui.QPen(QtCore.Qt.red, 2)
# ...
def setPen(self, pen):
self.pen = pen
self.update()
def paint(self, painter, option, widget):
# we are going to translate the painter to the "reference" position,
# and we are also changing the pen, let's save the state before that
painter.save()
painter.translate(.5, .5)
painter.setRenderHints(painter.Antialiasing)
# another painter save "level"
painter.save()
# apply the clipping to the painter
painter.setClipPath(self._shape)
painter.translate(self._reference)
super().paint(painter, option, widget)
painter.restore()
painter.setPen(self.pen)
# adjust the rectangle to precisely match the circle to the image
painter.drawEllipse(self._boundingRect.adjusted(.5, .5, -.5, -.5))
painter.restore()
# restore the state of the painter
You use wx.EVT_PAINT and wx.PaintDC to draw shapes, so that when window is resized (redrawn) shapes will not be lost. This works when the window is created. But, how will I preserve the shapes that I create after window is created?
Below, I present you a code, when the app first starts, a rectangle is drawn on the window. When user double clicks somewhere on the window, another rectangle is created. The initial rectangle is always preserved because it is bind to wx.EVT_PAINT event, so that it will be redrawn every time the window is redrawn.
But the second rectangle is not associated to the wx.EVT_PAINT, therefore it is lost when window is redrawn. How do I preserve the second rectangle as well?
import wx
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent, -1)
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Bind(wx.EVT_LEFT_DCLICK, self.on_left_double_click)
def OnPaint(self, evt):
dc = wx.PaintDC(self)
dc.DrawRectangle(50, 60, 90, 40)
def on_left_double_click(self, evt):
x = evt.GetX()
y = evt.GetY()
dc = wx.ClientDC(self)
dc.SetBrush(wx.Brush("yellow"))
dc.DrawRectangle(x, y, 90, 40)
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Test",style=wx.DEFAULT_FRAME_STYLE,size=wx.Size(400, 300))
self.main_panel = MyPanel(self)
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
There is no universal solution to this except handling every drawing operation in a wx.PaintDC. You would do something along the lines of the following:
def __init__(self, parent):
# ...
self.show_yellow_box = False
self.box_pos = None
def OnPaint(self, evt):
dc = wx.PaintDC(self)
dc.DrawRectangle(50, 60, 90, 40)
if self.show_yellow_box:
x, y = self.box_pos
dc.SetBrush(wx.Brush("yellow"))
dc.DrawRectangle(x, y, 90, 40)
def on_left_double_click(self, evt):
x = evt.GetX()
y = evt.GetY()
self.box_pos = (x, y)
self.show_yellow_box = True
self.Refresh() # important, to trigger EVT_PAINT on panel
If the operations in the paint event are more expensive, you probably will end up collecting the expensive drawing operations on the DC in a wx.MemoryDC and blit the bitmap content back onto the panel in the MyPanel.OnPaint.
There is a temporary DC (wx.Overlay/wx.OverlayDC), which is however only useful to apply temporary changes between paint events.
What Python-related code (PyGTK, Glade, Tkinter, PyQT, wxPython, Cairo, ...) could you easily use to create a GUI to do some or all of the following?
Part of the GUI has an immovable square grid.
The user can press a button to create a resizable rectangle.
The user can drag the rectangle anywhere on the grid, and it will snap to the grid.
The DiagramScene Eaxmple that comes with PyQt implements much of the functionality you want. It has a fixed background grid, you can create a rectangle object but it's not resizable and doesn't snap to grid.
This SO article has advice on resizing graphical objects with the mouse. It's for C++ Qt but the technique should be easy to replicate in PyQt.
For snap-to-grid I don't think there is any built-in functionality. You would probably need to reimplement the itemChange(GraphicsItemChange change, const QVariant &value) function. Pseudocode:
if (object not possitioned exactly on the grid):
(possition the item on the grid)
Repossitioning the item will cause itemChange to get called again, but that's ok because the item will be possitioned correctly and won't be moved again, so you'll not be stuck in an endless loop.
I was looking for a while for something like this, and finally managed to cook up a "minimal" working example with Python wx, utilizing wx.lib.ogl and its Diagram and ShapeCanvas classes. The code (below) results with something like this:
Note:
The app starts with the circle added; press SPACE to add rectangles at random position
Click an object to select it (to show handles); to deselect it, click the object again (clicking the background has no effect) - this is functionality of ogl
The grid is drawn "manually"; however the snapping-to-grid is functionality of ogl
Snap-to-grid only works automatically when moving shapes with mouse drag; for other purposes you must manually call it
Snap-to-grid - as well as resizing of shape by handles - works in respect to the center of each shape (not sure if ogl allows for changing that anchor to, say, bottom left corner)
The example uses a MyPanel class that does its own drawing, and inherits both from ogl.ShapeCanvas and from wx.Panel (though the mixin with wx.Panel can be dropped, and the code will still work the same) - which is then added to a wx.Frame. Note the code comments for some caveats (such as the use of ogl.ShapeCanvas blocking all key events, unless a SetFocus is performed on that widget first).
The code:
import wx
import wx.lib.ogl as ogl
import random
# tested on wxPython 2.8.11.0, Python 2.7.1+, Ubuntu 11.04
# started from:
# http://stackoverflow.com/questions/25756896/drawing-to-panel-inside-of-frame-in-wxpython/27804975#27804975
# see also:
# wxPython-2.8.11.0-demo/demo/OGL.py
# https://www.daniweb.com/software-development/python/threads/186203/creating-editable-drawing-objects-in-wxpython
# http://gscept.com/svn/Docs/PSE/Milestone%203/code/trunk/python_test/src/oglEditor.py
# http://nullege.com/codes/search/wx.lib.ogl.Diagram
# http://nullege.com/codes/show/src%40w%40e%40web2cms-HEAD%40web2py%40gluon%40contrib%40pyfpdf%40designer.py/465/wx.lib.ogl.Diagram/python
# https://www.daniweb.com/software-development/python/threads/204969/setfocus-on-canvas-not-working
# http://stackoverflow.com/questions/3538769/how-do-you-draw-a-grid-and-rectangles-in-python
# http://stackoverflow.com/questions/7794496/snapping-to-pixels-in-wxpython
# ogl.ShapeCanvas must go first, else TypeError: Cannot create a consistent method resolution
class MyPanel(ogl.ShapeCanvas, wx.Panel):#(wx.PyPanel): #PyPanel also works
def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0, name="MyPanel"):
super(MyPanel, self).__init__(parent, id, pos, size, style, name)
self.gridsize = 20 # in pixels
# must have these (w. Diagram) if using ogl.ShapeCanvas:
self.diagram = ogl.Diagram()
self.SetDiagram(self.diagram)
self.diagram.SetCanvas(self)
# set up snap to grid - note, like this it works only for drag (relative to shape center), not for resize via handles!
self.diagram.SetGridSpacing( self.gridsize )
self.diagram.SetSnapToGrid( True )
# initialize array of shapes with one element
self.shapes = []
self.MyAddShape(
ogl.CircleShape(85), # diameter - drag marquee will not be visible if (diameter mod gridsize == 0), as it will overlap with the grid lines
60, 60, wx.Pen(wx.BLUE, 3), wx.GREEN_BRUSH, "Circle"
)
self.Bind(wx.EVT_SIZE, self.OnSize)
self.Bind(wx.EVT_PAINT, self.OnPaint)
wx.EVT_KEY_DOWN(self, self.OnKeyPressedM)
def OnKeyPressedM(self, event):
keyCode = event.GetKeyCode()
print("MyPanel.OnKeyPressedM: %d"%(keyCode) )
# insert a rectangle here on [SPACE]:
if keyCode == wx.WXK_SPACE:
randx = random.randint(1, 300)
randy = random.randint(1, 200)
if self.diagram.GetSnapToGrid():
randx, randy = self.Snap(randx, randy) # must do snapping (if desired) manually, here at insertion!
self.MyAddShape(
ogl.RectangleShape(60, 20),
randx, randy, wx.BLACK_PEN, wx.LIGHT_GREY_BRUSH, "Rect %d"%(len(self.shapes))
)
self.Refresh(False)
event.Skip() # must have this, to have the MyFrame.OnKeyPressed trigger as well!
def OnSize(self, event):
#print("OnSize" +str(event))
self.Refresh() # must have here!
event.Skip()
def DrawBackgroundGrid(self):
dc = wx.PaintDC(self)
#print(dc)
rect = self.GetClientRect()
rx, ry, rw, rh = rect
dc.SetBrush(wx.Brush(self.GetForegroundColour()))
dc.SetPen(wx.Pen(self.GetForegroundColour()))
# draw ("tile") the grid
x = rx
while x < rx+rw:
y = ry
dc.DrawLine(x, ry, x, ry+rh) # long (vertical) lines
while y < ry+rh:
dc.DrawLine(x, y, x+self.gridsize, y) # short (horizontal) lines
y = y + self.gridsize
x = x + self.gridsize
def OnPaint(self, event):
dc = wx.PaintDC(self) # works
self.DrawBackgroundGrid()
# self.Refresh() # recurses here - don't use!
# self.diagram.GetCanvas().Refresh() # blocks here - don't use!
self.diagram.GetCanvas().Redraw(dc) # this to redraw the elements on top of the grid, drawn just before
# MyAddShape is from OGL.py:
def MyAddShape(self, shape, x, y, pen, brush, text):
# Composites have to be moved for all children to get in place
if isinstance(shape, ogl.CompositeShape):
dc = wx.ClientDC(self)
self.PrepareDC(dc)
shape.Move(dc, x, y)
else:
shape.SetDraggable(True, True)
shape.SetCanvas(self)
shape.SetX(x)
shape.SetY(y)
if pen: shape.SetPen(pen)
if brush: shape.SetBrush(brush)
if text:
for line in text.split('\n'):
shape.AddText(line)
#shape.SetShadowMode(ogl.SHADOW_RIGHT)
self.diagram.AddShape(shape)
shape.Show(True)
evthandler = MyEvtHandler(self)
evthandler.SetShape(shape)
evthandler.SetPreviousHandler(shape.GetEventHandler())
shape.SetEventHandler(evthandler)
self.shapes.append(shape)
return shape
# copyfrom OGL.pyl; modded
class MyEvtHandler(ogl.ShapeEvtHandler):
def __init__(self, parent): #
ogl.ShapeEvtHandler.__init__(self)
self.parent = parent
def UpdateStatusBar(self, shape):
x, y = shape.GetX(), shape.GetY()
width, height = shape.GetBoundingBoxMax()
self.parent.Refresh(False) # do here, to redraw the background after a drag move, or scale of shape
print("Pos: (%d, %d) Size: (%d, %d)" % (x, y, width, height))
def OnLeftClick(self, x, y, keys=0, attachment=0):
# note: to deselect a selected shape, don't click the background, but click the shape again
shape = self.GetShape()
canvas = shape.GetCanvas()
dc = wx.ClientDC(canvas)
canvas.PrepareDC(dc)
if shape.Selected():
shape.Select(False, dc)
#canvas.Redraw(dc)
canvas.Refresh(False)
else:
redraw = False
shapeList = canvas.GetDiagram().GetShapeList()
toUnselect = []
for s in shapeList:
if s.Selected():
# If we unselect it now then some of the objects in
# shapeList will become invalid (the control points are
# shapes too!) and bad things will happen...
toUnselect.append(s)
shape.Select(True, dc)
if toUnselect:
for s in toUnselect:
s.Select(False, dc)
##canvas.Redraw(dc)
canvas.Refresh(False)
self.UpdateStatusBar(shape)
def OnEndDragLeft(self, x, y, keys=0, attachment=0):
shape = self.GetShape()
ogl.ShapeEvtHandler.OnEndDragLeft(self, x, y, keys, attachment)
if not shape.Selected():
self.OnLeftClick(x, y, keys, attachment)
self.UpdateStatusBar(shape)
def OnSizingEndDragLeft(self, pt, x, y, keys, attch):
ogl.ShapeEvtHandler.OnSizingEndDragLeft(self, pt, x, y, keys, attch)
self.UpdateStatusBar(self.GetShape())
def OnMovePost(self, dc, x, y, oldX, oldY, display):
shape = self.GetShape()
ogl.ShapeEvtHandler.OnMovePost(self, dc, x, y, oldX, oldY, display)
self.UpdateStatusBar(shape)
if "wxMac" in wx.PlatformInfo:
shape.GetCanvas().Refresh(False)
def OnRightClick(self, *dontcare):
#self.log.WriteText("%s\n" % self.GetShape())
print("OnRightClick")
class MyFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, -1, "Custom Panel Grid Demo")
# This creates some pens and brushes that the OGL library uses.
# (else "global name 'BlackForegroundPen' is not defined")
# It should be called after the app object has been created, but
# before OGL is used.
ogl.OGLInitialize()
self.SetSize((300, 200))
self.panel = MyPanel(self) #wx.Panel(self)
self.panel.SetBackgroundColour(wx.Colour(250,250,250))
self.panel.SetForegroundColour(wx.Colour(127,127,127))
sizer_1 = wx.BoxSizer(wx.HORIZONTAL)
sizer_1.Add(self.panel, 1, wx.EXPAND | wx.ALL, 0)
self.SetSizer(sizer_1)
self.SetAutoLayout(1)
self.Layout()
self.Show(1)
# NOTE: on my dev versions, using ogl.Diagram causes _all_
# key press events, from *anywhere*, to stop propagating!
# Doing a .SetFocus on the ogl.ShapeCanvas panel,
# finally makes the Key events propagate!
# (troubleshoot via run.py from wx python demo)
self.panel.SetFocus()
self.Bind(wx.EVT_CHAR_HOOK, self.OnKeyPressed) # EVT_CHAR_HOOK EVT_KEY_DOWN
def OnKeyPressed(self, event):
print("MyFrame.OnKeyPressed (just testing)")
app = wx.App(0)
frame = MyFrame(None)
app.SetTopWindow(frame)
frame.Show()
app.MainLoop()
Those actions are not that difficult. All you really need for that is hit detection, which is not hard (is the cursor over the correct area? Okay, perform the operation then). The harder part is finding an appropriate canvas widget for the toolkit in use.
I'm trying to make a special splash screen that is displayed while the application is loading,
it outputs messages of the various components loading and features a progress bar.
The first job I am tackling is mapping a .png image to the frame that will host the splash screen.
import wx
class edSplash(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, -1, title, size=(410, 410), style=wx.NO_BORDER)
self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
self.Center()
self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
return
def OnEraseBackground(self, evt):
dc = evt.GetDC()
if not dc:
dc = wx.ClientDC(self)
rect = self.GetUpdateRegion().GetBox()
dc.SetClippingRect(rect)
tempBrush = wx.Brush((0,0,0,0),wx.TRANSPARENT)
print tempBrush
dc.SetBackground(tempBrush)
dc.SetBackgroundMode(wx.TRANSPARENT)
#dc.Clear()
img = wx.Image("splash.png", wx.BITMAP_TYPE_PNG, -1)
bmp = wx.BitmapFromImage(img)
dc.DrawBitmap(bmp, 0, 0, True)
def PushMessage(self, mesage):
print mesage
class edApp(wx.App):
def OnInit(self):
splash = edSplash(None, 'Ed')
self.SetTopWindow(splash)
splash.Show(True)
return True
if __name__ == '__main__':
edApp(redirect=False).MainLoop()
The problem is that dc.Clear() clears to an opaque rectangle, although i have set it's brush and mode to transparent (I think :D). Commenting out dc.Clear() gives me the desired variable transparency based on the .png's alpha channel but the window gathers image noise from the neighboring windows.
How could I get both the .png's transparency and have the background clearing to an transparent brush to keep from gathering image noise?
Maybe you should try putting the background image onto a panel rather than the frame. Here's one way to do it:
http://www.blog.pythonlibrary.org/2010/03/18/wxpython-putting-a-background-image-on-a-panel/