• callAfter 例子2


    Introduction

    The original bounty (whose content is farther down) required a scheduling widget for handling appointments in a medical doctor's office. This widget needed to support initially fixed time slots, dragging and dropping of appointments, etc.

     

    What Objects/Features are Involved

    • wx.grid
    • wx.lib.newevent
    • custom dragging source based on mouse click and movement

     

    Process Overview

    Generally speaking, the scheduling widget starts out in a fairly regular manner by hiding the row and column labels and disabling row/column resizing. It uses a custom EVT_SIZE handler to resize the schedule information column and leave 32 pixels to the right empty. On the platforms tested (Windows and Ubuntu), this resulted in no horizontal scrollbars.

    The content is initially populated with either fixed-length time slots and empty schedule information, or user-specified time slots and schedule information. Added to this is a slightly-more-than-minimal set of functionality to allow for the intuitive manipulation of schedule information (API spec due to Dr. Horst Herb), along with 'client data' information, which can be used as row ids for database entries or otherwise.

    Included are a handful of event bindings for a cell's contents being modified, a cell being clicked on, a cell dragged out (for deletion from a database), or a cell dragged in (for isnertion into a database). Modifying the content with the API or does not cause events to be posted for handling.

    The real trick with this widget was getting the grid to not select cells during drag. Some initial implementations used background color tricks to mask the selections, but the current version captures mouse click and drag events to usurp dragging behavior, disabling the underlying 'select all cells that have been dragged over', and which makes implementing drag and drop behavior fairly easy. The method used with the wx.grid can be used on other widgets to offer drag and drop behavior where previously such events weren't possible.

     

    Implementation with documentation

     

    切换行号显示
       1 
       2 '''
       3 schedule.py
       4 
       5 Version .6
       6 
       7 A wx.grid.Grid-based scheduling widget written by Josiah Carlson for the
       8 GNUMED project (http://www.gnumed.org) and related projects.  This widget and
       9 is licensed under the GNU GPL v. 2.  If you would like an alternately licensed
      10 version, please contact the author via phone, email, IM, or in writing; it is
      11 likely that you will be able to get the widget in a license that you desire.
      12 
      13 josiah.carlson@gmail.com or http://dr-josiah.blogspot.com/
      14 
      15 The base control
      16 ----------------
      17 
      18 The object that you will find of the most use is:
      19 ScheduleGrid(parent, data=None, from_time="8:00", to_time="17:00",
      20              default_timeslot=15)
      21 
      22 If data is a non-empty sequence of (time, appointment) pairs, then the content
      23 of the grid will become that data, and the size of the cells will be scaled
      24 based on the duration of the time slot.  In this case, the from_time and
      25 default_timeslot arguments are ignored, but the to_time argument is not, and
      26 will become a hidden entry that determines the duration of the final slot.
      27 
      28 If data is None or an empty sequence, then from_time and to_time are taken as
      29 'military' time literals, and sufficient time slots to fill the from_time to
      30 to_time slots, with a default duration of default_timeslot minutes.  If
      31 default_time is None, it will use a time slot of 15 minutes, with a random
      32 extra duration of 0, 15, 30, or 45 minutes (0 occurring with probability 1/2,
      33 and each of the others occurring with probability 1/6).  This random time slot
      34 assignment is for visual testing purposes only.
      35 
      36 
      37 Dragging a scheduled item from a control to another control (or itself) will
      38 move the scheduled item.  If the destination is empty, the information will
      39 fill the empty slot.  If the destination has a scheduled item already, it will
      40 split the destination time slot in half (rounding down to the nearest minute),
      41 then fill the newly created empty slot.
      42 
      43 
      44 Useful methods:
      45 
      46 SetSlot(time, text='', id=None, bkcolor=None, fgcolor=None)
      47     Set the slot at time 'time' with text 'text' and set it's client data
      48     to 'id', background colour is set to 'bkcolor' if specified, text
      49     colour to fgcolor if specified.  If a slot with the specified time
      50     'time' does not exist, create it and redraw the widget if necessary.
      51 
      52 SplitSlot(time, minutes=None, text='', id=None, bkcolor=None, fgcolor=None)
      53     If there is a slot that already exists, and it has content, split the
      54     slot so that the new time slot has duration minutes, or in half if
      55     minutes is None.  All other arguments have the same semantics as in
      56     SetSlot(...) .
      57 
      58     The previously existing slot will result in a TextEntered event,
      59     providing the new time for the squeezed slot.  No other "useful
      60     methods" cause a TextEntered event.
      61 
      62 
      63 GetSlot(time)
      64     Returns a dict with the keys time, text, id, bkcolor, fgcolor or None
      65     if that time slot does not exist.
      66 
      67 GetAllSlots()
      68     Returns a list of dicts as specified in GetSlot() in chronologic order
      69     for all slots of this widget.
      70 
      71 ClearSlot(time)
      72     Text and client data of this slot is erased, but slot remains.
      73 
      74 DeleteSlot(time)
      75     Slot is removed from the grid along with text and client data.
      76 
      77 [Get|Set|Clear]ClientData methods
      78     Gets/Sets/Clears per-time slot specified client data, specified as the
      79     'id' argument in SetSlot(), SplitSlot(), GetSlot(), and GetAllSlots().
      80 
      81 
      82 Usable event bindings
      83 ---------------------
      84 
      85 CellClicked and EVT_CELL_CLICKED
      86 
      87 If you use schedulewidget.Bind(EVT_CELL_CLICKED, fcn), whenever a cell is
      88 clicked, you will recieve a CellClicked event.  You can discover the row and
      89 column of the click with evt.row and evt.col respectively, the time of the
      90 scheduled item evt.time, and the item text itself with evt.text .
      91 
      92 If you have set the menu items with .SetPopup(), you will not recieve this
      93 event when the right mouse button is clicked.
      94 
      95 TextEntered and EVT_TEXT_ENTERED
      96 
      97 If you use schedulewidget.Bind(EVT_TEXT_ENTERED, fcn), whenever the content
      98 of a row's 'appointment' has been changed, either by the user changing the
      99 content by keyboard, or by a squeezed item being cleared for widget to itself
     100 drags, your function will be called with a TextEntered event. You can discover
     101 the row, text, and time of the event with the same attributes as the
     102 CellClicked event.  There is no col attribute.
     103 
     104 DroppedIn and EVT_DROPPED_IN
     105 DroppedOut and EVT_DROPPED_OUT
     106 BadDrop and EVT_BAD_DROP
     107 
     108 Events that are posted when a cell has been dropped into a control, dragged
     109 out of a control, or when a control has gotten bad data from a drop.
     110 
     111 '''
     112 
     113 import random
     114 import time
     115 
     116 import wx
     117 import wx.grid
     118 import wx.lib.newevent
     119 
     120 printevent=0
     121 
     122 dc = wx.DragCopy
     123 dm = wx.DragMove
     124 
     125 TextEntered, EVT_TEXT_ENTERED = wx.lib.newevent.NewEvent()
     126 CellClicked, EVT_CELL_CLICKED = wx.lib.newevent.NewEvent()
     127 DroppedIn, EVT_DROPPED_IN = wx.lib.newevent.NewEvent()
     128 DroppedOut, EVT_DROPPED_OUT = wx.lib.newevent.NewEvent()
     129 BadDrop, EVT_BAD_DROP = wx.lib.newevent.NewEvent()
     130 
     131 tp_to_name = {TextEntered:'Entered',
     132               CellClicked:'Clicked',
     133               DroppedIn:'Dropped In',
     134               DroppedOut:'Dropped Out',
     135               BadDrop:'Bad Drop'
     136               }
     137 
     138 tt = "%02i:%02i"
     139 def gethm(t):
     140     h,m = [int(i.lstrip('0') or '0') for i in t.split(':')]
     141     return h,m
     142 
     143 def cnt():
     144     i = 0
     145     while 1:
     146         yield i
     147         i += 1
     148 
     149 _counter = cnt()
     150 def timeiter(st, en, incr):
     151     if incr is None:
     152         incr = 15
     153         rr = lambda : random.choice((0, 0, 0, 15, 30, 45))
     154         nx = lambda : str(_counter.next())
     155     else:
     156         rr = lambda : 0
     157         nx = lambda : ''
     158     hs, ms = gethm(st)
     159     he, me = gethm(en)
     160     while (hs, ms) < (he, me):
     161         yield tt%(hs, ms), nx()
     162         ms += incr + rr()
     163         hs += ms//60
     164         ms %= 60
     165 
     166 def timediff(t1, t2):
     167     h2, m2 = gethm(t2)
     168     h1, m1 = gethm(t1)
     169     h2 -= h1
     170     m2 -= m1
     171     m2 += 60*h2
     172     return m2
     173 
     174 def addtime(t1, delta):
     175     h1, m1 = gethm(t1)
     176     m1 += delta
     177     h1 += m1//60
     178     m1 %= 60
     179     return tt%(h1, m1)
     180 
     181 def timetoint(t):
     182     h,m=gethm(t)
     183     return h*60+m
     184 
     185 minh = 17
     186 rightborder = 32
     187 dragsource = None
     188 
     189 class ScheduleDrop(wx.TextDropTarget):
     190     def __init__(self, window):
     191         wx.TextDropTarget.__init__(self)
     192         self.window = window
     193         self.d = None
     194 
     195     def OnDropText(self, x, y, text):
     196         try:
     197             data = eval(text)
     198         except:
     199             to = min(max(self.window.YToRow(y), 1), self.window.GetNumberRows()-2)
     200             wx.PostEvent(self.window, BadDrop(text=text, dest=to))
     201         else:
     202             self.window._dropped(y, data)
     203 
     204     def OnDragOver(self, x, y, d):
     205         self.d = d
     206         to = min(max(self.window.YToRow(y), 1), self.window.GetNumberRows()-2)
     207         if self.window[to,1]:
     208             return dc
     209         return dm
     210 
     211 class ScheduleGrid(wx.grid.Grid):
     212     def __init__(self, parent, data=None, from_time="8:00", to_time="17:00", default_timeslot=15):
     213         wx.grid.Grid.__init__(self, parent, -1)
     214         start, end, step = from_time, to_time, default_timeslot
     215         if data is None:
     216             times = list(timeiter(start, end, step))
     217         else:
     218             times = data
     219 
     220         self.SetDropTarget(ScheduleDrop(self))
     221 
     222         self.lasttime = addtime(end, 0)
     223         self.CreateGrid(len(times)+2, 2)
     224         global minh
     225         minh = self.GetRowSize(0)
     226 
     227         self.SetRowMinimalAcceptableHeight(0)
     228         self.SetColLabelSize(0)
     229         self.SetRowLabelSize(0)
     230         self.DisableDragColSize()
     231         self.DisableDragRowSize()
     232         self.Bind(wx.EVT_SIZE, self.OnSize)
     233         self.SetSelectionMode(1)
     234 
     235         self.SetReadOnly(0, 0, 1)
     236         for i,(j,k) in enumerate(times):
     237             i += 1
     238             self.SetCellValue(i, 0, j)
     239             self.SetReadOnly(i, 0, 1)
     240             self.SetCellValue(i, 1, k)
     241             self.SetCellRenderer(i, 1, WrappingRenderer())
     242             self.SetCellEditor(i, 1, WrappingEditor())
     243         i += 1
     244         self.SetReadOnly(i, 0, 1)
     245         self.SetCellValue(i, 0, self.lasttime)
     246 
     247         self.fixh()
     248 
     249         self.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, self._leftclick)
     250         self.Bind(wx.grid.EVT_GRID_CELL_RIGHT_CLICK, self._rightclick_menu_handler)
     251         self.Bind(wx.grid.EVT_GRID_EDITOR_SHOWN, self.OnShowEdit)
     252         self.Bind(wx.grid.EVT_GRID_EDITOR_HIDDEN, self.OnHideEdit)
     253         self.Bind(wx.grid.EVT_GRID_EDITOR_CREATED, self.OnCreateEdit)
     254         self.GetGridWindow().Bind(wx.EVT_MOTION, self._checkmouse)
     255         self.GetGridWindow().Bind(wx.EVT_LEFT_DOWN, self._checkmouse2)
     256         self.GetGridWindow().Bind(wx.EVT_LEFT_UP, self._checkmouse3)
     257 
     258 
     259         self.Bind(EVT_TEXT_ENTERED, self._handler)
     260         self.Bind(EVT_CELL_CLICKED, self._handler)
     261         self.Bind(EVT_DROPPED_IN, self._handler)
     262         self.Bind(EVT_DROPPED_OUT, self._handler)
     263 
     264         self.selected = 0
     265         self.dragging = 0
     266         self.evtseen = None
     267         self.dragstartok = 0
     268         self.lastpos = None
     269         self.clientdata = {}
     270         self.menu = None
     271         self.ids = []
     272         self.AutoSizeColumn(0, 0)
     273         self.p = wx.Panel(self, -1)
     274         self.p.Hide()
     275 
     276     def YToRow(self, y):
     277         _, y = self.CalcUnscrolledPosition(0, y)
     278         return wx.grid.Grid.YToRow(self, y)
     279 
     280     def _handler(self, evt):
     281         if printevent:
     282             print "[%s] %s"%(time.asctime(), tp_to_name[type(evt)])
     283         evt.Skip()
     284 
     285     def _checkmouse(self, evt):
     286         ## print dir(evt)
     287         if dragsource or self.dragging or not self.dragstartok or not evt.Dragging():
     288             return
     289         self._startdrag(evt.GetY())
     290 
     291     def _checkmouse2(self, evt):
     292         self.dragstartok = 1
     293         evt.Skip()
     294 
     295     def _checkmouse3(self, evt):
     296         self.dragstartok = 0
     297         evt.Skip()
     298 
     299     def _dropped(self, y, data):
     300         to = min(max(self.YToRow(y), 1), self.GetNumberRows()-2)
     301         time = self[to,0]
     302         wx.CallAfter(self.SplitSlot, time, **data)
     303         wx.CallAfter(wx.PostEvent, self, DroppedIn(time=time, **data))
     304 
     305     def _getduration(self, row):
     306         if row < self.GetNumberRows():
     307             return timediff(self[row,0], self[row+1,0])
     308         return timediff(self[row,0], self.lasttime)
     309 
     310     def __getitem__(self, key):
     311         if type(key) is tuple:
     312             row, col = key
     313             if row < 0:
     314                 row += self.GetNumberRows()
     315             if row >= 0:
     316                 return self.GetCellValue(row, col)
     317         raise KeyError("key must be a tuple of length 2")
     318 
     319     def __setitem__(self, key, value):
     320         if type(key) is tuple:
     321             row, col = key
     322             if row < 0:
     323                 row += self.GetNumberRows()
     324             if row >= 0:
     325                 return self.SetCellValue(row, col, value)
     326         raise KeyError("key must be a tuple of length 2")
     327 
     328     def _leftclick(self, evt):
     329         sel = evt.GetRow()
     330 
     331         if not self.GetGridCursorCol():
     332             self.MoveCursorRight(0)
     333         gr = self.GetGridCursorRow()
     334         if gr < sel:
     335             for i in xrange(sel-gr):
     336                 self.MoveCursorDown(0)
     337         else:
     338             for i in xrange(gr-sel):
     339                 self.MoveCursorUp(0)
     340         wx.PostEvent(self, CellClicked(row=sel, col=evt.GetCol(), **self._getdict(sel)))
     341         evt.Skip()
     342 
     343     def _rightclick_menu_handler(self, evt):
     344         sel = evt.GetRow()
     345         if not self.menu:
     346             wx.PostEvent(self, CellClicked(row=sel, col=evt.GetCol(), time=self[sel,0], text=self[sel,1]))
     347             return
     348 
     349         time, text = self[sel,0], self[sel,1]
     350         cdata = self.GetClientData(time)
     351 
     352         evt.Skip()
     353 
     354         menu = wx.Menu()
     355         def item((name, fcn)):
     356             def f(evt):
     357                 return fcn(time, text, cdata)
     358             id = wx.NewId()
     359             it = wx.MenuItem(menu, id, name)
     360             menu.AppendItem(it)
     361             self.Bind(wx.EVT_MENU, f, it)
     362             return id
     363         clear = map(item, self.menu)
     364         self.PopupMenu(menu)
     365         menu.Destroy()
     366 
     367     def SetPopup(self, menulist):
     368         self.menu = menulist
     369 
     370     def OnShowEdit(self, evt):
     371         x = self.GetCellEditor(evt.GetRow(), evt.GetCol()).GetControl()
     372         if x:
     373             wx.CallAfter(x.SetInsertionPointEnd)
     374         evt.Skip()
     375 
     376     def OnCreateEdit(self, evt):
     377         x = evt.GetControl()
     378         wx.CallAfter(x.SetInsertionPointEnd)
     379         evt.Skip()
     380 
     381     def OnHideEdit(self, evt):
     382         row = evt.GetRow()
     383         wx.CallAfter(self.OnDoneEdit, self[row,1], row)
     384         evt.Skip()
     385 
     386     def OnDoneEdit(self, old, row):
     387         check = (row, old, self[row,1])
     388         if old != check[-1] and self.evtseen != check:
     389             wx.PostEvent(self, TextEntered(**self._getdict(row)))
     390             if not self.GetGridCursorCol():
     391                 self.MoveCursorRight(0)
     392             for i in xrange(self.GetGridCursorRow()-row):
     393                 self.MoveCursorUp(0)
     394         self.evtseen = check
     395 
     396     def OnSize(self, evt):
     397         x,y = evt.GetSize()
     398         c1 = self.GetColSize(0)
     399         x -= c1
     400         x -= rightborder #otherwise it creates an unnecessary horizontal scroll bar
     401         if x > 20:
     402             self.SetColSize(1, x)
     403 
     404         evt.Skip()
     405 
     406     def fixh(self):
     407         self.SetRowSize(0, 0)
     408         self.SetRowSize(self.GetNumberRows()-1, 0)
     409         for rown in xrange(1, self.GetNumberRows()-1):
     410             td = max(self._getduration(rown), minh)
     411             self.SetRowSize(rown, td)
     412         self.ForceRefresh()
     413 
     414     def _startdrag(self, y):
     415         global dragsource
     416         if dragsource or self.dragging:
     417             return
     418 
     419         self.dragging = 1
     420         dragsource = self
     421 
     422         try:
     423 
     424             row = self.YToRow(y)
     425             if row < 1 or row >= self.GetNumberRows()-1:
     426                 return
     427 
     428             data = row, self._getduration(row), self[row,1]
     429 
     430             time = self[row,0]
     431             data = self._getdict(row)
     432             dcpy = dict(data)
     433             data.pop('time', None)
     434             datar = repr(data)
     435 
     436             d_data = wx.TextDataObject()
     437             d_data.SetText(datar)
     438 
     439             dropSource = wx.DropSource(self)
     440             dropSource.SetData(d_data)
     441             result = dropSource.DoDragDrop(wx.Drag_AllowMove)
     442             if result in (dc, dm):
     443                 self.ClearSlot(time)
     444                 wx.CallAfter(wx.PostEvent, self, DroppedOut(**dcpy))
     445             wx.CallAfter(self.SelectRow, row)
     446             wx.CallAfter(self.ForceRefresh)
     447         finally:
     448             dragsource = None
     449             self.dragging = 0
     450 
     451     def _findslot(self, time):
     452         t = timetoint(time)
     453         for i in xrange(1, self.GetNumberRows()-1):
     454             tt = timetoint(self[i,0])
     455             if tt >= t:
     456                 break
     457         return i, t==tt
     458 
     459     def SetSlot(self, time, text='', id=None, bkcolor=None, fgcolor=None):
     460         '''
     461         Set the slot at time 'time' with text 'text' and set it's client data
     462         to 'id', background colour is set to 'bkcolor' if specified, text
     463         colour to fgcolor if specified.  If a slot with the specified time
     464         'time' does not exist, create it and redraw the widget if necessary.
     465         '''
     466 
     467         i, exact = self._findslot(time)
     468         if not exact:
     469             self.InsertRows(i, 1)
     470             self[i,0] = time
     471         self[i,1] = text
     472         if id:
     473             self.SetClientData(time, id)
     474         if bkcolor:
     475             self.SetCellBackgroundColour(i, 0, bkcolor)
     476             self.SetCellBackgroundColour(i, 1, bkcolor)
     477         if fgcolor:
     478             self.SetCellTextColour(i, 0, fgcolor)
     479             self.SetCellTextColour(i, 1, fgcolor)
     480         if not exact:
     481             self.fixh()
     482         if not self.GetGridCursorCol():
     483             self.MoveCursorRight(0)
     484         for j in xrange(self.GetGridCursorRow()-i):
     485             self.MoveCursorUp(0)
     486         for j in xrange(i-self.GetGridCursorRow()):
     487             self.MoveCursorDown(0)
     488         wx.CallAfter(self.ClearSelection)
     489         wx.CallAfter(self.SelectRow, i)
     490         wx.CallAfter(self.ForceRefresh)
     491 
     492     def GetSlot(self, time):
     493         '''
     494         Returns a dict with the keys time, text, id, bkcolor, fgcolor or None
     495         if that time slot does not exist.
     496         '''
     497 
     498         i, exact = self._findslot(time)
     499         if not exact:
     500             return None
     501         return self._getdict(i)
     502 
     503     def _getdict(self, i):
     504         return dict(time=self[i,0], text=self[i,1],
     505                     id=self.GetClientData(self[i,0]),
     506                     bkcolor=self.GetCellBackgroundColour(i,0),
     507                     fgcolor=self.GetCellTextColour(i,0))
     508 
     509     def GetAllSlots(self):
     510         '''
     511         Returns a list of dicts as specified in GetSlot() in chronologic order
     512         for all slots of this widget.
     513         '''
     514 
     515         ret = []
     516         for i in xrange(1, self.GetNumberRows()-1):
     517             ret.append(self._getdict(i))
     518         return ret
     519 
     520     def ClearSlot(self, time):
     521         '''
     522         Text and client data of this slot is erased, but slot remains.
     523         '''
     524 
     525         i, exact = self._findslot(time)
     526         if not exact:
     527             return
     528         self[i,1] = ''
     529         self.ClearClientData(self[i,0])
     530         #do we clear background and foreground colors?
     531 
     532     def DeleteSlot(self, time):
     533         '''
     534         Slot is removed from the grid along with text and client data.
     535         '''
     536 
     537         i, exact = self._findslot(time)
     538         if not exact:
     539             return
     540         self.ClearClientData(self[i,0])
     541         self.DeleteRows(i, 1)
     542         self.fixh()
     543 
     544     def SplitSlot(self, time, minutes=None, text='', id=None, bkcolor=None, fgcolor=None):
     545         '''
     546         If there is a slot that already exists, and it has content, split the
     547         slot so that the new time slot has duration minutes, or in half if
     548         minutes is None.  All other arguments have the same semantics as in
     549         SetSlot(...) .
     550 
     551         The previously existing slot will result in a TextEntered event,
     552         providing the new time for the squeezed slot.  No other "useful
     553         methods" cause a TextEntered event.
     554         '''
     555 
     556         i, exact = self._findslot(time)
     557         if exact and self[i,1]:
     558             if not minutes:
     559                 minutes = self._getduration(i)//2
     560             cd = self.GetClientData(self[i,0])
     561             self.ClearClientData(self[i,0])
     562             nt = addtime(self[i,0], minutes)
     563             self[i,0] = nt
     564             self.SetClientData(nt, cd)
     565             wx.PostEvent(self, TextEntered(**self._getdict(i)))
     566         self.SetSlot(time, text, id, bkcolor, fgcolor)
     567 
     568     def GetClientData(self, time):
     569         return self.clientdata.get(timetoint(time), None)
     570 
     571     def SetClientData(self, time, data):
     572         if data != None:
     573             self.clientdata[timetoint(time)] = data
     574 
     575     def ClearClientData(self, time):
     576         self.clientdata.pop(timetoint(time), None)
     577 
     578 WrappingRenderer = wx.grid.GridCellAutoWrapStringRenderer
     579 WrappingEditor = wx.grid.GridCellAutoWrapStringEditor
     580 
     581 def pr(*args):
     582     print args
     583 
     584 if __name__ == '__main__':
     585     printevent = 1
     586     a = wx.App(0)
     587     b = wx.Frame(None)
     588     p = wx.Panel(b)
     589     ## op = wx.Panel(p)
     590     s = wx.BoxSizer(wx.HORIZONTAL)
     591     c = ScheduleGrid(p, default_timeslot=None)
     592     d = ScheduleGrid(p, default_timeslot=None)
     593 
     594     lst = [('print information', pr)]
     595 
     596     c.SetPopup(lst)
     597 
     598     s.Add(c, 1, wx.EXPAND)
     599     ## s.Add(op, 1, wx.EXPAND)
     600     s.Add(d, 1, wx.EXPAND)
     601     p.SetSizer(s)
     602     b.Show(1)
     603     a.MainLoop()
    

     

    The original bounty from Dr. Horst Herb

    I need a grid-like GUI element that

    • has a set start end end time
    • has slots in set time increments (e.g. 10 minutes each slot) but allows other time spans for each individual slot programmatically
    • allows to "squeeze in" slots, diminishing the size of the squeezed slot
    • displays the slots in a height proportional to their allocated time span
    • allows to drag slots somewhere else, with (=shifting) or without (=squeezing in) reallocating the times for all later slots

    Initialization:

    init( ..., from_time='08:00', to_time='19:00', default_timeslot=15, slot_width=150) where slot_width is the initial width in pixels

    the widget is painted like this:

     

    |^^^^^^^^^^^^^^^^^^^|  <- if the user clicks here, the thing scrolls to earlier times
    |08:00| (some text) |
    |08:15|             |
    |08:30|             |
    ...
    |"""""""""""""""""""| <- if the user clicks here, the thing scrolls to later
    • the minimum height of the cell is determined by the font size
    • if total height of from_time to to_time exceeds displayed height, a scroll bar appears to the right side
    • the space to the right of the displayed time is a text control like widget, allowing immediate text entry when getting the focus, end emitting a "TEXT_ENTERED" event with time and text content if the content was changed and the focus is lost
    • right and left clicking any cell emits an event with both the time of the clicked cell and the cell text content as event parameter

    I offer a bounty of $150 for this. If you need more details, please contact me. I'd imagine this won't be too difficult using the fantastic wxGrid widget

     

    Comments

    If you have any questions, please feel free to contact the author, whose information is available in his profile profile. You may also be able to use the widget soon in GNUmed.

    Note for wxPython < 2.9, this code needs to be changed to associate the drop target with windows within the grid, e.g.self.GetGridWindow().SetDropTarget(ScheduleDrop(self))rather than self.SetDropTarget(ScheduleDrop(self)) to be portable (see discussion).

    Brian

  • 相关阅读:
    DELETE和DELETE FROM有什么区别
    [转]DBA,SYSDBA,SYSOPER三者的区别
    DML语言练习,数据增删改查,复制清空表
    Oracle数据库sys为什么只能以sysdba登录
    Oracle添加数据文件创建表空间,创建用户代码
    ORACLE建表练习
    全局唯一标识符(GUID)
    [转]Java总结篇系列:Java泛型
    Strategy模式
    Android 第三方应用广告拦截实现
  • 原文地址:https://www.cnblogs.com/dengyigod/p/3094007.html
Copyright © 2020-2023  润新知