[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