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

1579 lines
51 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:
// MultiReco.cpp
//
// Description:
// This sample demonstrates such advanced features of the
// Microsoft Tablet PC Automation API used for handwriting
// recognition, as
// - serializing recognition results with a stroke collection
// - organizing stroke collections into a custom collection
// within the ink object
// - serializing ink object into/from an ISF file
// - enumerating the installed recognizers
// - creating a recognition context with a specific language
// recognizer
// - setting recognizer input guides
// - synchronous and asynchronous recognition
//
// This application is discussed in the Getting Started guide.
//
// (NOTE: For code simplicity, returned HRESULT is not checked
// for failure in the places where failures are not critical
// for the application)
//
// The interfaces used are:
// IInkRecognizers, IInkRecognizer, IInkRecognizerContext,
// IInkRecognitionResult, IInkRecognizerGuide,
// IInkCollector, IInkDisp, IInkRenderer, IInkDrawingAttributes,
// IInkCustomStrokes, IInkStrokes, IInkStroke
//
// Requirements:
// One or more handwriting recognizer must be installed on the system.
// Also, 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
#include <commdlg.h>
// 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
// Header for the safe string manipulation
#include <strsafe.h>
// Headers for Tablet PC Automation interfaces
#include <msinkaut.h>
#include <msinkaut_i.c>
// 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 "MultiReco.h" // contains the definition of CAddRecoApp
// The static members of the event sink templates are initialized here
// (defined in EventSinks.h). These _ATL_FUNC_INFO structures contain type information
// used to describe a method or property on a dispinterface.
// Type information of the Recognition event of IInkRecognitionEvents
const _ATL_FUNC_INFO IInkRecognitionEventsImpl<CMultiRecoApp>::mc_AtlFuncInfo =
{CC_STDCALL, VT_EMPTY, 3, {VT_BSTR, VT_VARIANT, VT_I4}};
// Type information of the Stroke event of IInkCollectorEvents
const _ATL_FUNC_INFO IInkCollectorEventsImpl<CMultiRecoApp>::mc_AtlFuncInfo =
{CC_STDCALL, VT_EMPTY, 3, {VT_UNKNOWN, VT_UNKNOWN, VT_BOOL|VT_BYREF}};
// The sample uses different colors for different collections of strokes. Below is the palette.
const COLORREF CMultiRecoApp::mc_crColors[] = {
RGB( 0, 0, 0)/*black*/, RGB(0xA0,0,0)/*red*/, RGB(0,0xA0,0)/*green*/, RGB(0,0,0xA0)/*blue*/,
RGB(0xA0,0xA0,0)/**/, RGB(0xA0,0,0xA0)/**/, RGB(0,0xA0,0xA0)/**/, RGB(0xA0,0xA0,0xA0)/*gray*/
};
const WCHAR gc_szAppName[] = L"Multiple Language Recognition with Serialization";
/////////////////////////////////////////////////////////
//
// 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_BAR_CLASSES;
if (TRUE == ::InitCommonControlsEx(&icc))
{
// Call the boilerplate function of the application
iRet = CMultiRecoApp::Run(nCmdShow);
}
else
{
::MessageBoxW(NULL, L"Error initializing the common controls.",
gc_szAppName, MB_ICONERROR | MB_OK);
}
// Release the module and the COM library
_Module.Term();
::CoUninitialize();
}
return iRet;
}
/////////////////////////////////////////////////////////
//
// CMultiRecoApp::Run
//
// The static CMultiRecoApp::Run is the boilerplate of the application.
// It instantiates and initializes an CMultiRecoApp 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 CMultiRecoApp::Run(
int nCmdShow
)
{
CMultiRecoApp theApp;
// Load and update the menu before creating the main window.
// Create menu items for the installed recognizers and for the
// supported factoids.
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 = CMultiRecoApp::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:
// Create an ink object
theApp.SendMessage(WM_COMMAND, ID_FILE_NEW);
// Create a recognition context using the first recognizer in the list
// and start new stroke collection
theApp.SendMessage(WM_COMMAND, ID_RECOGNIZER_FIRST);
// 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
{
::MessageBoxW(NULL, L"Error creating the window",
gc_szAppName, MB_ICONERROR | MB_OK);
::DestroyMenu(hMenu);
iRet = 0;
}
return iRet;
}
// Window message handlers //////////////////////////////
/////////////////////////////////////////////////////////
//
// CMultiRecoApp::OnCreate
//
// This WM_CREATE message handler creates the child windows,
// creates the required objects, sets their attributes,
// and enables pen input.
//
// Parameters:
// defined in the ATL's macro MESSAGE_HANDLER,
// none of them is used here
//
// Return Values (LRESULT):
// 0 if succeeded, or -1 otherwise
//
/////////////////////////////////////////////////////////
LRESULT CMultiRecoApp::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;
// Establish a connection to the collector's event source.
hr = IInkCollectorEventsImpl<CMultiRecoApp>::DispEventAdvise(m_spIInkCollector);
// There is nothing interesting the application can do without events
// from the ink collector
if (FAILED(hr))
return -1;
// 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 recognition guide objects
CreateRecoGuides();
return 0;
}
/////////////////////////////////////////////////////////
//
// CMultiRecoApp::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 CMultiRecoApp::OnDestroy(
UINT /*uMsg*/,
WPARAM /*wParam*/,
LPARAM /*lParam*/,
BOOL& /*bHandled*/
)
{
// Disable ink input and release the InkCollector object
if (m_spIInkCollector != NULL)
{
IInkCollectorEventsImpl<CMultiRecoApp>::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<CMultiRecoApp>::DispEventUnadvise(m_spIInkRecoContext);
m_spIInkRecoContext.Release();
}
// Release the other objects and collections
m_spIInkRecoGuideLined.Release();
m_spIInkRecoGuideBoxed.Release();
m_spIInkStrokes.Release();
m_spIInkRecognizers.Release();
// Post a WM_QUIT message to the application's message queue
::PostQuitMessage(0);
return 0;
}
/////////////////////////////////////////////////////////
//
// CMultiRecoApp::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 CMultiRecoApp::OnSize(
UINT /*uMsg*/,
WPARAM wParam,
LPARAM /*lParam*/,
BOOL& /*bHandled*/
)
{
if (wParam != SIZE_MINIMIZED)
{
UpdateLayout();
}
return 0;
}
// InkCollector event handlers ///////////////////////////
/////////////////////////////////////////////////////////
//
// CMultiRecoApp::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] the new stroke's interface pointer
// 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 CMultiRecoApp::OnStroke(
IInkCursor* /*pIInkCursor*/,
IInkStrokeDisp* pIInkStroke,
VARIANT_BOOL* pbCancel
)
{
if (m_spIInkStrokes == NULL) // shouldn't happen
return E_FAIL;
if (NULL == pIInkStroke)
return E_INVALIDARG;
HRESULT hr;
// Add the new stroke to the current collection
hr = m_spIInkStrokes->Add(pIInkStroke);
if (SUCCEEDED(hr))
{
// Cancel the previous background recognition requests
// which have not been processed yet
m_spIInkRecoContext->StopBackgroundRecognition();
// Ask the context to update the recognition results with newly added strokes
// When the results are ready, the recognition context will return them
// via the corresponding event RecognitionWithAlternates
CComVariant vCustomData;
m_spIInkRecoContext->BackgroundRecognize(vCustomData);
}
return hr;
}
// Recognition event handler ////////////////////////////
/////////////////////////////////////////////////////////
//
// CMultiRecoApp::OnRecognition
//
// The _IInkRecognitionEvents's Recognition event handler
//
// Parameters:
// BSTR bstrRecognizedString : the top result of the recognition
// VARIANT vCustomParam : not used here
// InkRecognitionStatus RecognitionStatus : not used here
//
// Return Values (HRESULT):
// always S_OK
//
/////////////////////////////////////////////////////////
HRESULT CMultiRecoApp::OnRecognition(
BSTR bstrRecognizedString,
VARIANT /*vCustomParam*/,
InkRecognitionStatus /*RecognitionStatus*/
)
{
if (m_wndResults.IsWindow())
{
// Update the output window with the new results
m_wndResults.UpdateString(bstrRecognizedString);
}
if (NULL != bstrRecognizedString)
::SysFreeString(bstrRecognizedString);
return S_OK;
}
// Command handlers /////////////////////////////////////
/////////////////////////////////////////////////////////
//
// CMultiRecoApp::OnClear
//
// Deletes all strokes and recognition results from the
// ink object and clears the windows.
//
// 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 CMultiRecoApp::OnClear(
WORD /*wNotifyCode*/,
WORD /*wID*/,
HWND /*hWndCtl*/,
BOOL& /*bHandled*/
)
{
if (m_spIInkDisp == NULL) // there is nothing to clear
return 0;
// Detach the current stroke collection from the recognition context and release it
if (m_spIInkRecoContext != NULL)
m_spIInkRecoContext->putref_Strokes(NULL);
m_spIInkStrokes.Release();
// Clear the custom strokes collection
if (m_spIInkCustomStrokes != NULL)
m_spIInkCustomStrokes->Clear();
// Delete all strokes from the Ink object
m_spIInkDisp->DeleteStrokes(NULL); // Passing NULL as a stroke collection pointer
// means asking to delete all strokes
// Get a new stroke collection from the ink object
CComVariant v; // This varaint is used to pass an array of ID's of the strokes
// which to be included into the new collection.
// Ask for an empty collection by passing an empty variant
if (SUCCEEDED(m_spIInkDisp->CreateStrokes(v, &m_spIInkStrokes)))
{
// Attach it to the recognition context
if (FAILED(m_spIInkRecoContext->putref_Strokes(m_spIInkStrokes)))
{
MessageBoxW(L"Unable to attach the stroke collection to "
L"the recognition context",
gc_szAppName, MB_ICONERROR | MB_OK);
}
}
else
{
MessageBoxW(L"Error creating new stroke collection.",
gc_szAppName, MB_ICONERROR | MB_OK);
}
// Update the child windows
m_wndInput.Invalidate();
m_wndResults.ResetResults(); // resets the text and updates the window
// Tell the results window that a new stroke collection
// is started, and set the color for it.
m_wndResults.AddString(
mc_crColors[m_iColor], // new current color
NULL // recognition result string, none yet
);
return 0;
}
/////////////////////////////////////////////////////////
//
// CMultiRecoApp::OnNewStrokes
//
// This command handler is called when user selects a recognizer
// in the "New Strokes" submenu. It saves the current stroke collection,
// creates a new recognition context if user selected a different language
// recognizer, and then starts a new collection of strokes attached to
// the new recognition context.
//
// 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 CMultiRecoApp::OnNewStrokes(
WORD /*wNotifyCode*/,
WORD wID,
HWND /*hWndCtl*/,
BOOL& /*bHandled*/
)
{
// Change cursor to the system's Hourglass
HCURSOR hCursor = ::SetCursor(::LoadCursor(NULL, IDC_WAIT));
// Save the current stroke collection if there is any
if (m_spIInkRecoContext != NULL)
{
// Cancel the previous background recognition requests
// which have not been processed yet
m_spIInkRecoContext->StopBackgroundRecognition();
// Let the context know that there'll be no more input
// for the attached stroke collection
m_spIInkRecoContext->EndInkInput();
// Add the stroke collection to the Ink object's CustomStrokes collection
SaveStrokeCollection();
}
// If a different recognizer was selected, create a new recognition context
// Else, reuse the same recognition context
if (wID != m_nCmdRecognizer)
{
// Get a pointer to the recognizer object from the recognizer collection
CComPtr<IInkRecognizer> spIInkRecognizer;
if ((m_spIInkRecognizers == NULL)
|| FAILED(m_spIInkRecognizers->Item(wID - ID_RECOGNIZER_FIRST,
&spIInkRecognizer))
|| (false == CreateRecoContext(spIInkRecognizer)))
{
// restore the cursor
::SetCursor(hCursor);
return 0;
}
// Update the status bar
m_bstrCurRecoName.Empty();
spIInkRecognizer->get_Name(&m_bstrCurRecoName);
UpdateStatusBar();
// Store the selected recognizer's command id
m_nCmdRecognizer = wID;
}
// Create a new stroke collection
StartNewStrokeCollection();
// restore the cursor
::SetCursor(hCursor);
return 0;
}
// Helper methods //////////////////////////////
/////////////////////////////////////////////////////////
//
// CMultiRecoApp::StartNewStrokeCollection
//
// Creates an empty stroke collection and attaches it
// to the recognition context.
//
// Parameters: none
//
// Return Values (void):
// ignore failures
//
/////////////////////////////////////////////////////////
void CMultiRecoApp::StartNewStrokeCollection()
{
if ((m_spIInkDisp == NULL) || (m_spIInkRecoContext == NULL))
return;
// Create a new empty stroke collection and attach it to the recognition context
CComVariant vt(0);
if (SUCCEEDED(m_spIInkDisp->CreateStrokes(vt, &m_spIInkStrokes)))
{
if (FAILED(m_spIInkRecoContext->putref_Strokes(m_spIInkStrokes)))
{
MessageBoxW(L"Failed to attach the stroke collection to "
L"the recognition context",
gc_szAppName, MB_ICONERROR | MB_OK);
}
// Change the color of the pen and text output
CComPtr<IInkDrawingAttributes> spIInkDrawAttrs;
if (SUCCEEDED(m_spIInkCollector->get_DefaultDrawingAttributes(&spIInkDrawAttrs)))
{
spIInkDrawAttrs->put_Color(mc_crColors[m_iColor]);
}
// Tell the output window that the new stroke collection is current now and
// it's future recognition results should be shown in the RichEdit control
// with a different color
m_wndResults.AddString(
mc_crColors[m_iColor], // new current color
NULL // recognition result string, none yet
);
}
else
{
MessageBoxW(L"Error creating new stroke collection.",
gc_szAppName, MB_ICONERROR | MB_OK);
}
}
/////////////////////////////////////////////////////////
//
// CMultiRecoApp::LoadMenu
//
// This method instantiates an enumerator object for the installed
// recognizers, loads the main menu resource and creates a menu item
// for the recognizers from the collection.
//
// Parameters:
// none
//
// Return Values (HMENU):
// The return value is a handle of the menu
// that'll be used for the main window
//
/////////////////////////////////////////////////////////
HMENU CMultiRecoApp::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)
{
::MessageBoxW(NULL, L"There are no handwriting recognizers installed.\n"
L"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;
miinfo.cbSize = sizeof(miinfo);
miinfo.fMask = MIIM_ID | MIIM_STATE | MIIM_TYPE;
miinfo.fType = 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 (NULL == hSubMenu)
{
::DestroyMenu(hMenu);
return NULL; // Not normal
}
// Remove the placeholding separator
::DeleteMenu(hSubMenu, 0, MF_BYPOSITION);
CComPtr<IInkRecognizer> spIInkRecognizer;
// Make sure that the number of menu items won't exceed
// the predefined range of the corresponding command ID's
if (lCount > (ID_RECOGNIZER_LAST - ID_RECOGNIZER_FIRST + 1))
lCount = ID_RECOGNIZER_LAST - ID_RECOGNIZER_FIRST + 1;
miinfo.fState = 0;
bool bAllFailed = true;
for (LONG i = 0; i < lCount; i++)
{
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
{
// This is a language recognizer. Add its name to the menu.
CComBSTR bstrName;
if (SUCCEEDED(spIInkRecognizer->get_Name(&bstrName)))
{
// 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 + i;
miinfo.dwTypeData = bstrName;
if ((::InsertMenuItemW(hSubMenu, (UINT)-1, TRUE, &miinfo) != 0)
&& (true == bAllFailed))
{
bAllFailed = false;
}
}
}
}
spIInkRecognizer.Release();
}
if (bAllFailed)
{
::MessageBoxW(
NULL,
L"Unable to find or load handwriting text recognizers.\n"
L"You need to have at least one in order to run this sample.\nExiting.",
gc_szAppName,
MB_ICONERROR | MB_OK);
::DestroyMenu(hMenu);
return NULL;
}
return hMenu;
}
/////////////////////////////////////////////////////////
//
// CMultiRecoApp::DoOpen
//
// This helper function creates new ink object or takes
// one loaded from a file and attaches it to the ink collector,
// obtains a pointer to the custom strokes of the ink
// and populates the stored recognition results from the stroke
// collections.
//
// Parameters:
// bool bNew : true - create new ink object
// false - let the user pick an .isf file
// and load ink object from it.
//
// Return Values (bool):
// true - new ink object created or loaded from a file
// false - if failed to create ink object, or the user
// canceled the "Open" dialog box
//
/////////////////////////////////////////////////////////
bool CMultiRecoApp::DoOpen(
bool bNew
)
{
// Disable inking
m_spIInkCollector->put_Enabled(VARIANT_FALSE);
// Create a new ink object
CComPtr<IInkDisp> spIInkDisp;
if (FAILED(spIInkDisp.CoCreateInstance(CLSID_InkDisp)))
{
MessageBoxW(L"Unable to create an ink object.",
gc_szAppName, MB_ICONERROR | MB_OK);
// Enable inking
m_spIInkCollector->put_Enabled(VARIANT_TRUE);
return false;
}
// Let user select an .isf file to open
if (false == bNew)
{
if (false == LoadFile(spIInkDisp))
{
// Don't proceed if opening a file or reading from it failed
// or if the user canceled the operation
// Enable inking
m_spIInkCollector->put_Enabled(VARIANT_TRUE);
return false;
}
}
else
{
// Reset the file name and update the window's title
UpdateFilename();
}
// Replace the old Ink object with the new one
m_spIInkDisp.Attach(spIInkDisp.Detach()); // Attach/Detach don't call AddRef/Release
m_spIInkCollector->putref_Ink(m_spIInkDisp);
// Release the current stroke collections
m_spIInkStrokes.Release();
m_spIInkCustomStrokes.Release();
m_wndResults.ResetResults();
// Obtain a pointer to the custom strokes collection of the new ink object
long lCount = 0;
if (SUCCEEDED(m_spIInkDisp->get_CustomStrokes(&m_spIInkCustomStrokes)))
{
m_spIInkCustomStrokes->get_Count(&lCount);
for (CComVariant vItem(0); vItem.lVal < lCount; vItem.lVal++)
{
IInkStrokes* pIInkStrokes;
if (SUCCEEDED(m_spIInkCustomStrokes->Item(vItem, &pIInkStrokes)))
{
// Get the color of the strokes or use GRAY if fail
COLORREF clr = RGB(0x80,0x80,0x80);
CComPtr<IInkStrokeDisp> spIInkStroke;
CComPtr<IInkDrawingAttributes> spIDrawAttrs;
if (SUCCEEDED(pIInkStrokes->Item(0, &spIInkStroke))
&& SUCCEEDED(spIInkStroke->get_DrawingAttributes(&spIDrawAttrs)))
{
spIDrawAttrs->get_Color((long*)&clr);
}
// Get the best result string
CComBSTR bstrToString;
if (SUCCEEDED(pIInkStrokes->ToString(&bstrToString)))
{
// Add the string to the RichEdit control
m_wndResults.AddString(
clr, // the strokes color
bstrToString // the recognition result string
);
}
pIInkStrokes->Release();
}
}
}
// Create an empty new stroke collection and attach it to the recognition context
m_iColor = WORD(lCount) % countof(mc_crColors); // next color to use
StartNewStrokeCollection();
// Reset the Dirty flag
m_spIInkDisp->put_Dirty(VARIANT_FALSE);
// Update the input window
m_wndInput.Invalidate();
// Enable inking
m_spIInkCollector->put_Enabled(VARIANT_TRUE);
return true;
}
/////////////////////////////////////////////////////////
//
// CMultiRecoApp::DoSave
//
// This function saves the current ink object into file.
//
//
// Parameters:
// bool bSaveAs: true - open "Save As..." common dialog box
// for the user to specify the filename
// false - save changes into the current file
// bool bAskUser: true - ask user whether she'd like to save
// the changes; it's used when DoSave
// is called from ID_FILE_NEW or ID_FILE_OPEN
// command handlers
// false - don't ask, just save
//
// Return Values (bool):
// true - succeeded or there's nothing to save or the user was
// was asked and said "No" to saving
// false - failed to save or canceled by the user
//
/////////////////////////////////////////////////////////
bool CMultiRecoApp::DoSave(
bool bSaveAs,
bool bAskUser
)
{
if (m_spIInkDisp == NULL)
return true;
// If there's been no changes since the last save,
// and it's not a SaveAs command, do nothing and return true
VARIANT_BOOL bDirty = VARIANT_TRUE;
if ((false == bSaveAs)
&& SUCCEEDED(m_spIInkDisp->get_Dirty(&bDirty))
&& VARIANT_FALSE == bDirty)
{
return true;
}
if (true == bAskUser)
{
int iRet = MessageBoxW(L"The ink object has been changed.\n"
L"Do you want to save the changes?",
gc_szAppName, MB_ICONQUESTION | MB_YESNOCANCEL);
if (IDCANCEL == iRet)
return false;
if (IDNO == iRet)
return true;
}
WCHAR wchFile[MAX_PATH];
WCHAR wchFileTitle[MAX_PATH];
wchFile[0] = wchFileTitle[0] = 0;
if (false == bSaveAs && 0 == m_wchFile[0])
bSaveAs = true;
// If the function is called from the ID_FILE_SAVEAS command handler,
// or the file is new, display the "SaveAs" common dialog box
// for the user to specify the file name
if (true == bSaveAs)
{
// Initialize the OPENFILENAME members.
OPENFILENAMEW ofn;
memset(&ofn, 0, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = m_hWnd;
ofn.lpstrDefExt = L"isf";
ofn.lpstrFilter = L"*.isf\0";
ofn.lpstrFileTitle= wchFileTitle;
ofn.nMaxFileTitle = countof(wchFileTitle);
ofn.lpstrFile= wchFile;
ofn.nMaxFile = countof(wchFile);
ofn.Flags = OFN_PATHMUSTEXIST | OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY;
// Display the dialog box
if (::GetSaveFileNameW(&ofn) == 0)
return false; // canceled by the user
}
// Add the current stroke collection to the Ink's CustomStrokes and release it
SaveStrokeCollection();
// Save the ink object into a variant using the ISF format
bool bOk = false;
CComVariant vData;
if (SUCCEEDED(m_spIInkDisp->Save(
IPF_InkSerializedFormat, // save as ISF
IPCM_Default, // use default compression
&vData) // the output
))
{
BYTE* pbData;
if (SUCCEEDED(::SafeArrayAccessData(vData.parray, (void**)&pbData)))
{
// Create new or overwrite the existing file
HANDLE hFile = ::CreateFileW(
bSaveAs ? wchFile : m_wchFile,
GENERIC_WRITE,
0,
NULL,
CREATE_ALWAYS,
0,
NULL);
if (INVALID_HANDLE_VALUE != hFile)
{
// Write the data into the file
ULONG ulWritten, ulSize = vData.parray->rgsabound[0].cElements;
if (TRUE == ::WriteFile(hFile, pbData, ulSize, &ulWritten, NULL))
{
bOk = true;
m_spIInkDisp->put_Dirty(VARIANT_FALSE);
}
else
{
MessageBoxW(L"Error writing file!",
gc_szAppName, MB_ICONERROR | MB_OK);
}
::CloseHandle(hFile);
// Update the filename string
if (true == bSaveAs)
{
UpdateFilename(wchFile, wchFileTitle);
}
}
else
{
MessageBoxW(L"Unable to create the file.",
gc_szAppName, MB_ICONERROR | MB_OK);
}
::SafeArrayUnaccessData(vData.parray);
}
}
return bOk;
}
/////////////////////////////////////////////////////////
//
// CMultiRecoApp::LoadFile
//
// Loads the ink object from the specified file.
//
// Parameters:
// IInkDisp* pIInkDisp : pointer to the ink object
//
// Return Values (bool):
// true - succeeded
// false - failed
//
/////////////////////////////////////////////////////////
bool CMultiRecoApp::LoadFile(
IInkDisp* pIInkDisp
)
{
WCHAR wchFile[MAX_PATH];
WCHAR wchFileTitle[MAX_PATH];
wchFile[0] = wchFileTitle[0] = 0;
// Initialize the OPENFILENAME members.
OPENFILENAMEW ofn;
memset(&ofn, 0, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = m_hWnd;
ofn.lpstrFilter = L"*.isf\0";
ofn.lpstrFileTitle= wchFileTitle;
ofn.nMaxFileTitle = countof(wchFileTitle);
ofn.lpstrFile= wchFile;
ofn.nMaxFile = countof(wchFile);
ofn.Flags = OFN_OVERWRITEPROMPT | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
// Display the "Open File..." common dialog box
if (::GetOpenFileNameW(&ofn) == 0)
{
return false;
}
// Open the file and populate the Ink object from it
HANDLE hFile = ::CreateFileW(wchFile, GENERIC_READ, 0, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (INVALID_HANDLE_VALUE == hFile)
{
MessageBoxW(L"Error opening the file!", gc_szAppName, MB_ICONERROR | MB_OK);
return false;
}
// Load the file into a safearrray of bytes, then load the ink object from the array
bool bOk = false;
DWORD dwRead, dwSize;
dwSize = ::GetFileSize(hFile, NULL);
if (dwSize != DWORD(-1))
{
VARIANT vtData;
VariantInit(&vtData);
vtData.vt = VT_ARRAY | VT_UI1;
vtData.parray = ::SafeArrayCreateVector(VT_UI1, 0, dwSize);
if (vtData.parray)
{
BYTE *pbData;
if (SUCCEEDED(::SafeArrayAccessData(vtData.parray, (void**)&pbData)))
{
if (TRUE == ::ReadFile(hFile, pbData, dwSize, &dwRead, NULL)
&& SUCCEEDED(pIInkDisp->Load(vtData)))
{
bOk = true;
}
::SafeArrayUnaccessData(vtData.parray);
}
::SafeArrayDestroy(vtData.parray);
}
if (false == bOk)
{
MessageBoxW(L"Error loading ink object from the file.",
gc_szAppName, MB_ICONERROR | MB_OK);
}
}
else
{
MessageBoxW(L"Error reading data from the file.\nUnknown file format.",
gc_szAppName, MB_ICONERROR | MB_OK);
}
// Close the file
::CloseHandle(hFile);
// Store the filenames and update the title
if (true == bOk)
{
UpdateFilename(wchFile, wchFileTitle);
}
return bOk;
}
/////////////////////////////////////////////////////////
//
// CMultiRecoApp::CreateRecoGuides
//
// Create recognizer guides for boxed and lined input.
// They are not required but recommended for getting
// better recognition results.
//
// Parameters: none
//
// Return Values (void):
// the result is not important - the application just
// won't use the guides if failed to create or initialize them
//
/////////////////////////////////////////////////////////
void CMultiRecoApp::CreateRecoGuides()
{
CComPtr<IInkRenderer> spIInkRenderer;
if ((m_spIInkCollector == NULL)
|| FAILED(m_spIInkCollector->get_Renderer(&spIInkRenderer)))
return;
HDC hdc = m_wndInput.GetDC();
if (NULL == hdc)
return;
// Initialize the recognizer guide data structure
_InkRecoGuide irg;
irg.midline = -1; // not use midline
irg.cRows = mc_iNumRowsCols;
irg.cColumns = mc_iNumRowsCols;
::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 with screen coordinates
m_wndInput.SetGuide(irg);
// Use IInkRenderer to transform the guide boxes
// rectangles into ink space coordinates
// - the guide's writing box
if ( SUCCEEDED(spIInkRenderer->PixelToInkSpace((long)hdc,
&irg.rectWritingBox.left,
&irg.rectWritingBox.top))
&& SUCCEEDED(spIInkRenderer->PixelToInkSpace((long)hdc,
&irg.rectWritingBox.right,
&irg.rectWritingBox.bottom))
// and the drawn box
&& SUCCEEDED(spIInkRenderer->PixelToInkSpace((long)hdc,
&irg.rectDrawnBox.left,
&irg.rectDrawnBox.top))
&& SUCCEEDED(spIInkRenderer->PixelToInkSpace((long)hdc,
&irg.rectDrawnBox.right,
&irg.rectDrawnBox.bottom))
)
{
// Create the boxed guide
if (SUCCEEDED(m_spIInkRecoGuideBoxed.CoCreateInstance(CLSID_InkRecognizerGuide)))
{
// Initialize it
if (FAILED(m_spIInkRecoGuideBoxed->put_GuideData(irg)))
{
m_spIInkRecoGuideBoxed.Release();
}
}
// Create the lined guide
if (SUCCEEDED(m_spIInkRecoGuideLined.CoCreateInstance(CLSID_InkRecognizerGuide)))
{
// Initialize it
irg.cColumns = 0; // no columns, just rows
if (FAILED(m_spIInkRecoGuideLined->put_GuideData(irg)))
{
m_spIInkRecoGuideLined.Release();
}
}
}
// Clean up
ReleaseDC(hdc);
}
/////////////////////////////////////////////////////////
//
// CMultiRecoApp::SaveStrokeCollection
//
// The method finalize the recognition of the current
// stroke collection and saves the best result with it.
// Then this stroke collection is added to the custom strokes
// collection of the ink object.
//
// Parameters:
// none
//
// Return Values (void):
// none - ignore any failures
//
/////////////////////////////////////////////////////////
void CMultiRecoApp::SaveStrokeCollection()
{
if (m_spIInkStrokes == NULL)
return;
long lCount = 0;
if (m_spIInkRecoContext != NULL)
{
if (SUCCEEDED(m_spIInkStrokes->get_Count(&lCount)) && 0 != lCount)
{
CComPtr<IInkRecognitionResult> spIInkRecoResult;
InkRecognitionStatus RecognitionStatus;
if (SUCCEEDED(m_spIInkRecoContext->Recognize(&RecognitionStatus, &spIInkRecoResult)) && (spIInkRecoResult != NULL))
{
if (SUCCEEDED(spIInkRecoResult->SetResultOnStrokes()))
{
CComBSTR bstr;
spIInkRecoResult->get_TopString(&bstr);
m_wndResults.UpdateString(bstr);
}
else
{
MessageBoxW(L"Unable to save recognition result "
L"in the stroke collection.",
gc_szAppName, MB_ICONERROR | MB_OK);
}
}
else
{
MessageBoxW(L"Recognition has failed. No results will be "
L"stored in the stroke collection.",
gc_szAppName, MB_ICONERROR | MB_OK);
}
}
// Detach the stroke collection from the old recognition context
m_spIInkRecoContext->putref_Strokes(NULL);
}
// Now add it to the ink's custom strokes collection
// Each item (stroke collection) of the custom strokes must be identified
// by a unique string. Here we generate a GUID for this.
if ((0 != lCount) && (m_spIInkCustomStrokes != NULL))
{
GUID guid;
WCHAR szGuid[40]; // format: "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}"
if (SUCCEEDED(::CoCreateGuid(&guid))
&& (::StringFromGUID2(guid, szGuid, countof(szGuid)) != 0))
{
CComBSTR bstrGuid(szGuid);
if (FAILED(m_spIInkCustomStrokes->Add(bstrGuid, m_spIInkStrokes)))
{
MessageBoxW(L"Failed to add the strokes to the Ink object's "
L"custom stroke collection",
gc_szAppName, MB_ICONERROR | MB_OK);
}
}
else
{
MessageBoxW(L"Failed to create a unique string id for the stroke collection",
gc_szAppName, MB_ICONERROR | MB_OK);
}
// Use another color for the next collection
m_iColor++;
if (m_iColor >= countof(mc_crColors))
{
m_iColor= 0;
}
}
m_spIInkStrokes.Release();
}
/////////////////////////////////////////////////////////
//
// CMultiRecoApp::CreateRecoContext
//
// As it follows from the name, this helper method, given
// a pointer to the recognizer, creates and initializes
// new recognition context.
//
// Parameters:
// IInkRecognizer* pIInkRecognizer
// : [in] the recognizer to create a context with
//
// Return Values (bool):
// true if succeeded creating new recognition context, false otherwise
//
/////////////////////////////////////////////////////////
bool CMultiRecoApp::CreateRecoContext(
IInkRecognizer* pIInkRecognizer
)
{
// Create a new recognition context
CComPtr<IInkRecognizerContext> spNewContext;
if (FAILED(pIInkRecognizer->CreateRecognizerContext(&spNewContext)))
return false;
// Replace the current context with the new one
if (m_spIInkRecoContext != NULL)
{
// Close the connection to the recognition events source
IInkRecognitionEventsImpl<CMultiRecoApp>::DispEventUnadvise(m_spIInkRecoContext);
}
m_spIInkRecoContext.Attach(spNewContext.Detach());
// Establish a connection with the recognition context's event source
if (FAILED(IInkRecognitionEventsImpl<CMultiRecoApp>::DispEventAdvise(m_spIInkRecoContext)))
{
MessageBoxW(L"Failed connect to the recognition context's event source.\n"
L"The input won't be recognized dynamically.",
gc_szAppName, MB_ICONERROR | MB_OK);
}
// Set the guide if it's supported by the recognizer and has been created
int cRows = 0, cColumns = 0;
bool fRequiresBoxGuide = false;
InkRecognizerCapabilities dwCapabilities = IRC_DontCare;
if (SUCCEEDED(pIInkRecognizer->get_Capabilities(&dwCapabilities)))
{
// Find out the language chosen. In the case of Japanese, Chinese and Korean we will
// create a box guide.
CComVariant vLanguages;
if (SUCCEEDED(pIInkRecognizer->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
{
ULONG ulSize = vLanguages.parray->rgsabound[0].cElements;
short *pbData;
if (SUCCEEDED(::SafeArrayAccessData(vLanguages.parray, (void**)&pbData)))
{
for(ULONG i = 0; i < ulSize; i++)
{
if ( 0x411 == pbData[i] || // Japanese
0x404 == pbData[i] || // Chinese
0x804 == pbData[i] || // Chinese
0x412 == pbData[i]) // Korean
{
fRequiresBoxGuide = true;
break;
}
}
::SafeArrayUnaccessData(vLanguages.parray);
}
}
}
// Try the boxed guide in case it is required
if (((IRC_BoxedInput & dwCapabilities) == IRC_BoxedInput)
&& (m_spIInkRecoGuideBoxed != NULL) && fRequiresBoxGuide)
{
HRESULT hr = m_spIInkRecoContext->putref_Guide(m_spIInkRecoGuideBoxed);
if (SUCCEEDED(hr))
{
cRows = cColumns = mc_iNumRowsCols;
}
}
// or the lined one
else if (((IRC_LinedInput & dwCapabilities) == IRC_LinedInput)
&& (m_spIInkRecoGuideLined != NULL))
{
if (SUCCEEDED(m_spIInkRecoContext->putref_Guide(m_spIInkRecoGuideLined)))
{
cRows = mc_iNumRowsCols;
}
}
}
// Update the input window
m_wndInput.SetRowsCols(cRows, cColumns);
return true;
}
/////////////////////////////////////////////////////////
//
// CMultiRecoApp::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 CMultiRecoApp::CreateChildWindows()
{
// Create windows for input and output
if ((m_wndResults.Create(m_hWnd, mc_iOutputWndId) == NULL)
|| (m_wndInput.Create(m_hWnd, CWindow::rcDefault, NULL,
WS_CHILD, WS_EX_CLIENTEDGE, (UINT)mc_iInputWndId) == NULL))
{
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)
{
// Set the status bar font
::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;
}
/////////////////////////////////////////////////////////
//
// CMultiRecoApp::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 CMultiRecoApp::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 output window
if (m_wndResults.IsWindow())
{
int cyResultsWnd = 100;
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);
}
}
/////////////////////////////////////////////////////////
//
// CMultiRecoApp::UpdateStatusBar
//
// This helper function sets the name of the currently selected
// recognizer to appear on the application's status bar.
//
// Parameters:
// none
//
// Return Values (void):
// none
//
/////////////////////////////////////////////////////////
void CMultiRecoApp::UpdateStatusBar()
{
if (::IsWindow(m_hwndStatusBar))
{
::SendMessage(m_hwndStatusBar, SB_SETTEXTW, NULL, (LPARAM)m_bstrCurRecoName.m_str);
}
}
/////////////////////////////////////////////////////////
//
// CMultiRecoApp::UpdateFilename
//
// This helper function stores the new file name
// and update the window's caption
//
// Parameters:
// WCHAR pwsFile : the fule name of the opened file
// WCHAR pwsFileTitle : the name and extension of the file, without path
//
// Return Values (void):
// none
//
/////////////////////////////////////////////////////////
void CMultiRecoApp::UpdateFilename(
WCHAR* pwsFile,
WCHAR* pwsFileTitle
)
{
// The title that appears in the window's caption bar
CComBSTR bstrTitle;
if ((NULL != pwsFile) && (NULL != pwsFileTitle))
{
// Copy the strings
StringCchCopyNExW(m_wchFile, MAX_PATH, pwsFile, MAX_PATH, NULL, NULL, STRSAFE_NULL_ON_FAILURE);
StringCchCopyNExW(m_wchFileTitle, MAX_PATH, pwsFileTitle, MAX_PATH, NULL, NULL, STRSAFE_NULL_ON_FAILURE);
bstrTitle = m_wchFileTitle;
}
else
{
m_wchFile[0] = m_wchFileTitle[0] = 0;
bstrTitle = L"New File";
}
// The application's title is combined from the application name and
// the name of the currently open file
bstrTitle += L" - ";
bstrTitle += gc_szAppName;
// Update the window's caption
::SetWindowTextW(m_hWnd, bstrTitle);
}