Robin Dunn wrote:
> Hi,
>
> In Tkinter you can plot a line in a Canvas
> and then attach for example a mouseEnter event:
>
> self.mycurve=self.MyCanvas.create_line(liste,width=3,fill="blue")
>
> self.MyCanvas.tag_bind(mycurve, "<Enter>", self.mouseEnter)
>
>
> How could one achieve something similar with wxPython
(eg. for a wxClientDC and a line created by dc.DrawLineList)?The wxDC classes are much lower level than the canvas in tkinter, and
are really only for drawing. You would have to work at a similarly low
level with the mouse events and calculate the mouse overs yourself.
That's dissapointing - it seems that for my purposes a Canvas a la Tkinter
is strongly lacking - I hope I don't have to return to Tkinter because
of this ... (And it is not just the mouse-over thing,
also moving an object like a circle seems to be much
more work with wxPython than with Tkinter; the same seems to apply
to plotting many points very quickly, ...). Sorry if this
sounds a bit negative - there are just too many buffers swirling
around in my head - Let's get more constructive:
So I aimed at a proof of concept for mouse-overs,
see the example code below.
The approach is: Every object is plotted in a second buffer with
a unique color. In an EVT_MOTION the "color" in the second buffer
at the mouse coordinate is checked and converted to an object_id.
It basically works fine on 24 Bit displays.
However, for displays with depth 16 it fails.
The point is that I would like to use
a bitmap of depth 24 to store up to 2**24 objects
by doing
self.MouseOverBuffer=wxEmptyBitmap(self.wi, self.he,24)
Now this call fails on a system with a display depth of
for example 16.
Is it necessary that the depth of the BitMap is coupled to the
depth of the display?
Is there a better way to achieve this ?
(Remark: all this is on debian linux, but I really
want it to be platform and machine independent)
Another option is to use the wxOGL library which provides a simple
shaped object canvas, but it is old a crufty and sometimes difficult to
use.
Hmm, this sounded discouraging enough so I haven't tried this yet ...
There are also some other canvas-like classes available, you can
probably find pointers in the mail archives.
I searched around for quite a while, but I did not find anything
useful (maybe I used the wrong keywords ;-().
Many thanks,
Arnd
P.S.: sorry, if this starts a new thread, but I recieve wxPython-users
as digest and my mail programm could only answer to a whole digest issue
;-). Is there a better way for this ?
And here the example code:
···
arnd.baecker@web.de wrote:
##############################
""" Aim: plot a line etc. in a ClientDC canvas and
associate a mouse-over with it.
Strategy: plot as normal and at the same time
the object is plotted into a second buffer with
a color which is unique to the corresponding object.
this should allow for 2**24=16777215 objects.
Parts of the code (those with a lot of comments
for the buffered drawing are
from Jean-Michel Fauth - "Drawing with a buffered bitmap",
postted on wxpython-users, 17 Jul 2003,
(many thanks for that - really helpful!).
Version 0.0.3, 02.09.2003, Arnd Baecker
"""
#import wx
from wxPython.wx import *
#from wxPython.lib.evtmgr import eventManager
from wxPython.lib.rcsizer import RowColSizer
# stupid IDs (IMHO)
ID_ABOUT=101
ID_OPEN=102
ID_BUTTON1=110
ID_EXIT=200
class MouseOverCanvas(wxWindow):
"""Subclass of a wxWindow to allow simple general plotting
of data with zoom, labels, and automatic axis scaling."""
def __init__(self, parent, id = -1, pos=wx.wxDefaultPosition,
size=wx.wxDefaultSize, style= wx.wxDEFAULT_FRAME_STYLE, name= ""):
"""Constucts a window, which can be a child of a frame, dialog or
any other non-control window"""
wx.wxWindow.__init__(self, parent, id, pos, size, style, name)
self.border = (14,14)
self.SetCursor(wxCROSS_CURSOR)
#self.SetBackgroundColour(wx.wxNamedColour("yellow"))
self.BufferBmp = None
self.MouseOverbuffer=None
EVT_SIZE(self, self.OnSize)
EVT_PAINT(self, self.OnPaint)
EVT_MOTION(self,self.OnMotion)
# OnSize is fired at the application start or when the frame is resized.
# The OnSize event is fired BEFORE the OnPaint event. wxWindows
# handles the events in this order. This is not due to the fact,
# that the code line EVT_SIZE(...) is placed before the line
# EVT_PAINT(...).
# The procedure OnSize() is the right place to define the
# BufferBmp and its size. self.BufferBmp is the picture in memory,
# which contains your drawing. self.BufferBmp is also used as a flag,
# a None value indicates no picture.
#
def OnSize(self, event):
# Get the size of the drawing area in pixels.
self.wi, self.he = self.GetSizeTuple()
# Create BufferBmp and set the same size as the drawing area.
self.BufferBmp = wxEmptyBitmap(self.wi, self.he)
memdc = wxMemoryDC()
memdc.SelectObject(self.BufferBmp)
# second buffer for mouse-over
# FIXME: this does not work on depth 16 displays
#self.MouseOverBuffer=wxEmptyBitmap(self.wi, self.he,24)
# FIXME: this makes it display depth dependent!!!!
self.MouseOverBuffer=wxEmptyBitmap(self.wi, self.he)
self.mouseoverdc= wxMemoryDC()
self.mouseoverdc.SelectObject(self.MouseOverBuffer)
self.lastobject=None # last object under mouse cursor
# Drawing job
ret = self.DoSomeDrawing(memdc)
# OnPaint is executed at the app start, when resizing or when
# the application windows becomes active. OnPaint copies the
# buffered picture, instead of preparing (again) the drawing job.
# This is the trick, copying is very fast.
# Note: if you forget to define the dc in this procedure,
# (no dc = ... code line), the application will run in
# an infinite loop. This is a common beginner's error. (I think)
# BeginDrawing() and EndDrawing() are for windows platforms (see doc).
def OnPaint(self, event):
print 'OnPaint in MyDrawingArea'
dc = wxPaintDC(self)
dc.BeginDrawing()
if self.BufferBmp != None:
print '...drawing'
dc.DrawBitmap(self.BufferBmp, 0, 0, True)
else:
print '...nothing to draw'
dc.EndDrawing()
def DoSomeDrawing(self, dc):
print 'DoSomeDrawing in MyDrawingArea'
dc.BeginDrawing()
# Clear everything
objectcolor=wxColor(0,0,2)
dc.SetBrush(wxBrush(objectcolor, wxSOLID))
dc.Clear()
# Draw the square with a fixed size.
dc.SetBrush(wxBrush(wxCYAN, wxSOLID))
dc.SetPen(wxPen(wxBLUE, 1, wxSOLID))
dc.DrawRectangle(10, 10, 200, 200)
# Mouse over buffer:
self.mouseoverdc.SetBrush(wxBrush(wxBLACK, wxSOLID))
self.mouseoverdc.Clear()
objectcolor=wxColor(0,0,1) # color in mouseoverdc for object 1
self.mouseoverdc.SetBrush(wxBrush(objectcolor, wxSOLID))
self.mouseoverdc.SetPen(wxPen(objectcolor, 1, wxSOLID))
self.mouseoverdc.DrawRectangle(10, 10, 200, 200)
# Draw the square2 with a fixed size.
dc.SetBrush(wxBrush(wxRED, wxSOLID))
dc.SetPen(wxPen(wxGREEN, 1, wxSOLID))
dc.DrawRectangle(80, 80, 100, 150)
# Mouse over buffer:
objectcolor=wxColor(0,0,10) # color in mouseoverdc for object 2
self.mouseoverdc.SetBrush(wxBrush(objectcolor, wxSOLID))
self.mouseoverdc.SetPen(wxPen(objectcolor, 1, wxSOLID))
self.mouseoverdc.DrawRectangle(80, 80, 100, 150)
def OnMotion(self,event):
# get mouse position
x,y=event.GetPosition()
# get color (=object) below the mouse pointer
overcol=self.mouseoverdc.GetPixel(x,y) # FIXME: system-dep ??
r,g,b=overcol.Red(),overcol.Green(),overcol.Blue()
#print "col=%s" % str(overcol)
#print r,g,b
object=r+g*256+b*256*256 # construct object id
print "motion",x,y,object,r,g,b
# Here we have to check, if we are inside an object
if(self.lastobject!=None):
if(object==self.lastobject):
print "... stayed in %d" % (object )
else:
print "... left %d" % (self.lastobject)
if(object!=256*255*256+256*255+255):
print "==> entered %d" % (object)
else:
object=None
else:
if(object!=256*255*256+256*255+255):
print "==> entered %d" % (object)
else:
object=None
self.lastobject=object
class MainWindow(wxFrame):
def __init__(self,parent,id,title):
wxFrame.__init__(self,parent,-4, title,size=(500,600),
style=wxDEFAULT_FRAME_STYLE|
wxNO_FULL_REPAINT_ON_RESIZE)
self.CreateStatusBar() # A Statusbar in the bottom of the window
# Setting up the menu.
filemenu= wxMenu()
filemenu.Append(ID_ABOUT, "&About"," Information about program")
filemenu.AppendSeparator()
filemenu.Append(ID_EXIT,"E&xit"," Terminate the program")
# Creating the menubar.
menuBar = wxMenuBar()
menuBar.Append(filemenu,"&File")
self.SetMenuBar(menuBar)
EVT_MENU(self, ID_ABOUT, self.OnAbout)
EVT_MENU(self, ID_EXIT, self.OnExit)
sizer =RowColSizer()
# Wigdet with Objects
self.plot= MouseOverCanvas(self,size=(400,250))
sizer.Add(self.plot,row=4,col=2,colspan=10,rowspan=1)
#Layout sizers
self.SetSizer(sizer)
self.Show(1) # wozu ???
def OnAbout(self,e):
d= wxMessageDialog( self, "Mouse over test\n"
" with wxPython","About MouseOverTest", wxOK)
# Create a message dialog box
d.ShowModal() # Shows it
d.Destroy() # finally destroy it when finished.
def OnExit(self,e):
self.Close(true) # Close the frame.
app = wxPySimpleApp()
frame = MainWindow(None, -1, "MouseOverTest")
frame.Show(1)
app.MainLoop()