I'm trying to make something of a Microsoft Paint-esque app using wxPython.
Currently, the app 'draws' onto the screen with a circular brush during a mouse left-down event. This is great, and the desired behavior. But I also need a circle of the same radius to 'follow' the mouse, without it drawing persistently onto wx.PaintDC.
That is, a circle of some radius follows the mouse around the screen, but only when the left mouse button is held, should the circle be 'permanently' drawn onto the buffer.
The approaches I've taken either (1) have a circle following the mouse around, but draw onto the PaintDC instance regardless of mouse-down, (2) follow the mouse around but never draw persistently onto the PaintDC instance, or (3) do not follow the mouse around, but appear and are drawn persistently on left-mouse down (see the example below).
Thank you!
import wx
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent, -1)
self.draw = False
self.radius = 50
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Bind(wx.EVT_MOTION, self.Draw)
self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
def OnPaint(self, event):
dc = wx.PaintDC(self)
def Draw(self, event):
if self.draw == True:
x = event.GetX()
y = event.GetY()
dc = wx.ClientDC(self)
pen = wx.Pen(wx.Colour(192,192,192,128), 2)
brush = wx.Brush(wx.Colour(192,192,192,128))
dc.SetPen(pen)
dc.SetBrush(brush)
dc.DrawCircle(x,y,self.radius)
def OnLeftDown(self, event):
self.draw = True
def OnLeftUp(self, event):
self.draw = False
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 the wx.Overlay class that does a pretty good job assisting with drawing temporary things on top of more permanent stuff. See the Overlay sample in the demo: https://github.com/wxWidgets/Phoenix/blob/master/demo/Overlay.py
Related
What I want to archive is a label that is created as soon as a button is pressed and follows the mouse until there is a 'click'.
My problem with that is that I can't seem to get the 'setMouseTracking(True)' command at the right widget...
import sys
from PyQt5 import QtCore, QtGui, QtWidgets, uic
from PyQt5.QtCore import Qt
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.setGeometry(0,0,1000,1100)
self.main = QtWidgets.QLabel()
self.setCentralWidget(self.main)
self.label = QtWidgets.QLabel()
canvas = QtGui.QPixmap(900, 900)
canvas.fill(QtGui.QColor('#ffffff')) # Fill entire canvas.
self.label.setPixmap(canvas)
# self.last_x, self.last_y = None, None
self.button = QtWidgets.QPushButton('create Block')
self.button.clicked.connect(self.buttonAction)
vbox = QtWidgets.QVBoxLayout()
vbox.addWidget(self.label)
vbox.addWidget(self.button)
self.main.setLayout(vbox)
# self.label.setMouseTracking(True)
self.setWindowTitle('testing')
def mouseMoveEvent(self, e):
# if self.last_x is None: # First event.
# self.last_x = e.x()
# self.last_y = e.y()
# return # Ignore the first time.
# painter = QtGui.QPainter(self.label.pixmap())
# painter.drawLine(self.last_x, self.last_y, e.x(), e.y())
# painter.end()
try:
self.image.move(e.x(), e.y())
except:
pass
self.update()
# Update the origin for next time.
# self.last_x = e.x()
# self.last_y = e.y()
def mouseReleaseEvent(self, e):
# self.last_x = None
# self.last_y = None
def buttonAction(self):
block = QtGui.QPixmap(20, 20)
block.fill(QtGui.QColor('blue'))
self.image = QtWidgets.QLabel(self.label)
self.image.setPixmap(block)
self.image.move(20,20)
self.image.show()
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
I don't know if my problem is that I attach setMouseTracking(True) to the wrong widget or if it is something else entirerly.
With clicking action it works, but that is not what I intend to do...
Edit: fixed some of the code issues
For clarification what my problem is: I have a canvas and a button inside an layout, as soon as the button is clicked a new canvas shall be created that follows the mouse pointer 'until' I click. So I don't want any kind of drag-and-drop action, but instead a small canvas that is following the mouse pointer.
This is needed as I intend to use the little canvas to show what an graphic would look like at a certain canvas position without printing it there. So the little canvas is something like a template.
There are some conceptual problems in your logic.
First of all, the mouse tracking only works for the widget it's set on. Also, if the widget accepts the mouse move event, the parent will not receive it.
In your case you are not receiving it because you are implementing the mouseMoveEvent in the main window, which by default ignores it if no mouse button is pressed (like most widgets).
While you could try to set it on the "target" widget and the parent (in your case, the canvas and the main window), you'll certainly have some issues at a certain point if any underlying widget accepts mouse movements; since you're going to need the "preview" only on the actual "canvas", there's no need to create a new widget, as you can just directly paint on the canvas instead, and finally draw on the actual pixmap only when needed.
This is a possible implementation:
class Canvas(QtWidgets.QLabel):
def __init__(self):
super().__init__()
pixmap = QtGui.QPixmap(900, 900)
pixmap.fill(QtCore.Qt.white)
self.setPixmap(pixmap)
self.setMouseTracking(True)
self.preview = False
def startPreview(self):
self.preview = True
self.update()
def drawMiniCanvas(self, pos):
pm = self.pixmap()
qp = QtGui.QPainter(pm)
qp.setBrush(QtCore.Qt.blue)
if self.size() != pm.size():
# if the pixmap is smaller than the actual size of the canvas, the position
# must be translated to its contents before painting
alignment = self.alignment()
pmRect = pm.rect()
if alignment == QtCore.Qt.AlignCenter:
pmRect.moveCenter(self.rect().center())
else:
if alignment & QtCore.Qt.AlignHCenter:
pmRect.moveLeft((self.width() - pm.width()) / 2)
elif alignment & QtCore.Qt.AlignRight:
pmRect.moveRight(self.width())
if alignment & QtCore.Qt.AlignVCenter:
pmRect.moveTop((self.height() - pm.height()) / 2)
elif alignment & QtCore.Qt.AlignBottom:
pmRect.moveBottom(self.height())
pos -= pmRect.topLeft()
qp.drawRect(pos.x(), pos.y(), 20, 20)
qp.end()
self.setPixmap(pm)
def mouseMoveEvent(self, event):
if self.preview:
self.update()
def mousePressEvent(self, event):
if self.preview:
if event.button() == QtCore.Qt.LeftButton:
self.drawMiniCanvas(event.pos())
self.preview = False
def paintEvent(self, event):
super().paintEvent(event)
if self.preview:
qp = QtGui.QPainter(self)
qp.drawRect(self.rect().adjusted(0, 0, -1, -1))
pos = self.mapFromGlobal(QtGui.QCursor.pos())
qp.setBrush(QtCore.Qt.blue)
qp.drawRect(pos.x(), pos.y(), 20, 20)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.setGeometry(0,0,1000,1100)
self.main = QtWidgets.QLabel()
self.setCentralWidget(self.main)
self.canvas = Canvas()
self.button = QtWidgets.QPushButton('create Block')
self.button.clicked.connect(self.canvas.startPreview)
vbox = QtWidgets.QVBoxLayout()
vbox.addWidget(self.canvas)
vbox.addWidget(self.button)
self.main.setLayout(vbox)
self.setWindowTitle('testing')
Note that I have left the main widget as a QLabel as per your code, but I strongly suggest to avoid so: QLabel has a complex management of its size, and even if you add a layout to it, the layout requirements will always be ignored; you should use a QWidget instead.
Finally, while the above code works, it's just a simple example based on your question; if you want to create a drawing tool, you should not use a QLabel, and for various reasons: for example, if you want to support scaling to fit the contents, not only the coordinate computation in drawMiniCanvas won't work (due to the scaling), but it will also not paint anything at all, and that's due to the way QLabel caches its contents whenever setScaledContents(True) is used (also, it won't respect the aspect ratio).
For advanced and interactive painting, it's usually better to use a QGraphicsScene shown inside a QGraphicsView.
I'm facing a problem with python.
i want to draw on my monitor a circle, that can move around.
let's say i have my browser open, i want to be able to make the circle go around on his own AND be able to use the mouse to press any button i want.
the idea is that the circle is connected to my hands movement thanks to Leap Motion, and i want to display the gestures i make while being able to use the mouse.
my worries are that i have to make a trasparent window which doesn't let me to use the mouse because i would clik on the trasparent window.
Thanks!
Found the solution, i used wxPython
Down below the code:
import wx, inspect, os, sys, win32gui
IMAGE_PATH = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) + '\cerchio.png'
class Cursor(wx.Frame):
def __init__(self, parent, log):
self.log = log
self.delta = wx.Point(0,0)
wx.Frame.__init__(self, parent, -1, "Shaped Window",
style =
wx.FRAME_SHAPED
| wx.SIMPLE_BORDER
| wx.FRAME_NO_TASKBAR
| wx.STAY_ON_TOP
)
self.timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.update, self.timer)
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.x=0
self.y=0
self.hasShape = False
self.SetClientSize( (50, 50) )
image = wx.Image(IMAGE_PATH, wx.BITMAP_TYPE_PNG)
image.SetMaskColour(255,255,255)
image.SetMask(True)
self.bmp = wx.BitmapFromImage(image)
self.SetWindowShape()
self.timer.Start(1)
dc = wx.ClientDC(self)
dc.DrawBitmap(self.bmp, 0, 0, True)
def OnExit(self, evt):
self.Close()
def SetWindowShape(self, *evt):
# Use the bitmap's mask to determine the region
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)
def OnExit(self, evt):
self.Close()
def update(self, event):
#self.x, self.y = win32gui.GetCursorPos()
self.SetPosition((self.x,self.y))
if __name__ == '__main__':
app = wx.App( False )
frm = Cursor(None, -1)
frm.Show()
app.MainLoop()
This maps 50x50 png image with white background (modify IMAGE_PATH) to the cursor position (you won't be able to click though)
I am trying to build a python class around QGraphicsRectItem (PySide or PyQt4) that provides mouse interaction through hover-overs, is movable, and re-sizable. I have pretty much everything working except:
For some reason, it seems as if the mouse hover area is not changing when the item is re-sized or moved. I need help solving this issue.
Maybe the problem is caused by inverting the y axis on the QGraphicsView:
QGraphicsView.scale(1,-1)
QGraphicsRectItem class:
class BoxResizable(QtGui.QGraphicsRectItem):
def __init__(self, rect, parent = None, scene = None):
QtGui.QGraphicsRectItem.__init__(self, rect, parent, scene)
self.setZValue(1000)
self._rect = rect
self._scene = scene
self.mouseOver = False
self.resizeHandleSize = 4.0
self.mousePressPos = None
self.mouseMovePos = None
self.mouseIsPressed = False
self.setFlags(QtGui.QGraphicsItem.ItemIsSelectable|QtGui.QGraphicsItem.ItemIsFocusable)
self.setAcceptsHoverEvents(True)
self.updateResizeHandles()
def hoverEnterEvent(self, event):
self.updateResizeHandles()
self.mouseOver = True
self.prepareGeometryChange()
def hoverLeaveEvent(self, event):
self.mouseOver = False
self.prepareGeometryChange()
def hoverMoveEvent(self, event):
if self.topLeft.contains(event.scenePos()) or self.bottomRight.contains(event.scenePos()):
self.setCursor(QtCore.Qt.SizeFDiagCursor)
elif self.topRight.contains(event.scenePos()) or self.bottomLeft.contains(event.scenePos()):
self.setCursor(QtCore.Qt.SizeBDiagCursor)
else:
self.setCursor(QtCore.Qt.SizeAllCursor)
QtGui.QGraphicsRectItem.hoverMoveEvent(self, event)
def mousePressEvent(self, event):
"""
Capture mouse press events and find where the mosue was pressed on the object
"""
self.mousePressPos = event.scenePos()
self.mouseIsPressed = True
self.rectPress = copy.deepcopy(self._rect)
# Top left corner
if self.topLeft.contains(event.scenePos()):
self.mousePressArea = 'topleft'
# top right corner
elif self.topRight.contains(event.scenePos()):
self.mousePressArea = 'topright'
# bottom left corner
elif self.bottomLeft.contains(event.scenePos()):
self.mousePressArea = 'bottomleft'
# bottom right corner
elif self.bottomRight.contains(event.scenePos()):
self.mousePressArea = 'bottomright'
# entire rectangle
else:
self.mousePressArea = None
QtGui.QGraphicsRectItem.mousePressEvent(self, event)
def mouseReleaseEvent(self, event):
"""
Capture nmouse press events.
"""
self.mouseIsPressed = False
self.updateResizeHandles()
self.prepareGeometryChange()
QtGui.QGraphicsRectItem.mouseReleaseEvent(self, event)
def mouseMoveEvent(self, event):
"""
Handle mouse move events.
"""
self.mouseMovePos = event.scenePos()
if self.mouseIsPressed:
# Move top left corner
if self.mousePressArea=='topleft':
self._rect.setTopLeft(self.rectPress.topLeft()-(self.mousePressPos-self.mouseMovePos))
# Move top right corner
elif self.mousePressArea=='topright':
self._rect.setTopRight(self.rectPress.topRight()-(self.mousePressPos-self.mouseMovePos))
# Move bottom left corner
elif self.mousePressArea=='bottomleft':
self._rect.setBottomLeft(self.rectPress.bottomLeft()-(self.mousePressPos-self.mouseMovePos))
# Move bottom right corner
elif self.mousePressArea=='bottomright':
self._rect.setBottomRight(self.rectPress.bottomRight()-(self.mousePressPos-self.mouseMovePos))
# Move entire rectangle, don't resize
else:
self._rect.moveCenter(self.rectPress.center()-(self.mousePressPos-self.mouseMovePos))
self.updateResizeHandles()
self.prepareGeometryChange()
QtGui.QGraphicsRectItem.mousePressEvent(self, event)
def boundingRect(self):
"""
Return bounding rectangle
"""
return self._boundingRect
def updateResizeHandles(self):
"""
Update bounding rectangle and resize handles
"""
self.offset = self.resizeHandleSize*(self._scene.graphicsView.mapToScene(1,0)-self._scene.graphicsView.mapToScene(0,1)).x()
self._boundingRect = self._rect.adjusted(-self.offset, self.offset, self.offset, -self.offset)
# Note: this draws correctly on a view with an inverted y axes. i.e. QGraphicsView.scale(1,-1)
self.topLeft = QtCore.QRectF(self._boundingRect.topLeft().x(), self._boundingRect.topLeft().y() - 2*self.offset,
2*self.offset, 2*self.offset)
self.topRight = QtCore.QRectF(self._boundingRect.topRight().x() - 2*self.offset, self._boundingRect.topRight().y() - 2*self.offset,
2*self.offset, 2*self.offset)
self.bottomLeft = QtCore.QRectF(self._boundingRect.bottomLeft().x(), self._boundingRect.bottomLeft().y(),
2*self.offset, 2*self.offset)
self.bottomRight = QtCore.QRectF(self._boundingRect.bottomRight().x() - 2*self.offset, self._boundingRect.bottomRight().y(),
2*self.offset, 2*self.offset)
def paint(self, painter, option, widget):
"""
Paint Widget
"""
# show boundingRect for debug purposes
painter.setPen(QtGui.QPen(QtCore.Qt.red, 0, QtCore.Qt.DashLine))
painter.drawRect(self._boundingRect)
# Paint rectangle
painter.setPen(QtGui.QPen(QtCore.Qt.black, 0, QtCore.Qt.SolidLine))
painter.drawRect(self._rect)
# If mouse is over, draw handles
if self.mouseOver:
# if rect selected, fill in handles
if self.isSelected():
painter.setBrush(QtGui.QBrush(QtGui.QColor(0,0,0)))
painter.drawRect(self.topLeft)
painter.drawRect(self.topRight)
painter.drawRect(self.bottomLeft)
painter.drawRect(self.bottomRight)
Rest of code for functioning example:
class graphicsScene(QtGui.QGraphicsScene):
def __init__ (self, parent = None):
QtGui.QGraphicsScene.__init__(self, parent)
def setGraphicsView(self, view):
self.graphicsView = view
app = QtGui.QApplication(sys.argv)
app.setStyle('GTK')
mainWindow = QtGui.QMainWindow()
scene = graphicsScene()
scene.setSceneRect(-100,-100, 200, 200)
# Set up view properties
view = QtGui.QGraphicsView()
view.setScene(scene)
view.scale(1,-1)
view.setRenderHint(QtGui.QPainter.Antialiasing)
view.setViewportUpdateMode(QtGui.QGraphicsView.BoundingRectViewportUpdate)
view.setHorizontalScrollBarPolicy ( QtCore.Qt.ScrollBarAlwaysOff )
view.setVerticalScrollBarPolicy ( QtCore.Qt.ScrollBarAlwaysOff )
view.setUpdatesEnabled(True)
view.setMouseTracking(True)
view.setCacheMode(QtGui.QGraphicsView.CacheBackground)
view.setTransformationAnchor(QtGui.QGraphicsView.AnchorUnderMouse)
scene.setGraphicsView(view)
# Add box
BoxResizable(QtCore.QRectF(-50, 50, 100.0, -100.0), parent = None, scene = scene)
mainWindow.setCentralWidget(view)
mainWindow.show()
app.exec_()
app.deleteLater()
sys.exit()
Adding the above class to a QGraphicsScene and subsequent inverted y axis QGraphicsView will produce something like this:
Any help or suggestions are appreciated! Thanks!
Found part of the problem: If I over-ride the shape function (add this function to the class) with:
def shape(self):
path = QtGui.QPainterPath()
path.addRect(self.boundingRect())
return path
The hover area changes size and position with the box sometimes. Acoording to the docs for QGraphicsItem.shape():
The default implementation calls boundingRect() to return a simple
rectangular shape...
Is this a bug in PyQt4?
Second problem: I think that the boundingRect and the shape rectangle need to have positive widths and heights? This is easily done by adding:
self._rect = self._rect.normalized()
to the mouseReleaseEvent function.
I have been working on this project for some time now - it was originally supposed to be a test to see if, using wxPython, I could build a button 'from scratch.' From scratch means: that i would have full control over all the aspects of the button (i.e. controlling the BMP's that are displayed... what the event handlers did... etc.)
I have run into several problems (as this is my first major python project.) Now, when the all the code is working for the life of me I can't get an image to display.
Basic code - not working
dc = wx.BufferedPaintDC(self)
dc.SetFont(self.GetFont())
dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
dc.Clear()
dc.DrawBitmap(wx.Bitmap("/home/wallter/Desktop/Mouseover.bmp"), 100, 100)
self.Refresh()
self.Update()
Full Main.py
import wx
from Custom_Button import Custom_Button
from wxPython.wx import *
ID_ABOUT = 101
ID_EXIT = 102
class MyFrame(wx.Frame):
def __init__(self, parent, ID, title):
wxFrame.__init__(self, parent, ID, title,
wxDefaultPosition, wxSize(400, 400))
self.CreateStatusBar()
self.SetStatusText("Program testing custom button overlays")
menu = wxMenu()
menu.Append(ID_ABOUT, "&About", "More information about this program")
menu.AppendSeparator()
menu.Append(ID_EXIT, "E&xit", "Terminate the program")
menuBar = wxMenuBar()
menuBar.Append(menu, "&File");
self.SetMenuBar(menuBar)
# The call for the 'Experiential button'
self.Button1 = Custom_Button(parent, -1,
wx.Point(100, 100),
wx.Bitmap("/home/wallter/Desktop/Mouseover.bmp"),
wx.Bitmap("/home/wallter/Desktop/Normal.bmp"),
wx.Bitmap("/home/wallter/Desktop/Click.bmp"))
# The following three lines of code are in place to try to get the
# Button1 to display (trying to trigger the Paint event (the _onPaint.)
# Because that is where the 'draw' functions are.
self.Button1.Show(true)
self.Refresh()
self.Update()
# Because the Above three lines of code did not work, I added the
# following four lines to trigger the 'draw' functions to test if the
# '_onPaint' method actually worked.
# These lines do not work.
dc = wx.BufferedPaintDC(self)
dc.SetFont(self.GetFont())
dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
dc.DrawBitmap(wx.Bitmap("/home/wallter/Desktop/Mouseover.bmp"), 100, 100)
EVT_MENU(self, ID_ABOUT, self.OnAbout)
EVT_MENU(self, ID_EXIT, self.TimeToQuit)
def OnAbout(self, event):
dlg = wxMessageDialog(self, "Testing the functions of custom "
"buttons using pyDev and wxPython",
"About", wxOK | wxICON_INFORMATION)
dlg.ShowModal()
dlg.Destroy()
def TimeToQuit(self, event):
self.Close(true)
class MyApp(wx.App):
def OnInit(self):
frame = MyFrame(NULL, -1, "wxPython | Buttons")
frame.Show(true)
self.SetTopWindow(frame)
return true
app = MyApp(0)
app.MainLoop()
Full CustomButton.py
import wx
from wxPython.wx import *
class Custom_Button(wx.PyControl):
def __init__(self, parent, id, Pos, Over_BMP, Norm_BMP, Push_BMP, **kwargs):
wx.PyControl.__init__(self,parent, id, **kwargs)
self.Bind(wx.EVT_LEFT_DOWN, self._onMouseDown)
self.Bind(wx.EVT_LEFT_UP, self._onMouseUp)
self.Bind(wx.EVT_LEAVE_WINDOW, self._onMouseLeave)
self.Bind(wx.EVT_ENTER_WINDOW, self._onMouseEnter)
self.Bind(wx.EVT_ERASE_BACKGROUND,self._onEraseBackground)
self.Bind(wx.EVT_PAINT,self._onPaint)
self.pos = Pos
self.Over_bmp = Over_BMP
self.Norm_bmp = Norm_BMP
self.Push_bmp = Push_BMP
self._mouseIn = False
self._mouseDown = False
def _onMouseEnter(self, event):
self._mouseIn = True
def _onMouseLeave(self, event):
self._mouseIn = False
def _onMouseDown(self, event):
self._mouseDown = True
def _onMouseUp(self, event):
self._mouseDown = False
self.sendButtonEvent()
def sendButtonEvent(self):
event = wx.CommandEvent(wx.wxEVT_COMMAND_BUTTON_CLICKED, self.GetId())
event.SetInt(0)
event.SetEventObject(self)
self.GetEventHandler().ProcessEvent(event)
def _onEraseBackground(self,event):
# reduce flicker
pass
def Iz(self):
dc = wx.BufferedPaintDC(self)
dc.DrawBitmap(self.Norm_bmp, 100, 100)
def _onPaint(self, event):
# The printing functions, they should work... but don't.
dc = wx.BufferedPaintDC(self)
dc.SetFont(self.GetFont())
dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
dc.Clear()
dc.DrawBitmap(self.Norm_bmp)
# This never printed... I don't know if that means if the EVT
# is triggering or what.
print '_onPaint'
# draw whatever you want to draw
# draw glossy bitmaps e.g. dc.DrawBitmap
if self._mouseIn: # If the Mouse is over the button
dc.DrawBitmap(self.Over_bmp, self.pos)
else: # Since the mouse isn't over it Print the normal one
# This is adding on the above code to draw the bmp
# in an attempt to get the bmp to display; to no avail.
dc.DrawBitmap(self.Norm_bmp, self.pos)
if self._mouseDown: # If the Mouse clicks the button
dc.DrawBitmap(self.Push_bmp, self.pos)
This code won't work? I get no BMP displayed why? How do i get one? I've gotten the staticBitmap(...) to display one, but it won't move, resize, or anything for that matter... - it's only in the top left corner of the frame?
Note: the frame is 400pxl X 400pxl - and the "/home/wallter/Desktop/Mouseover.bmp"
Are your sure you code is working without exceptions because when I run it i get many errors, read the points below and you should have a button which at least draws correctly
When O run it it gives error because Custom_Button is passed NULL parent instead pass frame e.g. Custom_Button(self, ...)
Your drawBitmap call is also wrong, it throws exception, instead of dc.DrawBitmap(self.Norm_bmp) it should be dc.DrawBitmap(self.Norm_bmp, 0, 0)
dc.DrawBitmap(self.Over_bmp, self.pos) also throws error as pos should be x,y not a tuple so instead do dc.DrawBitmap(self.Over_bmp, *self.pos)
and lastly you do not need to do "from wxPython.wx import *" instead just do "from wx import *" and instead of wxXXX class names use wx.XXX, instead of true use True etc
here is my working code
from wx import *
ID_ABOUT = 101
ID_EXIT = 102
class Custom_Button(wx.PyControl):
def __init__(self, parent, id, Pos, Over_BMP, Norm_BMP, Push_BMP, **kwargs):
wx.PyControl.__init__(self,parent, id, **kwargs)
self.Bind(wx.EVT_LEFT_DOWN, self._onMouseDown)
self.Bind(wx.EVT_LEFT_UP, self._onMouseUp)
self.Bind(wx.EVT_LEAVE_WINDOW, self._onMouseLeave)
self.Bind(wx.EVT_ENTER_WINDOW, self._onMouseEnter)
self.Bind(wx.EVT_ERASE_BACKGROUND,self._onEraseBackground)
self.Bind(wx.EVT_PAINT,self._onPaint)
self.pos = Pos
self.Over_bmp = Over_BMP
self.Norm_bmp = Norm_BMP
self.Push_bmp = Push_BMP
self._mouseIn = False
self._mouseDown = False
def _onMouseEnter(self, event):
self._mouseIn = True
def _onMouseLeave(self, event):
self._mouseIn = False
def _onMouseDown(self, event):
self._mouseDown = True
def _onMouseUp(self, event):
self._mouseDown = False
self.sendButtonEvent()
def sendButtonEvent(self):
event = wx.CommandEvent(wx.wxEVT_COMMAND_BUTTON_CLICKED, self.GetId())
event.SetInt(0)
event.SetEventObject(self)
self.GetEventHandler().ProcessEvent(event)
def _onEraseBackground(self,event):
# reduce flicker
pass
def Iz(self):
dc = wx.BufferedPaintDC(self)
dc.DrawBitmap(self.Norm_bmp, 100, 100)
def _onPaint(self, event):
# The printing functions, they should work... but don't.
dc = wx.BufferedPaintDC(self)
dc.SetFont(self.GetFont())
dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
dc.Clear()
dc.DrawBitmap(self.Norm_bmp, 0, 0)
# This never printed... I don't know if that means if the EVT
# is triggering or what.
print '_onPaint'
# draw whatever you want to draw
# draw glossy bitmaps e.g. dc.DrawBitmap
if self._mouseIn: # If the Mouse is over the button
dc.DrawBitmap(self.Over_bmp, *self.pos)
else: # Since the mouse isn't over it Print the normal one
# This is adding on the above code to draw the bmp
# in an attempt to get the bmp to display; to no avail.
dc.DrawBitmap(self.Norm_bmp, *self.pos)
if self._mouseDown: # If the Mouse clicks the button
dc.DrawBitmap(self.Push_bmp, *self.pos)
class MyFrame(wx.Frame):
def __init__(self, parent, ID, title):
wx.Frame.__init__(self, parent, ID, title,
wx.DefaultPosition, wx.Size(400, 400))
self.CreateStatusBar()
self.SetStatusText("Program testing custom button overlays")
menu = wx.Menu()
menu.Append(ID_ABOUT, "&About", "More information about this program")
menu.AppendSeparator()
menu.Append(ID_EXIT, "E&xit", "Terminate the program")
menuBar = wx.MenuBar()
menuBar.Append(menu, "&File");
self.SetMenuBar(menuBar)
# The call for the 'Experiential button'
s = r"D:\virtual_pc\mockup\mockupscreens\embed_images\toolbar\options.png"
self.Button1 = Custom_Button(self, -1,
wx.Point(100, 100),
wx.Bitmap(s),
wx.Bitmap(s),
wx.Bitmap(s))
self.Button1.Show(True)
EVT_MENU(self, ID_ABOUT, self.OnAbout)
EVT_MENU(self, ID_EXIT, self.TimeToQuit)
def OnAbout(self, event):
dlg = wxMessageDialog(self, "Testing the functions of custom "
"buttons using pyDev and wxPython",
"About", wxOK | wxICON_INFORMATION)
dlg.ShowModal()
dlg.Destroy()
def TimeToQuit(self, event):
self.Close(true)
class MyApp(wx.App):
def OnInit(self):
frame = MyFrame(None, -1, "wxPython | Buttons")
frame.Show(True)
self.SetTopWindow(frame)
return True
app = MyApp(0)
app.MainLoop()
I've written this small app that draws lines between two points selected by the user and it works but how do I keep the lines I draw from disappearing whenever the window is minimized or gets covered by another open window?
class SimpleDraw(wx.Frame):
def __init__(self, parent, id, title, size=(640, 480)):
self.points = []
wx.Frame.__init__(self, parent, id, title, size)
self.Bind(wx.EVT_LEFT_DOWN, self.DrawDot)
self.SetBackgroundColour("WHITE")
self.Centre()
self.Show(True)
def DrawDot(self, event):
self.points.append(event.GetPosition())
if len(self.points) == 2:
dc = wx.ClientDC(self)
dc.SetPen(wx.Pen("#000000", 10, wx.SOLID))
x1, y1 = self.points[0]
x2, y2 = self.points[1]
dc.DrawLine(x1, y1, x2, y2)
# reset the list to empty
self.points = []
if __name__ == "__main__":
app = wx.App()
SimpleDraw(None, -1, "Title Here!")
app.MainLoop()
Your issue is that you are only drawing when the user clicks. The resize/erase (when another window covers yours) problems are because your window doesn't maintain a "buffer" which it can redraw.
Here, I've modified your sample, it seems to be working okay.
import wx
class SimpleDraw(wx.Frame):
def __init__(self, parent, id, title, size=(640, 480)):
self.points = []
wx.Frame.__init__(self, parent, id, title, size)
self.Bind(wx.EVT_LEFT_DOWN, self.DrawDot)
self.Bind(wx.EVT_PAINT, self.Paint)
self.SetBackgroundColour("WHITE")
self.Centre()
self.Show(True)
self.buffer = wx.EmptyBitmap(640, 480) # draw to this
dc = wx.BufferedDC(wx.ClientDC(self), self.buffer)
dc.Clear() # black window otherwise
def DrawDot(self, event):
self.points.append(event.GetPosition())
if len(self.points) == 2:
dc = wx.BufferedDC(wx.ClientDC(self), self.buffer)
dc.Clear()
dc.SetPen(wx.Pen("#000000", 10, wx.SOLID))
x1, y1 = self.points[0]
x2, y2 = self.points[1]
dc.DrawLine(x1, y1, x2, y2)
# reset the list to empty
self.points = []
def Paint(self, event):
wx.BufferedPaintDC(self, self.buffer)
if __name__ == "__main__":
app = wx.App(0)
SimpleDraw(None, -1, "Title Here!")
app.MainLoop()
You have to structure your program differently in a GUI environment. Typically, you maintain a data structure called your model. In your case, you already have a start of one, self.points. Then you only draw on the window in response to a paint event. The windowing system will send you paint events when the window needs painting, including when it is first displayed, when it is maximized, and when it is revealed from beneath another window.
In your program, you'd bind the LeftDown event to a function that modifies self.points and invalidates the window, which would usually cause the windowing system to send you paint events. You'd bind the Paint event to a function that draws on the window.