Custom calendar :: refresh problem
Patrick J. Anderson
pat.j.anderson at gmail.com
Sat Nov 3 14:43:15 PDT 2007
I'm trying to build a custom planner and being new to wxPython and
desktop programming in general, I'm still learning how to do things.
Below is a little test I wrote. Its a frame with a splitter window. On
the left there is a calendar control which I use to change dates and on
the right, I have a notebook with 3 pages: 'Day', 'Week', 'Month', which
correspond to the date selected in the calendar on the left.
I have created the daily, weekly and monthly views of the planner with a
wx.FlexGridSizer and mostly wx.Panels.
I decided to recreate the properties and layout of each notebook page.
I'm not sure if this is the right approach to building such widgets, as
for example the CalendarCtrl is drawn on the screen and not combined from
individual panels, but this is just a prototype.
However, as you may notice if you run it, I notice that the notebook
pages flicker and quite often I see a small panel in the top left corner,
which I believed shows when I loop through the days to create the display.
Any way this could be avoided and the transition made smoother?
Here's the full code:
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
from calendar import Calendar, day_name, day_abbr
from datetime import datetime, date
import wx
import wx.calendar as cal
from wx.lib.scrolledpanel import ScrolledPanel
FRAME_SIZE = (960,700)
PLANNER_COLOURS = {
'bg_header_normal': '#330',
'fg_header_normal': '#cc9',
'bg_header_today': '#000',
'fg_header_today': '#ffc',
'bg_hour_nonworking': '#cc9',
'fg_hour_nonworking': '#996',
'bg_hour_working': '#eeb',
'fg_hour_working': '#330',
'bg_day_normal': '#cc9',
'fg_day_normal': '#996',
'bg_day_first': '#dfdfaf',
'fg_day_first': '#3f3f0f',
'bg_day_today': '#ffc',
'fg_day_today': '#330',
'bg_day_busy': '#fff',
'fg_day_busy': '#000',
}
class MainFrame(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
planner = PlannerSplitterWindow(self, style = wx.SP_3D)
hbox = wx.BoxSizer(wx.HORIZONTAL)
hbox.Add(planner, 1, wx.EXPAND)
self.statusbar = self.CreateStatusBar()
self.SetAutoLayout(True)
self.SetSizer(hbox)
class PlannerSplitterWindow(wx.SplitterWindow):
proportion = 0.75
def __init__(self, *args, **kwds):
wx.SplitterWindow.__init__(self, *args, **kwds)
hbox = wx.BoxSizer(wx.HORIZONTAL)
self.left_pane = CalendarCtrlPanel(self, wx.NewId(), name =
'PlannerCalendarCtrl')
self.right_pane = PlannerBookPanel(self, wx.NewId(), name =
'PlannerBook')
self.SplitVertically(self.left_pane, self.right_pane)
self.SetMinimumPaneSize(300)
hbox.Add(self, 1, wx.ALL|wx.EXPAND, 0)
self.SetAutoLayout(True)
self.SetSizer(hbox)
if not 0 < self.proportion < 1:
raise ValueError, "proportion value for ProportionalSplitter
must be between 0 and 1."
self.ResetSash()
self.Bind(wx.EVT_SIZE, self.OnReSize)
self.Bind(wx.EVT_SPLITTER_SASH_POS_CHANGED, self.OnSashChanged,
id = self.GetId())
##hack to set sizes on first paint event
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.firstpaint = True
def SplitHorizontally(self, win1, win2):
if self.GetParent() is None:
return False
return wx.SplitterWindow.SplitHorizontally(self, win1, win2,
int(round(self.GetParent().GetSize().GetHeight() *
self.proportion)))
def SplitVertically(self, win1, win2):
if self.GetParent() is None:
return False
return wx.SplitterWindow.SplitVertically(self, win1, win2,
int(round(self.GetParent().GetSize().GetWidth() *
self.proportion)))
def GetExpectedSashPosition(self):
if self.GetSplitMode() == wx.SPLIT_HORIZONTAL:
total = max(self.GetMinimumPaneSize(),self.GetParent
().GetClientSize().height)
else:
total = max(self.GetMinimumPaneSize(),self.GetParent
().GetClientSize().width)
return int(round(total * self.proportion))
def ResetSash(self):
self.SetSashPosition(self.GetExpectedSashPosition())
def OnReSize(self, event):
"""Window has been resized, so we need to adjust the sash based
on self.proportion."""
self.ResetSash()
event.Skip()
def OnSashChanged(self, event):
"""We'll change self.proportion now based on where user dragged
the sash."""
position = float(self.GetSashPosition())
if self.GetSplitMode() == wx.SPLIT_HORIZONTAL:
total = max(self.GetMinimumPaneSize(),self.GetParent
().GetClientSize().height)
else:
total = max(self.GetMinimumPaneSize(),self.GetParent
().GetClientSize().width)
self.proportion = position / total
event.Skip()
def OnPaint(self,event):
if self.firstpaint:
if self.GetSashPosition() != self.GetExpectedSashPosition():
self.ResetSash()
self.firstpaint = False
event.Skip()
class CalendarCtrlPanel(wx.Panel):
def __init__(self, *args, **kwargs):
wx.Panel.__init__(self, *args, **kwargs)
self.SetAutoLayout(True)
self.layout = self._doLayout()
def _doLayout(self):
vbox = wx.BoxSizer(wx.VERTICAL)
hbox = wx.BoxSizer()
self.calendar = MiniCalendar(self, wx.NewId(), wx.DateTime_Now())
self.Bind(cal.EVT_CALENDAR_SEL_CHANGED,
self.calendar.OnCalSelChanged, self.calendar)
vbox.Add(hbox, 0, wx.ALL | wx.GROW, 1)
vbox.Add(self.calendar, 0, wx.ALL | wx.GROW, 1)
self.SetSizer(vbox)
class MiniCalendar(cal.CalendarCtrl):
def __init__(self, *args, **kwargs):
cal.CalendarCtrl.__init__(self, *args, **kwargs)
def OnCalSelChanged(self, event):
cal = event.GetEventObject()
dp = wx.FindWindowByName('DayPlanner')
wp = wx.FindWindowByName('WeekPlanner')
mp = wx.FindWindowByName('MonthPlanner')
dp.onDateChanged(cal.GetDate())
wp.onDateChanged(cal.GetDate())
mp.onDateChanged(cal.GetDate())
class PlannerBookPanel(wx.Panel):
def __init__(self, *args, **kwargs):
wx.Panel.__init__(self, *args, **kwargs)
self.today = datetime.today()
self.date = self.today
self.nb = wx.Notebook(self, wx.NewId(),
style = wx.NB_BOTTOM |
wx.TAB_TRAVERSAL |
wx.SUNKEN_BORDER)
self.createPanels()
self.addPanels()
self.doLayout()
def createPanels(self):
self.pane_day = DayPlannerPanel(self.nb, wx.NewId(),
name = 'DayPlanner', style = wx.EXPAND)
self.pane_week = WeekPlannerPanel(self.nb, wx.NewId(),
name = 'WeekPlanner', style = wx.EXPAND)
self.pane_month = MonthPlannerPanel(self.nb, wx.NewId(),
name = 'MonthPlanner', style = wx.EXPAND)
def addPanels(self):
self.nb.AddPage(self.pane_day, 'Day')
self.nb.AddPage(self.pane_week, 'Week')
self.nb.AddPage(self.pane_month, 'Month')
def doLayout(self):
self.hbox = wx.BoxSizer(wx.HORIZONTAL)
self.hbox.Add(self.nb, 1, wx.ALL|wx.EXPAND, 1)
self.SetAutoLayout(True)
self.SetSizer(self.hbox)
class DayPlannerPanel(ScrolledPanel):
def __init__(self, *args, **kwargs):
ScrolledPanel.__init__(self, *args, **kwargs)
self.properties = self.setProperties()
self.layout = self.doLayout()
def onDateChanged(self, d):
self.properties = self.setProperties(d)
self.layout = self.doLayout()
#self.Refresh(True)
print 'Day planner date has changed to %s' % self.date
def setProperties(self, d = wx.DateTime_Now()):
self.date = d
self.SetBackgroundColour(PLANNER_COLOURS['bg_header_normal'])
self.work_hours = []
[self.work_hours.append(i) for i in range(9, 22)]
def doLayout(self):
self.fbox = wx.FlexGridSizer(0, 2, 1, 1)
self.fbox.AddGrowableCol(1)
for i in range(1,24):
self.fbox.AddGrowableRow(i)
self.headers = self._createHeaders()
for h in range(1, 12):
self._createHourRow(h)
for h in range(12, 24):
self._createHourRow(h)
self.SetAutoLayout(True)
self.SetSizer(self.fbox)
self.SetupScrolling()
def _createHeaders(self):
hp1 = wx.Panel(self, wx.NewId())
hp1.SetBackgroundColour("%s" % PLANNER_COLOURS
['bg_header_normal'])
hbox1 = wx.BoxSizer()
hour_label = wx.StaticText(hp1, wx.NewId(), '',
wx.DefaultPosition, [50, -1],)
hbox1.Add(hour_label, 1, wx.ALL|wx.GROW, 5)
hp1.SetSizer(hbox1)
self.fbox.Add(hp1, 1, wx.GROW)
hp2 = wx.Panel(self, wx.NewId())
hp2.SetBackgroundColour("%s" % PLANNER_COLOURS
['bg_header_normal'])
hbox2 = wx.BoxSizer()
self.date_header = wx.StaticText(hp2, wx.NewId(), str(self.date))
self.date_header.SetForegroundColour('%s' % PLANNER_COLOURS
['fg_header_normal'])
hbox2.Add(self.date_header, 1, wx.ALL|wx.ALIGN_CENTER|wx.GROW, 5)
hp2.SetSizer(hbox2)
self.fbox.Add(hp2, 1, wx.GROW)
def _createHourRow(self, hour):
if hour <= 11:
x = 'am'
else:
x = 'pm'
l_panel = wx.Panel(self, wx.NewId())
l_box = wx.BoxSizer(wx.VERTICAL)
h_label = wx.StaticText(l_panel, wx.NewId(), '%i %s' % (hour, x),
style = wx.ALIGN_RIGHT)
if hour not in self.work_hours:
l_panel.SetBackgroundColour('%s' % PLANNER_COLOURS
['bg_hour_nonworking'])
h_label.SetFont(wx.Font(10, wx.SWISS, wx.ITALIC, wx.LIGHT))
h_label.SetForegroundColour('%s' % PLANNER_COLOURS
['fg_hour_nonworking'])
else:
l_panel.SetBackgroundColour('%s' % PLANNER_COLOURS
['bg_hour_working'])
h_label.SetFont(wx.Font(10, wx.SWISS, wx.ITALIC, wx.NORMAL))
h_label.SetForegroundColour('%s' % PLANNER_COLOURS
['fg_hour_working'])
l_box.Add(h_label, 1, wx.ALL | wx.EXPAND, 5)
l_panel.SetSizer(l_box)
self.fbox.Add(l_panel, 1, wx.ALL | wx.EXPAND, 0)
h_panel = HourPanel(self, wx.NewId())
if not hour in self.work_hours:
h_panel.SetBackgroundColour('%s' % PLANNER_COLOURS
['bg_hour_nonworking'])
else:
h_panel.SetBackgroundColour('%s' % PLANNER_COLOURS
['bg_hour_working'])
h_box = wx.BoxSizer(wx.VERTICAL)
h_panel.SetSizer(h_box)
self.fbox.Add(h_panel, 1, wx.ALL | wx.EXPAND, 0)
class HourPanel(wx.Panel):
def __init__(self, *args, **kwargs):
wx.Panel.__init__(self, *args, **kwargs)
class WeekPlannerPanel(ScrolledPanel):
def __init__(self, *args, **kwargs):
ScrolledPanel.__init__(self, *args, **kwargs)
self.setProperties()
self.doLayout()
def onDateChanged(self, d):
self.Freeze()
self.setProperties(d)
self.removeHeaders()
self.removeWeekDays()
self.doLayout()
#self.Refresh()
self.Thaw()
print 'Week Planner date has changed to %s' % self.date
def setProperties(self, d = wx.DateTime_Now()):
self.today = datetime.today()
self.date = d
py_date = cal._wxdate2pydate(self.date)
calendar = Calendar(0)
self.weeks = calendar.monthdatescalendar(py_date.year,
py_date.month)
self.num_weeks = len(self.weeks)
for w in self.weeks:
if py_date in w:
self.week = w
self.SetBackgroundColour(PLANNER_COLOURS['bg_header_normal'])
def doLayout(self):
self.fbox = wx.FlexGridSizer(2, 7, 1, 1)
# expand weekday columns and row
for c in range(7):
self.fbox.AddGrowableCol(c)
self.fbox.AddGrowableRow(1)
self.addHeaders()
self.addWeekDays()
self.SetAutoLayout(True)
self.SetSizer(self.fbox)
#self.SetupScrolling()
def addHeaders(self):
self.headers = []
i = 0
for d in self.week:
self.headers.append(wx.Button(self, wx.NewId(), d.strftime('%
a %d %b')))
if (d.day == self.today.day
and d.month == self.today.month
and d.year == self.today.year):
self.headers[i].SetBackgroundColour(PLANNER_COLOURS
['bg_header_today'])
self.headers[i].SetForegroundColour(PLANNER_COLOURS
['fg_header_today'])
else:
self.headers[i].SetBackgroundColour(PLANNER_COLOURS
['bg_header_normal'])
self.headers[i].SetForegroundColour(PLANNER_COLOURS
['fg_header_normal'])
self.fbox.Add(self.headers[i], 1, wx.ALL|wx.EXPAND, 0)
i += 1
def addWeekDays(self):
self.weekdays = []
i = 0
for d in self.week:
self.weekdays.append(WeekDayPanel(self, wx.NewId()))
if (d.day == self.today.day
and d.month == self.today.month
and d.year == self.today.year):
self.weekdays[i].SetBackgroundColour(PLANNER_COLOURS
['bg_day_today'])
else:
self.weekdays[i].SetBackgroundColour(PLANNER_COLOURS
['bg_day_normal'])
self.fbox.Add(self.weekdays[i], 1, wx.ALL|wx.EXPAND,0)
i += 1
def removeHeaders(self):
[self.fbox.Remove(i) for i in self.headers]
def removeWeekDays(self):
[self.fbox.Remove(i) for i in self.weekdays]
class WeekDayPanel(wx.Panel):
def __init__(self, *args, **kwargs):
wx.Panel.__init__(self, *args, **kwargs)
class MonthPlannerPanel(ScrolledPanel):
def __init__(self, *args, **kwargs):
ScrolledPanel.__init__(self, *args, **kwargs)
self.properties = self.setProperties()
self.layout = self.doLayout()
#self.Bind(wx.EVT_SIZE, self.OnSize, self)
#self.Bind(wx.EVT_PAINT, self.OnPaint, self)
def onDateChanged(self, d):
self.properties = self.setProperties(d)
self.layout = self.doLayout()
self.Refresh()
print 'Month planner date has changed to %s\n---------' %
self.date
def setProperties(self, d = wx.DateTime_Now()):
self.date = d
py_date = cal._wxdate2pydate(self.date)
calendar = Calendar(0)
self.month_dates = list(calendar.itermonthdates(py_date.year,
py_date.month))
self.no_days = len(self.month_dates)
self.no_rows = self.no_days / 7
self.SetBackgroundColour(PLANNER_COLOURS['bg_header_normal'])
def doLayout(self):
self.fbox = wx.FlexGridSizer(0, 7, 1, 1)
# expand all cols and date rows
for c in range(7):
self.fbox.AddGrowableCol(c)
for r in range(1, self.no_rows + 1):
self.fbox.AddGrowableRow(r)
self.headers = self._createHeaders()
self.days = self._createMonthDays()
self.SetAutoLayout(True)
self.SetSizer(self.fbox)
self.SetupScrolling()
def _createHeaders(self):
# add headers to the calendar
for h in day_abbr:
hp = wx.Panel(self, wx.NewId())
hp.SetBackgroundColour(PLANNER_COLOURS['bg_header_normal'])
text = wx.StaticText(hp, wx.NewId(), str(h))
text.SetForegroundColour(PLANNER_COLOURS['fg_header_normal'])
hbox = wx.BoxSizer(wx.HORIZONTAL)
hbox.Add(text, 1, wx.ALL, 5)
hp.SetSizer(hbox)
self.fbox.Add(hp, 1, wx.GROW)
wx.Yield()
def _createMonthDays(self):
# add calendar day panels
for d in self.month_dates:
p = MonthDayPanel(self, wx.NewId(), style = wx.TAB_TRAVERSAL)
p.create(d)
self.fbox.Add(p, 1, wx.ALL|wx.GROW, 0)
class MonthDayPanel(wx.Panel):
def __init__(self, *args, **kwargs):
wx.Panel.__init__(self, *args, **kwargs)
self.today = date.today()
def create(self, date):
vbox = wx.BoxSizer(wx.VERTICAL)
if (date.day == self.today.day
and date.month == self.today.month
and date.year == self.today.year):
self.SetBackgroundColour(PLANNER_COLOURS['bg_day_today'])
d = wx.StaticText(self, wx.NewId(), str("%s %s" %
(date.strftime('%b'), date.day)))
d.SetFont(wx.Font(8, wx.SYS_SYSTEM_FONT, wx.NORMAL, wx.BOLD))
d.SetForegroundColour(PLANNER_COLOURS['fg_day_today'])
elif date.day == 1:
self.SetBackgroundColour(PLANNER_COLOURS['bg_day_first'])
d = wx.StaticText(self, wx.NewId(), str("%s %s" %
(date.strftime('%b'), date.day)))
d.SetFont(wx.Font(8, wx.SYS_SYSTEM_FONT, wx.NORMAL, wx.BOLD))
d.SetForegroundColour(PLANNER_COLOURS['fg_day_first'])
else:
self.SetBackgroundColour(PLANNER_COLOURS['bg_day_normal'])
d = wx.StaticText(self, wx.NewId(), str(date.day))
d.SetFont(wx.Font(8, wx.SYS_SYSTEM_FONT, wx.NORMAL,
wx.NORMAL))
d.SetForegroundColour(PLANNER_COLOURS['fg_day_normal'])
vbox.Add(d, 0, wx.ALL, 3)
self.SetSizer(vbox)
class Application(wx.App):
def __init__(self, *args, **kwargs):
wx.App.__init__(self, *args, **kwargs)
def OnInit(self):
self.frame = MainFrame(None, wx.NewId(), 'Calendar Test', size =
FRAME_SIZE)
self.SetTopWindow(self.frame)
self.frame.Show()
return True
def getFrame(self):
return self.frame
if __name__ == "__main__":
app = Application()
app.MainLoop()
More information about the wxpython-users
mailing list