Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

N-Track : Work-Time tracking system

0.00/5 (No votes)
24 Jul 2002 1  
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*)&param);
    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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here