Active Application Watcher in .NET Using Windows Forms

Up to now, I have seen so many applications that will show system usage in terms of memory, processor...But, A user don't want this all details. He May expect to know how much time, he is spending on each application like browser, Winamp by the end of day...This application will help out a user to know how much time , he is spending on each application every day. This application assumes that the window, which is active as the application on which the user is working. So, it will log that application details like window title, process name and time spent on it in an xml file. It will continue like that until the application is closed. Once the application is closed, it will show entire active application's details in a browser with proper format.

 

I created this application using VS.NET 2003 in Windows Forms using c#. I will explain the design and coding of this application in the following steps:


Step 1:


Create a windows Application selecting c# language and name it as WinApplWacther.Next, place a timer, MainMenu, NotifyIcon, Datagrid from Toolbox present in Windows Forms Tab as shown below:

Image1.jpg 

 

Than set these properties to each control as shown below:

 

Timer:->  Enabled = true, Interval = 500.

NotifyIcon:-> Icon = path of icon to be shown in taskbar.

MainMenu1:-> create two menuitems with text (caption) as &Refresh, E&xit.

DataGrid1:-> Dock = Fill, PreferredColumnWidth = 160.

Form1 (Main Window):-> Text = Win Appl Watcher, ShowIntaskbar = false, WindowState = Minimized, Menu = MainMenu1.

 

After setting all this properties, we will start writing the code as shown below:

Step 2:

 

First add this namespaces in the code window:

using System.Diagnostics;

using System.Runtime.InteropServices;

using System.Xml;

using System.IO;

 

Than declare all the required variables:


public static string appName,prevvalue;

public static Stack applnames;

public static Hashtable applhash;

public static DateTime  applfocustime;

public static string appltitle;

public static Form1 form1;

public static string tempstr;

public TimeSpan applfocusinterval;

public DateTime logintime; 

 

Write this code in Main method:

 

applnames = new Stack();

applhash  = new Hashtable();

form1= new Form1();

Application.Run(form1);

 

This is used to initialize the things required and to run the form.

 

Than add this code to timer1 tick event:

//This is used to monitor and save active application's  details in Hashtable for future saving in xml file...

try

{

    bool isNewAppl = false;

    IntPtr hwnd =APIFuncs.getforegroundWindow();

    Int32 pid = APIFuncs.GetWindowProcessID(hwnd);

    Process p = Process.GetProcessById(pid);

    appName = p.ProcessName;

    appltitle = APIFuncs.ActiveApplTitle().Trim().Replace("\0","");

    if(!applnames.Contains(appltitle+"$$$!!!"+appName))

    {

        applnames.Push(appltitle+"$$$!!!"+appName);

        applhash.Add(appltitle+"$$$!!!"+appName,0);

        isNewAppl = true;

    }

    if(prevvalue!=(appltitle+"$$$!!!"+appName))

    {

        IDictionaryEnumerator en = applhash.GetEnumerator();

        applfocusinterval = DateTime.Now.Subtract(applfocustime);

        while (en.MoveNext())

        {

            if(en.Key.ToString() == prevvalue)

            {

                double prevseconds =Convert.ToDouble(en.Value);

                applhash.Remove(prevvalue);

                applhash.Add(prevvalue,(applfocusinterval.TotalSeconds+prevseconds));

                break;

            }

        }

        prevvalue= appltitle+"$$$!!!"+appName;

        applfocustime = DateTime.Now;

    }

    if(isNewAppl)

    applfocustime = DateTime.Now;

}

catch(Exception ex)

{

    MessageBox.Show(ex.Message+":"+ex.StackTrace);

}

          

The basic idea behind this application is, I am using a timer to continuously watch whether the active window is changed or not.If it is changed than it will update previous active window with updated time spent. I will explain in detail about this below:

Here, I am using a stack and hashtable to log active applications details. I am getting active application title and its process name by using user32.dll.Than, I am appending both title, its process name and checking whether that already exist in the stack. If it exists, we will just update the time spent on it with present time spent plus previous time spent value. If not we will create a new item in the stack and in hashtable with timespent as 0.In the hashtable, we are using key as windowtitle plus processname and value as timespent on it. If the active application is changed, than it will update hashtable with new time spent on it. So, the timer will check whether the active window is changed or not continuously.

 

Next, write this code in notifyIcon1_Click:

try

{

    if(form1.Visible == true)

    {

        form1.Visible = false;

        notifyIcon1.Text = "Application Watcher is in Invisible Mode";

    }

    else

    {

        form1.Visible = true;

        form1.Focus();

        form1.WindowState = FormWindowState.Normal;

        notifyIcon1.Text = "Application Watcher is in Visible Mode";

        BindGrid();

    }

}

catch{} 

                   

