📄 timectrl.py
字号:
# Get wxDateTime representations of bounds:
min = self.GetMin()
max = self.GetMax()
midnight = wx.DateTimeFromDMY(1, 0, 1970)
if min <= max: # they don't span midnight
ret = min <= value <= max
else:
# have to break into 2 tests; to be in bounds
# either "min" <= value (<= midnight of *next day*)
# or midnight <= value <= "max"
ret = min <= value or (midnight <= value <= max)
## dbg('in bounds?', ret, indent=0)
return ret
def IsValid( self, value ):
"""
Can be used to determine if a given value would be a legal and
in-bounds value for the control.
"""
try:
self.__validateValue(value)
return True
except ValueError:
return False
def SetFormat(self, format):
self.SetParameters(format=format)
def GetFormat(self):
if self.__displaySeconds:
if self.__fmt24hr: return '24HHMMSS'
else: return 'HHMMSS'
else:
if self.__fmt24hr: return '24HHMM'
else: return 'HHMM'
#-------------------------------------------------------------------------------------------------------------
# these are private functions and overrides:
def __OnTextChange(self, event=None):
## dbg('TimeCtrl::OnTextChange', indent=1)
# Allow Maskedtext base control to color as appropriate,
# and Skip the EVT_TEXT event (if appropriate.)
##! WS: For some inexplicable reason, every wxTextCtrl.SetValue()
## call is generating two (2) EVT_TEXT events. (!)
## The the only mechanism I can find to mask this problem is to
## keep track of last value seen, and declare a valid EVT_TEXT
## event iff the value has actually changed. The masked edit
## OnTextChange routine does this, and returns True on a valid event,
## False otherwise.
if not BaseMaskedTextCtrl._OnTextChange(self, event):
return
## dbg('firing TimeUpdatedEvent...')
evt = TimeUpdatedEvent(self.GetId(), self.GetValue())
evt.SetEventObject(self)
self.GetEventHandler().ProcessEvent(evt)
## dbg(indent=0)
def SetInsertionPoint(self, pos):
"""
This override records the specified position and associated cell before
calling base class' function. This is necessary to handle the optional
spin button, because the insertion point is lost when the focus shifts
to the spin button.
"""
## dbg('TimeCtrl::SetInsertionPoint', pos, indent=1)
BaseMaskedTextCtrl.SetInsertionPoint(self, pos) # (causes EVT_TEXT event to fire)
self.__posCurrent = self.GetInsertionPoint()
## dbg(indent=0)
def SetSelection(self, sel_start, sel_to):
## dbg('TimeCtrl::SetSelection', sel_start, sel_to, indent=1)
# Adjust selection range to legal extent if not already
if sel_start < 0:
sel_start = 0
if self.__posCurrent != sel_start: # force selection and insertion point to match
self.SetInsertionPoint(sel_start)
cell_start, cell_end = self._FindField(sel_start)._extent
if not cell_start <= sel_to <= cell_end:
sel_to = cell_end
self.__bSelection = sel_start != sel_to
BaseMaskedTextCtrl.SetSelection(self, sel_start, sel_to)
## dbg(indent=0)
def __OnSpin(self, key):
"""
This is the function that gets called in response to up/down arrow or
bound spin button events.
"""
self.__IncrementValue(key, self.__posCurrent) # changes the value
# Ensure adjusted control regains focus and has adjusted portion
# selected:
self.SetFocus()
start, end = self._FindField(self.__posCurrent)._extent
self.SetInsertionPoint(start)
self.SetSelection(start, end)
## dbg('current position:', self.__posCurrent)
def __OnSpinUp(self, event):
"""
Event handler for any bound spin button on EVT_SPIN_UP;
causes control to behave as if up arrow was pressed.
"""
## dbg('TimeCtrl::OnSpinUp', indent=1)
self.__OnSpin(wx.WXK_UP)
keep_processing = False
## dbg(indent=0)
return keep_processing
def __OnSpinDown(self, event):
"""
Event handler for any bound spin button on EVT_SPIN_DOWN;
causes control to behave as if down arrow was pressed.
"""
## dbg('TimeCtrl::OnSpinDown', indent=1)
self.__OnSpin(wx.WXK_DOWN)
keep_processing = False
## dbg(indent=0)
return keep_processing
def __OnChar(self, event):
"""
Handler to explicitly look for ':' keyevents, and if found,
clear the m_shiftDown field, so it will behave as forward tab.
It then calls the base control's _OnChar routine with the modified
event instance.
"""
## dbg('TimeCtrl::OnChar', indent=1)
keycode = event.GetKeyCode()
## dbg('keycode:', keycode)
if keycode == ord(':'):
## dbg('colon seen! removing shift attribute')
event.m_shiftDown = False
BaseMaskedTextCtrl._OnChar(self, event ) ## handle each keypress
## dbg(indent=0)
def __OnSetToNow(self, event):
"""
This is the key handler for '!' and 'c'; this allows the user to
quickly set the value of the control to the current time.
"""
self.SetValue(wx.DateTime_Now().FormatTime())
keep_processing = False
return keep_processing
def __LimitSelection(self, event):
"""
Event handler for motion events; this handler
changes limits the selection to the new cell boundaries.
"""
## dbg('TimeCtrl::LimitSelection', indent=1)
pos = self.GetInsertionPoint()
self.__posCurrent = pos
sel_start, sel_to = self.GetSelection()
selection = sel_start != sel_to
if selection:
# only allow selection to end of current cell:
start, end = self._FindField(sel_start)._extent
if sel_to < pos: sel_to = start
elif sel_to > pos: sel_to = end
## dbg('new pos =', self.__posCurrent, 'select to ', sel_to)
self.SetInsertionPoint(self.__posCurrent)
self.SetSelection(self.__posCurrent, sel_to)
if event: event.Skip()
## dbg(indent=0)
def __IncrementValue(self, key, pos):
## dbg('TimeCtrl::IncrementValue', key, pos, indent=1)
text = self.GetValue()
field = self._FindField(pos)
## dbg('field: ', field._index)
start, end = field._extent
slice = text[start:end]
if key == wx.WXK_UP: increment = 1
else: increment = -1
if slice in ('A', 'P'):
if slice == 'A': newslice = 'P'
elif slice == 'P': newslice = 'A'
newvalue = text[:start] + newslice + text[end:]
elif field._index == 0:
# adjusting this field is trickier, as its value can affect the
# am/pm setting. So, we use wxDateTime to generate a new value for us:
# (Use a fixed date not subject to DST variations:)
converter = wx.DateTimeFromDMY(1, 0, 1970)
## dbg('text: "%s"' % text)
converter.ParseTime(text.strip())
currenthour = converter.GetHour()
## dbg('current hour:', currenthour)
newhour = (currenthour + increment) % 24
## dbg('newhour:', newhour)
converter.SetHour(newhour)
## dbg('converter.GetHour():', converter.GetHour())
newvalue = converter # take advantage of auto-conversion for am/pm in .SetValue()
else: # minute or second field; handled the same way:
newslice = "%02d" % ((int(slice) + increment) % 60)
newvalue = text[:start] + newslice + text[end:]
try:
self.SetValue(newvalue)
except ValueError: # must not be in bounds:
if not wx.Validator_IsSilent():
wx.Bell()
## dbg(indent=0)
def _toGUI( self, wxdt ):
"""
This function takes a wxdt as an unambiguous representation of a time, and
converts it to a string appropriate for the format of the control.
"""
if self.__fmt24hr:
if self.__displaySeconds: strval = wxdt.Format('%H:%M:%S')
else: strval = wxdt.Format('%H:%M')
else:
if self.__displaySeconds: strval = wxdt.Format('%I:%M:%S %p')
else: strval = wxdt.Format('%I:%M %p')
return strval
def __validateValue( self, value ):
"""
This function converts the value to a wxDateTime if not already one,
does bounds checking and raises ValueError if argument is
not a valid value for the control as currently specified.
It is used by both the SetValue() and the IsValid() methods.
"""
## dbg('TimeCtrl::__validateValue(%s)' % repr(value), indent=1)
if not value:
## dbg(indent=0)
raise ValueError('%s not a valid time value' % repr(value))
valid = True # assume true
try:
value = self.GetWxDateTime(value) # regularize form; can generate ValueError if problem doing so
except:
## dbg('exception occurred', indent=0)
raise
if self.IsLimited() and not self.IsInBounds(value):
## dbg(indent=0)
raise ValueError (
'value %s is not within the bounds of the control' % str(value) )
## dbg(indent=0)
return value
#----------------------------------------------------------------------------
# Test jig for TimeCtrl:
if __name__ == '__main__':
import traceback
class TestPanel(wx.Panel):
def __init__(self, parent, id,
pos = wx.DefaultPosition, size = wx.DefaultSize,
fmt24hr = 0, test_mx = 0,
style = wx.TAB_TRAVERSAL ):
wx.Panel.__init__(self, parent, id, pos, size, style)
self.test_mx = test_mx
self.tc = TimeCtrl(self, 10, fmt24hr = fmt24hr)
sb = wx.SpinButton( self, 20, wx.DefaultPosition, (-1,20), 0 )
self.tc.BindSpinButton(sb)
sizer = wx.BoxSizer( wx.HORIZONTAL )
sizer.Add( self.tc, 0, wx.ALIGN_CENTRE|wx.LEFT|wx.TOP|wx.BOTTOM, 5 )
sizer.Add( sb, 0, wx.ALIGN_CENTRE|wx.RIGHT|wx.TOP|wx.BOTTOM, 5 )
self.SetAutoLayout( True )
self.SetSizer( sizer )
sizer.Fit( self )
sizer.SetSizeHints( self )
self.Bind(EVT_TIMEUPDATE, self.OnTimeChange, self.tc)
def OnTimeChange(self, event):
## dbg('OnTimeChange: value = ', event.GetValue())
wxdt = self.tc.GetWxDateTime()
## dbg('wxdt =', wxdt.GetHour(), wxdt.GetMinute(), wxdt.GetSecond())
if self.test_mx:
mxdt = self.tc.GetMxDateTime()
## dbg('mxdt =', mxdt.hour, mxdt.minute, mxdt.second)
class MyApp(wx.App):
def OnInit(self):
import sys
fmt24hr = '24' in sys.argv
test_mx = 'mx' in sys.argv
try:
frame = wx.Frame(None, -1, "TimeCtrl Test", (20,20), (100,100) )
panel = TestPanel(frame, -1, (-1,-1), fmt24hr=fmt24hr, test_mx = test_mx)
frame.Show(True)
except:
traceback.print_exc()
return False
return True
try:
app = MyApp(0)
app.MainLoop()
except:
traceback.print_exc()
__i=0
## CHANGELOG:
## ====================
## Version 1.3
## 1. Converted docstrings to reST format, added doc for ePyDoc.
## 2. Renamed helper functions, vars etc. not intended to be visible in public
## interface to code.
##
## Version 1.2
## 1. Changed parameter name display_seconds to displaySeconds, to follow
## other masked edit conventions.
## 2. Added format parameter, to remove need to use both fmt24hr and displaySeconds.
## 3. Changed inheritance to use BaseMaskedTextCtrl, to remove exposure of
## nonsensical parameter methods from the control, so it will work
## properly with Boa.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -