[wxPython-users] info bubble and annotation over grid cell UPDATED

Timothy Smith timothy at open-networks.net
Wed Nov 1 03:29:52 PST 2006


Timothy Smith wrote:

> Robin Dunn wrote:
>
>> Timothy Smith wrote:
>>
>>> Robin Dunn wrote:
>>>
>>
>>>>> i've also run into an odd problem. when the grid is initialsed the 
>>>>> defaultcellrenderer isn't set to RosterTimeRenderer, only afte i 
>>>>> edit a cell does it change it.
>>>>
>>>>
>>>>
>>>>
>>>> It seems to be doing it properly here.  The prints in your Draw are 
>>>> showing up without needing to click on the cell first.
>>>
>>>
>>>
>>> no i definately have a problem here. when i first start the demo i 
>>> get an error message of
>>>
>>> Traceback (most recent call last):
>>>  File "demo.py", line 55, in Draw
>>>    
>>> dc.DrawText(grid.GetCellEditor(row,col).GetControl().Start.GetValue(),x,y) 
>>>
>>> AttributeError: 'NoneType' object has no attribute 'Start
>>>
>>> for each cell in my grid, no values are displayed, but once i've put 
>>> a cell into edit mode and back out again suddenly my Start() values 
>>> are displayed by the renderer.
>>>
>>
>> You're mixing apples and oranges...  Your CellRenderer is working 
>> fine, but it is trying to access the widget in your CellEditor to get 
>> the value, and the cell editor's widget is definitely not not created 
>> until it is needed.  Even after it is created the same widget will be 
>> shared by all cells that use the same editor, so you don't wan to be 
>> pulling values from the widget anyway, except when the widget is 
>> finished editing a cell, and then you want to just put the value from 
>> the widget into the table.  Your CellRenderer should be getting the 
>> values that it is going to display from the table, that's what it's 
>> there for.
>>
>>
>

sorry robbin i still can't drag and drop or edit the timectrl inside the 
grid in edit mode. it appears to cancel the edit the moment i click on 
the timectrl. strange it didn't do that initally.