This code is used to show/hide main window.

 

Next, write this in form_load event:

form1.Visible = false;

notifyIcon1.Text = "Application Watcher is in Invisible Mode";

logintime = DateTime.Now;

form1.Text = "Login Time is at :"+DateTime.Now.ToLongTimeString();

if(!System.IO.File.Exists(@"c:\appldetails.xml"))

{

    System.IO.File.Create(@"c:\appldetails.xml").Close();

}

 

This is used to hide form on startup, to note down application start time (this will consider as login time because this will run on windows OS startup) and to create xml log file, if it is not exist.

 

Next, write this onclick of Refresh menuitem,

 

BindGrid();

 

To show latest xml file contents in the grid.

 

Next, write this onclick of Exit menuitem,

SaveandShowDetails();

TimeSpan timeinterval = DateTime.Now.Subtract(logintime);

System.Diagnostics.EventLog.WriteEntry("Application Watcher Total Time Details",timeinterval.Hours+" Hrs "+timeinterval.Minutes+" Mins",System.Diagnostics.EventLogEntryType.Information);

MessageBox.Show("Actual Time Spent :"+timeinterval.Hours+" Hrs "+timeinterval.Minutes+" Mins","Application Watcher Total Time Details");

 

StreamReader freader = new StreamReader(@"c:\appldetails.xml");

XmlTextReader xmlreader = new XmlTextReader(freader);

string tottime = "";

while(xmlreader.Read())

{

    if(xmlreader.NodeType== XmlNodeType.Element && xmlreader.Name=="TotalSeconds")

    {

        tottime += ";"+xmlreader.ReadInnerXml().ToString();

    }

}

string[] tottimes = tottime.Split(';');

long totsecs = 0;

foreach(string str in tottimes)

{

    if(str != string.Empty)

    {

        if(str.IndexOf("Seconds") != -1)

        {

            totsecs += Convert.ToInt64(str.Substring(0,str.Length-8)); 

        }

        else

        {

            totsecs += Convert.ToInt64(str.Substring(0,str.Length-8)) * 60;

        }

    }

}

MessageBox.Show((totsecs/60)+" Minutes");

showdetailsinIE();        

 

This will save the contents of hashtable in xml file and than it will show total time and computed time of all applications opened so far by you and finally it will show the xml contents in a browser with proper format.

 

Next copy this entire code of user-defined methods:

private void showdetailsinIE()

