“Reader Mode” – we’ve all used it, and whether we recognise it by name (or one of its many others, such as autoscroll, middle-click scroll, etc), we’ve come to expect it from the software we use the most – web browsers, text editors, e-mail clients and more. However, if you’ve been living under a rock since the mainstream adoption of the wheel mouse, let me explain how it works:

You activate Reader Mode by middle-clicking on a scrollable container/control. A glyph appears on the window in the location where you pressed the mouse button and the cursor changes to a scroll symbol. As you move the pointer away from the glyph, the display begins to automatically scroll in that direction. The further you move the pointer from the original location, the faster the display scrolls. (You can slow down the scroll rate by moving the pointer closer to the glyph.) This offers an alternative to continuously rotating the mouse wheel, allowing you to scroll through content while keeping your hand still.

Reader Mode behaves slightly differently depending on whether you release the middle button straight away, or keep it held down while scrolling. If the former is true, it goes into a sort-of “hands-free” mode where you must click again to exit. In the latter situation, it terminates when you release the middle mouse button.

DataGridViewRM example

Reader Mode in Windows Forms

Very few Windows Forms controls support Reader Mode as-is; the only ones i’m aware that do are RichTextBox and WebBrowser. Many more controls could benefit from Reader Mode support, such as:

  • Panel
  • PictureBox
  • ListView
  • TreeView
  • DataGridView

It’s the latter of those that I will focus on in this article.

Adding Reader Mode support

So, how do we add support for Reader Mode to a control that doesn’t already have it? You might be tempted to just implement it from scratch (handling mouse events, calculating offsets, etc), but there are many reasons why this is inadvisable:

  • Re-inventing the wheel
  • Subtle differences in behaviour easily introduced
  • Will not adapt to changes in the standard implementation
  • More complex using pure Windows Forms

Thankfully, there is an API for Reader Mode which can be imported from the Windows Common Controls library (comctl32.dll). It consists of:

  • READERMODEINFO structure – For configuring Reader Mode behaviour.
  • DoReaderMode() function – Puts the control into Reader Mode.
  • TranslateDispatch callback – Redirects window messages while in Reader Mode, allowing you to handle them independently of normal control behaviour.
  • ReaderScroll callback – Does the grunt work, notifying the control to start scrolling or update its scroll rate.
[DllImport("comctl32.dll", SetLastError = true, EntryPoint = "#383")]
public static extern void DoReaderMode(ref InteropTypes.READERMODEINFO prmi);

public delegate bool TranslateDispatchCallbackDelegate(ref Message lpmsg);

public delegate bool ReaderScrollCallbackDelegate(ref READERMODEINFO prmi, int dx, int dy);

[Flags]
public enum ReaderModeFlags {
    None = 0x00,
    ZeroCursor = 0x01,
    VerticalOnly = 0x02,
    HorizontalOnly = 0x04
}

[StructLayout(LayoutKind.Sequential)]
public struct READERMODEINFO {
    public int cbSize;
    public IntPtr hwnd;
    public ReaderModeFlags fFlags;
    public IntPtr prc;
    public ReaderScrollCallbackDelegate pfnScroll;
    public TranslateDispatchCallbackDelegate fFlags2;
    public IntPtr lParam;
}

The basic life cycle for Reader Mode, therefore, is:

  1. In the handler for the control’s MouseDown event, check for the middle mouse button and:
    1. Display a glyph at the location the mouse button was pressed (i’m using a PictureBox for this).
    2. Activate the mechanism used to auto-scroll the control (i’m using a Timer for this).
    3. Initialise and enter Reader Mode by calling DoReaderMode().
  2. The user will move the mouse pointer. In the ReaderScroll callback:
    1. Store the delta values (dx, dy) so that they are visible to the auto-scroll mechanism (Timer).
    2. If either of the values changes from zero, we will need to exit Reader Mode when the mouse button is released.
  3. At regular intervals (~25ms), the auto-scroll mechanism must:
    1. Increment (or decrement) the horizontal and/or vertical scroll bar offsets in proportion to the delta values from the previous step.
  4. The user will either release the mouse button or, in “hands-free” mode, click again. In the TranslateDispatch callback:
    1. If the window message corresponds to an event that will exit Reader Mode, stop the auto-scroll mechanism and hide the glyph.
    2. Depending on the type of event, either allow the default Reader Mode implementation to handle the message or handle it ourselves.
  5. Reader Mode will exit based on its own internal logic.

Default Reader Mode implementation

The default Reader Mode implementation is very simplistic. It will not draw the glyph, scroll the control (although it does generate the delta values) and it behaves slightly differently from the “standard” behaviour we see in most applications:

  • No “hands-free” mode; Reader Mode always exits when the middle mouse button is released
    • We can overcome this by preventing the default implementation from handling the MouseUp event until the delta values change
  • Stops responding to user input if the mouse pointer leaves the bounds of the control’s parent window
    • This is avoided by preventing the default implementation from handling the MouseLeave event
  • Only sends non-zero delta values when the pointer goes outside the ‘scrolling area’
    • Conventional behaviour is restored by setting the ‘scrolling area’ to the bounds* of the glyph (instead of the bounds of the control)

* – Note that the rectangle structure expected by Reader Mode is expressed in terms of left, top, right and bottom (rather than the conventional left, top, width, height representation). This gave me a nasty surprise the first time around.

Applying Reader Mode to the DataGridView

The DataGridView control is an excellent candidate for Reader Mode; scrolling through pages of tabular data can be a real pain, and the additional of Reader Mode functionality would help to make the control more user-friendly.

The easiest method is to subclass the control. I’ve created a child class called DataGridViewRM which adds the Reader Mode functionality and allows it to be toggled on/off via a design-time property. This effectively makes it a drop-in replacement for the original control.

As I eluded to above, we need to add some child controls to facilitate the behaviour; a PictureBox and a Timer. These must be initialised in the constructor and released in the control’s Dispose() method.

In terms of actually scrolling the control, the DataGridView uses its own logic (although its scroll bar controls are publicly-accessible, manipulating them does not scroll the control properly). It exposes two properties to allow the scroll offsets to be changed; HorizontalScrollingOffset and VerticalScrollingOffset. Unfortunately, through a quirk in Windows Forms, the latter property does not have a set accessor. Thankfully, it can be accessed using reflection:

// for better performance, retrieve this only once (in the constructor)
PropertyInfo setVerticalScrollingOffset 
    = GetType().GetProperty("VerticalOffset", BindingFlags.NonPublic | BindingFlags.Instance);

[Browsable(false)]
public new int VerticalScrollingOffset {
    get {
        return base.VerticalScrollingOffset;
    }
    set {
        setVerticalScrollingOffset.SetValue(this, value, null);
    }
}

All that remains is to handle the MouseDown event:

protected override void OnMouseDown(MouseEventArgs e) {
    if (ReaderModeEnabled) {
        if (e.Button == MouseButtons.Middle) {
            DataGridView.HitTestInfo hit = HitTest(e.X, e.Y);

            // reader mode should only be activated when the button 
            // is clicked over the scrollable part of the control
            switch (hit.Type) {
                case DataGridViewHitTestType.Cell:
                case DataGridViewHitTestType.HorizontalScrollBar:
                case DataGridViewHitTestType.None:
                case DataGridViewHitTestType.VerticalScrollBar:
                    // position and show the PictureBox
                    // start the Timer
                    // call DoReaderMode()
                    return;
                }
            }
        }

        // otherwise, process the event normally
        base.OnMouseDown(e);
}

The rest of the Reader Mode implementation falls within the callback methods and the Timer‘s Tick handler, as outlined earlier. You can view the complete implementation by downloading the source code below:

Download

Source code (Visual Studio 2012 solution, .NET Framework 4.0 – includes example application)

Final words

I hope you find this enhanced DataGridView control useful; it demonstrates just one of many controls that can be modified to support Reader Mode. I hope my explanation makes it easy for you to implement Reader Mode in your own controls.

Since the introduction of the wheel mouse, being able to operate scroll bars without the need for precision or the use of the mouse buttons has proven to be a godsend. User interfaces became friendlier and smoother as a result.

Unfortunately, the default handling of the MouseWheel event in Windows Forms is at-odds with the average user’s expectation about how and when this event should fire. In the vast majority of applications which support the mouse wheel, users expect to be able to affect a scrollable control or panel by simply moving the mouse over it and rotating the wheel. In WinForms, however, the MouseWheel event will only fire if the control has focus. In practice, this adds an unnecessary extra click to the process of using the mouse wheel and hinders the convenience offered by the wheel in the first place.

Thankfully, it is very simple to alter this behaviour and bring it in line with the user’s expectations. Better still, it is an application-wide solution and requires no changes to any control, panel or form.

The mechanism employed here is known as a message filter. Whenever the application receives a window message (the basic primitive used by all Win32 applications to handle interactivity, painting, events, etc), it is first passed to the message filter’s PreFilterMessage() method. Returning true from this method prevents the message from being processed in the normal way.

Our solution intercepts the WM_MOUSEWHEEL message, which is sent to the application when it is active and the user rotates the mouse wheel. Using the native method WindowFromPoint(), we can determine which control is under the mouse pointer at the time. We can then call another native method, SendMessage(), to send a copy of the original message to that control, allowing it to be processed as if the control was in focus at the time.

internal class MouseWheelMessageFilter : IMessageFilter {

    const int WM_MOUSEWHEEL = 0x20a;

    public bool PreFilterMessage(ref Message m) {
        if (m.Msg == WM_MOUSEWHEEL) {
            // LParam contains the location of the mouse pointer
            Point pos = new Point(m.LParam.ToInt32() & 0xffff, m.LParam.ToInt32() >> 16);
            IntPtr hWnd = WindowFromPoint(pos);
            if (hWnd != IntPtr.Zero && hWnd != m.HWnd && Control.FromHandle(hWnd) != null) {
                // redirect the message to the correct control
                SendMessage(hWnd, m.Msg, m.WParam, m.LParam);
                return true;
            }
        }
        return false;
    }

    // P/Invoke declarations
    [DllImport("user32.dll")]
    private static extern IntPtr WindowFromPoint(Point pt);
    [DllImport("user32.dll")]
    private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
}

All we need do to use the message filter is to add the following line somewhere in the initialisation of the program (e.g. in the Main() method):

Application.AddMessageFilter(new MouseWheelMessageFilter());

This technique works for almost every type of control. A notable exception is the WebBrowser control, which uses its own logic to handle the rotation of the mouse wheel.

Use this code and enjoy the improvement in the usability of your application!

I’ve been taking a break from my usual work in C#/WinForms to improve my web development skills. As such, i’ve been learning PHP, HTML5, jQuery and CSS3, among other things.

I recently had an opportunity to work with the Google Maps API to create a map that shows the locations of a large set of users. In doing so, I discovered some useful extensions that improve the appearance, functionality and performance of the user map. In this post, i’ll share some of those with you as I walk through the process of creating such a map.

Example of a map of users

Google Maps API

The Google Maps API is available on a few different platforms, but the one we’ll be using is the JavaScript version. This allows us to create the map and add the markers after the page loads, using a script. To use the Maps API, load the script in the <head> section of your page:

<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?sensor=true&v=3.exp"></script>

Step 1: The location data

The first step is to assemble the data that will populate the markers on the map. For the purposes of this post, i’m going to limit this data to:

  • The name of the user
  • A URL to the user’s avatar (a small image file)
  • The user’s location – latitude (+ve = North, -ve = South) and longitude (+ve = East, -ve = West), ideally stored in separate fields

The source and representation of this data will be application-specific (e.g. PHP/MySQL, C#/MS-SQL, etc), so it must first be transformed into a useful format that can be read using JavaScript. JSON is by far the simplest option for this. In my case, working with PHP, all I need do is query the data from the database and encode the resulting associative array using the json_encode() method. In .NET, JSON support can be added using a library like Json.NET.

A neat way of loading your data so it can be accessed by the user map script is to wrap the JSON in an assignment statement, effectively turning it into an executable script itself:

var external_data = [ {"username":"user1", "url":"user1.png", "lat":"-31.94", "lng":"115.87"}, {...} ];

If this script is dynamically generated, all you need do is add it to your <head> element after the Maps API:

<script type="text/javascript" src="my_data_source_url.json"></script>

Step 2: Embedding a map

It’s almost fiendishly simple to embed a Google Map on a page. The only markup required is a <div> with a class/ID that you can reference in the script. It’s a good idea to wrap this inside another <div> to give you more flexibility when applying styles:

<div id="google-map-container">
   <div id="google-map"></div>
</div>

Everything else is done in JavaScript. To initialise the map, we need to pass a few parameters to its constructor; the coordinates the map is centered on, the zoom level and the type of map (road/satellite/etc):

var center = new google.maps.LatLng(-31.9554, 115.8585);

var map = new google.maps.Map(document.getElementById('google-map'), {
    zoom: 9,
    center: center,
    mapTypeId: google.maps.MapTypeId.ROADMAP
});

The above code runs after the page has loaded (in jQuery, you can use the $(document).ready() function for this).

Step 3: Creating map markers

The conventional way to create a map marker is to instantiate the google.maps.Marker class and pass the map, coordinates and other properties to its constructor. While you can customise the appearance of a marker to some degree (including by replacing the icon with a custom image), they do not offer as much flexibility as I would like. For my user map, I want to be able to control the style of the markers using CSS – which means I need to be able to apply classes to them.

Thankfully, there is an extension to the API which builds on the basic marker, giving you the choice of specifying the content using HTML markup instead. To use the RichMarker class, add the following to your <head> tag:

<script type="text/javascript" src="https://google-maps-utility-library-v3.googlecode.com/svn/trunk/richmarker/src/richmarker-compiled.js"></script>

You can then create a marker using the following code (assume the variable user is an element taken from the location data):

var marker = new RichMarker({
    map: map,
    position: new google.maps.LatLng(user.lat, user.lng),
    content: '<img src="' + user.url + '" title="' + user.username + '" class="my-map-marker" />'
});

You can then apply a style to the my-map-marker class in a stylesheet.

However, there is a problem associated with this approach; performance degrades when you add a large number of markers to the map. Since we want to display a large number of users on our map, this is highly undesirable.

Thankfully, there is another very useful extension to the Maps API that addresses this problem: MarkerCluster dynamically reduces markers in close proximity to each other into ‘cluster’ markers, which display the number of markers contained within them. As you zoom in, clusters are replaced by smaller clusters, and finally by the markers themselves. You can click on a cluster to zoom in to the appropriate level required to display all included markers. To use this extension, add the following to your <head> tag:

<script type="text/javascript" src="https://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclusterer/src/markerclusterer_compiled.js"></script>

Instead of directly associating the markers with the map object, we add them to an array and pass this to the MarkerCluster class:

var markers = [];
for (var i = 0; i < external_data.length; i++) {
    var user = external_data[i];

    var marker = new RichMarker({
        position: new google.maps.LatLng(user.lat, user.lng),
        content: '<img src="' + user.url + '" title="' + user.username + '" class="my-map-marker" />'
    });

    markers.push(marker);
}

var cluster = new MarkerClusterer(map, markers);

This object now becomes responsible for controlling which markers are displayed on the map at any given time. Its behaviour can be customised, but the defaults are quite sensible for our scenario.

…and that’s it – everything you need to create a map showing the locations of your users.

Next steps

Now that we’ve got our user map up and running, we can start building additional functionality, such as interactivity. To capture when the user clicks on a map marker, attach an event handler using the following syntax:

google.maps.event.addListener(marker, 'click', my_click_handler_function);

The event handler function has a single parameter containing a MouseEvent object, which has a latLng property describing the location on the map that was clicked (i.e. the location of the marker).

The Maps API provides a built-in InfoWindow class, which you can use to display a ‘speech bubble’ callout containing the HTML markup of your choice. It works like this:

function my_click_handler_function(evt) {
    infoWindow = new google.maps.InfoWindow({
        position: evt.latLng,
        content: 'Show information about the user here'
    });
}

By setting the marker’s draggable property to true, you can allow the user to move the marker around on the map. You can handle this using the dragstart and dragend events. For example, you could update the current user’s location by making an AJAX call when the user finishes dragging the point.

I’ve shown how to create markers from a simple data source, but you might derive marker locations from other sources that are available at run-time. If the browser supports geolocation (most modern browsers do), you can create a marker from the user’s current location:

if (navigator.geolocation) {
    navigator.geolocation.getCurrentPosition(
        function (position) {
            // called if the location is found
            var latLng = new google.maps.LatLng(position.coords.latitude, position.coords.longitude);

            /* create a marker here */
        },
        function () {
            // called if an error occurs
            alert('Unable to determine your location.');
        }
    );
}

If you don’t have exact map coordinates for a marker, you can use the Google Places API to obtain the coordinates given a place name (or search term). To do this, you must add &libraries=places to the URL for the main API script. See the Places API documentation for more on how to transform a place name into map coordinates.

Final words

Integrating Google Maps into a web application is much simpler than you might think. By adding your own data to the map (in this case, locations of users) in the form of markers, you can combine the power of both systems and create genuinely useful features for your site. Hopefully, my suggestions and pointers in this area will help to make your Maps integration faster, smoother and more elegant.

I recently attempted to transform a boring, alphabetical list of tags into a more visually-appealing (and more informational) tag cloud. No doubt you’ve seen tag clouds on blogs and other websites (there’s even one on here!) but for the uninformed:

A tag cloud is a form of ‘weighted list’ where reusable tags (keywords, categories, etc) are arranged on a diagram in such a way that more frequently-used tags appear in a larger font. The arrangement of the tags themselves can be ordered (usually alphabetically) or a more organic layout can be used (e.g. a force-based algorithm). Their purpose is to show an extra dimension of information (i.e. frequency) and thus draw attention to more popular tags.

This blog has already dedicated a lot of time to the generation and layout of diagrams, so this article will focus on another important part of any tag cloud algorithm: how to assign a suitable font size to each tag. This is a somewhat tricky problem in that:

  • The total number of tags and upper bound of each tag’s frequency are unknown
  • We obviously want to constrain the font size so that it falls within a sensible range
  • Regardless of the distribution of the frequencies, we want to have strong variation in font sizes

Before I settled on a solution, I tried a number of other approaches:

Proportional sizing

Given a query to determine the frequency of use for tags, such as:

SELECT Tag, COUNT(*) as [Frequency]
FROM PostsTags
GROUP BY Tag

We can use a formula like this to assign font sizes:

size = minSize + ((maxSize-minSize) * (frequency / maxFrequency))
Proportional sizing
Results using proportional sizing

This formula scales each tag according to its proportion to the maximum value, ensuring the font size is between minSize and maxSize. The problem is that it is adversely affected by outliers; e.g. if 90% of tags vary by only 10^1 and 10% vary by 10^3, there will be one or two enormous tags and lots of very small ones (with not much variance between them).

Sizing by random sample

In this approach, n tags are randomly selected and their frequencies become the threshold values for each font size (where n is the number of different font sizes that can be used). This takes advantage of the fact that psuedo-random numbers are uniformly-distributed, so querying for the number of frequency levels we want (instead of all values) will give a good indication of the different values in the set.

The SQL query for a random sample (in this case, of size 10) is of the form:

SELECT TOP(10) Tag, COUNT(*) as [Frequency]
FROM PostsTags
GROUP BY Tag
ORDER BY newid()
Sizing by random sample
Results using sizing by random sample

The result, however, is less-than ideal; the resulting font sizes are random (since we are working with a random sample) and outliers are likely to be ignored (in particular the minimum and maximum, e.g. the most frequent tag will appear as large as the second most-frequent).

Sizing by order using ROW_NUMBER()

This approach uses the T-SQL ROW_NUMBER() windowing function to assign a sequential number to each tag, sorted according to frequency; so, the least-used tag will be numbered as 1, the most-used tag as n (total number of tags). This means that, regardless of the gap between each tag’s frequency, we will get a uniform increase in font size.

The SQL query would look something like this:

SELECT Tag, COUNT(*) as [Frequency], ROW_NUMBER() OVER (ORDER BY COUNT(*)) as [Order]
FROM PostsTags
GROUP BY Tag

Then, sizes would be assigned using the following formula:

size = minSize + ((maxSize - minSize) * ((order - 1) / maxOrder)))

(Since order starts at 1, we subtract 1 because we want the right-hand size of the expression to evaluate to zero, i.e. size = minSize)

Sizing using ROW_NUMBER()
Results using sizing by ROW_NUMBER()

The problem with this approach is that tags with equal frequencies will not be assigned the same font size; rather, they will increase in font size according to table-order. Since a large number of tags are likely to have been used between 0 and 1 times, the result will be unsatisfactory.

The solution: sizing using DENSE_RANK()

The approach above is based on a good premise, but isn’t quite right. If only there was some way of sizing according to order where equal frequencies were assigned the same value… oh wait, there is! The T-SQL DENSE_RANK() windowing function behaves much like ROW_NUMBER(), except that it assigns the same value when there are ties in the set. (We could have used the normal RANK() function, but this leaves gaps in the numbering)

The SQL looks something like this:

SELECT Tag, COUNT(*) as [Frequency], DENSE_RANK() OVER (ORDER BY COUNT(*)) as [Rank]
FROM PostsTags
GROUP BY Tag

Then, sizes are assigned using the following formula:

size = minSize + ((maxSize - minSize) * ((rank - 1) / maxRank))

(I’ve opted to make maxSize an exclusive boundary, to avoid a situation where maxRank is 1 and we risk dividing by zero)

Sizing using DENSE_RANK()
Results using sizing by DENSE_RANK()

The result is a tag cloud where:

  • Font sizes increase uniformly from the least-used tag to the most-used tag
  • Outliers are not ignored, nor do they result in hugely disproportionate font sizes
  • Tags with a frequency of zero are always assigned minSize
  • Upon first use, a tag immediately stands apart from the unused tags

Keeping font sizes low until tags reach maturity

If you don’t want the maximum font size to be assigned until a certain frequency is reached, you can apply a transformation to the ratio:

size = minSize + ((maxSize - minSize) * ((rank - 1) / maxRank) * Math.Min(1, maxFrequency / threshold))

This will allow font sizes to gradually approach maxSize as the most-used tag’s frequency approaches threshold.

Final words

Hopefully this article gives you an idea of some of the complexities involved in producing good-looking tag clouds, and allows you to avoid some of the common pitfalls associated with this.