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);
ITrack* GetTracks(String*,String*,String*,TrackFilter)[];
ITrack* GetTracks(String*,String*,String*,String*,TrackFilter)[];
ITrack* GetTracks(String*,String*,String*,String*,String*)[];
ITrack* GetTracks(String*,String*)[];
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()
{
}
protected:
void OnStart(String*[])
{
}
};
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.