Improving the Single-Finger Panning Experience

If you build an application that targets Windows Touch, it automatically provides basic panning support. However, you can use the WM_GESTURE message to provide enhanced support for single-finger panning.

Overview

To improve the single-finger panning experience, use these steps, as explained in subsequent sections of this topic:

  • Create an application with scroll bars and with flicks disabled.
  • Add support for gesture pan messages.
  • Enable bounce.

Create an Application with Scroll Bars and with Flicks Disabled

Before you begin, you must create an application with scroll bars. The section Legacy Support for Panning with Scrollbars explains this process. If you want to start with the example content, go to that section and create an application with scroll bars and then disable flicks. If you already have an application with functioning scroll bars, disable flicks as described in that section.

Add Custom Panning Support for Gesture Pan Messages

To support gesture pan messages, you must handle them in the WndProc method. The gesture messages are used to determine horizontal and vertical deltas for pan messages. The deltas are used to update the scroll bar object, which updates the user interface.

First, update the Windows version settings in the targetver.h file to enable Windows Touch. The following code shows the various Windows version settings that should replace those in targetver.h.

#ifndef WINVER                  // Specifies that the minimum required platform is Windows Vista.
#define WINVER 0x0601           // Change this to the appropriate value to target other versions of Windows.
#endif

#ifndef _WIN32_WINNT            // Specifies that the minimum required platform is Windows Vista.
#define _WIN32_WINNT 0x0601     // Change this to the appropriate value to target other versions of Windows.
#endif

Next, add the UXTheme.h file to your project and add the uxtheme.lib library to the additional dependencies of your project.

#include <uxtheme.h>

Next, add the following variables to the top of the WndProc function. These will be used in calculations for panning.

// The following are used for the custom panning handler      
BOOL bResult = FALSE;

static int scale = 8;   // altering the scale value will change how fast the page scrolls
static int lastY = 0;   // used for panning calculations (initial / previous vertical position)
static int lastX = 0;   // used for panning calculations (initial / previous horizontal position)
GESTUREINFO gi;  

Next, add the handler for the WM_GESTURE message so that the scroll bars are updated with deltas based on panning gestures. This gives you a much finer-grained control of panning.

The following code gets the GESTUREINFO structure from the lParam, saves the last y-coordinate from the structure, and determines the change in position to update the scroll bar object. The following code should be placed in your WndProc switch statement.

    case WM_GESTURE:        
        // Get all the vertial scroll bar information
        si.cbSize = sizeof (si);
        si.fMask  = SIF_ALL;
        GetScrollInfo (hWnd, SB_VERT, &si);
        yPos = si.nPos;

        ZeroMemory(&gi, sizeof(GESTUREINFO));
        gi.cbSize = sizeof(GESTUREINFO);
        bResult = GetGestureInfo((HGESTUREINFO)lParam, &gi);

        if (bResult){
            // now interpret the gesture            
            switch (gi.dwID){
                case GID_BEGIN:
                   lastY = gi.ptsLocation.y;
                   CloseGestureInfoHandle((HGESTUREINFO)lParam);
                   break;                     
                // A CUSTOM PAN HANDLER
                // COMMENT THIS CASE OUT TO ENABLE DEFAULT HANDLER BEHAVIOR
                case GID_PAN:                                                  
                    
                    si.nPos -= (gi.ptsLocation.y - lastY) / scale;

                    si.fMask = SIF_POS;
                    SetScrollInfo (hWnd, SB_VERT, &si, TRUE);
                    GetScrollInfo (hWnd, SB_VERT, &si);                                                        
                                               
                    yOverpan -= lastY - gi.ptsLocation.y;
                    lastY = gi.ptsLocation.y;
                     
                    if (gi.dwFlags & GF_BEGIN){
                        BeginPanningFeedback(hWnd);
                        yOverpan = 0;
                    } else if (gi.dwFlags & GF_END) {
                        EndPanningFeedback(hWnd, TRUE);
                        yOverpan = 0;
                    }
                           
                    if (si.nPos == si.nMin || si.nPos >= (si.nMax - si.nPage)){                    
                        // we reached the bottom / top, pan
                        UpdatePanningFeedback(hWnd, 0, yOverpan, gi.dwFlags & GF_INERTIA);
                    }
                    ScrollWindow(hWnd, 0, yChar * (yPos - si.nPos), NULL, NULL);
                    UpdateWindow (hWnd);                    
                                        
                    return DefWindowProc(hWnd, message, lParam, wParam);
                case GID_ZOOM:
                   // Add Zoom handler 
                   return DefWindowProc(hWnd, message, lParam, wParam);
                default:
                   // You have encountered an unknown gesture
                   return DefWindowProc(hWnd, message, lParam, wParam);
             }          
        }else{
            DWORD dwErr = GetLastError();
            if (dwErr > 0){
                // something is wrong 
                // 87 indicates that you are probably using a bad
                // value for the gi.cbSize
            }
        } 
        return DefWindowProc (hWnd, message, wParam, lParam);

Now, when you perform the pan gesture on your window, you will see the text scroll with inertia. At this point, you might want to change the text to have more lines so that you can explore panning large sections of text.

Boundary Feedback in WndProc

Boundary feedback is a type of visual feedback given to users when they reach the end of a pannable area. It is triggered by the application when a boundary is reached. In the previous example implementation of the WM_GESTURE message, the end condition (si.nPos == si.yPos) from the WM_GESTURE case is used to test that you have reached the end of a pannable region. The following variables are used to track values and test errors.

// The following are used for panning feedback (Window Bounce)
static int animCount = 0;
static DWORD dwErr   = 0;

static BOOL isOverpan  = FALSE;
static long xOverpan   = 0;
static long yOverpan   = 0;

The pan gesture case is updated to trigger boundary feedback. The following code illustrates the GID_PAN case from the WM_GESTURE message handler.

                case GID_PAN:                                                  
                    
                    si.nPos -= (gi.ptsLocation.y - lastY) / scale;

                    si.fMask = SIF_POS;
                    SetScrollInfo (hWnd, SB_VERT, &si, TRUE);
                    GetScrollInfo (hWnd, SB_VERT, &si);                                                        
                                               
                    yOverpan -= lastY - gi.ptsLocation.y;
                    lastY = gi.ptsLocation.y;
                     
                    if (gi.dwFlags & GF_BEGIN){
                        BeginPanningFeedback(hWnd);
                        yOverpan = 0;
                    } else if (gi.dwFlags & GF_END) {
                        EndPanningFeedback(hWnd, TRUE);
                        yOverpan = 0;
                    }
                           
                    if (si.nPos == si.nMin){                    
                        // we reached the top, pan upwards in y direction
                        UpdatePanningFeedback(hWnd, 0, yOverpan, gi.dwFlags & GF_INERTIA);
                    }else if (si.nPos >= (si.nMax - si.nPage)){
                        // we reached the bottom, pan downwards in y direction
                        UpdatePanningFeedback(hWnd, 0, yOverpan, gi.dwFlags & GF_INERTIA);
                    }
                    ScrollWindow(hWnd, 0, yChar * (yPos - si.nPos), NULL, NULL);
                    UpdateWindow (hWnd);                    
                                        
                    return DefWindowProc(hWnd, message, lParam, wParam);
  

Now your application's window should have boundary feedback when a user pans past the bottom of the scroll bar region.

Windows Touch Gestures

BeginPanningFeedback

EndPanningFeedback

UpdatePanningFeedback