|
|
/****************************************************************************
Copyright (c) 1998-1999 Microsoft Corporation Module Name: cplinputlimiter.cpp Author: toddb - 10/06/98
****************************************************************************/
#include "cplPreComp.h"
class CInputLimiter { public: BOOL SubclassWindow(HWND hwnd, DWORD dwFlags); static VOID HideToolTip();
protected: BOOL OnChar( HWND hwnd, TCHAR wParam ); LRESULT OnPaste(HWND hwnd, WPARAM wParam, LPARAM lParam); BOOL IsValidChar(TCHAR ch, BOOL bPaste); BOOL UnsubclassWindow(HWND hwnd); void ShowToolTip(HWND hwnd); void CreateToolTipWindow(HWND hwnd); static LRESULT CALLBACK SubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); static LRESULT CALLBACK ListenerProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); static VOID CALLBACK TimerProc(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime);
DWORD m_dwFlags; // determines which characters are allowed
WNDPROC m_pfnSuperProc; // the super class proc
static HWND s_hwndToolTip; // shared by all instances
static UINT_PTR s_uTimerID; // shared timer
static TCHAR s_szTipText[512]; // the text to be shown in the tooltip
};
HWND CInputLimiter::s_hwndToolTip = NULL; UINT_PTR CInputLimiter::s_uTimerID = 0; TCHAR CInputLimiter::s_szTipText[512] = {0};
// Limiting the input on a combo box is a special case because you first
// have to find the edit box and then LimitInput on that.
BOOL CALLBACK FindTheEditBox( HWND hwnd, LPARAM lParam ) { // The combo box only has one child, subclass it
LimitInput(hwnd,(DWORD)lParam); return FALSE; }
BOOL LimitCBInput(HWND hwnd, DWORD dwFlags) { return EnumChildWindows(hwnd, FindTheEditBox, dwFlags); }
BOOL LimitInput(HWND hwnd, DWORD dwFlags) { CInputLimiter * pil = new CInputLimiter;
if (!pil) { return FALSE; }
BOOL bResult = pil->SubclassWindow(hwnd, dwFlags);
if (!bResult) { delete pil; }
return bResult; }
void HideToolTip() { CInputLimiter::HideToolTip(); }
BOOL CInputLimiter::SubclassWindow(HWND hwnd, DWORD dwFlags) { if ( !IsWindow(hwnd) ) return FALSE;
m_dwFlags = dwFlags;
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)this);
m_pfnSuperProc = (WNDPROC)GetWindowLongPtr(hwnd, GWLP_WNDPROC); SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)CInputLimiter::SubclassProc);
return TRUE; }
BOOL CInputLimiter::UnsubclassWindow(HWND hwnd) { SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)m_pfnSuperProc);
m_dwFlags = 0;
delete this;
return TRUE; }
LRESULT CALLBACK CInputLimiter::SubclassProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { CInputLimiter * pthis = (CInputLimiter*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
// cache pthis->m_pfnSuperProc because we always need in and
// pthis might be deleted before we get around to using it
WNDPROC pfn = pthis->m_pfnSuperProc;
switch (uMsg) { case WM_CHAR: if (!pthis->OnChar(hwnd, (TCHAR)wParam)) { return 0; } break;
case WM_PASTE: return pthis->OnPaste(hwnd, wParam, lParam);
case WM_KILLFOCUS: HideToolTip(); break;
case WM_DESTROY: pthis->UnsubclassWindow(hwnd); break;
default: break; }
return CallWindowProc(pfn, hwnd, uMsg, wParam, lParam); }
BOOL CInputLimiter::OnChar( HWND hwnd, TCHAR ch ) { // if the char is a good one return TRUE, this will pass the char on to the
// default window proc. For a bad character do a beep and then display the
// ballon tooltip pointing at the control.
if ( IsValidChar(ch, FALSE) ) return TRUE;
// if we get here then an invalid character was entered
MessageBeep(MB_OK);
ShowToolTip(hwnd);
return FALSE; }
BOOL CInputLimiter::IsValidChar(TCHAR ch, BOOL bPaste) { // certain characters get converted into WM_CHAR messages even though we don't want
// to consider them. We check for these characters first. Currently, this list includes:
// backspace
// control characters, such as ctrl-x and ctrl-v
if ( ch == TEXT('\b') ) return TRUE;
if ( !bPaste && (0x8000 & GetKeyState(VK_CONTROL)) ) return TRUE;
if ( m_dwFlags & LIF_ALLOWALPHA ) { if ( (ch >= TEXT('a') && ch <= TEXT('z')) || (ch >= TEXT('A') && ch <= TEXT('Z')) ) { return TRUE; } } if ( m_dwFlags & LIF_ALLOWNUMBER ) { if ( ch >= TEXT('0') && ch <= TEXT('9') ) { return TRUE; } } if ( m_dwFlags & LIF_ALLOWDASH ) { if ( ch == TEXT('-') || ch == TEXT('(') || ch == TEXT(')')) { return TRUE; } } if ( m_dwFlags & LIF_ALLOWPOUND ) { if ( ch == TEXT('#') ) { return TRUE; } } if ( m_dwFlags & LIF_ALLOWSTAR ) { if ( ch == TEXT('*') ) { return TRUE; } } if ( m_dwFlags & LIF_ALLOWSPACE ) { if ( ch == TEXT(' ') ) { return TRUE; } } if ( m_dwFlags & LIF_ALLOWCOMMA ) { if ( ch == TEXT(',') ) { return TRUE; } } if ( m_dwFlags & LIF_ALLOWPLUS ) { if ( ch == TEXT('+') ) { return TRUE; } } if ( m_dwFlags & LIF_ALLOWBANG ) { if ( ch == TEXT('!') ) { return TRUE; } } if ( m_dwFlags & LIF_ALLOWATOD ) { if ( (ch >= TEXT('a') && ch <= TEXT('d')) || (ch >= TEXT('A') && ch <= TEXT('D')) ) { return TRUE; } }
return FALSE; }
void CInputLimiter::ShowToolTip(HWND hwnd) { if ( !s_hwndToolTip ) { CreateToolTipWindow(hwnd); }
// Set the tooltip display point
RECT rc; GetWindowRect(hwnd, &rc); SendMessage(s_hwndToolTip, TTM_TRACKPOSITION, 0, MAKELONG((rc.left+rc.right)/2,rc.bottom));
TOOLINFO ti = {0}; ti.cbSize = sizeof(ti); ti.hwnd = NULL; ti.uId = 1;
// Set the tooltip text
UINT iStrID; if ( m_dwFlags == LIF_ALLOWNUMBER ) { // use the "0-9" text
iStrID = IDS_DIGITSONLY; } else if ( m_dwFlags == (LIF_ALLOWNUMBER|LIF_ALLOWSPACE) ) { // use the "0-9, ' '" text
iStrID = IDS_DIGITLIST; } else if ( m_dwFlags == (LIF_ALLOWNUMBER|LIF_ALLOWSPACE|LIF_ALLOWCOMMA) ) { // use the "0-9, ' ', ','" text
iStrID = IDS_MULTIDIGITLIST; } else if ( m_dwFlags == (LIF_ALLOWNUMBER|LIF_ALLOWSTAR|LIF_ALLOWPOUND|LIF_ALLOWCOMMA) ) { // use the "0-9, #, *, ','" text
iStrID = IDS_PHONEPADCHAR; } else if ( m_dwFlags == (LIF_ALLOWNUMBER|LIF_ALLOWPOUND|LIF_ALLOWSTAR|LIF_ALLOWSPACE|LIF_ALLOWCOMMA) ) { // use the "0-9, #, *, ' ', ','" text
iStrID = IDS_PHONENUMBERCHAR; } else if ( m_dwFlags == (LIF_ALLOWNUMBER|LIF_ALLOWPOUND|LIF_ALLOWSTAR|LIF_ALLOWSPACE|LIF_ALLOWCOMMA|LIF_ALLOWPLUS|LIF_ALLOWBANG|LIF_ALLOWATOD) ) { // use the "0-9, A-D, a-d, #, *, +, !, ' ', ',' " text
iStrID = IDS_PHONENUMBERCHAREXT; } else { // We should never reach this point, but if we do then we display a generic invalid character dialog
iStrID = IDS_ALLPHONECHARS; } LoadString(GetUIInstance(),iStrID,s_szTipText,ARRAYSIZE(s_szTipText)); ti.lpszText = s_szTipText; SendMessage(s_hwndToolTip, TTM_UPDATETIPTEXT, 0, (LPARAM)&ti);
// Show the tooltip
SendMessage(s_hwndToolTip, TTM_TRACKACTIVATE, TRUE, (LPARAM)&ti);
// Set a timer to hide the tooltip
if ( s_uTimerID ) { KillTimer(NULL,s_uTimerID); } s_uTimerID = SetTimer(NULL, 0, 10000, (TIMERPROC)CInputLimiter::TimerProc); }
// CreateToolTipWindow
//
// Creates our tooltip control. We share this one tooltip control and use it for all invalid
// input messages. The control is hiden when not in use and then shown when needed.
//
void CInputLimiter::CreateToolTipWindow(HWND hwnd) { HWND hwndParent;
do { hwndParent = hwnd; hwnd = GetParent(hwnd); } while (hwnd);
s_hwndToolTip = CreateWindow(TOOLTIPS_CLASS, NULL, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP | TTS_BALLOON, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, hwndParent, NULL, GetUIInstance(), NULL);
if (s_hwndToolTip) { SetWindowPos(s_hwndToolTip, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
TOOLINFO ti = {0}; RECT rc = {2,2,2,2};
ti.cbSize = sizeof(ti); ti.uFlags = TTF_TRACK | TTF_TRANSPARENT; ti.hwnd = NULL; ti.uId = 1; ti.lpszText = s_szTipText;
// set the version so we can have non buggy mouse event forwarding
SendMessage(s_hwndToolTip, CCM_SETVERSION, COMCTL32_VERSION, 0); SendMessage(s_hwndToolTip, TTM_ADDTOOL, 0, (LPARAM)&ti); SendMessage(s_hwndToolTip, TTM_SETMAXTIPWIDTH, 0, 500); SendMessage(s_hwndToolTip, TTM_SETMARGIN, 0, (LPARAM)&rc); } }
VOID CALLBACK CInputLimiter::TimerProc(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime) { // When the timer fires we hide the tooltip window
HideToolTip(); }
void CInputLimiter::HideToolTip() { if ( s_uTimerID ) { KillTimer(NULL,s_uTimerID); s_uTimerID = 0; } if ( s_hwndToolTip ) { PostMessage(s_hwndToolTip, TTM_TRACKACTIVATE, FALSE, 0); } }
LRESULT CInputLimiter::OnPaste(HWND hwnd, WPARAM wParam, LPARAM lParam) { // There are hundred of lines of code in user to successfully handle a paste into an edit control.
// We need to leverage all that code while still disallowing invalid input to result from the paste.
// As a result, what we need to do is to get the clip board data, validate that data, place the
// valid data back onto the clipboard, call the default window proc to let user do it's thing, and
// then restore the clipboard to it's original format.
if ( OpenClipboard(hwnd) ) { HANDLE hdata; UINT iFormat; DWORD cchBad = 0; // count of the number of bad characters
// REVIEW: Should this be based on the compile type or the window type?
// Compile time check for the correct clipboard format to use:
if ( sizeof(WCHAR) == sizeof(TCHAR) ) { iFormat = CF_UNICODETEXT; } else { iFormat = CF_TEXT; }
hdata = GetClipboardData(iFormat);
if ( hdata ) { LPTSTR pszData; pszData = (LPTSTR)GlobalLock(hdata); if ( pszData ) { DWORD dwSize; HANDLE hClone; HANDLE hNew;
// we need to copy the original data because the clipboard owns the hdata
// pointer. That data will be invalid after we call SetClipboardData.
// We start by calculating the size of the data:
dwSize = (DWORD)GlobalSize(hdata)+sizeof(TCHAR);
// Use the prefered GlobalAlloc for clipboard data
hClone = GlobalAlloc(GMEM_MOVEABLE|GMEM_DDESHARE, dwSize); hNew = GlobalAlloc(GMEM_MOVEABLE|GMEM_DDESHARE, dwSize); if ( hClone && hNew ) { LPTSTR pszClone; LPTSTR pszNew;
pszClone = (LPTSTR)GlobalLock(hClone); pszNew = (LPTSTR)GlobalLock(hNew); if ( pszClone && pszNew ) { int iNew = 0;
// copy the original data as-is
memcpy((LPVOID)pszClone, (LPVOID)pszData, (size_t)dwSize); // ensure that it's NULL terminated
pszClone[ (dwSize/sizeof(TCHAR))-1 ] = NULL;
for ( LPTSTR psz = pszClone; *psz; psz++ ) { if ( IsValidChar(*psz, TRUE) ) { pszNew[iNew++] = *psz; } else { cchBad++; } } pszNew[iNew] = NULL;
// If there are any characters in the paste buffer then we paste the validated string
if ( *pszNew ) { // we always set the new string. Worst case it's identical to the old string
GlobalUnlock(hNew); pszNew = NULL; SetClipboardData(iFormat, hNew); hNew = NULL;
// call the super proc to do the paste
CallWindowProc(m_pfnSuperProc, hwnd, WM_PASTE, wParam, lParam);
// The above call will have closed the clipboard on us. We try to re-open it.
// If this fails it's no big deal, that simply means the SetClipboardData
// call below will fail which is good if somebody else managed to open the
// clipboard in the mean time.
OpenClipboard(hwnd);
// and then we always set it back to the original value.
GlobalUnlock(hClone); pszClone = NULL; SetClipboardData(iFormat, hClone); hClone = NULL; } }
if ( pszClone ) { GlobalUnlock(hClone); }
if ( pszNew ) { GlobalUnlock(hNew); } }
if ( hClone ) { GlobalFree( hClone ); }
if ( hNew ) { GlobalFree( hNew ); }
// at this point we are done with hdata so unlock it
GlobalUnlock(hdata); } } CloseClipboard();
if ( cchBad ) { // Show the error balloon
MessageBeep(MB_OK);
ShowToolTip(hwnd); } } return TRUE; }
|