Click here to Skip to main content
15,892,697 members
Articles / Desktop Programming / Win32

The Windows Mobile 6.5 Gestures API: An Introduction

Rate me:
Please Sign up or sign in to vote.
4.82/5 (11 votes)
30 Oct 2009CPOL6 min read 48.4K   805   26  
This article contains a brief look into the new Gestures API that is available for Windows Mobile 6.5 Professional Edition. We will walk through creating a small Smart Device application that is able to read gestures made by the user, and report back information about these gestures to the screen.
/******************************************************************************

	Description:

	Contains the source code for the GesturesTest application for Windows
	Mobile 6.5 Professional Edition.  Submitted to Code Project on 10/28/2009.

	Author:

	David Cole (cor2879@gmail.com)

	Version:

	1.0.0.0

	Platform:

	Windows Mobile 6.5 Professional Edition
	Win32
	C++
******************************************************************************/

#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <gesture.h>
#include <aygshell.h>

#define LABEL_WIDTH		240
#define LABEL_HEIGHT	125

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
LRESULT WINAPI OnCreate(HWND hwnd, WPARAM wParam, LPARAM lParam);
LRESULT WINAPI OnGesture(HWND hwnd, WPARAM wParam, LPARAM lParam);
LRESULT WINAPI OnColorStatic(HWND hwnd, WPARAM wParam, LPARAM lParam);
LRESULT WINAPI OnSize(HWND hwnd, WPARAM wParam, LPARAM lParam);
VOID WINAPI GetGestureDirection(WORD wDirection, OUT TCHAR* szDirection, IN DWORD dwSize);

HINSTANCE g_hinst = NULL;
HWND g_hwndLabel = NULL;

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nShowCmd)
{
	static TCHAR szAppName[] = TEXT("GesturesTest");

	HWND hwnd;
	MSG msg;
	WNDCLASS wndClass;

	ZeroMemory(&wndClass, sizeof(WNDCLASS));

	g_hinst = hInstance;

	wndClass.style = CS_HREDRAW | CS_VREDRAW;
	wndClass.lpfnWndProc = WndProc;
	wndClass.hInstance = hInstance;
	wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
	wndClass.hbrBackground = CreateSolidBrush(RGB(0, 0, 0));
	wndClass.lpszClassName = szAppName;

	if (!RegisterClass(&wndClass))
	{
		MessageBox(NULL, TEXT("There was a problem registering the Windows Class"),
			szAppName, MB_ICONERROR);

		return 0;
	}


	hwnd = CreateWindow(szAppName,					// Window Class Name
						TEXT("Gestures Test"),		// Window Caption
						WS_VISIBLE,					// Window Style
						0,							// Initial X position
						0,							// Initial Y position
						CW_USEDEFAULT,				// Initial Width
						CW_USEDEFAULT,				// Initial Height
						NULL,						// Parent Window Handle
						NULL,						// Menu Handle
						g_hinst,					// Program Instance Handle
						NULL);						// Creation Parameters

	if (hwnd)
	{
		ShowWindow(hwnd, nShowCmd);
		UpdateWindow(hwnd);

		while (GetMessage(&msg, NULL, 0, 0))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}

		if (hwnd)
		{
			CloseHandle(hwnd);
		}

		UnregisterClass(szAppName, hInstance);

		return msg.wParam;
	}

	return GetLastError();
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch(msg)
	{
	case WM_CREATE:
		return OnCreate(hwnd, wParam, lParam);
	case WM_GESTURE:
		return OnGesture(hwnd, wParam, lParam);
	case WM_CTLCOLORSTATIC:
		return OnColorStatic(hwnd, wParam, lParam);
	case WM_ACTIVATE:
		if (WA_INACTIVE == (DWORD)wParam)
		{
			return SendMessage(hwnd, WM_DESTROY, 0,0);
		}
		return DefWindowProc(hwnd, msg, wParam, lParam);
	case WM_SIZE:
		return OnSize(hwnd, wParam, lParam);
	case WM_CLOSE:
		return SendMessage(hwnd, WM_DESTROY, 0, 0);
	case WM_DESTROY:
		if (g_hwndLabel)
		{
			CloseHandle(g_hwndLabel);
		}
		PostQuitMessage(0);
		return 0;
	default:
		return DefWindowProc(hwnd, msg, wParam, lParam);
	}
}

