[wxPython-users] info bubble and annotation over grid cell UPDATED
Timothy Smith
timothy at open-networks.net
Thu Nov 2 21:04:03 PST 2006
Robin Dunn wrote:
> Timothy Smith wrote:
>
>> ok after some playing around i've found the problem lies with using =
>> the transient window, it grabs focus and won't let go. so i've =
>> decieded to go with making my own custom frame and drawing my info on =
>> it. that way i get exactly what i want.
>> i'm still stumped on why i can't edit my timectrl's inside the cell =
>> though...
>
>
> When the cell editor is created you push a new wx.EvtHandler onto the =
> widget's event handler chain. This wx.EvtHandler has an event binding =
> for EVT_KILL_FOCUS such that when the widget loses focus it calls grid =
> methods that complete the edit session and hide the editor. (It =
> assumes that if some other widget is getting focus then you are done =
> editing.) Since your control has child controls, then it loses the =
> focus as soon as you try to activate the child. So you can avoid this =
> behavior by not calling PushEventHandler, but you'll also lose the =
> other built-in functionality of handling the Enter or ESC keys, but =
> you can probably take care of those yourself without too much hassle.
>
>
ahh yes it works. i'm not concerned about enter or esc keys really. if =
it's needed i can add it later.
i commented out
#if evtHandler:
# self._tc.PushEventHandler(evtHandler)
from my edit control, i can edit the cell however now when i close the =
app i get a seg fault? not when i edit, but when i close the app.
-------------- next part --------------
import wx
import wx.grid
import wx.lib.evtmgr as event
#compatability checks between versions
import re
if re.search(r"2.6", wx.VERSION_STRING):
from wx.lib.masked import TimeCtrl
elif re.search(r"2.4", wx.VERSION_STRING):
import wx.lib.timectrl as TimeCtrl
class RosteredShiftsFrame(wx.Frame):
def __init__(self, parent, id=3Dwx.ID_ANY, size=3Dwx.Size(100,100),pos=3Dw=
x.DefaultPosition):
wx.Frame.__init__(self, None, -1, "", pos, size,
style=3Dwx.FRAME_SHAPED |
wx.SIMPLE_BORDER |
wx.FRAME_NO_TASKBAR |
wx.STAY_ON_TOP)
panel =3D wx.Panel(self, -1)
self.panel =3D panel
self.Show(True)
#hack for windows
parent.parent.SetFocus()
class TimeControl(wx.Control):
def __init__(self, parent,id):
wx.Control.__init__(self, parent, -1)
Nametxt =3D wx.StaticText(self,id,"Name:")
self.Name =3D wx.StaticText(self,id,"")
Starttxt =3D wx.StaticText(self,id,"Start:")
Finishtxt =3D wx.StaticText(self,id,"Finish:")
Breakstxt =3D wx.StaticText(self,id,"Breaks:")
#spinStart =3D wx.SpinButton(self,-1,wx.DefaultPosition,wx.Size(-1,20),0)
self.Start =3D TimeCtrl(self,id,value =3D '00:00',style =3D wx.TE_PROCESS=
_TAB,format =3D 'HHMM',fmt24hr =3D False, displaySeconds =3D False)
self.Finish =3D TimeCtrl(self,id,value =3D '00:00',style =3D wx.TE_PROCES=
S_TAB,format =3D 'HHMM',fmt24hr =3D False, displaySeconds =3D False)
self.Breaks =3D wx.SpinCtrl(self,-1,min=3D0,max=3D60,size =3D wx.Size(40,=
20))
Totaltxt =3D wx.StaticText(self,id,"Total:")
self.Total =3D wx.StaticText(self,id,"")
=
GridSizer =3D wx.GridSizer(5,2,2)
GridSizer.AddMany([
(Nametxt,0),(self.Name,0),
(Starttxt,0),(self.Start,0,wx.EXPAND),
(Finishtxt,0),(self.Finish,0,wx.EXPAND),
(Breakstxt,0),(self.Breaks,0,wx.EXPAND),
(Totaltxt,0),(self.Total,0)
])
=
#this strange setup is needed because the gridsizer isn't layout correctl=
y in the cell for some reason
Sizer =3D wx.BoxSizer(wx.VERTICAL)
Sizer.Add(GridSizer,0)
=
Sizer2 =3D wx.BoxSizer(wx.HORIZONTAL)
Sizer2.Add(Sizer,0)
=
self.SetSizer(Sizer2)
self.Layout()
=
def SetValue(self,val):
#in here is how i write to the cells once i'm finished editing
#"Name: " + val +"\n\n Start: "+ self.Start.GetValue() +"\n\nFinish: "+se=
lf.Finish.GetValue()+ "\n\nBreaks: " + str(self.Breaks.GetValue()) +"\n\nTo=
tal: " +self.Total.GetLabel()
"write to tthe cell"
=
def SetInsertionPoint(self):
self.Start.SetFocus()
class RosterTimeRenderer(wx.grid.PyGridCellRenderer):
def __init__(self):
wx.grid.PyGridCellRenderer.__init__(self)
def Draw(self, grid, attr, dc, rect, row, col, isSelected): =
dc.SetBackgroundMode(wx.SOLID)
dc.SetBrush(wx.Brush(wx.Colour(red=3D167,green=3D202,blue=3D216), wx.SOLI=
D))
dc.SetPen(wx.TRANSPARENT_PEN)
dc.DrawRectangle(rect.x, rect.y, rect.width, rect.height)
dc.SetBackgroundMode(wx.TRANSPARENT)
dc.SetFont(attr.GetFont())
x =3D rect.x + 1
y =3D rect.y + 1
dc.DrawText(grid.GetCellValue(row,col),x+1,y+1)
#dc.DrawText(grid.GetCellEditor(row,col).GetControl().Start.GetValue(),x+=
1,y+10)
def GetBestSize(self, grid, attr, dc, row, col):
text =3D grid.GetCellValue(row, col)
dc.SetFont(attr.GetFont())
w, h =3D dc.GetTextExtent(text)
return wx.Size(w, h)
def Clone(self):
return RosterTimeRenderer()
=
class RosterTimeEditor(wx.grid.PyGridCellEditor):
"""
This is a sample GridCellEditor that shows you how to make your own custom
grid editors. All the methods that can be overridden are show here. The
ones that must be overridden are marked with "*Must Override*" in the
docstring.
=
Notice that in order to call the base class version of these special
methods we use the method name preceded by "base_". This is because these
methods are "virtual" in C++ so if we try to call wxGridCellEditor.Create
for example, then when the wxPython extension module tries to call
ptr->Create(...) then it actually calls the derived class version which
looks up the method in this class and calls it, causing a recursion loop.
If you don't understand any of this, don't worry, just call the "base_"
version instead.
"""
def __init__(self):
wx.grid.PyGridCellEditor.__init__(self)
=
=
def Create(self, parent, id, evtHandler):
"""
Called to create the control, which must derive from wxControl.
*Must Override*
"""
=
self._tc =3D TimeControl(parent,-1)
#self._tc =3D timectrl.TimeCtrl(parent, id, display_seconds =3D False, fm=
t24hr=3DTrue)
self._tc.SetInsertionPoint()
self.SetControl(self._tc)
#comment this out so that i can access the timectrl and spinctrl in my ti=
mecontrol object
#if evtHandler:
# self._tc.PushEventHandler(evtHandler)
=
=
def SetSize(self, rect):
"""
Called to position/size the edit control within the cell rectangle.
If you don't fill the cell (the rect) then be sure to override
PaintBackground and do something meaningful there.
"""
=
self._tc.SetDimensions(rect.x, rect.y, rect.width+2, rect.height+2,
wx.SIZE_ALLOW_MINUS_ONE)
=
=
def Show(self, show, attr):
"""
Show or hide the edit control. You can use the attr (if not None)
to set colours or fonts for the control.
"""
=
self.base_Show(show, attr)
=
=
def BeginEdit(self, row, col, grid):
"""
Fetch the value from the table and prepare the edit control
to begin editing. Set the focus to the edit control.
*Must Override*
"""
=
self.startValue =3D grid.GetTable().GetValue(row, col)
self._tc.SetValue(self.startValue)
self._tc.SetFocus()
=
def EndEdit(self, row, col, grid):
"""
Complete the editing of the current cell. Returns True if the value
has changed. If necessary, the control may be destroyed.
*Must Override*
"""
=
#hack this because my control does all the checking
#print "the edit ended"
return True
=
=
=
def Reset(self):
"""
Reset the value in the control back to its starting value.
*Must Override*
"""
=
self._tc.SetValue(self.startValue)
self._tc.SetInsertionPointEnd()
=
=
def IsAcceptedKey(self, evt):
"""
Return True to allow the given key to start editing: the base class
version only checks that the event has no modifiers. F2 is special
and will always start the editor.
"""
=
=
## Oops, there's a bug here, we'll have to do it ourself..
##return self.base_IsAcceptedKey(evt)
=
return (not (evt.ControlDown() or evt.AltDown()) and
evt.GetKeyCode() !=3D wx.WXK_SHIFT)
=
=
def StartingKey(self, evt):
"""
If the editor is enabled by pressing keys on the grid, this will be
called to let the editor do something about that first key if desired.
"""
=
evt.Skip()
=
def Destroy(self):
"""final cleanup"""
=
self.base_Destroy()
=
=
def Clone(self):
"""
Create a new object which is the copy of this one
*Must Override*
"""
=
return RosterTimeEditor()
=
class Grid2(wx.grid.Grid):
def __init__(self, parent):
wx.grid.Grid.__init__(self, parent, -1)
=
self.parent =3D parent
=
self.table =3D Grid2Table()
self.SetTable(self.table, True)
#self.SetDefaultCellBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_C=
OLOUR_BTNFACE))
=
self.SetGridLineColour(wx.BLACK)
self.SetColLabelSize(0)
self.SetRowLabelSize(0)
self.AutoSizeRows() =
self.AutoSizeColumns()
self.EnableEditing(False)
event.eventManager.Register(self._checkmouse,wx.EVT_MOTION,self.GetGridWi=
ndow())
event.eventManager.Register(self._checkmouse2,wx.EVT_LEFT_DOWN,self.GetGr=
idWindow())
event.eventManager.Register(self._checkmouse3,wx.EVT_LEFT_UP,self.GetGrid=
Window())
self.dragstartok =3D 0
self.dragging =3D 0
=
#event.eventManager.Register(self.OnMouseOver,wx.EVT_MOTION,self.GetGridW=
indow())
#event.eventManager.Register(self.OnMouseOff,wx.EVT_LEAVE_WINDOW,self)
#self.ShiftsWin =3D None
###having a few problems with both my ShiftsWin frame so disabled it to f=
ocus on =
###fixing the roster grids editing problem =
=
def OnMouseOver(self,evt):
"this displays a box showing the staff's shifts"
if self.ShiftsWin =3D=3D None:
self.ShiftsWin =3D RosteredShiftsFrame(self,pos=3Dwx.GetMousePosition())
#print "mouse over grid"
=
def OnMouseOff(self, evt):
"destroy window is the mouse leaves the grid"
#print "mouse left grid"
self.ShiftsWin.Destroy()
self.ShiftsWin =3D None
=
def _checkmouse(self, evt):
=
if self.dragging or not self.dragstartok or not evt.Dragging():
return
self.dragging =3D 1
try:
self._startdrag(evt)
finally:
self.dragging =3D 0
=
def _checkmouse2(self, evt):
self.dragstartok =3D 1
evt.Skip()
=
def _checkmouse3(self, evt):
self.dragstartok =3D 0 =
evt.Skip()
=
def _startdrag(self, evt):
#get the position and text, or an image, etc.
text =3D self.GetCellValue(self.GetGridCursorRow(),self.GetGridCursorCol(=
))
=
data =3D wx.TextDataObject()
data.SetText(text)
ds =3D wx.DropSource(self)
ds.SetData(data)
result =3D ds.DoDragDrop(wx.Drag_AllowMove)
if result =3D=3D wx.DragCopy:
"copy"
elif result =3D=3D wx.DragMove:
"moved"
else:
"failed"
=
=
class Grid2Table(wx.grid.PyGridTableBase):
def __init__(self):
wx.grid.PyGridTableBase.__init__(self)
self.rowLabels =3D [[''],[''],[''],['']]
self.colLabels =3D ['']
self.dataTypes =3D [wx.grid.GRID_VALUE_STRING]
self.data =3D [['bob'],['jane'],['adam'],['anna-marie']
=
]
=
=
def GetNumberRows(self):
return len(self.rowLabels)
=
def GetNumberCols(self):
return len(self.colLabels)
def IsEmptyCell(self, row, col):
try:
return not self.data[row][col]
except IndexError:
return True
def GetValue(self, row, col):
try:
return self.data[row][col]
except IndexError:
return ''
def SetValue(self, row, col, value):
try:
self.data[row][col] =3D value
except IndexError:
# add a new row
self.data.append([''] * self.GetNumberCols())
self.SetValue(row, col, value)
# tell the grid we've added a row
msg =3D wx.grid.GridTableMessage(self, # The=
table
wx.grid.GRIDTABLE_NOTIFY_ROWS_APPENDED, # what we did to it
0) # how many
self.GetView().ProcessTableMessage(msg)
def GetTypeName(self, row, col):
return self.dataTypes[col]
def CanGetValueAs(self, row, col, typeName):
colType =3D self.dataTypes[col].split(':')[0]
if typeName =3D=3D colType:
return True
else:
return False
def CanSetValueAs(self, row, col, typeName):
return self.CanGetValueAs(row, col, typeName)
=
def GetColLabelValue(self, col):
return self.colLabels[col]
def GetRowLabelValue(self, row):
return self.rowLabels[row]
=
class Grid1(wx.grid.Grid):
def __init__(self, parent):
wx.grid.Grid.__init__(self, parent, -1)
=
self.parent =3D parent
=
self.table =3D Grid1Table()
self.SetTable(self.table, True)
self.SetDefaultCellBackgroundColour(wx.Colour(red=3D167,green=3D202,blue=
=3D216))
#have to hack rowlabel to 1 because 0 stuffs up xytocell
self.SetRowLabelSize(1)
self.SetDefaultColSize(135)
self.SetDefaultRowSize(125)
self.SetGridLineColour(wx.BLACK)
#self.SetDefaultCellAlignment(wx.ALIGN_CENTRE,wx.ALIGN_BOTTOM)
self.SetDefaultEditor(RosterTimeEditor())
self.SetDefaultRenderer(RosterTimeRenderer())
=
=
=
def XYToCell(self, x, y):
rowwidth =3D self.GetGridRowLabelWindow().GetRect().width
colheight =3D self.GetGridColLabelWindow().GetRect().height
yunit, xunit =3D self.GetScrollPixelsPerUnit()
xoff =3D self.GetScrollPos(wx.HORIZONTAL) * xunit
yoff =3D self.GetScrollPos(wx.VERTICAL) * yunit
=
x +=3D xoff - rowwidth
xpos =3D 0
for col in range(self.GetNumberCols()):
nextx =3D xpos + self.GetColSize(col) =
if xpos <=3D x <=3D nextx:
break
xpos =3D nextx
y +=3D yoff - colheight
ypos =3D 0
for row in range(self.GetNumberRows()):
nexty =3D ypos + self.GetRowSize(row)
if ypos <=3D y <=3D nexty:
break
ypos =3D nexty
=
return row, col
=
class Grid1Table(wx.grid.PyGridTableBase):
def __init__(self):
wx.grid.PyGridTableBase.__init__(self)
self.colLabels =3D ['Monday','Tuesday','Wednesday','Thursday','Friday','S=
aturday','Sunday']
self.rowLabels =3D ['']
self.dataTypes =3D [wx.grid.GRID_VALUE_STRING,
wx.grid.GRID_VALUE_STRING,
wx.grid.GRID_VALUE_STRING,
wx.grid.GRID_VALUE_STRING,
wx.grid.GRID_VALUE_STRING,
wx.grid.GRID_VALUE_STRING,
wx.grid.GRID_VALUE_STRING]
self.data =3D [["""Name:\n
Start:\n
Finish:\n
Breaks:\n
Total:""",
"""Name:\n
Start:\n
Finish:\n
Breaks:\n
Total:""",
"""Name:\n
Start:\n
Finish:\n
Breaks:\n
Total:""",
"""Name:\n
Start:\n
Finish:\n
Breaks:\n
Total:""",
"""Name:\n
Start:\n
Finish:\n
Breaks:\n
Total:""",
"""Name:\n
Start:\n
Finish:\n
Breaks:\n
Total:""",
"""Name:\n
Start:\n
Finish:\n
Breaks:\n
Total:"""
]]
=
def GetNumberRows(self):
return len(self.rowLabels)
=
def GetNumberCols(self):
return len(self.colLabels)
def IsEmptyCell(self, row, col):
try:
return not self.data[row][col]
except IndexError:
return True
def GetValue(self, row, col):
try:
return self.data[row][col]
except IndexError:
return ''
def SetValue(self, row, col, value):
try:
self.data[row][col] =3D value
except IndexError:
# add a new row
self.data.append([''] * self.GetNumberCols())
self.SetValue(row, col, value)
# tell the grid we've added a row
msg =3D wx.grid.GridTableMessage(self, # The=
table
wx.grid.GRIDTABLE_NOTIFY_ROWS_APPENDED, # what we did to it
1) # how many
self.GetView().ProcessTableMessage(msg)
def GetTypeName(self, row, col):
return self.dataTypes[col]
def CanGetValueAs(self, row, col, typeName):
colType =3D self.dataTypes[col].split(':')[0]
if typeName =3D=3D colType:
return True
else:
return False
def CanSetValueAs(self, row, col, typeName):
return self.CanGetValueAs(row, col, typeName)
=
def GetColLabelValue(self, col):
return self.colLabels[col]
=
class TextDropTarget(wx.TextDropTarget):
def __init__(self, obj):
wx.TextDropTarget.__init__(self)
self.obj =3D obj
def OnDropText(self, x, y, data):
=
row, col =3D self.obj.XYToCell(x, y)
if row > -1 and col > -1:
#i must rewrite the cell's contents including previous times with the ne=
w name
self.obj.SetCellValue(row,col,"Name: " + data +"\n\n" + self.obj.GetCell=
Value(row,col).split("\n\n",1)[1])
self.obj.Refresh()
=
class MainFrame(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title, size =3D (800,600))
=
self.DragFromGrid =3D Grid2(self)
=
self.DragToGrid =3D Grid1(self)
dt2 =3D TextDropTarget(self.DragToGrid)
self.DragToGrid.SetDropTarget(dt2)
Sizer =3D wx.BoxSizer(wx.HORIZONTAL)
Sizer.Add(self.DragFromGrid,1,wx.EXPAND)
Sizer.Add(self.DragToGrid,8,wx.EXPAND)
self.SetSizer(Sizer)
self.Layout()
=
class App(wx.App):
"""This class is the application handle for wxPython"""
def OnInit(self):
frame =3D MainFrame(None, -1, 'demo app')
frame.Show(True)
return True
=
=
def Main():
"""Main function, the last thing to run, runs =
the wxPython application in a loop"""
App().MainLoop()
Main()
More information about the wxpython-users
mailing list