N-Track : Work-Time tracking system
A work time tracking system that includes a Managed C++ remoting server and MFC clients that use the managed extensions. The application demonstrates how to mix managed and unmanaged code.
Overview
N-Track is a work-time tracking system developed for Microsoft Windows. It
came about by accident. I had been involved in the development of an ASP based
work-time tracking system for my company. Later on I had planned on converting
it to .NET. But sadly my company wanted a version that could run on a Linux box
and thus the web application was recoded using PHP. Anyway I had planned on
using a remoting server and I didn't want to drop the idea. That's when Chris
Maunder started this VC++.NET competition and I thought what the heck, specially
after James T Johnson also gave it the green signal. It's not much of an app
really, but it demonstrates how you can write a Windows service using Managed
C++ and how you can set it up as a remoting server. It also shows how you can
mix managed code with your MFC applications that can then act as remoting
clients.
Stability issues
This application is fairly raw and has not been tested intensively. It's
still in an advanced beta stage and there might be bugs, mostly due to a lack of
comprehensive error checks. But I look forward to getting your feedback and
comments so that I can fix the errors and improve on the application.
Goal
To prove that mixing managed and unmanaged code is a piece of cake with VC++
.NET. In this application we have a .NET remoting server coded entirely using
MC++ and MFC client programs that use managed extensions to talk to the remoting
server. I may not have done as well as I had hoped to, but I'll keep striving at
bettering this effort. And anyway it really was great fun doing all this weird
stuff, as in converting a String* to a LPCTSTR and
other similarly bizarre stuff.
Installation
Download the N-Track installer, N-TrackSetup.zip and extract it
to any folder. It contains just a single file called N-TrackSetup.msi
which is a Windows Installer package file. Double click on
N-TrackSetup.msi and follow on-screen instructions. You don't have much
to select except perhaps the destination folder where you would want the
software to be installed. You must be logged in as Administrator because the
remoting server is written as a Windows service and the installer will try to
install the service on your machine. Under the extremely remote contingency that
your system does not have the Windows installer pack installed on it, you will
not be able to run the N-TrackSetup.msi file. But you can search for the
file InstMsiW.Exe on Google or MSDN which will install Microsoft Windows
installer pack on your machine. It's a 1.8 Mb download and thus I have not
included it with the installation package for N-Track.
N-Track Client

Well, the N-Track client is the program that you distribute to your
employees. In the demo project that comes with the installation the client will
only run on the same machine as the server, because the client is set to
connect to localhost. I guess in the next release this will have to be changed
so that the client reads the remoting server host information from a
configuration file which is created during the installation. In fact the N-Track
client should have it's own installer package, instead of being bundled with the
N-Track Admin tool and the N-Track remoting server as it is currently. Anyway it
pops up a login dialog and you have to enter your username/password into the box
and if you enter it correctly you get the screen shown above.
By default the date control will show the current date. But you can change it
to any date you want to. You can have only 5 entries for any particular day,
which is a restriction I placed on the client for UI requirements. The remoting
server and the database have no such requirements. If you want to, you may write
your own client that allows more than 5 entries per day. The Project combobox
will list all projects and the Activity combobox will list all activities. In
the hours box you can fill in the hours you spent on any particular project.
Using the date control if you move back to another date, the comboboxes will
show the correct entries for that day and the hour boxes will show the entered
hours. You can then modify any of the entries if you feel like it. The changes
will not be saved unless you click on the Save button. You can to save for each
date you have entered entries for. If you want to delete an entry, simply set
the hours box to zero and the entry will get deleted. You can also change your
password if you want to.
N-Track Admin
The N-Track admin is the program that is used by the N-Track administrator.
It contains four sub-programs - User Manager, Project Manager, Activity Manager
and Report Manager. The first three are used to add, edit, delete users,
projects and activities. The report manager is used to generate tracking reports
of various forms. Each of these are discussed briefly below.
User Manager

The User manager is the sub-program that is used by the N-Track administrator
to manage the users who will be using the N-Track work-time tracking system.
Every user will be allotted a username and password as well as a full name for
identification and reporting purposes. The basic UI is a dialog with a list
control in report view that shows all users, passwords and full names. You can
add new users, delete existing users as well as modify information for existing
users. This tool is handy when an user has forgotten his password in which case
you can tell him or her what the password was.
Project Manager

This is very similar in UI to the User manager and performs the analogous
role with regard to projects. You can add new projects where each project has a
unique project code and a descriptive name which can be longer and which will
appear on the reports and also on the N-Track client. You can modify and delete
existing projects if the requirement arises.
Activity Manager

The Activity manager is also very much identical to the User manager and
Project manager. The work done is tracked using activities. Thus you need to
specify the regular and the not so regular activities that any of your users
might be involved in as part of his work. The activity code has to be unique.
The activity description is a descriptive phrasing of the activity and this will
be used in the reports generated by the Report Manager. It's suggested
that you have an activity called General that will act as a catch-all activity,
just as you might also want to add a project called General. Thus if someone
keeps tracking General-General as his project and activity you can be sure he's
sitting idle.
Report Manager

The Report manager also called as Report generator is used to create reports
based on the tracking data. You specify a start date and an end date for the
report. By default the end date is the current date and the start date is
exactly one week behind. This was done so, due to my strong belief that most
companies take weekly reports more often than they take any other kind of
report. You can select an user, project and activity. If you don't select one,
it will default to all users, all projects and all activities. You can change
the type of report by selecting one of the four options in the Order-by
combobox. It's not easy to describe how it works through words. But you can try
it out. Choose various combinations for the first three comboboxes and try out
each of the four options in the Order-by combobox. The Order-by setting is most
noticeable and effective when the other three comboboxes are set to All users,
All projects and All activities. I list below, screenshots of a few sample
reports. There are lots of different reports that are possible, so try them all
out.
The reports are popped up in a new window, that's sizeable and which contains
a web-browser control. Thus you actually get an HTML report. The report window
has a menu with just two options in the sub-menu. One to close the window and
the other to print the report. Thus you can take print outs of your reports. The
printer select dialog will pop up and you can change any printer settings there
if you feel the need to and then simply print. If you have a color printer you
might have to select the gray-scale mode because some of the text is in white
and because most printers ignore background colors you'll end up with a lot of
invisible text. In the next version I'll probably add options to customize the
colors of the HTML reports.
Sample reports

This is a comprehensive weekly report for all users, projects and activities
with the Order-by field set to Project.

This is a project wise report for all users who have worked on that project
with the Order-by field set to Username.

This is a specialized report for a particular project on a particular
activity with the Order-by field set to Username.

