I had a problem, common to a lot of people, of reporting how I am spending my time during the day. I looked at many of the free time tracking programs out there and they didn't really fit my personal needs. So, being a programmer, I wrote a program that met my needs. I am posting it here in the hope that it can meet the needs of others as well.
Personal Time Tracker (
PTT) will track time spent on tasks and categorize them by customer. It show time in hours and minutes as well as hours and percentage of an hour. Where I work, we report time in the form of 1.5 for 1 and 1/2 hours.
Another reason (the main reason maybe?) I wrote
PTT was also to play around with some .NET features I had not used before. So, there are a lot of .NET tricks and techniques in the code provided.
The main features of this program are:
- Tracks billable and non-billable time
- Reporting using the .NET reporting Framework and a custom report dialog
- Undo support
DataGridView with some tweaks
- Systray application with dynamic menus
- Automatic saving at a user defined interval
Using the Application
This application does not have a main menu. It is controlled through context sensitive menus and some buttons. When minimized, you right click on the systray icon to access the menu. When the app is visible, you right click on the grid.
The first thing you will need to do is set up your customers and tasks. To set up customers click on the Customer List... button and set up tasks by clicking on the Task List... button. Click on the blank line in each of the dialogs to add a new entry.
Once you have customers and tasks, then you can start tracking your time against them. The easiest way to do that is to right click in the data grid and select a customer from the context menu and then select a task from the customer sub-menu.
You can temporarily stop tracking time against the current activity by checking the Pause check box. When you come back to work on that activity you can uncheck the pause check box and an interruption time will be added from when you paused the activity until when you unpaused it. Or, after pausing an activity you may decide to start a different activity. In that case an interruption is inserted into the paused activity and a new activity is started.
If you modify the start and end time of an activity log entry, then the other log entries will adjust to account for the new time entered. For example, if you change the stop time to be after the start time of the next log entry then the next log entry's start time will be adjusted to be just after to the edited log's end time.
The activity logs are stored in XML files with a different file for each month. This way you can archive off old data as desired. If you wish to view the time logs for that month again, you can simply place those files back in the directory with the application again.
About the Code
PTT is a WinForms app written using C# and the .NET 3.0 Framework, therefore it requires some form of Visual Studio 2008 if you want to build the solution.
I had a lot of information to display in the
DataGridView on each line and one of these cells needed to hold a description of the activity being performed. So, instead of having a really long cell, I wrote a class to help me manage a multi-line text cell. This class is called
DataGridViewMemoCell and is a subclass of
It automatically detects if the edit box would go outside the bounding rectangle for the grid control and adjust as required in the
The actual cell edit control is called
MemoBox and is a subclass of
DataGridViewTextBoxEditingControl. The look of the cell edit control is set in the
A Note About Shared Sub-Menus
It is important to understand when sharing a sub-menu between more than one parent menu that you can't do it....but you can imitate it.
PTT builds its customer and task sub-menu dynamically since this is data driven. This sub-menu is displayed in the main grid context menu and in the systray menu. I didn't want to have to do this for two different menus so I decided to simply share the customer sub-menu between the two menus. My first attempt was to create a place holder in one menu and then grab a reference to that menu on start up and add it to the other menu. That sort of worked. The problem was that when you add a menu to another menu, it removes itself from its previous parent. That means a menu can only be owned by one other menu. That made sense once I thought about it. That was not a show stopper since only one menu would ever be visible at a time. So, instead of sharing the sub-menu, I intercepted the
Opening event of the menu and added the customer sub-menu at that time if it was not already in the menu.
Having Esc Minimize the Application
I wanted to very easily hide
PTT in the systray. I decided that pressing the ESC key would be a good way to accomplish this. I have seen this in other applications so it is kind of a standard. This was a bit tricky though since I didn't have a main menu. I ended up making a button that on startup I had to move behind the grid to hide it. I then attached a shortcut key of ESC to that button. On the click handler for the button, I simply minimized the form. I tried making the button hidden but then the shortcut didn't work.
I wanted to have reports that showed a summary of activity. I decided that using the .NET printing hooks would be the easiest way to accomplish this. So I wrote a dialog to host a
PrintPreviewControl. I then wrote a custom subclass of
ReportPrintDocument which allowed me to set the type of report to generate and the options for the report. Someday I will make subclasses of the print document for each report type but I didn't get around to that yet.
I needed a stack to hold the undo information but I wanted to limit the number of elements that could be in that stack. So I wrote the
LimitedStack<T> class to handle that for me. I probably could have sub-classed
Stack<T> but there was so little that I needed from it that it wasn't really worth it and I didn't need most of what it offered for my purposes.
Detecting StandBy Mode
I had a problem of forgetting to click the Done with Task button before putting my laptop into StandBy mode when leaving for the day. So, I added code to detect when the computer is entering stand by mode and to clear the current task and save. This is accomplished by using the
Microsoft.Win32.SystemEvents.PowerModeChanged event. This behavior is optional and is controlled through a new check box. Here is the event handler for that event:
private void OnPowerModeChanged(object sender,
&& e.Mode == Microsoft.Win32.PowerModes.Suspend
&& null != m_currentActivity)
m_currentActivity.StopTime = DateTime.Now;
m_currentActivity = null;
m_isDirty = !LogFileManager.SaveDataFile();
17th April, 2008: 188.8.131.52 - First posted version
11th August 2008: 184.108.40.206
- Added Standby detection
- Fixed some problems of manually editing times
- Fixed a bug of trying to add log entries when no customers or tasks are set up