Personal Schedule Management Tool

Time is always ticking no matter you care it or you don’t care it, everyone knows it including myself, to remind myself don’t waste too much time on gaming, reading news or something else, I developed a little tool which I call it “Personal Schedule Management Tool” to achieve this simple goal.


I create an XML file to store a serial of [time stamp/task to do] pairs, the tool will start with Windows and load the XML file, a working timer behind it will periodically check whether there is a task needs to be done at defined time stamp, once the condition is matched, the tool will

  1. Shows Windows Balloon Tips with the task as content, screenshot below.
  2. Invokes managed Microsoft Speech API: SpeechSynthesizer.Speak() to remind me even if I am not in front of my dev-box at that time.

Here is my XML to store schedule items:

<?xml version="1.0" encoding="utf-8" ?>
  <ScheduleItem TriggerHour="18" TriggerMinute="20" Content="Wayne, time to have your dinner." />
  <ScheduleItem TriggerHour="19" TriggerMinute="0" Content="Wayne! It is time to learn technologies and programming skills, i.e. CODING TIME!" />
  <ScheduleItem TriggerHour="20" TriggerMinute="30" Content="OK, your eye and brain need rest, time to do some body execise - running, sit-ups, push-up, etc.  Enjoy:)" />
  <ScheduleItem TriggerHour="21" TriggerMinute="0" Content="Well, sweat off your face and have a bath:)." />
  <ScheduleItem TriggerHour="21" TriggerMinute="30" Content="All right, well come back, you know you should read some books!" />
  <ScheduleItem TriggerHour="23" TriggerMinute="0" Content="Wayne, thanks for the hard work! Time to sleep, have a good night!" />

UI Implementation

The tool is a WinForm developed in C#, it has no Windows and starts in system tray, to achieve this I set MainWindow’s FormBorderStyle = None, ShowIcon = False and ShowInTaskBar = False, see below.


One more thing is drag two controls onto the form: System.Windows.Forms.NotifyIcon, System.Windows.Forms.ContextMenuStrip and  and naming the instances as “systrayIcon” and “systrayMenu”.


After that I need add menu items, code snippet below:

this.editScheduleItemsToolStripMenuItem.Name = "editScheduleItemsToolStripMenuItem";
this.editScheduleItemsToolStripMenuItem.Text = "Edit Schedule Items";
this.editScheduleItemsToolStripMenuItem.Click += new System.EventHandler(this.editScheduleItemsToolStripMenuItem_Click);

All right, its appearance is like below:


Functionality implementation


            // Initializes timer
            scheduleTimer = new System.Timers.Timer()
                                Interval = 1000d * 60 * 10, // 10 minutes
                                Enabled = true
            this.scheduleTimer.Elapsed += new System.Timers.ElapsedEventHandler(scheduleTimer_Elapsed);

        void scheduleTimer_Elapsed(object sender, ElapsedEventArgs e)
            DateTime currentTime = e.SignalTime;

                foreach (ScheduleItem scheduleItem in scheduleItems)
                    if (currentTime.Hour == scheduleItem.TriggerHour
                    && currentTime.Minute == scheduleItem.TriggerMinute)
                        LogManager.WriteAsync(String.Format("{0}, notification occurred: {1}.{2}",
                            currentTime.ToLocalTime(), scheduleItem.Content, Environment.NewLine));

                        // Trigger bubble/voice notification
                        this.systrayIcon.ShowBalloonTip(8000, Constants.BalloonTitle, scheduleItem.Content, ToolTipIcon.Info);

                        break; // Avoid redundant check
            catch (Exception ex)

            LogManager.WriteAsync(String.Format("Schedule check at: {0}{1}", currentTime.ToLocalTime(), Environment.NewLine));

Load schedule items from XML using LINQ:

    public static class ScheduleItemsReader
        private static readonly String ScheduleItemsXmlLocation = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ScheduleItems.xml");

        public static IEnumerable<ScheduleItem> Load()
            IList<ScheduleItem> scheduleItems = new List<ScheduleItem>();

            IEnumerable<XElement> scheduleItemsCollection = from scheduleItem in XElement.Load(ScheduleItemsXmlLocation).Descendants("ScheduleItem")
                                                            select scheduleItem;

            foreach (XElement ele in scheduleItemsCollection)
                ScheduleItem scheduleItem = new ScheduleItem();
                scheduleItem.TriggerHour = ushort.Parse(ele.Attribute("TriggerHour").Value);
                scheduleItem.TriggerMinute = ushort.Parse(ele.Attribute("TriggerMinute").Value);
                scheduleItem.Content = ele.Attribute("Content").Value;


            return scheduleItems;

Invoke Speech API:

    public static class SoundNotifier
        public static void Phonate(String sentence)
            using (SpeechSynthesizer speaker = new SpeechSynthesizer())  // Dispose the its instance as soon as it spoke fnished
                ReadOnlyCollection<InstalledVoice> installedVoices = speaker.GetInstalledVoices();

                CultureInfo cultureInfo = CultureInfo.GetCultureInfo("en-US");

                PromptBuilder pb = new PromptBuilder();
                pb.StartVoice(VoiceGender.Female, VoiceAge.Adult);
                pb.StartStyle(new PromptStyle(PromptVolume.Loud));

                pb.AppendText(sentence, PromptEmphasis.Strong);




        public static void WriteAsync(String content)
            FileStream fileStream = null;

            LogFilePath = String.Format(LogFilePath, DateTime.Now.ToString("MM-dd-yyyy"));
            if (File.Exists(LogFilePath))
                fileStream = File.OpenWrite(LogFilePath);
                fileStream = File.Create(LogFilePath);
            FileInfo logFile = new FileInfo(LogFilePath);

            StringBuilder logBuilder = new StringBuilder();

            byte[] data = Encoding.UTF8.GetBytes(logBuilder.ToString());
            fileStream.Position = logFile.Length; // Move to the end of the existing text file

            fileStream.BeginWrite(data, 0, data.Length, new AsyncCallback(r =>
                AsyncState asyncState = r.AsyncState as AsyncState;

            }), new AsyncState(fileStream, data, m_ManualResetEvent));

            m_ManualResetEvent.WaitOne(100, false);

Source download: