Entries:
Comments:
Posts:

Loading User Information from Channel 9

Something went wrong getting user information from Channel 9

Latest Achievement:

Loading User Information from MSDN

Something went wrong getting user information from MSDN

Visual Studio Achievements

Latest Achievement:

Loading Visual Studio Achievements

Something went wrong getting the Visual Studio Achievements

Activity Monitor

image Have you ever wondered how much you work in a day? You might keep track of when you start and stop, but do you really keep track of every interruption? In this article, learn about how to keep track of user activity and see how to build a component to add to the Visual Studio toolbox.
Arian's Blog

Difficulty: Intermediate
Time Required: 1-3 hours
Cost: None
Software: Visual Basic or Visual C# Express Editions
Hardware: None
Download:

Introduction

I've built time-tracking applications before, but I'm not that great at using them. It seems that I just can't remember to pause and resume the timer consistently enough. If I keep it logged in while I run to the store, it's not all that accurate!

This article talks about the Win32 API call that you can use to get a handle on user activity. To be more useful, I decided to also add the necessary glue to expose the features as a component to add to your applications from the Visual Studio toolbox, much as a Timer or StatusBar control is used. This makes it easy to wire up the properties and events with less coding later.

To run this sample, you will need to have Visual Studio 2005 Express Edition installed (either Visual C# or Visual Basic version). An archive containing the full source code and a pre-compiled EXE is linked from the top of the article. Feel free to use this as it is or to expand it as you see fit.

Is Anyone There?

So the big question is: how do you know if the user is active? At some level, Windows must know, since the screensaver is based on idle time, but how does it know? If you think about it, user activity is really only measurable by user input. If the mouse is being clicked or the keyboard pressed, the user is clearly active. How do we know if the mouse is being clicked or the keyboard pressed? Why Windows messages, of course!

You've probably hooked into form events to detect if it's closing, moved, or if a drag-and-drop operation is occurring. You can also hook into events to see mouse movements and key presses. Unfortunately, this is limited to events within your form and its controls. Once the mouse moves out of the form, it's no longer visible. As it turns out, you can hook into events at the system level as well, though you'll see a lot more coming through. This isn't as straight-forward from managed code. It is possible though. On the other hand, if filtering system messages isn't to your liking, there's always the GetLastInputInfo method!

Visual Basic

<DllImport("User32.dll")> _
Private Shared Function GetLastInputInfo(ByRef plii As LASTINPUTINFO) As Boolean
End Function

Visual C#

[DllImport("User32.dll")]
private static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);

This wonderfully simple function populates a structure with the timestamp of the last time a mouse or keyboard message occurred at the system level. With this in hand, you just need to decide how to determine when the user is truly idle. For example, if the last user input timestamp was 2 seconds ago, is the user idle? Maybe they are reading a dialog and are about to click. Has it been a minute? Maybe they are reading a PDF that they just opened. The definition of idle depends on what the scenario is. Keeping track of the user's active/idle state is more than just calling GetLastInputInfo.

To be more comprehensive, I also added a timer to the class to determine how much time the user has spent in the Active state, when the timer was started (or reset), a feature to enable/disable/reset the timer, and properties to determine how much time to consider until a user is inactive. It also raises events when the user switches between active and idle states. Bundling it into a component simplifies the main code and makes it easier to add the relevant features.

Visual Basic

Private Sub GetLastInput(ByVal userState As Object)
    GetLastInputInfo(Me.lastInput)
    Me._lastActivity = Me.lastInput.dwTime

    If (Environment.TickCount - Me.lastInput.dwTime) > Me._idleThreshold Then
        If Me._userActiveState <> UserActivityState.Inactive Then
            Me._userActiveState = UserActivityState.Inactive
            Me.activityStopWatch.Stop()
            Me.RaiseUserIdleEvent()
        End If
    ElseIf Me._userActiveState <> UserActivityState.Active Then
        Me._userActiveState = UserActivityState.Active
        Me.activityStopWatch.Start()
        Me.RaiseUserActiveEvent()
    End If
End Sub

Visual C#

private void GetLastInput(object userState)
{
    GetLastInputInfo(ref this.lastInput);
    this._lastActivity = this.lastInput.dwTime;

    if ((Environment.TickCount - this.lastInput.dwTime) > this._idleThreshold)
    {
        if (this._userActiveState != UserActivityState.Inactive)
        {
            this._userActiveState = UserActivityState.Inactive;
            this.activityStopWatch.Stop();
            this.RaiseUserIdleEvent();
        }
    }
    else if (this._userActiveState != UserActivityState.Active)
    {
        this._userActiveState = UserActivityState.Active;
        this.activityStopWatch.Start();
        this.RaiseUserActiveEvent();
    }
}

Another Tool in the ToolBox

Components in the Visual Studio Toolbox fit into categories: components and controls. It's a pretty fine line – essentially a control is a component with a user interface. Components are the controls that aren't visible at runtime and appear beneath the form at design time. By creating a user activity component, you can drag it from the Toolbox to the form, set properties and wire up events in the Properties pane, and keep the form code as uncluttered as possible.

Creating a component isn't much more work than creating any well-encapsulated class. In fact, I originally created the UserActivityTimer class like any other class. I just realized that it made more sense to create it as a component for better reuse.

The first step is to extend the System.ComponentModel.Component class. Already, your class (if public) will show up in the Toolbox when you rebuild the project. It will also show your public properties and events when dragged to a form, though it's not a very complete view. Provided you have setup your class properly with properties and events, you will have a pretty easy time finishing your work. Adding attributes to the class, properties, and events help you to create an experience closer to the Microsoft-supplied components/controls. Unless specified, all attributes are in the System.ComponentModel namespace.

Attribute Level Usage Description

System.Drawing.ToolboxBitmap

Class

ToolboxBitmap(typeof(UserActivityTimer), "images.Control.ico")

Specifies a bitmap for this component to show in the toolbox

DefaultProperty

Class

DefaultProperty("IdleThreshold")

The bolded property to which the Properties pane defaults

DefaultEvent

Class

DefaultEvent("UserActive")

The bolded event to which the Properties pane defaults

Description

Property/ Event

Description("User-defined data associated with the object."

The description that appears in the lower part of the Properties pane

Bindable

Property

Bindable(true)

Indicates if property is used for data binding and raises an event when the value changes

DefaultValue

Property

DefaultValue("") or DefaultValue(1000)

The value that will show in the Properties pane if no change is made. This value is also bolded when set to indicate that it hasn't been overridden

Category

Property

Category("Data")

Specifies the grouping within the Properties pane (Data,General, Behavior)

Browsable

Property

Browsable(false)

Whether or not it should show at all. Good for properties that make no sense at design-time

TypeConverter

Property

TypeConverter (typeof(StringConverter))

Since the Properties pane allows entries as string, the TypeConverter handles back-and-forth

 

Visual Basic

<DefaultValue(False)> _
<Category("Behavior")> _
<Description("The current state of the timer.")> _
Public Property Enabled() As Boolean
    Get
        Return Me._timerEnabled
    End Get
    Set(ByVal value As Boolean)
        ' If in design-time change the value but not the actual state
        If Not Me.DesignMode Then
            If value = True Then
                Enable()
            Else
                Disable()
            End If
        Else
            Me._timerEnabled = value
        End If
    End Set
End Property

Visual C#

[DefaultValue(false)]
[Category("Behavior")]
[Description("The current state of the timer.")]
public bool Enabled
{
    get
    {
        return this._timerEnabled;
    }
    set
    {
        // If in design-time change the value but not the actual state
        if (!this.DesignMode)
        {
            if (value == true) Enable();
            else Disable();
        }
        else
        {
            this._timerEnabled = value;
        }
    }
}

Note also, the check to the DesignMode property.  This comes from inheriting from the Component class.  This is important, because when a developer sets properties in the Visual Studio designer, the object actually gets its properties set.  Often, you don't really want to take any action in design mode.  This is how you tell the difference.

With the component built, its properties show up in the Properties pane just like any other component:

image

Figure 1 - The component's properties

Putting the Control to Work

With a component in place, it's much easier to create an application around it. For this sample, I decided to create a simple user interface to expose the information. It doesn't expose all information, but it's a good sampling of useful data. You can enable or disable the timer from the notification icon in the system tray.

clip_image002

Figure 2 - User Interface at runtime

All information shown is obtained through properties of the component. A standard Timer component is used to update the UI. Formatting the time properly is manual work, and the number of seconds must be multiplied by 1000 to convert it to milliseconds. The events are raised from the component which runs on its own thread. For this reason, it's not possible to directly set the UI controls when the event fires without causing a threading exception. There are two ways to solve this.

You could delegate the call to the form's thread, as is done in the sample. This adds a small amount of complexity in code and clarity, but is a pretty common solution, and with proper code comments anyone should be able to grasp it. The problem with this method is that a flood of events will cause a flood of UI updates. This might not be very efficient.

Another way is to update state variables in the form class when events fire. Then, when the form's UI update timer fires, it could use the state variables to determine what to show. There would be potential issues with threading concurrency if the event fires at the same moment as the Timer executes, but this can either be handled with locks, or ignored at the expense of occasionally inaccurate information. This also reduces UI updates to the Timer's update interval regardless of how often events fire.

Visual Basic

Private Sub updateTimer_Tick(ByVal sender As Object, ByVal e As EventArgs) Handles updateTimer.Tick
    Dim ts As TimeSpan = userTimer.ActiveTime

    ' Not necessary to update the status label since the active/idle events to it
    statusLabel.Text = userTimer.UserActiveState.ToString()

    Dim totalActive As String = String.Format("{0:00}:{1:00}:{2:00}", ts.Hours, ts.Minutes, ts.Seconds)

    timerLabel.Text = String.Format("{0} since {1}", totalActive, userTimer.LastResetTime.ToShortTimeString())

    appNotifyIcon.Text = String.Format("Coding 4 Fun - User Activity - Active {0}", totalActive)
End Sub

Visual C#

private void updateTimer_Tick(object sender, EventArgs e)
{
    TimeSpan ts = userTimer.ActiveTime;

    // Not necessary to update the status label since the active/idle events to it
    statusLabel.Text = userTimer.UserActiveState.ToString();

    string totalActive = string.Format("{0:00}:{1:00}:{2:00}",
        ts.Hours, ts.Minutes, ts.Seconds);

    timerLabel.Text = string.Format("{0} since {1}",
        totalActive, userTimer.LastResetTime.ToShortTimeString());

    appNotifyIcon.Text = string.Format("Coding 4 Fun - User Activity - Active {0}", totalActive);
}

Next Steps

This application isn't terribly useful as it is, but it could be a good foundation for a time tracking application. Adding a few fields to select a project would let you keep track of time spent. You could use the Enable/Disable properties to let a user pause the timer, and the Reset method to switch projects.

Another purpose would be in corporate development to track user productivity to a fine level. With a higher interval it might also serve as a good way to close unneeded resources such as network/database connections when a user isn't actively using an application anyway. You could achieve the same effect when the screensaver kicks in, but this makes it easy to use an independent threshold. Just drop the UserActivityTimer onto a form, set the IdleThreshold property, and wire up some actions to the events. Hopefully it's intuitive enough to put to use quickly.

Conclusion

In this article, I've shown how to compute total user activity time and keep track of the user's state with events using a Win32 API call that returns the last keyboard/mouse input event at the system level. This functionality is then bundled into a component for easy use in other applications. The sample application exposes this information to test it out and demonstrate how to use it.

I threw it together in order to keep better track of my own time, but hopefully it will be useful for other projects as well. If you haven't yet, download Visual Studio 2005 Express Edition for Visual C# or Visual Basic and have fun with it!


Avatar80 Arian Kulp is an independent software developer and writer working in the Midwest.  He has been coding since the fifth grade on various platforms, and also enjoys photography, nature, and spending time with his family.  Arian can be reached through his web site at http://www.ariankulp.com.

Tags:

Follow the Discussion

  • KuldeepKuldeep

    I just enhanced this tool by adding functionality to show how much time is spent is which all applications. As they dont allow us to upload the code in reply, I will try to upload the code at some other location and will provide the link here.

  • Clint RutkasClint I'm a "developer"

    @Kuldeep:  Put it on CodePlex or the Channel9 Sandbox

    @ddod: One possible way to track this is to see what the active process is.

    http://www.pinvoke.net/default.aspx/user32/GetActiveWindow.html

    http://www.codeguru.com/vb/controls/vb_shell/article.php/c3053/

  • ddodddod

    I just read Kuldeep's comment.  I seems that he is claiming he has modified this code to include actual application usage.

    I would love to see the way you did this. Did you ever get this code uploaded anywhere?

  • ddodddod

    Looking at the running process is one way that we are currently investigating.  We are looking into several other ways (one of which is using WMI to watch for start and delete events).  However we are always interested in seeing what others are doing.

    Thanks for he help.

  • srinivassrinivas

    hey, why don't you use stand by time .This tell exactly how much time the user id idle

  • jasonjason

    @srinivas, That's a good idea in theory, however there is also a certain amount of time that needs to be elapsed before the computer goes into standbye.

    For example, if I just decide to pick up and go out somewhere, my computer won't go into standbye at all because I've turned that feature off. Another good example is when users change their standbye times to, say, 15 or 20 minutes. Unless there is a way to detect what their standbye time is, I can't see this being very accurate. But, I still like it! Smiley

  • MikeMike

    Hi, is there a programatic way that I can simulate input so as to reset the idle timer?

Remove this comment

Remove this thread

close

Comments Closed

Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation, please create a new thread in our Forums,
or Contact Us and let us know.