This is a person-wise report for a user for all the projects and activities
with the Order-by field set to Date.
Tech details
Because the application consists of multiple projects, it might appear to be
rather complicated at first glance but in reality it's a very simple
application. I'll just do a quick run through of various areas that might be
interesting. For anything else you can always refer the source code or you
can request additional information on any particular area through the message
board at the bottom of this article. Basically the entire application was
developed using Visual Studio .NET. Yeah, my company finally bought an MSDN
subscription. So to build the applications you'll probably need VS.NET. If you
only have the .NET SDK you can compile the server, but you won't be able to
build the MFC applications because even the Platform SDK does not include
MFC.
The application consists of the following associated elements :
- The remoting server which has been written as a Windows service and coded
entirely using Managed C++
- The service installer for the remoting server which is a straightforward
Win32 application with no MFC and no .NET
- The N-Track library which is a DLL that defines the various interfaces for
the remoting objects as well as certain enumerations. This DLL is required by
all programs that are part of N-Track. To build the projects from source code,
you'll need to copy the DLL to both the source and the debug/release directories
of those projects. This is also a total MC++ project.
- The N-Track admin program which consists of the User manager, Project
manager, Activity manager and Report generator. This is an MFC project compiled
with the /clr option.
- The N-Track client program which is used by the users to track data is also
an MFC project that is compiled with the /clr option.
- The database is a single standalone MS Access MDB file.
The TrackLib interfaces
The TrackLib interface DLL is used by all the component applications of the
N-Track work-time tracking system. When we remote complex objects, both the
remoting server and the remoting client need type information on the objects
that are remoted. Thus the use of interfaces is unavoidable so that both the
server and the client programs can use this DLL for getting type info on the
remoted objects. As a rule we only remote interface objects and not class
objects for the above mentioned reason. The five interfaces that are defined are
listed below.
IUser
The IUser interface wraps an user object. You can get and set
passwords and full names as well as delete the user object.
public __gc __interface IUser
{
String* GetUsername();
String* GetPassword();
String* GetFullname();
void SetPassword(String*);
void SetFullname(String*);
bool Delete();
};
IProject
Similar to IUser, IProject wraps a project
object.
public __gc __interface IProject
{
String* GetProjectcode();
String* GetProjectname();
void SetProjectname(String*);
bool Delete();
};
IActivity
Just as above, IActivity wraps an activity object.
public __gc __interface IActivity
{
String* GetActivitycode();
String* GetActivitydescription();
void SetActivitydescription(String*);
bool Delete();
};
ITrack
The ITrack interface wraps a tracking object which is basically
a tracking entry of a particular user for a particular project for a particular
activity on a particular date.
public __gc __interface ITrack
{
String* GetProjectcode();
String* GetActivitycode();
String* GetUsername();
String* GetDate();
int GetHours();
void SetHours(int);
void Delete();
};
IBasic - the remoted object
IBasic defines the interface implemented by the object that is
remoted. It contains several essential worker methods that give the clients
access to some of the other more specific objects required by the clients.
You'll notice that I am returning arrays of the other objects and might be
wondering why I didn't return a collection object. Well, I must say I considered
the idea once, specially when the arrays were giving me a lot of trouble, but
basically I do not need any of the extra convenience offered by collection
objects and put simply even an array is a sort of elementary collection. Horses
for courses, said the beggar-maid and so be it, I thought and thus I am
returning arrays. If in future the program gets modified enough to warrant
collection objects, then I might think about implementing them, but for now it's
arrays for us.
public __gc __interface IBasic
{
IProject* GetProjects()[];
IActivity* GetActivities()[];
IUser* GetUsers()[];
IUser* GetUser(String* username);
IActivity* GetActivity(String* activitycode);
IProject* GetProject(String* projectcode);
/*user, project, activity*/
ITrack* GetTracks(String*,String*,String*,TrackFilter)[];
/*user-activity, project-activity, user-project*/
ITrack* GetTracks(String*,String*,String*,String*,TrackFilter)[];
/*user-project-activity*/
ITrack* GetTracks(String*,String*,String*,String*,String*)[];
ITrack* GetTracks(String*,String*)[];//all
void DeleteTracks(String*,String*);
bool AddTrack(String*,String*,String*,String*,int);
bool AddActivity(String*, String*);
bool AddUser(String*,String*,String*);
bool AddProject(String*,String*);
};
The remoting server
The remoting server is implemented through a Windows service mainly because I
wanted the remoting service to be up even if the user has not logged on to
Windows. Writing services with .NET is very simple and painless. All you do is
to derive a class from ServiceProcess::ServiceBase and implement
your service code in the OnStart override. Our service basically
looks like this in skeletal form.
__gc class TrackService : public ServiceProcess::ServiceBase
{
public:
TrackService()
{
//set some basic stuff here
}
protected:
void OnStart(String*[])
{
//start remoting
}
};
And in the OnStart override we simply start our remoting server.
I have created a TcpChannel that will listen on port 9999. If you
have some other process that's already using this port, you'll have to change
this to something else. Or better still, uninstall that other rogue process.
Just kidding, heheh.
void TrackService::OnStart(String* s[])
{
m_TcpChan = new TcpChannel(9999);
ChannelServices::RegisterChannel(m_TcpChan);
RemotingConfiguration::RegisterWellKnownServiceType(
Type::GetType("CBasic"),
"NTrack",WellKnownObjectMode::SingleCall);
}
CBasic is a class that implements IBasic and thus
we actually return a CBasic object as the remote object. And since
the client is expecting a IBasic object it happily accepts what we
send it. I won't discuss the implementation details of the CBasic
class here. You can take a look at the source code if you are interested in how
I have implemented it. And if you have any suggestions on how I can improve the
code please feel welcome to submit your feedback using the forum for this
article.
Service installer
I have written a simple program that will install the Windows service. It
looks for the service executable in the same directory from where it was run and
uses my CServiceHelper class which simplifies service related work
for me. If you want to take a look at that class, it's here on the code
project.
CServiceHelper m_sh;
m_sh.SetServiceDisplayName("N-Track Service 1.0");
m_sh.SetServiceName("NTrackService");
m_sh.SetServicePath(spath);
m_sh.SetAutoStart(true);
if(m_sh.Create())
{
m_sh.Start();
}
The N-Track client project
This has been written as an MFC project that uses the managed extensions. The
basic interface is a dialog box. The main dialog class has an
IBasic* member object called m_RemoteObject.
gcroot<IBasic*> m_RemoteObject;
I initialize this in my OnInitDialog handler.
#undef GetObject
m_RemoteObject = static_cast
(Activator::GetObject(__typeof(IBasic),
S"tcp://localhost:9999/NTrack"));
Populating the project and activity comboboxes is a piece of cake. For
example here is show I populate the project combobox.
IProject* projarr[] = m_RemoteObject->GetProjects();
for (int i =0; i < projarr->Length; i++)
{
CString tmp = projarr[i]->GetProjectname();
CString tmpcod = projarr[i]->GetProjectcode();
m_projmap[tmp] = tmpcod;
for (int j =0 ; j <5; j++)
m_plist[j].InsertString(0,tmp);
}
if (projarr->Length > 0)
for (int j =0 ; j <5; j++)
m_plist[j].SetCurSel(0);
The N-Track admin project
Just like the client project, this is also an MFC application that uses the
managed extensions. The main dialog is just a sort of jumping point for the four
sub-programs. The four sub-programs are basically modal dialogs. The Report
Manager dialog box has a Generate-Report button that pops up another window to
show the report. I derived a class from CFrameWnd and created a
view derived from CHtmlView because I wanted to do my reports using
HTML. I have a class called CReportMaker which actually generates
the HTML reports. You might want to take a look at it's implementation. This is
how we bring up the report.
if(IsWindow(pframe->m_hWnd))
{
CReportMaker rm(startdate,enddate,username,projectcode,
activitycode,ordby,m_RemoteObject);
pframe->m_pReportView->m_html = rm.GetHtmlString();
pframe->ShowWindow(SW_SHOW);
pframe->UpdateWindow();
pframe->m_pReportView->RenderView();
}
RenderView() simply fills up the browser object in our view with
the HTML we just generated.
void CReportView::RenderView()
{
IHTMLDocument2 *pDoc=(IHTMLDocument2 *)GetHtmlDocument();
if(!pDoc)
{
return;
}
HRESULT hr;
SAFEARRAY* psa = SafeArrayCreateVector(VT_VARIANT, 0, 1);
VARIANT *param;
BSTR bsData = m_html.AllocSysString();
hr = SafeArrayAccessData(psa, (LPVOID*)¶m);
param->vt = VT_BSTR;
param->bstrVal = bsData;
hr = pDoc->write(psa);
hr = pDoc->close();
SysFreeString(bsData);
SafeArrayDestroy(psa);
}
The database
I chose MS Access because that's the only one I had access to. For realistic
purposes I presume you might want to use SQL Server or Oracle or some such heavy
duty database. I list below the very elementary table structure of the database.
Keep it simple Nish, they told me and I kept it simple though I didn't really
have any other choice not being a very knowledgeable guy with regard to
databases.
Table - Usr
| Field-Name |
Type |
Size |
| username |
Text |
20 |
| passwd |
Text |
20 |
| fullname |
Text |
100 |
Table - Project
| Field-Name |
Type |
Size |
| projectcode |
Text |
20 |
| projectname |
Text |
100 |
Table - Activity
| Field-Name |
Type |
Size |
| activitycode |
Text |
20 |
| activitydescription |
Text |
100 |
Table - Track
| Field-Name |
Type |
Size |
| username |
Text |
20 |
| projectcode |
Text |
20 |
| activitycode |
Text |
20 |
| tdate |
Date/Time |
8 |
| hours |
Integer |
2 |
Building & testing the sources
- First compile and build the TrackLib project. This is required for all other
projects.
- Now copy the TrackLib DLL to the source and debug/release directories of the
service project, the admin project and the client project.
- Compile and build the service project.
- Compile and build the service installer project.
- Now copy the service installer executable to the same directory as the
service executable and run it from there. This will install the service on your
machine, provided you are logged in as Administrator or a user with enough
privileges.
- Compile and build the Admin project.
- Compile and build the Client project.
- Unzip the MDB file into the same folder as the service executable.
- Now you may run both the Admin tool and the Client tool.
- If you want the client program to run from other machines, replace localhost
with the name of machine where the service is running. Now all other machines on
the network can use the client program to track their data.
- If you want to run the admin program from another machine, do the same as
above.
Conclusion
There must be innumerable ways in which this application can be improved and
there must be countless features that would be very useful additions. Currently
this application does not provide much in the way of security. So you should not
distribute the admin client among your staff. I am hoping to get some good
strong feedback and criticism so that I can improve this application. I would
also like to thank James T Johnson and Rama Krishna for some absolutely
wonderful help when I kept posting questions in the Managed C++ forums.