[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