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