2025-11-28 00:35:46 +09:00

2198 lines
73 KiB
C++

// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// Module:
// AdvReco.cpp
//
// Description:
// This sample demonstrates such advanced features of the
// Microsoft Tablet PC Automation API used for handwriting
// recognition, as
// - enumerating the installed recognzers
// - creating a recognition context with a specific language
// recognizer
// - setting recognition input scopes
// - using guides to improve the recognition quality
// - dynamic background recognition
// - gesture recognition.
//
// This application is discussed in the Getting Started guide.
//
// (NOTE: For code simplicity, returned HRESULT is not checked
// on failure in the places where failures are not critical
// for the application or very unexpected)
//
// The interfaces used are:
// IInkRecognizers, IInkRecognizer, IInkRecoContext,
// IInkRecognitionResult, IInkRecognitionGuide, IInkGesture
// IInkCollector, IInkDisp, IInkRenderer, IInkStrokes, IInkStroke
//
// Requirements:
// One or more handwriting recognizer must be installed on the system;
// Appropriate Asian fonts need to be installed to output the results
// of the Asian recognizers.
//
//--------------------------------------------------------------------------
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0500
#endif
// Windows header files
#include <windows.h>
#include <commctrl.h> // need it to call CreateStatusWindow
// The following definitions may be not found in the old headers installed with VC6,
// so they're copied from the newer headers found in the Microsoft Platform SDK
#ifndef ListView_SetCheckState
#define ListView_SetCheckState(hwndLV, i, fCheck) \
ListView_SetItemState(hwndLV, i, INDEXTOSTATEIMAGEMASK((fCheck ? 2 : 1)), LVIS_STATEIMAGEMASK)
#endif
#ifndef ListView_GetCheckState
#define ListView_GetCheckState(hwndLV, i) \
((((UINT)(SNDMSG((hwndLV), LVM_GETITEMSTATE, (WPARAM)(i), LVIS_STATEIMAGEMASK))) >> 12) -1)
#endif
#ifndef MIIM_STRING
#define MIIM_STRING 0x00000040
#endif
#ifndef MIIM_FTYPE
#define MIIM_FTYPE 0x00000100
#endif
// A useful macro to determine the number of elements in the array
#define countof(array) (sizeof(array)/sizeof(array[0]))
// ATL header files
#include <atlbase.h> // defines CComModule, CComPtr, CComVariant
CComModule _Module;
#include <atlwin.h> // defines CWindowImpl
#include <atlcom.h> // defines IDispEventSimpleImpl
// Headers for Tablet PC Automation interfaces
#include <msinkaut.h>
#include <msinkaut_i.c>
#include <tpcerror.h>
// The application header files
#include "resource.h" // main symbols, including command ID's
#include "EventSinks.h" // defines the IInkEventsImpl and IInkRecognitionEventsImpl
#include "ChildWnds.h" // definitions of the CInkInputWnd and CRecoOutputWnd
#include "AdvReco.h" // contains the definition of CAddRecoApp
// The names of the supported Input Scopes.
// All these names are used both to create a menu item and
// as the parameter in IInkRecoContext::put_Factoid(name).
const LPOLESTR gc_pwsInputScopes[] = {
L"(!IS_DEFAULT)",
L"(!IS_URL)",
L"(!IS_FILE_FULLFILEPATH)",
L"(!IS_FILE_FILENAME)",
L"(!IS_EMAIL_USERNAME)",
L"(!IS_EMAIL_SMTPEMAILADDRESS)",
L"(!IS_LOGINNAME)",
L"(!IS_PERSONALNAME_FULLNAME)",
L"(!IS_PERSONALNAME_PREFIX)",
L"(!IS_PERSONALNAME_GIVENNAME)",
L"(!IS_PERSONALNAME_MIDDLENAME)",
L"(!IS_PERSONALNAME_SURNAME)",
L"(!IS_PERSONALNAME_SUFFIX)",
L"(!IS_ADDRESS_FULLPOSTALADDRESS)",
L"(!IS_ADDRESS_POSTALCODE)",
L"(!IS_ADDRESS_STREET)",
L"(!IS_ADDRESS_STATEORPROVINCE)",
L"(!IS_ADDRESS_CITY)",
L"(!IS_ADDRESS_COUNTRYNAME)",
L"(!IS_ADDRESS_COUNTRYSHORTNAME)",
L"(!IS_CURRENCY_AMOUNTANDSYMBOL)",
L"(!IS_CURRENCY_AMOUNT)",
L"(!IS_DATE_FULLDATE)",
L"(!IS_DATE_MONTH)",
L"(!IS_DATE_DAY)",
L"(!IS_DATE_YEAR)",
L"(!IS_DATE_MONTHNAME)",
L"(!IS_DATE_DAYNAME)",
L"(!IS_DIGITS)",
L"(!IS_NUMBER)",
L"(!IS_ONECHAR)",
L"(!IS_TELEPHONE_FULLTELEPHONENUMBER)",
L"(!IS_TELEPHONE_COUNTRYCODE)",
L"(!IS_TELEPHONE_AREACODE)",
L"(!IS_TELEPHONE_LOCALNUMBER)",
L"(!IS_TIME_FULLTIME)",
L"(!IS_TIME_HOUR)",
L"(!IS_TIME_MINORSEC)",
L"((0|1|2|3|4|5|6|7|8|9) ?(0|1|2|3|4|5|6|7|8|9) ?(0|1|2|3|4|5|6|7|8|9) ?-? ?)?(0|1|2|3|4|5|6|7|8|9) ?(0|1|2|3|4|5|6|7|8|9) ?(0|1|2|3|4|5|6|7|8|9) ?-? ?(0|1|2|3|4|5|6|7|8|9) ?(0|1|2|3|4|5|6|7|8|9) ?(0|1|2|3|4|5|6|7|8|9) ?(0|1|2|3|4|5|6|7|8|9)",
L"(!IS_PERSONALNAME_FULLNAME)|((!IS_PERSONALNAME_PREFIX)? +(!IS_PERSONALNAME_GIVENNAME)+ +(!IS_PERSONALNAME_MIDDLENAME)* +(!IS_PERSONALNAME_SURNAME)+)",
L"MN(0|1|2|3|4|5|6|7|8|9)(0|1|2|3|4|5|6|7|8|9)(A|B)(!IS_DIGITS)(X|Y)((0|1)*)"
};
// Specifies the maximum allowed length of menu items in the
// input scope menu. Any items that exceed this value will
// be truncated.
const LONG gc_lMaxInputScopeMenuItemLength = 40;
// The set of the single stroke gestures known to this application
const InkApplicationGesture gc_igtSingleStrokeGestures[] = {
IAG_Scratchout, IAG_Triangle, IAG_Square, IAG_Star, IAG_Check,
IAG_Circle, IAG_DoubleCircle, IAG_Curlicue, IAG_DoubleCurlicue,
IAG_SemiCircleLeft, IAG_SemiCircleRight,
IAG_ChevronUp, IAG_ChevronDown, IAG_ChevronLeft,
IAG_ChevronRight, IAG_Up, IAG_Down, IAG_Left, IAG_Right, IAG_UpDown, IAG_DownUp,
IAG_LeftRight, IAG_RightLeft, IAG_UpLeftLong, IAG_UpRightLong, IAG_DownLeftLong,
IAG_DownRightLong, IAG_UpLeft, IAG_UpRight, IAG_DownLeft, IAG_DownRight, IAG_LeftUp,
IAG_LeftDown, IAG_RightUp, IAG_RightDown, IAG_Tap
};
// The following array of indices to the gc_igtSingleStrokeGestures makes the subset
// of gestures recommended for use in the mixed collection mode (ICM_InkAndGesture)
// (the others still can be used in the mixed mode but it's not recommended because
// of their similarity with some characters).
const UINT gc_nRecommendedForMixedMode[] = {
0 /*Scratchout*/, 3/*Star*/, 6/*Double Circle*/,
7 /*Curlicue*/, 8 /*Double Curlicue*/, 25 /*Down-Left Long*/ };
// The set of the multiple stroke gestures known to this application
const InkApplicationGesture gc_igtMultiStrokeGestures[] = {
IAG_ArrowUp, IAG_ArrowDown, IAG_ArrowLeft,
IAG_ArrowRight, IAG_Exclamation, IAG_DoubleTap
};
// The static members of the event sink templates are initialized here
// (defined in EventSinks.h)
const _ATL_FUNC_INFO IInkRecognitionEventsImpl<CAdvRecoApp>::mc_AtlFuncInfo =
{CC_STDCALL, VT_EMPTY, 3, {VT_UNKNOWN, VT_VARIANT, VT_I4}};
const _ATL_FUNC_INFO IInkCollectorEventsImpl<CAdvRecoApp>::mc_AtlFuncInfo[2] = {
{CC_STDCALL, VT_EMPTY, 3, {VT_UNKNOWN, VT_UNKNOWN, VT_BOOL|VT_BYREF}},
{CC_STDCALL, VT_EMPTY, 4, {VT_UNKNOWN, VT_UNKNOWN, VT_VARIANT, VT_BOOL|VT_BYREF}}
};
const TCHAR gc_szAppName[] = TEXT("Advanced Recognition");
/////////////////////////////////////////////////////////
//
// WinMain
//
// The WinMain function is called by the system as the
// initial entry point for a Win32-based application.
//
// Parameters:
// HINSTANCE hInstance, : [in] handle to current instance
// HINSTANCE hPrevInstance, : [in] handle to previous instance
// LPSTR lpCmdLine, : [in] command line
// int nCmdShow : [in] show state
//
// Return Values (int):
// 0 : The function terminated before entering the message loop.
// non zero: Value of the wParam when receiving the WM_QUIT message
//
/////////////////////////////////////////////////////////
int APIENTRY WinMain(
HINSTANCE hInstance,
HINSTANCE /*hPrevInstance*/, // not used here
LPSTR /*lpCmdLine*/, // not used here
int nCmdShow
)
{
int iRet = 0;
// Initialize the COM library and the application module
if (S_OK == ::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED))
{
_Module.Init(NULL, hInstance);
// Register the common control classes used by the application
INITCOMMONCONTROLSEX icc;
icc.dwSize = sizeof(icc);
icc.dwICC = ICC_LISTVIEW_CLASSES | ICC_BAR_CLASSES;
if (TRUE == ::InitCommonControlsEx(&icc))
{
// Call the boilerplate function of the application
iRet = CAdvRecoApp::Run(nCmdShow);
}
else
{
::MessageBox(NULL, TEXT("Error initializing the common controls."),
gc_szAppName, MB_ICONERROR | MB_OK);
}
// Release the module and the COM library
_Module.Term();
::CoUninitialize();
}
return iRet;
}
/////////////////////////////////////////////////////////
//
// CAdvRecoApp::Run
//
// The static CAdvRecoApp::Run is the boilerplate of the application.
// It instantiates and initializes an CAdvRecoApp object and runs the
// application's message loop.
//
// Parameters:
// int nCmdShow : [in] show state
//
// Return Values (int):
// 0 : The function terminated before entering the message loop.
// non zero: Value of the wParam when receiving the WM_QUIT message
//
/////////////////////////////////////////////////////////
int CAdvRecoApp::Run(
int nCmdShow
)
{
CAdvRecoApp theApp;
// Load and update the menu before creating the main window.
// Create menu items for the installed recognizers and for the
// supported input scopes.
HMENU hMenu = theApp.LoadMenu();
if (NULL == hMenu)
return 0;
int iRet;
// Load the icon from the resource and associate it with the window class
WNDCLASSEX& wc = CAdvRecoApp::GetWndClassInfo().m_wc;
wc.hIcon = wc.hIconSm = ::LoadIcon(_Module.GetResourceInstance(),
MAKEINTRESOURCE(IDR_APPICON));
// Create the application's main window
if (theApp.Create(NULL, CWindow::rcDefault, gc_szAppName,
WS_OVERLAPPEDWINDOW, 0, (UINT)hMenu) != NULL)
{
// Initialize the application object:
// Use no guides
theApp.SendMessage(WM_COMMAND, ID_GUIDE_NONE);
// Set input scope to default
theApp.SendMessage(WM_COMMAND, ID_INPUTSCOPE_FIRST);
// Create a recognition context with the default recognizer
theApp.SendMessage(WM_COMMAND, ID_RECOGNIZER_DEFAULT);
// Set the collection mode to ICM_InkOnly
theApp.SendMessage(WM_COMMAND, ID_MODE_INK);
// Show and update the main window
theApp.ShowWindow(nCmdShow);
theApp.UpdateWindow();
// Run the boilerplate message loop
MSG msg;
while (::GetMessage(&msg, NULL, 0, 0) > 0)
{
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
iRet = msg.wParam;
}
else
{
::MessageBox(NULL, TEXT("Error creating the window"),
gc_szAppName, MB_ICONERROR | MB_OK);
::DestroyMenu(hMenu);
iRet = 0;
}
return iRet;
}
// Window message handlers //////////////////////////////
/////////////////////////////////////////////////////////
//
// CAdvRecoApp::OnCreate
//
// This WM_CREATE message handler creates and obtains interface
// pointers to the required Automation objects, sets their
// attributes, creates the child windows and enables pen input.
//
// Parameters:
// defined in the ATL's macro MESSAGE_HANDLER,
// none of them is used here
//
// Return Values (LRESULT):
// always 0
//
/////////////////////////////////////////////////////////
LRESULT CAdvRecoApp::OnCreate(
UINT /*uMsg*/,
WPARAM /*wParam*/,
LPARAM /*lParam*/,
BOOL& /*bHandled*/
)
{
// Create child windows for ink input and recognition output,
// listview controls for the lists of gestures, and a status bar
if (false == CreateChildWindows())
return -1;
HRESULT hr;
// Create an ink collector object.
hr = m_spIInkCollector.CoCreateInstance(CLSID_InkCollector);
if (FAILED(hr))
return -1;
// Get a pointer to the ink object interface.
hr = m_spIInkCollector->get_Ink(&m_spIInkDisp);
if (FAILED(hr))
return -1;
// Get an empty ink strokes collection from the ink object.
// It'll be used as a storage for the ink
hr = m_spIInkDisp->get_Strokes(&m_spIInkStrokes);
if (FAILED(hr))
return -1;
// Establish a connection to the collector's event source.
// Depending on the selected collection mode, the application will be
// listening to either Stroke or Gesture events, or both.
hr = IInkCollectorEventsImpl<CAdvRecoApp>::DispEventAdvise(m_spIInkCollector);
// There is nothing interesting the application can do without events
// from the ink collector
if (FAILED(hr))
return -1;
// Set the recommended subset of gestures
PresetGestures();
// Enable ink input in the m_wndInput window
hr = m_spIInkCollector->put_hWnd((long)m_wndInput.m_hWnd);
if (FAILED(hr))
return -1;
hr = m_spIInkCollector->put_Enabled(VARIANT_TRUE);
if (FAILED(hr))
return -1;
// Create a recognizer guide object
if (SUCCEEDED(m_spIInkRecoGuide.CoCreateInstance(CLSID_InkRecognizerGuide)))
{
_InkRecoGuide irg;
irg.midline = -1; // not use midline
irg.cRows = irg.cColumns = 0; // no guide lines
::SetRect(&irg.rectWritingBox, 0, 0, mc_iGuideColWidth, mc_iGuideRowHeight);
// Make the guide's DrawnBox a little smaller than the the actual area
// in which a user is expected to draw the ink (WritingBox)
irg.rectDrawnBox = irg.rectWritingBox;
::InflateRect(&irg.rectDrawnBox, -mc_cxBoxMargin, -mc_cyBoxMargin);
// Set the m_wndInput's guide data
m_wndInput.SetGuide(irg);
// Use IInkRenderer to transform the guide boxes rectangles
// into ink space coordinates
hr = m_spIInkCollector->get_Renderer(&m_spIInkRenderer);
if (SUCCEEDED(hr))
{
HDC hdc = m_wndInput.GetDC();
if (NULL != hdc)
{
// Convert the guide's writing box
m_spIInkRenderer->PixelToInkSpace((long)hdc,
&irg.rectWritingBox.left,
&irg.rectWritingBox.top);
m_spIInkRenderer->PixelToInkSpace((long)hdc,
&irg.rectWritingBox.right,
&irg.rectWritingBox.bottom);
// and the drawn box
m_spIInkRenderer->PixelToInkSpace((long)hdc,
&irg.rectDrawnBox.left,
&irg.rectDrawnBox.top);
m_spIInkRenderer->PixelToInkSpace((long)hdc,
&irg.rectDrawnBox.right,
&irg.rectDrawnBox.bottom);
// Initialize the guide
hr = m_spIInkRecoGuide->put_GuideData(irg);
ReleaseDC(hdc);
}
else
{
hr = E_FAIL;
}
}
// Don't use m_spIInkRecoGuide if failed to set the guide data
if (FAILED(hr))
{
m_spIInkRecoGuide.Release();
}
}
return 0;
}
/////////////////////////////////////////////////////////
//
// CAdvRecoApp::OnDestroy
//
// The WM_DESTROY message handler.
// Used for clean up and also to post a quit message to
// the application itself.
//
// Parameters:
// defined in the ATL's macro MESSAGE_HANDLER,
// none is used here
//
// Return Values (LRESULT):
// always 0
//
/////////////////////////////////////////////////////////
LRESULT CAdvRecoApp::OnDestroy(
UINT /*uMsg*/,
WPARAM /*wParam*/,
LPARAM /*lParam*/,
BOOL& /*bHandled*/
)
{
// Disable ink input and release the InkCollector object
if (m_spIInkCollector != NULL)
{
IInkCollectorEventsImpl<CAdvRecoApp>::DispEventUnadvise(m_spIInkCollector);
m_spIInkCollector->put_Enabled(VARIANT_FALSE);
m_spIInkCollector.Release();
}
// Detach the strokes collection from the recognition context
// and stop listening to the recognition events
if (m_spIInkRecoContext != NULL)
{
m_spIInkRecoContext->EndInkInput();
IInkRecognitionEventsImpl<CAdvRecoApp>::DispEventUnadvise(m_spIInkRecoContext);
m_spIInkRecoContext.Release();
}
// Release the other objects and collections
m_spIInkRecoGuide.Release();
m_spIInkStrokes.Release();
m_spIInkRecognizers.Release();
// Post a WM_QUIT message to the application's message queue
::PostQuitMessage(0);
return 0;
}
/////////////////////////////////////////////////////////
//
// CAdvRecoApp::OnSize
//
// The WM_SIZE message handler is needed to update
// the layout of the child windows
//
// Parameters:
// defined in the ATL's macro MESSAGE_HANDLER,
// wParam of the WN_SIZE message is the only used here.
//
// Return Values (LRESULT):
// always 0
//
/////////////////////////////////////////////////////////
LRESULT CAdvRecoApp::OnSize(
UINT /*uMsg*/,
WPARAM wParam,
LPARAM /*lParam*/,
BOOL& /*bHandled*/
)
{
if (wParam != SIZE_MINIMIZED)
{
UpdateLayout();
}
return 0;
}
// InkCollector event handlers ///////////////////////////
/////////////////////////////////////////////////////////
//
// CAdvRecoApp::OnStroke
//
// The _IInkCollectorEvents's Stroke event handler.
// See the Tablet PC Automation API Reference for the
// detailed description of the event and its parameters.
//
// Parameters:
// IInkCursor* pIInkCursor : [in] not used here
// IInkStrokeDisp* pInkStroke : [in]
// VARIANT_BOOL* pbCancel : [in,out] option to cancel the gesture,
// default value is FALSE, not modified here
//
// Return Values (HRESULT):
// S_OK if succeeded, E_FAIL or E_INVALIDARG otherwise
//
/////////////////////////////////////////////////////////
HRESULT CAdvRecoApp::OnStroke(
IInkCursor* /*pIInkCursor*/,
IInkStrokeDisp* pIInkStroke,
VARIANT_BOOL* /* pbCancel */
)
{
if (NULL == pIInkStroke)
return E_INVALIDARG;
if (m_spIInkStrokes == NULL)
return S_OK;
// Add the new stroke to the collection
HRESULT hr = m_spIInkStrokes->Add(pIInkStroke);
if (SUCCEEDED(hr) && m_spIInkRecoContext != NULL)
{
// Cancel the previous background recognition requests
// which have not been processed yet
m_spIInkRecoContext->StopBackgroundRecognition();
// Update the recognition results
CComVariant vCustomData; // no custom data
m_spIInkRecoContext->BackgroundRecognizeWithAlternates(vCustomData);
}
return hr;
}
/////////////////////////////////////////////////////////
//
// CAdvRecoApp::OnGesture
//
// The _IInkCollectorEvents's Gesture event handler.
// See the Tablet PC Automation API Reference for the
// detailed description of the event and its parameters.
//
// Parameters:
// IInkCursor* pIInkCursor : [in] not used here
// IInkStrokes* pInkStrokes : [in] the collection
// VARIANT vGestures : [in] safearray of IDispatch interface pointers
// of the recognized Gesture objects
// VARIANT_BOOL* pbCancel : [in,out] option to cancel the gesture,
// default value is FALSE
//
// Return Values (HRESULT):
// S_OK if succeeded, E_FAIL or E_INVALIDARG otherwise
//
/////////////////////////////////////////////////////////
HRESULT CAdvRecoApp::OnGesture(
IInkCursor* /*pIInkCursor*/,
IInkStrokes* pInkStrokes,
VARIANT vGestures,
VARIANT_BOOL* pbCancel
)
{
if (((VT_ARRAY | VT_DISPATCH) != vGestures.vt) || (NULL == vGestures.parray))
return E_INVALIDARG;
if (0 == vGestures.parray->rgsabound->cElements)
return E_INVALIDARG;
// The gestures in the array are supposed to be ordered by their recognition
// confidence level. This sample picks up the top one.
// NOTE: when in the InkAndGesture collection mode, besides the gestures expected
// by the application there also can come a gesture object with the id IAG_NoGesture
// This application cancels the event if the object with ISG_NoGesture has
// the top confidence level (the first item in the array).
InkApplicationGesture idGesture = IAG_NoGesture;
IDispatch** ppIDispatch;
HRESULT hr = ::SafeArrayAccessData(vGestures.parray, (void HUGEP**)&ppIDispatch);
if (SUCCEEDED(hr))
{
CComQIPtr<IInkGesture> spIInkGesture(ppIDispatch[0]);
if (spIInkGesture != NULL)
{
hr = spIInkGesture->get_Id(&idGesture);
}
::SafeArrayUnaccessData(vGestures.parray);
}
// Load the name of the gesture from the resource string table
UINT idGestureName;
bool bAccepted; // will be true, if the gesture is known to this application
if (IAG_NoGesture != idGesture)
{
bAccepted = GetGestureName(idGesture, idGestureName);
}
else // ignore the event (IAG_NoGesture had the highest confidence level,
// or something has failed
{
bAccepted = false;
idGestureName = 0;
}
// If the current collection mode is ICM_GestureOnly or if we accept
// the gesture, the gesture's strokes will be removed from the ink object,
// So, the window needs to be updated in the strokes' area.
if (ID_MODE_GESTURES == m_nCmdMode || true == bAccepted)
{
// Get the rectangle to update.
RECT rc;
CComPtr<IInkRectangle> spIInkRect;
if (m_spIInkRenderer != NULL
&& pInkStrokes != NULL
&& SUCCEEDED(pInkStrokes->GetBoundingBox(IBBM_Default, &spIInkRect))
&& SUCCEEDED(spIInkRect->GetRectangle(&rc.top, &rc.left,
&rc.bottom, &rc.right)))
{
// Transform the bounding box coordinates from ink space to screen
HDC hdc = m_wndInput.GetDC();
if (NULL != hdc)
{
if (FAILED(m_spIInkRenderer->InkSpaceToPixel((long)hdc, &rc.left, &rc.top))
|| FAILED(m_spIInkRenderer->InkSpaceToPixel((long)hdc, &rc.right,
&rc.bottom)))
{
// Failed mapping from ink space to screen,
// update entire client area of the input window
m_wndInput.GetClientRect(&rc);
}
ReleaseDC(hdc);
}
else
{
// Failed getting an hdc, update entire client area of the input window
m_wndInput.GetClientRect(&rc);
}
}
else
{
// Failed getting the bounding box, update entire client area of the input window
m_wndInput.GetClientRect(&rc);
}
m_wndInput.InvalidateRect(&rc);
}
else // if something's failed,
// or the gesture is either unknown or unchecked in the list
{
// Reject the gesture. The InkCollector will fire Stroke event(s)
// for the strokes, so they'll be handled in the OnStroke method.
*pbCancel = VARIANT_TRUE;
idGestureName = IDS_GESTURE_UNKNOWN;
}
// Update the results window as well
m_wndResults.SetGestureName(idGestureName);
m_wndResults.Invalidate();
return hr;
}
// Recognition event handlers ////////////////////////////
/////////////////////////////////////////////////////////
//
// CAdvRecoApp::OnRecognitionWithAlternates
//
// The _IInkRecognitionEvents's RecognitionWithAlternates event handler
//
// Parameters:
// IInkRecognitionResult* pIInkRecoResult :
// VARIANT vCustomParam : not used here
// InkRecognitionStatus RecognitionStatus : not used here
//
// Return Values (HRESULT):
// S_OK if succeeded, E_FAIL or E_INVALIDARG otherwise
//
/////////////////////////////////////////////////////////
HRESULT CAdvRecoApp::OnRecognitionWithAlternates(
IInkRecognitionResult* pIInkRecoResult,
VARIANT /*vCustomParam*/,
InkRecognitionStatus /*RecognitionStatus*/
)
{
if (NULL == pIInkRecoResult)
return E_INVALIDARG;
// Reset the old results
m_wndResults.ResetResults();
// Get the best lCount results
HRESULT hr;
CComPtr<IInkRecognitionAlternates> spIInkRecoAlternates;
hr = pIInkRecoResult->AlternatesFromSelection(
0, // in: selection start
-1, // in: selection length; -1 means "up to the last one"
CRecoOutputWnd::mc_iNumResults, // in: the number of alternates we're interested in
&spIInkRecoAlternates // out: the receiving pointer
);
// Count the returned alternates, it may be less then we asked for
LONG lCount = 0;
if (SUCCEEDED(hr) && SUCCEEDED(spIInkRecoAlternates->get_Count(&lCount)))
{
// Get the alternate strings
IInkRecognitionAlternate* pIInkRecoAlternate = NULL;
for (LONG iItem = 0; (iItem < lCount) && (iItem < CRecoOutputWnd::mc_iNumResults); iItem++)
{
// Get the alternate string if there is one
if (SUCCEEDED(spIInkRecoAlternates->Item(iItem, &pIInkRecoAlternate)))
{
BSTR bstr = NULL;
if (SUCCEEDED(pIInkRecoAlternate->get_String(&bstr)))
{
m_wndResults.m_bstrResults[iItem].Attach(bstr);
}
pIInkRecoAlternate->Release();
}
}
}
// Update the output window with the new results
m_wndResults.Invalidate();
return S_OK;
}
// Command handlers /////////////////////////////////////
/////////////////////////////////////////////////////////
//
// CAdvRecoApp::OnRecognizer
//
// This command handler is called when user selects a recognizer
// from the "Recognizer" submenu.
//
// Parameters:
// defined in the ATL's macro COMMAND_RANGE_HANDLER
// Here only wID - the id of the command associated
// with the recognizer menu item - is used.
//
// Return Values (LRESULT):
// always 0
//
/////////////////////////////////////////////////////////
LRESULT CAdvRecoApp::OnRecognizer(
WORD /*wNotifyCode*/,
WORD wID,
HWND /*hWndCtl*/,
BOOL& /*bHandled*/
)
{
if (m_spIInkRecognizers == NULL || wID == m_nCmdRecognizer)
return 0;
// Get a pointer to the recognizer object from the recognizer collection
// Use DefaultRecognizer method to get a pointer to the default recognizer
// or use index for any other one
HRESULT hr;
CComPtr<IInkRecognizer> spIInkRecognizer;
if (ID_RECOGNIZER_DEFAULT == wID)
{
// The first parameter is the language id, passing 0 means that the language
// id will be retrieved using the user default-locale identifier
hr = m_spIInkRecognizers->GetDefaultRecognizer(0, &spIInkRecognizer);
}
else
{
hr = m_spIInkRecognizers->Item(wID - ID_RECOGNIZER_FIRST, &spIInkRecognizer);
}
// Create new recognition context
if (SUCCEEDED(hr) && UseRecognizer(spIInkRecognizer))
{
// Update the menu and the status bar
UpdateMenuRadioItems(mc_iSubmenuRecognizers, wID, m_nCmdRecognizer);
m_bstrCurRecoName.Empty();
spIInkRecognizer->get_Name(&m_bstrCurRecoName);
UpdateStatusBar();
// Store the selected recognizer's command id
m_nCmdRecognizer = wID;
}
return 0;
}
/////////////////////////////////////////////////////////
//
// CAdvRecoApp::OnInputScopeCoerce
//
// This command handler is called when user checks on or off
// the "Coerce to InputScope" menu item in the "InputScope" submenu.
//
// Parameters:
// defined in the ATL's macro COMMAND_ID_HANDLER
// Only wID - the id of the command associated
// with the clicked menu item - is used here.
//
// Return Values (LRESULT):
// always 0
//
/////////////////////////////////////////////////////////
LRESULT CAdvRecoApp::OnInputScopeCoerce(
WORD /*wNotifyCode*/,
WORD wID,
HWND /*hWndCtl*/,
BOOL& /*bHandled*/
)
{
// Ignore the command if the recognition context
// has not been created
if (m_spIInkRecoContext == NULL )
return 0;
// Need to reset the recognition context to modify the property
if (m_spIInkStrokes != NULL)
{
m_spIInkRecoContext->putref_Strokes(NULL);
}
// Reset the RecognitionFlags property of the recognition context
if (FAILED(m_spIInkRecoContext->put_RecognitionFlags (m_bCoerceInputScope?IRM_None:IRM_Coerce)))
{
MessageBox(TEXT("Failed to reset the RecognitionFlags property!"),
gc_szAppName, MB_ICONERROR | MB_OK);
// Re-attach the stroke collection to the context
if (m_spIInkStrokes != NULL)
{
m_spIInkRecoContext->putref_Strokes(m_spIInkStrokes);
}
return 0;
}
m_bCoerceInputScope = !m_bCoerceInputScope;
// Re-attach the stroke collection to the context
if (m_spIInkStrokes != NULL)
{
m_spIInkRecoContext->putref_Strokes(m_spIInkStrokes);
}
// Update the recognition results
CComVariant vCustomData; // no custom data
m_spIInkRecoContext->BackgroundRecognizeWithAlternates(vCustomData);
// Update the menu item
HMENU hMenu = GetMenu();
if (NULL != hMenu)
{
HMENU hSubMenu = ::GetSubMenu(hMenu, mc_iSubmenuInputScopes);
if (NULL != hSubMenu)
{
::CheckMenuItem(hSubMenu, wID,
MF_BYCOMMAND | (m_bCoerceInputScope ? MF_CHECKED : MF_UNCHECKED));
}
}
return 0;
}
/////////////////////////////////////////////////////////
//
// CAdvRecoApp::OnInputScope
//
// This command handler is called when user selects
// a input scope name in the "Input Scope" submenu.
//
// Parameters:
// defined in the ATL's macro COMMAND_RANGE_HANDLER
// Only wID - the id of the command associated
// with the clicked menu item - is used here.
//
// Return Values (LRESULT):
// always 0
//
/////////////////////////////////////////////////////////
LRESULT CAdvRecoApp::OnInputScope(
WORD /*wNotifyCode*/,
WORD wID,
HWND /*hWndCtl*/,
BOOL& /*bHandled*/
)
{
UINT iInputScope = wID - ID_INPUTSCOPE_FIRST; // get the index to gc_pwsInputScopes
// Return if user clicks on the currently selected Input Scope menu item or if
// the command id is not in the valid range (shouldn't happen with error free code)
if (wID == m_nCmdInputScope || iInputScope >= countof(gc_pwsInputScopes))
return 0;
// Tell the context to use the Input Scope, by assigning the input scope name
// to the corresponding property of the context.
if (m_spIInkRecoContext != NULL)
{
// Need to reset the recognition context in order to change the property
if (m_spIInkStrokes != NULL)
{
m_spIInkRecoContext->putref_Strokes(NULL);
}
CComBSTR bstrInputScope(gc_pwsInputScopes[iInputScope]);
HRESULT hr = m_spIInkRecoContext->put_Factoid(bstrInputScope);
// Re-attach the stroke collection to the context
if (m_spIInkStrokes != NULL)
{
m_spIInkRecoContext->putref_Strokes(m_spIInkStrokes);
}
if (SUCCEEDED(hr))
{
// Update the recognition results
CComVariant vCustomData; // no custom data
m_spIInkRecoContext->BackgroundRecognizeWithAlternates(vCustomData);
}
else if (TPC_E_INVALID_PROPERTY == hr) // the input scope is not supported
{
MessageBox(TEXT("This factoid is not supported by the recognizer."),
gc_szAppName, MB_ICONINFORMATION | MB_OK);
return 0;
}
else
{
MessageBox(TEXT("Failed to set the context's Factoid property!"),
gc_szAppName, MB_ICONERROR | MB_OK);
return 0;
}
}
// Update the menu and the status bar
UpdateMenuRadioItems(mc_iSubmenuInputScopes, wID, m_nCmdInputScope);
m_nCmdInputScope = wID;
UpdateStatusBar();
return 0;
}
/////////////////////////////////////////////////////////
//
// CAdvRecoApp::OnGuide
//
// This command handler is called when user selects a type
// of the recognition guide in the "Guide" submenu.
//
// Parameters:
// defined in the ATL's macro COMMAND_RANGE_HANDLER
// Only wID - the id of the command associated
// with the clicked menu item - is used here.
//
// Return Values (LRESULT):
// always 0
//
/////////////////////////////////////////////////////////
LRESULT CAdvRecoApp::OnGuide(
WORD /*wNotifyCode*/,
WORD wID,
HWND /*hWndCtl*/,
BOOL& /*bHandled*/
)
{
// Do nothing, if the InkRecoGuide object was not created,
// or user selected the currently used type of guide.
if (m_spIInkRecoGuide == NULL || wID == m_nCmdGuide)
return 0;
// The sizes of the drawing and writing boxes of the guide were set
// in CAdvRecoApp::OnCreate, when the guide object was created.
// There is no need to modify them in order to switch the guide from
// one type to another. This can be done by setting the guides properties
// Rows and Columns to 0 or not 0.
int cRows = 0, cColumns = 0;
if (ID_GUIDE_LINES == wID || ID_GUIDE_BOXES == wID)
{
cRows = mc_iNumRowsCols;
if (ID_GUIDE_BOXES == wID)
{
cColumns = mc_iNumRowsCols;
}
}
// Put the new values
if (SUCCEEDED(m_spIInkRecoGuide->put_Rows(cRows))
&& SUCCEEDED(m_spIInkRecoGuide->put_Columns(cColumns)))
{
HRESULT hr = S_OK;
if (m_spIInkRecoContext != NULL)
{
// Need to reset the recognition context in order to change the property
if (m_spIInkStrokes != NULL)
{
m_spIInkRecoContext->putref_Strokes(NULL);
}
// Set the updated guide
hr = m_spIInkRecoContext->putref_Guide(m_spIInkRecoGuide);
// Re-attach the stroke collection to the context
if (m_spIInkStrokes != NULL)
{
m_spIInkRecoContext->putref_Strokes(m_spIInkStrokes);
}
// Update the recognition results
CComVariant vCustomData; // no custom data
m_spIInkRecoContext->BackgroundRecognizeWithAlternates(vCustomData);
}
if (SUCCEEDED(hr))
{
// Update the input window (this call will force it to redraw the guide).
m_wndInput.SetRowsCols(cRows, cColumns);
// Update the menu
UpdateMenuRadioItems(mc_iSubmenuGuides, wID, m_nCmdGuide);
// store the selected guide's associated command id
m_nCmdGuide = wID;
}
else
{
MessageBox(TEXT("Error setting the guide to the recognition context.\n"),
gc_szAppName, MB_ICONERROR | MB_OK);
}
}
else
{
MessageBox(TEXT("Error setting the guide's Rows and/or Columns property.\n"),
gc_szAppName, MB_ICONERROR | MB_OK);
}
return 0;
}
/////////////////////////////////////////////////////////
//
// CAdvRecoApp::OnMode
//
// This command handler is called when user selects
// a different collection mode from the "Mode" submenu.
// NOTE: Changing collection mode has no effect on
// the recognition results of the existing strokes.
//
// Parameters:
// defined in the ATL's macro COMMAND_RANGE_HANDLER
// Only wID - the id of the command associated
// with the clicked menu item - is used here.
//
// Return Values (LRESULT):
// always 0
//
/////////////////////////////////////////////////////////
LRESULT CAdvRecoApp::OnMode(
WORD /*wNotifyCode*/,
WORD wID,
HWND /*hWndCtl*/,
BOOL& /*bHandled*/
)
{
// Do nothing, id user selected the same mode.
if (wID == m_nCmdMode)
return 0;
InkCollectionMode icm;
switch (wID)
{
default:
return 0;
case ID_MODE_INK:
icm = ICM_InkOnly;
break;
case ID_MODE_INK_AND_GESTURES:
icm = ICM_InkAndGesture;
break;
case ID_MODE_GESTURES:
icm = ICM_GestureOnly;
break;
}
// Disable input to switch the collection mode
if (m_spIInkCollector != NULL
&& SUCCEEDED(m_spIInkCollector->put_Enabled(VARIANT_FALSE)))
{
// Set the new mode
if (SUCCEEDED(m_spIInkCollector->put_CollectionMode(icm)))
{
// Update the menu
UpdateMenuRadioItems(mc_iSubmenuModes, wID, m_nCmdMode);
m_nCmdMode = wID; // store the selected mode's associated command id
// Show or hide the gesture list views
UpdateLayout();
}
else
{
TCHAR* pszErrorMsg;
if (ID_MODE_INK == m_nCmdMode)
{
pszErrorMsg = TEXT("Unable to change the CollectionMode property on the ")
TEXT("InkCollector.\nA possible reason is that the selected ")
TEXT("mode requires there to be a gesture recognizer installed.");
}
else
{
pszErrorMsg = TEXT("Unable to change the CollectionMode property ")
TEXT("on the InkCollector.");
}
MessageBox(pszErrorMsg, gc_szAppName, MB_ICONERROR | MB_OK);
}
// Enable input
if (FAILED(m_spIInkCollector->put_Enabled(VARIANT_TRUE)))
{
MessageBox(TEXT("Error enabling InkCollector after changing collection mode!"),
gc_szAppName, MB_ICONERROR | MB_OK);
}
}
return 0;
}
/////////////////////////////////////////////////////////
//
// CAdvRecoApp::OnRecognize
//
// This command handler is called when user clicks on "Recognize"
// in the Ink menu.
// It's required for East Asian recognizers to finalize the recognition
//
// Parameters:
// defined in the ATL's macro COMMAND_ID_HANDLER
// none of them is used here
//
// Return Values (LRESULT):
// always 0
//
/////////////////////////////////////////////////////////
LRESULT CAdvRecoApp::OnRecognize(
WORD /*wNotifyCode*/,
WORD /*wID*/,
HWND /*hWndCtl*/,
BOOL& /*bHandled*/
)
{
if (m_spIInkRecoContext != NULL)
{
m_spIInkRecoContext->EndInkInput();
// Recognize
CComPtr<IInkRecognitionResult> spIInkRecoResult;
InkRecognitionStatus ink_reco_status = IRS_NoError;
if (SUCCEEDED(m_spIInkRecoContext->Recognize(&ink_reco_status, &spIInkRecoResult)))
{
CComVariant vCustomData; // no custom data
OnRecognitionWithAlternates(spIInkRecoResult, vCustomData, ink_reco_status);
}
else
{
MessageBox(TEXT("Error code returned from the context's Recognize method."),
gc_szAppName, MB_ICONERROR | MB_OK);
}
// Re-attach the stroke collection to the context
if (m_spIInkStrokes != NULL)
{
if (FAILED(m_spIInkRecoContext->putref_Strokes(m_spIInkStrokes)))
{
MessageBox(TEXT("Failed to attach the stroke collection to the recognition context!"),
gc_szAppName, MB_ICONERROR | MB_OK);
}
}
}
return 0;
}
/////////////////////////////////////////////////////////
//
// CAdvRecoApp::OnClear
//
// This command handler is called when user clicks on "Clear"
// in the Ink menu. It's supposed to delete the collected ink,
// and update the child windows after that.
//
// Parameters:
// defined in the ATL's macro COMMAND_ID_HANDLER
// none of them is used here
//
// Return Values (LRESULT):
// always 0
//
/////////////////////////////////////////////////////////
LRESULT CAdvRecoApp::OnClear(
WORD /*wNotifyCode*/,
WORD /*wID*/,
HWND /*hWndCtl*/,
BOOL& /*bHandled*/
)
{
if (m_spIInkDisp != NULL)
{
// Delete all strokes from the Ink object, ignore returned value
m_spIInkDisp->DeleteStrokes(0);
// Release the old stroke collection and get an empty one
m_spIInkStrokes.Release();
CComVariant vt(0);
if (FAILED(m_spIInkDisp->CreateStrokes(vt, &m_spIInkStrokes)))
{
MessageBox(TEXT("Failed to get the stroke collection from the Ink object!"),
gc_szAppName, MB_ICONERROR | MB_OK);
}
// Attach the new stroke collection to the recognition context.
// If get_Strokes has failed, m_spIInkStrokes is NULL, so no
// stroke collection will be attached to the context
if (m_spIInkRecoContext != NULL)
{
if (FAILED(m_spIInkRecoContext->putref_Strokes(m_spIInkStrokes)))
{
MessageBox(TEXT("Failed to attach the stroke collection to the recognition context!"),
gc_szAppName, MB_ICONERROR | MB_OK);
}
}
}
// Update the child windows
m_wndResults.ResetResults(); // empties the strings
m_wndResults.Invalidate();
m_wndInput.Invalidate();
return 0;
}
/////////////////////////////////////////////////////////
//
// CAdvRecoApp::OnExit
//
// This command handler is called when user clicks
// on "Exit" in the Ink menu.
//
// Parameters:
// defined in the ATL's macro COMMAND_ID_HANDLER
// none of them is used here
//
// Return Values (LRESULT):
// always 0
//
/////////////////////////////////////////////////////////
LRESULT CAdvRecoApp::OnExit(
WORD /*wNotifyCode*/,
WORD /*wID*/,
HWND /*hWndCtl*/,
BOOL& /*bHandled*/
)
{
// Close the application window
SendMessage(WM_CLOSE);
return 0;
}
// Helper methods //////////////////////////////
/////////////////////////////////////////////////////////
//
// CAdvRecoApp::LoadMenu
//
// This method instantiates an enumerator object for the installed
// recognizers, loads the main menu resource and creates a menu item
// for each recognizer from the collection.
// Also, it fills the Input Scope menu with the items for the supported
// Input Scopes.
//
// Parameters:
// none
//
// Return Values (HMENU):
// The return value is a handle of the menu
// that'll be used for the main window
//
/////////////////////////////////////////////////////////
HMENU CAdvRecoApp::LoadMenu()
{
HRESULT hr = S_OK;
// Create the enumerator for the installed recognizers
hr = m_spIInkRecognizers.CoCreateInstance(CLSID_InkRecognizers);
if (FAILED(hr))
return NULL;
// Get the number of the recognizers known to the system
LONG lCount = 0;
hr = m_spIInkRecognizers->get_Count(&lCount);
if (0 == lCount)
{
::MessageBox(NULL, TEXT("There are no handwriting recognizers installed.\n")
TEXT("You need to have at least one in order to run this sample.\nExiting."),
gc_szAppName, MB_ICONERROR | MB_OK);
return NULL;
}
// Load the menu of the main window
HMENU hMenu = ::LoadMenu(_Module.GetResourceInstance(), MAKEINTRESOURCE(IDR_MENU));
if (NULL == hMenu)
return NULL; // Not normal
MENUITEMINFOW miinfo;
memset(&miinfo, 0, sizeof(miinfo));
miinfo.cbSize = sizeof(miinfo);
miinfo.fMask = MIIM_ID | MIIM_STATE | MIIM_FTYPE | MIIM_STRING;
miinfo.fType = MFT_RADIOCHECK | MFT_STRING;
// Get the submenu id for the recognizers menu.
// This submenu will contain the list of installed recognizers
HMENU hSubMenu = ::GetSubMenu(hMenu, mc_iSubmenuRecognizers);
if (hSubMenu)
{
CComPtr<IInkRecognizer> spIInkRecognizer;
// The ID_RECOGNIZER_FIRST is defined in the resource.h file.
// It's the base id for the commands that fill this submenu
miinfo.wID = ID_RECOGNIZER_FIRST;
miinfo.fState = 0;
for (LONG i = 0; i < lCount; i++, miinfo.wID++)
{
if (FAILED(m_spIInkRecognizers->Item(i, &spIInkRecognizer)))
continue;
// Filter out non-language recognizers by checking for
// the languages supported by the recognizer - there'll be
// none if it's a gesture or object recognizer.
CComVariant vLanguages;
if (SUCCEEDED(spIInkRecognizer->get_Languages(&vLanguages)))
{
if ((VT_ARRAY == (VT_ARRAY & vLanguages.vt)) // it should be an array
&& (NULL != vLanguages.parray)
&& (0 < vLanguages.parray->rgsabound[0].cElements)) // with at least one element
{
CComBSTR bstrName;
if (SUCCEEDED(spIInkRecognizer->get_Name(&bstrName)))
{
miinfo.dwTypeData = bstrName;
::InsertMenuItemW(hSubMenu, (UINT)-1, TRUE, &miinfo);
}
}
spIInkRecognizer.Release();
}
}
}
// Get the Input Scope submenu and create command items
// for each input scope name defined in gc_pwsInputScopes[]
hSubMenu = ::GetSubMenu(hMenu, mc_iSubmenuInputScopes);
if (hSubMenu)
{
// The ID_INPUTSCOPE_FIRST is defined in the resource.h file.
// It's the base id for the commands that fill this submenu
miinfo.wID = ID_INPUTSCOPE_FIRST;
miinfo.fState = 0;
lCount = countof(gc_pwsInputScopes);
for(LONG i = 0; i < lCount; i++, miinfo.wID++)
{
// Set the input scope menu item text. If the text
// length exceeds the maximum allowed value, then truncate
// it and append "...".
if (wcslen(gc_pwsInputScopes[i]) <= gc_lMaxInputScopeMenuItemLength)
{
miinfo.dwTypeData = gc_pwsInputScopes[i];
}
else
{
CComBSTR bstrInputScope(gc_lMaxInputScopeMenuItemLength-3, gc_pwsInputScopes[i]);
bstrInputScope += L"...";
miinfo.dwTypeData = bstrInputScope;
}
::InsertMenuItemW(hSubMenu, (UINT)-1, TRUE, &miinfo);
}
}
return hMenu;
}
/////////////////////////////////////////////////////////
//
// CAdvRecoApp::UpdateInputScopeMenu
//
// This helper method updates the enabled/disabled state
// of submenus in the InputScope menu.
// It's called whenever the user selects a different
// recognizer. Verification of support for the InputScope
// is accomplished by calling put_Factoid, and checking
// the returned HRESULT.
//
// Parameters:
// none
//
// Return Values (void):
// none
//
/////////////////////////////////////////////////////////
void CAdvRecoApp::UpdateInputScopeMenu()
{
HMENU hMenu = GetMenu();
if (NULL != hMenu)
{
HMENU hSubMenu = ::GetSubMenu(hMenu, mc_iSubmenuInputScopes);
if (NULL != hSubMenu)
{
if (m_spIInkRecoContext != NULL)
{
// Cache the current Factoid property so that we can revert later
CComBSTR bstrInputScope;
if (FAILED(m_spIInkRecoContext->get_Factoid(&bstrInputScope)))
{
MessageBox(TEXT("Failed to get the context's Factoid property."),
gc_szAppName, MB_ICONERROR | MB_OK);
return;
}
// Need to reset the recognition context in order to change the Factoid property
if (m_spIInkStrokes != NULL)
{
m_spIInkRecoContext->putref_Strokes(NULL);
}
// Enable or disable the InputScope submenu items
MENUITEMINFO miinfo;
memset(&miinfo, 0, sizeof(miinfo));
miinfo.cbSize = sizeof(miinfo);
miinfo.fMask = MIIM_STATE;
miinfo.wID = ID_INPUTSCOPE_FIRST;
for(LONG i = 0; i < countof(gc_pwsInputScopes); i++, miinfo.wID++)
{
CComBSTR bstrTestInputScope(gc_pwsInputScopes[i]);
HRESULT hr = m_spIInkRecoContext->put_Factoid(bstrTestInputScope);
if (FAILED(hr))
{
miinfo.fState = MFS_DISABLED;
}
else
{
miinfo.fState = MFS_ENABLED;
}
::SetMenuItemInfo(hSubMenu, miinfo.wID, FALSE, &miinfo);
}
// Revert to the cached Factoid property
if (FAILED(m_spIInkRecoContext->put_Factoid(bstrInputScope)))
{
MessageBox(TEXT("Failed to set the context's Factoid property."),
gc_szAppName, MB_ICONERROR | MB_OK);
}
// Re-attach the stroke collection to the context
if (m_spIInkStrokes != NULL)
{
m_spIInkRecoContext->putref_Strokes(m_spIInkStrokes);
}
}
}
}
}
/////////////////////////////////////////////////////////
//
// CAdvRecoApp::UpdateMenuRadioItems
//
// As it follows from the name, this helper method updates
// the specified radio items in the submenu.
// It's called for the appropriate items, whenever user selects
// a different recognizer, input scope, or guide mode.
//
// Parameters:
// UINT iSubMenu : [in] the submenu to make updates in
// UINT idCheck : [in] the menu item to check
// UINT idUncheck : [in] the menu item to uncheck
//
// Return Values (void):
// none
//
/////////////////////////////////////////////////////////
void CAdvRecoApp::UpdateMenuRadioItems(
UINT iSubMenu,
UINT idCheck,
UINT idUncheck
)
{
// Update the menu
HMENU hMenu = GetMenu();
if (NULL != hMenu)
{
HMENU hSubMenu = ::GetSubMenu(hMenu, iSubMenu);
if (NULL != hSubMenu)
{
MENUITEMINFO miinfo;
miinfo.cbSize = sizeof(miinfo);
miinfo.fMask = MIIM_STATE | MIIM_FTYPE;
::GetMenuItemInfo(hSubMenu, idCheck, FALSE, &miinfo);
miinfo.fType |= MFT_RADIOCHECK;
miinfo.fState |= MFS_CHECKED;
::SetMenuItemInfo(hSubMenu, idCheck, FALSE, &miinfo);
if (0 != idUncheck)
{
::GetMenuItemInfo(hSubMenu, idUncheck, FALSE, &miinfo);
miinfo.fType |= MFT_RADIOCHECK;
miinfo.fState &= ~MFS_CHECKED;
::SetMenuItemInfo(hSubMenu, idUncheck, FALSE, &miinfo);
}
}
}
}
/////////////////////////////////////////////////////////
//
// CAdvRecoApp::UseRecognizer
//
// As it follows from the name, this helper method updates
// the specified radio items in the submenu.
// It's called for the appropriate items, whenever user selects
// a different recognizer, input scope, or guide mode.
//
// Parameters:
// IInkRecognizer* pIInkRecognizer : [in] the newly selected recognizer
//
// Return Values (bool):
// true if succeeded creating new recognition context, false otherwise
//
/////////////////////////////////////////////////////////
bool CAdvRecoApp::UseRecognizer(
IInkRecognizer* pIInkRecognizer
)
{
if (NULL == pIInkRecognizer)
return false;
// Create a new recognition context
CComPtr<IInkRecognizerContext> spNewContext;
HRESULT hr = pIInkRecognizer->CreateRecognizerContext(&spNewContext);
if (FAILED(hr))
{
MessageBox(TEXT("Error creating a new recognition context!"),
gc_szAppName, MB_ICONERROR | MB_OK);
return false;
}
// Change cursor to the system's Hourglass
HCURSOR hCursor = ::SetCursor(::LoadCursor(NULL, IDC_WAIT));
// Detach and release the old context
if (m_spIInkRecoContext != NULL)
{
// Disconnect from the recognition events source
IInkRecognitionEventsImpl<CAdvRecoApp>::DispEventUnadvise(m_spIInkRecoContext);
// Reset and release the recognition context
m_spIInkRecoContext->putref_Strokes(NULL);
m_spIInkRecoContext.Release();
}
// Establish a connection with the recognition context's event source
hr = IInkRecognitionEventsImpl<CAdvRecoApp>::DispEventAdvise(spNewContext);
if (FAILED(hr))
{
MessageBox(TEXT("Error connecting to the recognition context's event source!"),
gc_szAppName, MB_ICONERROR | MB_OK);
}
// Get the recognizer's capabilities flags.
// (use IRC__DontCare if get_Capabilities fails)
InkRecognizerCapabilities dwCapabilities;
if (FAILED(pIInkRecognizer->get_Capabilities(&dwCapabilities)))
{
dwCapabilities = IRC_DontCare;
}
// Update the Guide menu items
const UINT nGuideCmds[] = {ID_GUIDE_BOXES, ID_GUIDE_LINES, ID_GUIDE_NONE};
const UINT nGuideFlags[] = {IRC_BoxedInput, IRC_LinedInput, IRC_FreeInput};
HMENU hMenu = GetMenu();
HMENU hSubMenu = hMenu ? ::GetSubMenu(hMenu, mc_iSubmenuGuides) : NULL;
UINT nCmdGuid = 0;
for (ULONG i = 0; i < countof(nGuideCmds) && i < countof(nGuideFlags); i++)
{
UINT nFlags;
if (m_spIInkRecoGuide != NULL &&
(((nGuideFlags[i] & dwCapabilities) == nGuideFlags[i])
|| ((IRC_DontCare & dwCapabilities) == IRC_DontCare)))
{
nFlags = MF_BYCOMMAND | MF_ENABLED;
if ((0 == nCmdGuid) || (nGuideCmds[i] == m_nCmdGuide))
{
nCmdGuid = nGuideCmds[i];
}
}
else
{
nFlags = MF_BYCOMMAND | MF_GRAYED;
}
// Update the menu item
if (NULL != hSubMenu)
{
::EnableMenuItem(hSubMenu, nGuideCmds[i], nFlags);
}
}
// Change the guide selection if the current guide is not supported by the recognizer
if (m_nCmdGuide != nCmdGuid)
{
SendMessage(WM_COMMAND, nCmdGuid);
}
// Set the recognition context properties before attaching the stroke collection to it
// Set the guide
if (m_spIInkRecoGuide != NULL && 0 != m_nCmdGuide)
{
if (FAILED(spNewContext->putref_Guide(m_spIInkRecoGuide)))
{
MessageBox(TEXT("Failed to set guide to the new recognition context!"),
gc_szAppName, MB_ICONERROR | MB_OK);
}
}
// Clear currently set input scope. -1 is being used to indicate no input scope
m_nCmdInputScope = -1;
m_bCoerceInputScope = false;
// Reset the Input Scope to baseline
CComBSTR bstrFactoid(FACTOID_DEFAULT);
if (FAILED(spNewContext->put_Factoid(bstrFactoid)))
{
MessageBox(TEXT("Failed to set factoid to the new recognition context!"),
gc_szAppName, MB_ICONERROR | MB_OK);
}
// Attach the stroke collection to the context
hr = spNewContext->putref_Strokes(m_spIInkStrokes);
if (FAILED(hr))
{
MessageBox(TEXT("Error attaching the stroke collection to the new recognition context!"),
gc_szAppName, MB_ICONERROR | MB_OK);
}
// Use the new context
m_spIInkRecoContext.Attach(spNewContext.Detach());
// Update the radio item check state of the input scope menu
UpdateMenuRadioItems(mc_iSubmenuInputScopes, ID_INPUTSCOPE_FIRST, m_nCmdInputScope);
// Update the enabled/disabled state of the input scope menu
UpdateInputScopeMenu();
// Update the coerce input scope menu item
hSubMenu = hMenu ? ::GetSubMenu(hMenu, mc_iSubmenuInputScopes) : NULL;
if (NULL != hSubMenu)
{
::CheckMenuItem(hSubMenu, ID_INPUTSCOPE_COERCE, MF_BYCOMMAND | MF_UNCHECKED);
}
// Select an appropriate font for the recognition output
LANGID wLandId = ::GetUserDefaultLangID();
CComVariant vLangIDs;
if (SUCCEEDED(pIInkRecognizer->get_Languages(&vLangIDs)) && NULL != vLangIDs.parray)
{
WORD* pwLIDs;
if (SUCCEEDED(::SafeArrayAccessData(vLangIDs.parray, (void HUGEP**)&pwLIDs)))
{
wLandId = pwLIDs[0];
::SafeArrayUnaccessData(vLangIDs.parray);
}
}
if (false == m_wndResults.UpdateFont(wLandId))
{
MessageBox(TEXT("Can not find an appropriate font for the selected recognizer.\n")
TEXT("The default font will be used for text output"), gc_szAppName);
}
// Reset the current results
m_wndResults.ResetResults();
m_wndResults.Invalidate();
// Update the recognition results
CComVariant vCustomData; // no custom data
m_spIInkRecoContext->BackgroundRecognizeWithAlternates(vCustomData);
// restore the cursor
::SetCursor(hCursor);
return true;
}
/////////////////////////////////////////////////////////
//
// CAdvRecoApp::OnLVColumnClick
//
// When user clicks on the column header in either of the gesture
// listview controls, the CAdvRecoApp object receives a WM_NOTIFY
// message that is mapped to this handler for the actual processing.
// The application checks or unchecks all the items in the control
// the notification came from.
//
// Parameters:
// defined in the ATL's macro NOTIFY_HANDLER
//
// Return Values (LRESULT):
// always 0
//
/////////////////////////////////////////////////////////
LRESULT CAdvRecoApp::OnLVColumnClick(
int idCtrl,
LPNMHDR /*pnmh*/,
BOOL& bHandled
)
{
if (mc_iSSGestLVId == idCtrl)
{
m_bAllSSGestures = !m_bAllSSGestures;
ListView_SetCheckState(m_hwndSSGestLV, -1, m_bAllSSGestures);
}
else if (mc_iMSGestLVId == idCtrl)
{
m_bAllMSGestures = !m_bAllMSGestures;
ListView_SetCheckState(m_hwndMSGestLV, -1, m_bAllMSGestures);
}
else
{
bHandled = FALSE;
}
return 0;
}
/////////////////////////////////////////////////////////
//
// CAdvRecoApp::OnLVItemChanging
//
// When user checks or unchecks an item in either of the gesture
// listview controls, the state of the item is changing and the CAdvRecoApp
// object receives a WM_NOTIFY message that is mapped to this handler
// for the actual processing.
// The application set or reset the gesture status in the InkCollector.
//
// Parameters:
// defined in the ATL's macro NOTIFY_HANDLER
//
// Return Values (LRESULT):
// TRUE to
//
/////////////////////////////////////////////////////////
LRESULT CAdvRecoApp::OnLVItemChanging(
int idCtrl,
LPNMHDR pnmh,
BOOL& /*bHandled*/
)
{
if (m_spIInkCollector == NULL)
return FALSE;
LPNMLISTVIEW pnmv = (LPNMLISTVIEW)pnmh;
LRESULT lRet;
// Ignore all the changes which are not of the item state,
// and the item state changes other then checked/unchecked (LVIS_STATEIMAGEMASK)
if (LVIF_STATE == pnmv->uChanged && 0 != (LVIS_STATEIMAGEMASK & pnmv->uNewState))
{
lRet = TRUE; // prevent the change if something is wrong
BOOL bChecked = ((LVIS_STATEIMAGEMASK & pnmv->uNewState) >> 12) == 2;
InkApplicationGesture igtGesture = IAG_NoGesture;
if (mc_iSSGestLVId == idCtrl)
{
if (pnmv->iItem >= 0 && pnmv->iItem < countof(gc_igtSingleStrokeGestures))
igtGesture = gc_igtSingleStrokeGestures[pnmv->iItem];
}
else if (mc_iMSGestLVId == idCtrl)
{
if (pnmv->iItem >= 0 && pnmv->iItem < countof(gc_igtMultiStrokeGestures))
igtGesture = gc_igtMultiStrokeGestures[pnmv->iItem];
}
if (IAG_NoGesture != igtGesture && SUCCEEDED(
m_spIInkCollector->SetGestureStatus(igtGesture, bChecked ? VARIANT_TRUE : VARIANT_FALSE)))
{
// Allow the change in the control's item state
lRet = FALSE;
}
}
else
{
// Allow all the other changes
lRet = FALSE;
}
return lRet;
}
/////////////////////////////////////////////////////////
//
// CAdvRecoApp::CreateChildWindows
//
// This helper method is called from WM_CREATE message handler.
// The child windows and controls are created and initialized here.
//
// Parameters:
// none
//
// Return Values (bool):
// true if the windows have been created successfully,
// false otherwise
//
/////////////////////////////////////////////////////////
bool CAdvRecoApp::CreateChildWindows()
{
if ((m_wndInput.Create(m_hWnd, CWindow::rcDefault, NULL,
WS_CHILD, WS_EX_CLIENTEDGE, (UINT)mc_iInputWndId) == NULL)
|| (m_wndResults.Create(m_hWnd, CWindow::rcDefault, NULL,
WS_CHILD, WS_EX_CLIENTEDGE, (UINT)mc_iOutputWndId) == NULL))
{
return false;
}
HINSTANCE hInst = _Module.GetResourceInstance();
// Create a listview control for the list of the single stroke gestures
m_hwndSSGestLV = ::CreateWindowEx(WS_EX_CLIENTEDGE, WC_LISTVIEW, NULL,
WS_VISIBLE | WS_CHILD | WS_BORDER | LVS_REPORT,
0, 0, 1, 1,
m_hWnd, (HMENU)mc_iSSGestLVId,
_Module.GetModuleInstance(), NULL);
if (NULL == m_hwndSSGestLV)
return false;
//
ListView_SetExtendedListViewStyleEx(m_hwndSSGestLV, LVS_EX_CHECKBOXES, LVS_EX_CHECKBOXES);
// Create a column
LV_COLUMN lvC;
lvC.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
lvC.fmt = LVCFMT_LEFT;
lvC.iSubItem = 0;
lvC.cx = mc_cxGestLVWidth - 22;
lvC.pszText = TEXT("Single Stroke Gestures");
if (-1 == ListView_InsertColumn(m_hwndSSGestLV, lvC.iSubItem, &lvC))
return false;
// Insert items - the names of the single stroke gestures.
TCHAR szText[100]; // large enough to load a gesture name into
LV_ITEM lvItem;
lvItem.mask = LVIF_TEXT /*| LVIF_IMAGE*/ | LVIF_STATE;
lvItem.state = 0;
lvItem.stateMask = 0;
lvItem.pszText = szText;
lvItem.iSubItem = 0;
for (ULONG i = 0; i < mc_cNumSSGestures; i++)
{
lvItem.iItem = i;
// Load the names from the application resource, there should be
// mc_cNumSSGestures names there with sequential id's starting
// with IDS_SSGESTURE_FIRST
::LoadString(hInst, IDS_SSGESTURE_FIRST + i, szText, countof(szText));
if (-1 == ListView_InsertItem(m_hwndSSGestLV, &lvItem))
return false;
}
// Create a listview control for the list of the multi-stroke gestures
m_hwndMSGestLV = ::CreateWindowEx(WS_EX_CLIENTEDGE, WC_LISTVIEW, NULL,
WS_VISIBLE | WS_CHILD | WS_BORDER | LVS_REPORT,
0, 0, 1, 1,
m_hWnd, (HMENU)mc_iMSGestLVId,
_Module.GetModuleInstance(), NULL);
if (NULL == m_hwndMSGestLV)
return false;
//
ListView_SetExtendedListViewStyleEx(m_hwndMSGestLV, LVS_EX_CHECKBOXES, LVS_EX_CHECKBOXES);
// Create a column
lvC.pszText = TEXT("Multiple Stroke Gestures");
ListView_InsertColumn(m_hwndMSGestLV, lvC.iSubItem, &lvC);
// Insert items - the names of the single stroke gestures.
for (ULONG i = 0; i < mc_cNumMSGestures; i++)
{
lvItem.iItem = i;
// Load the names from the application resource, there should be
// mc_cNumMSGestures names there with sequential id's starting
// with IDS_MSGESTURE_FIRST
::LoadString(hInst, IDS_MSGESTURE_FIRST + i, szText, countof(szText));
if (-1 == ListView_InsertItem(m_hwndMSGestLV, &lvItem))
return false;
}
// Create a status bar (Ignore if it fails, the application can live without it).
m_hwndStatusBar = ::CreateStatusWindow(
WS_CHILD|WS_VISIBLE|WS_CLIPCHILDREN|WS_CLIPSIBLINGS|SBARS_SIZEGRIP,
NULL, m_hWnd, (UINT)mc_iStatusWndId);
if (NULL != m_hwndStatusBar)
{
::SendMessage(m_hwndStatusBar,
WM_SETFONT,
(LPARAM)::GetStockObject(DEFAULT_GUI_FONT), FALSE);
}
// Update the child windows' positions and sizes so that they cover
// entire client area of the main window.
UpdateLayout();
return true;
}
/////////////////////////////////////////////////////////
//
// CAdvRecoApp::UpdateLayout
//
// This helper method is called when the size of the main
// window has been changed and the child windows' positions
// need to be updated so that they cover entire client area
// of the main window.
//
// Parameters:
// none
//
// Return Values (void):
// none
//
/////////////////////////////////////////////////////////
void CAdvRecoApp::UpdateLayout()
{
RECT rect;
GetClientRect(&rect);
// update the size and position of the status bar
if (::IsWindow(m_hwndStatusBar)
&& ((DWORD)::GetWindowLong(m_hwndStatusBar, GWL_STYLE) & WS_VISIBLE))
{
::SendMessage(m_hwndStatusBar, WM_SIZE, 0, 0);
RECT rectStatusBar;
::GetWindowRect(m_hwndStatusBar, &rectStatusBar);
if (rect.bottom > rectStatusBar.bottom - rectStatusBar.top)
{
rect.bottom -= rectStatusBar.bottom - rectStatusBar.top;
}
else
{
rect.bottom = 0;
}
}
// update the size and position of the gesture listviews
if (::IsWindow(m_hwndSSGestLV) && ::IsWindow(m_hwndMSGestLV))
{
if (ID_MODE_INK != m_nCmdMode)
{
// calculate the rectangle covered by the list views
RECT rcGest = rect;
if (rcGest.right < mc_cxGestLVWidth)
{
rcGest.left = 0;
}
else
{
rcGest.left = rcGest.right - mc_cxGestLVWidth;
}
rect.right = rcGest.left;
if (ID_MODE_GESTURES == m_nCmdMode)
{
int iHeight;
RECT rcItem;
if (TRUE == ListView_GetItemRect(m_hwndMSGestLV, 0, &rcItem, LVIR_BOUNDS))
{
iHeight = rcItem.top + (rcItem.bottom - rcItem.top)
* (countof(gc_igtMultiStrokeGestures) + 1);
}
else
{
iHeight = (rcGest.bottom - rcGest.top) / 3;
}
// show the multiple stroke gesture listview control
::SetWindowPos(m_hwndMSGestLV, NULL,
rcGest.left, rcGest.bottom - iHeight,
rcGest.right - rcGest.left, iHeight,
SWP_NOZORDER | SWP_NOACTIVATE | SWP_SHOWWINDOW);
rcGest.bottom -= iHeight;
}
else if (WS_VISIBLE ==
(((DWORD)::GetWindowLong(m_hwndMSGestLV, GWL_STYLE)) & WS_VISIBLE))
{
// hide the multiple stroke gesture listview control
::ShowWindow(m_hwndMSGestLV, SW_HIDE);
}
// show the single stroke gesture listview control
::SetWindowPos(m_hwndSSGestLV, NULL,
rcGest.left, rcGest.top,
rcGest.right - rcGest.left, rcGest.bottom - rcGest.top,
SWP_NOZORDER | SWP_NOACTIVATE | SWP_SHOWWINDOW);
}
else
{
// hide the single stroke gesture listview control
if (WS_VISIBLE ==
(((DWORD)::GetWindowLong(m_hwndSSGestLV, GWL_STYLE)) & WS_VISIBLE))
{
::ShowWindow(m_hwndSSGestLV, SW_HIDE);
}
// hide the multiple stroke gesture listview control
if (WS_VISIBLE ==
(((DWORD)::GetWindowLong(m_hwndMSGestLV, GWL_STYLE)) & WS_VISIBLE))
{
::ShowWindow(m_hwndMSGestLV, SW_HIDE);
}
}
}
// update the size and position of the output window
if (::IsWindow(m_wndResults.m_hWnd))
{
int cyResultsWnd = m_wndResults.GetBestHeight();
if (cyResultsWnd > rect.bottom)
{
cyResultsWnd = rect.bottom;
}
::SetWindowPos(m_wndResults.m_hWnd, NULL,
rect.left, rect.bottom - cyResultsWnd,
rect.right - rect.left, cyResultsWnd - rect.top,
SWP_NOZORDER | SWP_NOACTIVATE | SWP_SHOWWINDOW);
rect.bottom -= cyResultsWnd;
}
// update the size and position of the ink input window
if (::IsWindow(m_wndInput.m_hWnd))
{
::SetWindowPos(m_wndInput.m_hWnd, NULL,
rect.left, rect.top,
rect.right - rect.left, rect.bottom - rect.top,
SWP_NOZORDER | SWP_NOACTIVATE | SWP_SHOWWINDOW);
}
}
/////////////////////////////////////////////////////////
//
// CAdvRecoApp::UpdateStatusBar
//
// This helper function outputs the names of the currently selected
// recognizer and input scope to the application's status bar.
//
// Parameters:
// none
//
// Return Values (void):
// none
//
/////////////////////////////////////////////////////////
void CAdvRecoApp::UpdateStatusBar()
{
if (NULL == m_hwndStatusBar)
return;
UINT iInputScope = m_nCmdInputScope - ID_INPUTSCOPE_FIRST;
if (iInputScope >= sizeof(gc_pwsInputScopes)/sizeof(gc_pwsInputScopes[0]))
{
iInputScope = 0;
}
CComBSTR bstrStatus(m_bstrCurRecoName);
// If an input scope is set, add to the status bar string
if (m_nCmdInputScope != -1)
{
bstrStatus += L"; Input Scope: ";
bstrStatus += gc_pwsInputScopes[iInputScope];
}
// Set the new text int the status bar
::SendMessage(m_hwndStatusBar, SB_SETTEXTW, NULL, (LPARAM)bstrStatus.m_str);
}
/////////////////////////////////////////////////////////
//
// CAdvRecoApp::GetGestureName
//
// This helper function returns the resource id of
// the string with the name of the given gesture.
//
// Parameters:
// InkApplicationGesture idGesture : [in] the gesture's id
// UINT& idGestureName : [out] the id of the string with the the name
// of the gesturein the resource string table
// Return Values (bool):
// true if the gesture is known to the application, false otherwise
//
/////////////////////////////////////////////////////////
bool CAdvRecoApp::GetGestureName(
InkApplicationGesture igtGesture,
UINT& idGestureName
)
{
idGestureName = IDS_GESTURE_UNKNOWN;
// This function should not be called when the collection mode is ICM_InkOnly
if (ID_MODE_INK == m_nCmdMode)
return false;
// First, try to find the gesture among the single stroke ones
ULONG iCount = countof(gc_igtSingleStrokeGestures);
ULONG i;
for (i = 0; i < iCount; i++)
{
if (gc_igtSingleStrokeGestures[i] == igtGesture)
{
idGestureName = IDS_SSGESTURE_FIRST + i;
break;
}
}
// If this is not a known single stroke gesture and the current collection mode
// is ICM_GestureOnly, this may be a multi-stroke one.
if (i == iCount && ID_MODE_GESTURES == m_nCmdMode)
{
iCount = countof(gc_igtMultiStrokeGestures);
for (i = 0; i < iCount; i++)
{
if (gc_igtMultiStrokeGestures[i] == igtGesture)
{
idGestureName = IDS_MSGESTURE_FIRST + i;
break;
}
}
}
return (IDS_GESTURE_UNKNOWN != idGestureName);
}
/////////////////////////////////////////////////////////
//
// CAdvRecoApp::PresetGestures
//
// Sets the status of the recommended subset of gestures
// to TRUE in InkCollector
//
// Parameters:
// none
//
// Return Values (void):
// none
//
/////////////////////////////////////////////////////////
void CAdvRecoApp::PresetGestures()
{
// This function should not be called before the listview controls have been created
if (0 == ::IsWindow(m_hwndSSGestLV) || 0 == ::IsWindow(m_hwndMSGestLV))
return;
// Set the status of the single stroke gestures
ULONG iNumGestures = countof(gc_igtSingleStrokeGestures);
ULONG iNumSubset = countof(gc_nRecommendedForMixedMode);
for (ULONG i = 0; i < iNumSubset; i++)
{
if (gc_nRecommendedForMixedMode[i] < iNumGestures)
ListView_SetCheckState(m_hwndSSGestLV, gc_nRecommendedForMixedMode[i], TRUE);
}
// Set the status of the multiple stroke gestures
iNumGestures = countof(gc_igtMultiStrokeGestures);
for (ULONG i = 0; i < iNumGestures; i++)
{
ListView_SetCheckState(m_hwndMSGestLV, i, TRUE);
}
}