>------------------------------------------------------------------------
>
>
>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 TransientPopup(wx.PopupTransientWindow):
>	"""Adds a bit of text and mouse movement to the wxPopupWindow"""
>	def __init__(self, parent, style):
>		wx.PopupTransientWindow.__init__(self, parent, style)
>		panel = wx.Panel(self, -1)
>		panel.SetBackgroundColour("#FFB6C1")
>		#need to grab all shifts this user has allocated to them
>		Shifts = "Currently assigned shifts for " + parent.GetCellValue(0,0) + " :\n"
>		for x in range(0,parent.parent.DragToGrid.GetNumberRows()):
>			for y in range(0,parent.parent.DragToGrid.GetNumberCols()):
>				if parent.parent.DragToGrid.GetCellValue(x,y) == parent.GetCellValue(0,0):
>					Shifts = Shifts + parent.parent.DragToGrid.GetColLabelValue(y) + '\n'
>		st = wx.StaticText(panel, -1,Shifts,
>		pos=(10,10))
>		sz = st.GetBestSize()
>		panel.SetSize( (sz.width+20, sz.height+20) )
>		self.SetSize(panel.GetSize())
>		
>	def ProcessLeftDown(self, evt):
>		return False
>        
>class TimeControl(wx.Control):
>	def __init__(self, parent,id):
>		wx.Control.__init__(self, parent, -1)
>		Nametxt = wx.StaticText(self,id,"Name:")
>		self.Name = wx.StaticText(self,id,"")
>		Starttxt = wx.StaticText(self,id,"Start:")
>		Finishtxt = wx.StaticText(self,id,"Finish:")
>		Breakstxt = wx.StaticText(self,id,"Breaks:")
>		#spinStart = wx.SpinButton(self,-1,wx.DefaultPosition,wx.Size(-1,20),0)
>		self.Start = TimeCtrl(self,id,value = '00:00',style = wx.TE_PROCESS_TAB,format = 'HHMM',fmt24hr = False, displaySeconds = False)
>		self.Finish = TimeCtrl(self,id,value = '00:00',style = wx.TE_PROCESS_TAB,format = 'HHMM',fmt24hr = False, displaySeconds = False)
>		self.Breaks = wx.SpinCtrl(self,-1,min=0,max=60,size = wx.Size(40,20))
>		Totaltxt = wx.StaticText(self,id,"Total:")
>		self.Total = wx.StaticText(self,id,"")
>		
>		GridSizer = wx.GridSizer(5,2,5)
>		GridSizer.AddMany([
>		(Nametxt,0),(self.Name,0),
>		(Starttxt,0),(self.Start,0),
>		(Finishtxt,0),(self.Finish,0),
>		(Breakstxt,0),(self.Breaks,0),
>		(Totaltxt,0),(self.Total,0)
>		])
>		
>		#this strange setup is needed because the gridsizer isn't layout correctly in the cell for some reason
>		Sizer = wx.BoxSizer(wx.VERTICAL)
>		Sizer.Add(GridSizer,0,wx.EXPAND)
>		
>		Sizer2 = wx.BoxSizer(wx.HORIZONTAL)
>		Sizer2.Add(Sizer,0,wx.EXPAND)
>		
>		self.SetSizer(Sizer2)
>		self.Layout()
>		
>	def SetValue(self,val):
>		#in here is how i write to the cells once i'm finished editing
>		#self.Name.SetLabel(self.Name.GetLabel().split(':')[0] + val)
>		print val
>	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=167,green=202,blue=216), wx.SOLID))
>		dc.SetPen(wx.TRANSPARENT_PEN)
>		dc.DrawRectangle(rect.x, rect.y, rect.width, rect.height)
>		dc.SetBackgroundMode(wx.TRANSPARENT)
>		dc.SetFont(attr.GetFont())
>		x = rect.x + 1
>		y = 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 = grid.GetCellValue(row, col)
>		dc.SetFont(attr.GetFont())
>		w, h = 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 = TimeControl(parent,-1)
>		#self._tc = timectrl.TimeCtrl(parent, id, display_seconds = False, fmt24hr=True)
>		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 = 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
>		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() != 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.
>		"""
>		
>		key = evt.GetKeyCode()
>		ch = None
>		if key in [wx.WXK_NUMPAD0, wx.WXK_NUMPAD1, wx.WXK_NUMPAD2, wx.WXK_NUMPAD3, wx.WXK_NUMPAD4,
>				   wx.WXK_NUMPAD5, wx.WXK_NUMPAD6, wx.WXK_NUMPAD7, wx.WXK_NUMPAD8, wx.WXK_NUMPAD9]:
>			ch = ch = chr(ord('0') + key - wx.WXK_NUMPAD0)
>		
>		elif key < 256 and key >= 0 and chr(key) in string.printable:
>			ch = chr(key)
>			if not evt.ShiftDown():
>				ch = ch.lower()
>		
>		if ch is not None:
>			# For this example, replace the text.  Normally we would append it.
>			self._tc.AppendText(ch)
>			self._tc.SetValue(ch)
>			self._tc.SetInsertionPointEnd()
>		else:
>			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 = parent
>		
>		self.table = Grid2Table()
>		self.SetTable(self.table, True)
>		#self.SetDefaultCellBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_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.GetGridWindow())
>		event.eventManager.Register(self._checkmouse2,wx.EVT_LEFT_DOWN,self.GetGridWindow())
>		event.eventManager.Register(self._checkmouse3,wx.EVT_LEFT_UP,self.GetGridWindow())
>		#self.GetGridWindow().Bind(wx.EVT_MOTION, self._checkmouse)
>		#self.GetGridWindow().Bind(wx.EVT_LEFT_DOWN, self._checkmouse2)
>		#self.GetGridWindow().Bind(wx.EVT_LEFT_UP, self._checkmouse3)
>		self.dragstartok = 0
>		self.dragging = 0
>		
>		#event.eventManager.Register(self.OnMouseOver,wx.grid.EVT_GRID_CELL_LEFT_CLICK,self)
>		event.eventManager.Register(self.OnMouseOver,wx.EVT_MOTION,self.GetGridWindow())
>			
>	def OnMouseOver(self,evt):
>		"this displays a box showing the staff's shifts"
>		win = TransientPopup(self, wx.SIMPLE_BORDER)
>		cell = evt.GetEventObject()
>		pos = cell.ClientToScreen( (0,0) )
>		sz =  cell.GetSize()
>		win.Position(pos, (0, sz.height))
>		win.Popup()
>		evt.Skip()
>		
>	def _checkmouse(self, evt):
>		if self.dragging or not self.dragstartok or not evt.Dragging():
>			return
>		self.dragging = 1
>		try:
>			self._startdrag(evt)
>		finally:
>			self.dragging = 0
>			
>	def _checkmouse2(self, evt):
>		self.dragstartok = 1
>		evt.Skip()
>		 
>	def _checkmouse3(self, evt):
>		self.dragstartok = 0
>		evt.Skip()
>
>	def _startdrag(self, evt):
>	#get the position and text, or an image, etc.
>		text = self.GetCellValue(self.GetGridCursorRow(),self.GetGridCursorCol())
>  
>		data = wx.TextDataObject()
>		data.SetText(text)
>		ds = wx.DropSource(self)
>		ds.SetData(data)
>		result = ds.DoDragDrop(wx.Drag_AllowMove)
>		if result == wx.DragCopy:
>			"copy"
>		elif result == wx.DragMove:
>			"moved"
>		else:
>			"failed"
>             
>        	
>class Grid2Table(wx.grid.PyGridTableBase):
>
>	def __init__(self):
>		wx.grid.PyGridTableBase.__init__(self)
>
>		self.rowLabels = [[''],[''],[''],['']]
>		self.colLabels = ['']
>		self.dataTypes = [wx.grid.GRID_VALUE_STRING]
>
>		self.data = [['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] = 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 = 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 = self.dataTypes[col].split(':')[0]
>		if typeName == 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 = parent
>		
>		self.table = Grid1Table()
>		self.SetTable(self.table, True)
>		self.SetDefaultCellBackgroundColour(wx.Colour(red=167,green=202,blue=216))
>		#have to hack rowlabel to 1 because 0 stuffs up xytocell
>		self.SetRowLabelSize(1)
>		self.SetDefaultColSize(200)
>		self.SetDefaultRowSize(200)
>		self.SetGridLineColour(wx.BLACK)
>		#self.SetDefaultCellAlignment(wx.ALIGN_CENTRE,wx.ALIGN_BOTTOM)
>		self.SetDefaultEditor(RosterTimeEditor())
>		self.SetDefaultRenderer(RosterTimeRenderer())
>		
>	
>  
>	def XYToCell(self, x, y):
>
>		rowwidth = self.GetGridRowLabelWindow().GetRect().width
>		colheight = self.GetGridColLabelWindow().GetRect().height
>		yunit, xunit = self.GetScrollPixelsPerUnit()
>		xoff =  self.GetScrollPos(wx.HORIZONTAL) * xunit
>		yoff = self.GetScrollPos(wx.VERTICAL) * yunit
>		
>		x += xoff - rowwidth
>		xpos = 0
>		for col in range(self.GetNumberCols()):
>			nextx = xpos + self.GetColSize(col)           
>			if xpos <= x <= nextx:
>				break
>			xpos = nextx
>		y += yoff - colheight
>		ypos = 0
>		for row in range(self.GetNumberRows()):
>			nexty = ypos + self.GetRowSize(row)
>			if ypos <= y <= nexty:
>				break
>			ypos = nexty
>		
>		return row, col
>			
>class Grid1Table(wx.grid.PyGridTableBase):
>
>	def __init__(self):
>		wx.grid.PyGridTableBase.__init__(self)
>
>		self.colLabels = ['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday']
>		self.rowLabels = ['']
>		self.dataTypes = [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 = [["""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] = 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 = 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 = self.dataTypes[col].split(':')[0]
>		if typeName == 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 = obj
>	def OnDropText(self, x, y, data):
>		
>		row, col = self.obj.XYToCell(x, y)
>		if row > -1 and col > -1:
>			self.obj.SetCellValue(row,col,data)
>			self.obj.Refresh()
>			
>class MainFrame(wx.Frame):
>	def __init__(self, parent, id, title):
>		wx.Frame.__init__(self, parent, id, title, size = (800,600))
>	
>		self.DragFromGrid = Grid2(self)
>		
>		self.DragToGrid = Grid1(self)
>		dt2 = TextDropTarget(self.DragToGrid)
>		self.DragToGrid.SetDropTarget(dt2)
>
>		Sizer = 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 = 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()
>
>  
>
>------------------------------------------------------------------------
>
>---------------------------------------------------------------------
>To unsubscribe, e-mail: wxPython-users-unsubscribe at lists.wxwidgets.org
>For additional commands, e-mail: wxPython-users-help at lists.wxwidgets.org
>





More information about the wxpython-users mailing list