custom grid cell can't edit timectrl
Timothy Smith
timothy at open-networks.net
Thu Nov 2 19:12:09 PST 2006
Hello i've started a new thread to help clarify my problem.
attached is a small demo program showing my issue.
i use the custom control TimeControl when editing the cell on grid1. the =
control is shown correctly, however as soon as i try click on one of the =
timectrl boxes to enter a time, it cancels the edit mode on the cell.
i suspect i need to over ride one of the default events? but i have no =
idea which one.
my other problem is also related to the editor. under windows, =
TimeControl is not shown correctly. it just displays the contents of the =
cell unformated in one line. it works prefectly for me under x11 =
(freebsd 6.0 gtk)
-------------- 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: "+sel=
f.Finish.GetValue()+ "\n\nBreaks: " + str(self.Breaks.GetValue()) +"\n\nTot=
al: " +self.Total.GetLabel()
=
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)
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