LRESULT WINAPI OnCreate(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
	if (!hwnd)
	{
		return ERROR_INVALID_PARAMETER;
	}

	RECT rect;
	GetClientRect(hwnd, &rect);

	int iLabelLeft = (rect.right >> 1) - (LABEL_WIDTH >> 1);
	int iLabelTop = (rect.bottom >> 1) - (LABEL_HEIGHT >> 1);
	
	// Create a Text Label and center it on screen
	g_hwndLabel = CreateWindowEx(0,
								 TEXT("STATIC"),
								 0,
								 WS_CHILD | WS_VISIBLE | SS_CENTER,
								 iLabelLeft,
								 iLabelTop,
								 LABEL_WIDTH,
								 LABEL_HEIGHT,
								 hwnd,
								 NULL,
								 g_hinst,
								 NULL);

	// Create an empty menu bar for our app
	SHMENUBARINFO mbi;
	ZeroMemory(&mbi, sizeof(SHMENUBARINFO));
	mbi.cbSize = sizeof(SHMENUBARINFO);
	mbi.hwndParent = hwnd;
	SHCreateMenuBar(&mbi);
	DrawMenuBar(hwnd);

	// Set the font
	LOGFONT lf;
	ZeroMemory(&lf, sizeof(LOGFONT));
	StringCchCopy(lf.lfFaceName, 32, TEXT("Courier New"));

	HFONT hFont = CreateFontIndirect(&lf);
	SendMessage(g_hwndLabel, WM_SETFONT, (WPARAM)hFont, 0);
	CloseHandle(hFont);

	return ERROR_SUCCESS;
}

VOID WINAPI GetGestureDirection(WORD wDirection, OUT TCHAR* szDirection,
								IN DWORD dwSize)
{
	if (dwSize <= 0)
	{
		dwSize = 16;
	}

	switch (wDirection)
	{
	case ARG_SCROLL_RIGHT:
		StringCchCopy(szDirection, dwSize, TEXT("RIGHT"));
		break;
	case ARG_SCROLL_UP:
		StringCchCopy(szDirection, dwSize, TEXT("UP"));
		break;
	case ARG_SCROLL_LEFT:
		StringCchCopy(szDirection, dwSize, TEXT("LEFT"));
		break;
	case ARG_SCROLL_DOWN:
		StringCchCopy(szDirection, dwSize, TEXT("DOWN"));
		break;
	case ARG_SCROLL_NONE:
	default:
		StringCchCopy(szDirection, dwSize, TEXT("NONE"));
		break;
	}

	return;
}

LRESULT WINAPI OnGesture(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
	if (!hwnd || !wParam || !lParam)
	{
		return ERROR_INVALID_PARAMETER;
	}

	GESTUREINFO gi;
	ZeroMemory(&gi, sizeof(GESTUREINFO));

	gi.cbSize = sizeof(GESTUREINFO);

	if (TKGetGestureInfo((HGESTUREINFO)lParam, &gi))
	{
		int iBufferSize = 128;
		PTSTR szGestureText = new TCHAR[iBufferSize];
		ZeroMemory(szGestureText, sizeof(TCHAR) * iBufferSize);

		switch(wParam)
		{
		/* No action on GID_BEGIN or GID_END events.  Otherwise
		   all we would ever see on the screen is GID_END :-)
		*/
		case GID_BEGIN:
		case GID_END:
			return DefWindowProc(hwnd, WM_GESTURE, wParam, lParam);
			break;
		/* A pan event occurs when a user drags their finger (or stylus) across
		   the screen.
		*/
		case GID_PAN:
		{
			/* According to MSDN, a GID_PAN event is supposed to produce direction,
			   angle, and velocity data when the GF_INERTIA flag is flipped on.  I
			   have yet to be able to reproduce this behavior.
			*/
			if (gi.dwFlags & GF_INERTIA)
			{
				WORD wDirection = GID_SCROLL_DIRECTION(gi.ullArguments);
				WORD wAngle = GID_SCROLL_ANGLE(gi.ullArguments);
				WORD wVelocity = GID_SCROLL_VELOCITY(gi.ullArguments);
				PTSTR szDirection = new TCHAR[16];
				ZeroMemory(szDirection, sizeof(TCHAR) * 16);

				GetGestureDirection(wDirection, szDirection, 16);

				StringCchPrintf(szGestureText, iBufferSize, 
					TEXT("GID_PAN\nX: %d, Y: %d\nDirection: %s\nAngle: %d\nVelocity: %d"),
					gi.ptsLocation.x, gi.ptsLocation.y, szDirection, wAngle, wVelocity);
				SetWindowText(g_hwndLabel, szGestureText);
				delete[] szGestureText;

				if (szDirection)
				{
					delete[] szDirection;
				}
			}
			else
			{
				StringCchPrintf(szGestureText, iBufferSize, TEXT("GID_PAN\nX: %d, Y: %d"),
					gi.ptsLocation.x, gi.ptsLocation.y);
				SetWindowText(g_hwndLabel, szGestureText);
				delete[] szGestureText;
			}
			break;
		}
		/* A Scroll event occurs after a user quickly flicks their finger (or stylus) across
		   the screen.  This is distinguishable from a Pan because a Pan -always- occurs when
		   a finger or stylus is dragged, while a Scroll -only- occurs after the user releases
		   their finger or stylus from the screen after a Pan event, and only if that Pan event
		   occurs fast enough to register as a scroll.  You'll see what I mean if you build and
		   run the code on a touch enabled 6.5 device.  The angle values are in a raw argument 
		   form and can be converted to radians using the GID_ROTATE_ANGLE_FROM_ARGUMENT macro.  
		   I elected not to do that here.
		*/
		case GID_SCROLL:
		{
			WORD wDirection = GID_SCROLL_DIRECTION(gi.ullArguments);
			WORD wAngle = GID_SCROLL_ANGLE(gi.ullArguments);
			WORD wVelocity = GID_SCROLL_VELOCITY(gi.ullArguments);
			PTSTR szDirection = new TCHAR[16];
			ZeroMemory(szDirection, sizeof(TCHAR) * 16);

			GetGestureDirection(wDirection, szDirection, 16);

			StringCchPrintf(szGestureText, iBufferSize, 
				TEXT("GID_SCROLL\nX: %d, Y: %d\nDirection: %s\nAngle: %d\nVelocity: %d"),
				gi.ptsLocation.x, gi.ptsLocation.y, szDirection, wAngle, wVelocity);
			SetWindowText(g_hwndLabel, szGestureText);
			delete[] szGestureText;

			if (szDirection)
			{
				delete[] szDirection;
			}
			break;
		}
		/* A Hold event occurs when the user touches the screen and does not move their
		   finger (or the stylus) and also does not release the screen.  It usually takes
		   about two seconds for a touch event to register as a GID_HOLD message on my
		   phone.  MSDN states that the amount of time required is defined arbitrarily
		   at some lower level, possibly by the OEM.  Therefore the amount of time it
		   takes for a GID_HOLD message to appear may vary by device.
		*/
		case GID_HOLD:
			StringCchPrintf(szGestureText, iBufferSize, TEXT("GID_HOLD\nX: %d, Y: %d"),
				gi.ptsLocation.x, gi.ptsLocation.y);
			SetWindowText(g_hwndLabel, szGestureText);
			delete[] szGestureText;
			break;

		/* A Select event occurs when the user touches the screen.  It is comparable to 
		   left clicking a mouse.
		*/
		case GID_SELECT:
			StringCchPrintf(szGestureText, iBufferSize, TEXT("GID_SELECT\nX: %d, Y: %d"),
				gi.ptsLocation.x, gi.ptsLocation.y);
			SetWindowText(g_hwndLabel, szGestureText);
			delete[] szGestureText;
			break;

		/* A Double Select event occurs when the user double taps the screen.  That is,
		   quickly taps the screen twice.  It is comparable to double clicking the left
		   button of a mouse.
		*/
		case GID_DOUBLESELECT:
			StringCchPrintf(szGestureText, iBufferSize, TEXT("GID_DOUBLESELECT\nX: %d, Y: %d"),
				gi.ptsLocation.x, gi.ptsLocation.y);
			SetWindowText(g_hwndLabel, szGestureText);
			delete[] szGestureText;
			break;
		default:
			break;
		}

		return ERROR_SUCCESS;
	}
	else
	{
		return ERROR_INVALID_PARAMETER;
	}
}

LRESULT WINAPI OnColorStatic(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
	static HBRUSH hBrush = CreateSolidBrush(RGBA(0,0,0,0));

	HWND hwndStatic = (HWND)lParam;
	HDC dc = (HDC)wParam;

	SetTextColor(dc, RGB(100, 255, 100));
	SetBkColor(dc, RGBA(0, 0, 0, 255));
	SetBkMode(dc, TRANSPARENT);

	return (LRESULT)hBrush;
}

LRESULT WINAPI OnSize(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
	if (g_hwndLabel)
	{
		RECT rect;
		GetClientRect(hwnd, &rect);

		int iLabelLeft = (rect.right >> 1) - (LABEL_WIDTH >> 1);
		int iLabelTop = (rect.bottom >> 1) - (LABEL_HEIGHT >> 1);

		MoveWindow(g_hwndLabel,
				   iLabelLeft,
				   iLabelTop,
				   LABEL_WIDTH,
				   LABEL_HEIGHT,
				   TRUE);
	}

	return DefWindowProc(hwnd, WM_SIZE, wParam, lParam);
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Microsoft
United States United States
Software Engineer from Western NC specializing in C#, .Net Framework, Javascript, and C/C++ development. I love solving complex problems and injecting witty sarcasm into the situation. When one gets out of control I apply the other. Currently working as a Software Development Engineer at Microsoft, where I have constructed a test automation harness for Microsoft Fixit products ,a native code application for Windows Mobile Marketplace, a WCF service, an abstraction library for Microsoft Log Parser that allows Log Parser records to be processed with Linq as they are returned, and a Windows Sidebar Gadget.

Comments and Discussions