{

    //To create XSL file,if it is not existing....

    if(!File.Exists(@"c:\appl_xsl.xsl"))

    {

        File.Create(@"c:\appl_xsl.xsl").Close();

        string xslcontents ="<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?><xsl:stylesheet version=\"1.0\"    

        xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"><xsl:template match=\"/\"> <html> <body> 

        <h2>My Applications Details</h2>  <table border=\"1\"> <tr bgcolor=\"#9acd32\">  <th>Window

        Title</th>  <th>Process Name</th>  <th>Total Time</th> </tr> <xsl:for-each

        select=\"ApplDetails/Application_Info\"><xsl:sort select=\"ApplicationName\"/>  

        <tr>  <td><xsl:value-of select=\"ProcessName\"/></td>  <td><xsl:value-of

        select=\"ApplicationName\"/></td> 

        <td><xsl:value-of select=\"TotalSeconds\"/></td> </tr> </xsl:for-each>  </table> </body>

        </html></xsl:template></xsl:stylesheet>";

        StreamWriter xslwriter = new StreamWriter(@"c:\appl_xsl.xsl");

        xslwriter.Write(xslcontents);

        xslwriter.Flush();

        xslwriter.Close();

    }

    //TO show the contents of xml file in IE with a proper xsl....

    System.Diagnostics.Process ie = new Process();

    System.Diagnostics.ProcessStartInfo ieinfo = new ProcessStartInfo(@"C:\Program Files\Internet    

    Explorer\iexplore.exe",@"c:\appldetails.xml");

    ie.StartInfo = ieinfo;

    bool started = ie.Start();

    Application.Exit();

} 

  

This method is used to create a xsl file for showing xml log contents in browser and to open browser process for showing xml contents in it.

 

private void TestFocusedChanged()

{

    //This is used to handle hashtable,if its length is 1.It means number of active applications is only one....

    try

    {

        if(applhash.Count == 1)

        {

            IDictionaryEnumerator en = applhash.GetEnumerator();

            applfocusinterval = DateTime.Now.Subtract(applfocustime);

                             

            while (en.MoveNext())

            {

                if(en.Key.ToString() == appltitle+"$$$!!!"+appName)

                {

                    applhash.Remove(appltitle+"$$$!!!"+appName);

                    applhash.Add(appltitle+"$$$!!!"+appName,applfocusinterval.TotalSeconds);

                    break;

                }

            }

        }

    }
    
catch(Exception ex)

    {

        MessageBox.Show(ex.Message);

    }

}

 

This method is used to handle the situation where active application exists is only one.

 

private void BindGrid()

{

    //This is used to bind grid with update contents of xml file....

    SaveandShowDetails();

    DataSet ds = new DataSet();

    ds.ReadXml(@"c:\appldetails.xml");

    dataGrid1.DataSource = ds;

}

This method is used to show xml contents in the grid.

 

private void SaveandShowDetails()

{

    //This is used to save contents of hashtable in a xml file....

    try

    {

        TestFocusedChanged();

        System.IO.StreamWriter writer = new System.IO.StreamWriter(@"c:\appldetails.xml",false);

        IDictionaryEnumerator en = applhash.GetEnumerator();

        writer.Write("<?xml version=\"1.0\"?>");

        writer.WriteLine("");

        writer.Write("<?xml-stylesheet type=\"text/xsl\" href=\"appl_xsl.xsl\"?>");

        writer.WriteLine("");

        writer.Write("<ApplDetails>");

        while (en.MoveNext())

        {

            if(!en.Value.ToString().Trim().StartsWith("0"))

            {

                writer.Write("<Application_Info>");

                writer.Write("<ProcessName>");     

                string processname = "<![CDATA["+en.Key.ToString().Trim().Substring(0,en.Key.ToString().Trim

                ().LastIndexOf ("$$$!!!")).Trim()+"]]>";

                processname = processname.Replace("\0","");

                writer.Write(processname);

                writer.Write("</ProcessName>");                

                       

                writer.Write("<ApplicationName>");

                string applname = "<![CDATA["+en.Key.ToString().Trim().Substring(en.Key.ToString().Trim

                ().LastIndexOf("$$$!!!") + 6).Trim()+"]]>";

                       

                writer.Write(applname);

                writer.Write("</ApplicationName>");            

                       

                writer.Write("<TotalSeconds>");                

                if((Convert.ToDouble(en.Value)/60) < 1)

                {

                    writer.Write(Convert.ToInt32((Convert.ToDouble(en.Value)))+" Seconds");

                }

                else

                {

                    writer.Write(Convert.ToInt32((Convert.ToDouble(en.Value))/60)+" Minutes");

                }

                writer.Write("</TotalSeconds>");

                writer.Write("</Application_Info>");

            }

        }

        writer.Write("</ApplDetails>");

        writer.Flush();

        writer.Close();

    }

    catch(Exception ex)

    {

        MessageBox.Show(ex.Message);

    }

}

 

This method is used to save all the contents of hashtable in appldetails file. 
 

Finally, in order the access members of user32.dll for getting active window title and its process name, I created a separate file as APIFuncs.cs with following code shown below:

#region Windows API Functions Declarations

//This Function is used to get Active Window Title...

[System.Runtime.InteropServices.DllImport("user32.dll",CharSet=System.Runtime.InteropServices.CharSet.Auto)]

public static extern int GetWindowText(IntPtr hwnd,string lpString, int cch);

 

//This Function is used to get Handle for Active Window...

[System.Runtime.InteropServices.DllImport("user32.dll", CharSet=System.Runtime.InteropServices.CharSet.Auto)]

private static extern IntPtr GetForegroundWindow();

 

//This Function is used to get Active process ID...

[System.Runtime.InteropServices.DllImport("user32.dll", CharSet=System.Runtime.InteropServices.CharSet.Auto)]

private static extern Int32 GetWindowThreadProcessId(IntPtr hWnd,out Int32 lpdwProcessId);

#endregion

 

#region User-defined Functions

public static  Int32 GetWindowProcessID(IntPtr hwnd)

{

    //This Function is used to get Active process ID...

    Int32 pid;

    GetWindowThreadProcessId(hwnd, out pid);

    return pid;

}


public static IntPtr getforegroundWindow()

{

    //This method is used to get Handle for Active Window using GetForegroundWindow() method present in  

    user32.dll

    return GetForegroundWindow();

}

public static string ActiveApplTitle()

{

    //This method is used to get active application's title using GetWindowText() method present in user32.dll

    IntPtr hwnd =getforegroundWindow();

    if (hwnd.Equals(IntPtr.Zero)) return "";

    string lpText = new string((char) 0, 100);

    int intLength = GetWindowText(hwnd, lpText, lpText.Length);

    if ((intLength <= 0) || (intLength > lpText.Length)) return "unknown";

    return lpText.Trim();

}

           

#endregion

 

The Final output will be like this as shown below: 

 

Image2.jpg 

 

The xml file will be like this as shown below:

 

Image3.jpg 

 

I am attaching the code for reference.I hope this code will be useful for all...