UNDOCUMENTED API: SHLimitInputEditWithFlags
Easily apply category filters, paste handling, and automated tooltips to an edit control.
Microsoft being Microsoft, they only begrudgingly documented a function called SHLimitInputEdit for the DOJ settlement, and did so poorly. This is a weird function; it takes an edit hwnd, and an object that implements IShellFolder and IItemNameLimits. The former doesn't even matter (unless it's been implemented in newer versions of Windows; I haven't checked). When you implement IItemNameLimits, you get a single call to GetValidCharacters, where you can supply a string of either included or excluded characters (only 1 can be used, so if you specify any excluded characters, included becomes null). It's an odd way of doing things.
But it turns out, that's a front end for an actually much more interesting and useful but completely undocumented, SHLimitInputEditWithFlags, an API Geoff Chappell found as exported at ordinal #754 in shell32.dll (it's still ordinal only in Windows 10, even though it's been kicking around since Windows XP).
This function allows a wide variety of options. Instead of just being able to specify an exact string, you can use CT_TYPE1 categories, which in addition to the standard upper, lower, digits... has some handy options like categories for hexadecimal, punctuation, or control characters. It also implements custom categories; binary, octal, and ASCII a-z/A-Z. It also provides control over the tooltip-- you can have no tooltip, or specify the title, message, and icon (a TTI_* default icon or custom hIcon), and set alignment, width, and timeout (including timing out immediately if a valid input is received). It also handles pasting in several different ways; filtering in the valid chars, pasting until the 1st invalid char, or canceling the paste. If the paste is modified, it puts what was pasted on the clipboard (optionally). The pasting options and automatic control over the tooltip is what really makes this worthwhile over just manually checking KeyPress events or WM_CHAR messages.
Requirements
-No dependencies.
-Function present on Windows XP through at least Windows 10 (I haven't checked 11).
Details
Code:
Public Declare Function SHLimitInputEditWithFlags Lib "shell32" Alias "#754" (ByVal hwndEdit As Long, pil As LIMITINPUTSTRUCT) As Long
Code:
Public Type LIMITINPUTSTRUCT
cbSize As Long 'Size of structure. Must set.
dwMask As LI_Mask 'LIM_* values.
dwFlags As LI_Flags 'LIF_* values.
hInst As Long 'App.hInstance or loaded module hInstance.
pszFilter As Long 'String via StrPtr, LICF_* category, LPSTR_TEXTCALLBACK to set via LIN_GETDISPINFO, or resource id in .hInst.
pszTitle As Long 'Optional. String via StrPtr, LPSTR_TEXTCALLBACK to set via LIN_GETDISPINFO, or resource id in .hInst.
pszMessage As Long 'Ignore if tooltip disabled. String via StrPtr, LPSTR_TEXTCALLBACK to set via LIN_GETDISPINFO, or resource id in .hInst.
hIcon As Long 'See TTM_SETTITLE. Can be TTI_* default icon, hIcon, or I_ICONCALLBACK to set via LIN_GETDISPINFO.
hwndNotify As Long 'Window to send notifications to. Must specify if any callbacks used or bad character notifications enabled.
iTimeout As Long 'Timeout in milliseconds. Defaults to 10000 if not set.
cxTipWidth As Long 'Tooltip width. Default 500px.
End Type
Code:
'Values for LIMITINPUTSTRUCT.dwMask
Public Enum LI_Mask
LIM_FLAGS = &H1 'dwFlags used
LIM_FILTER = &H2 'pszFilter used
LIM_HINST = &H8 'hinst contains valid data. Generally must be set.
LIM_TITLE = &H10 'pszTitle used. Tooltip title.
LIM_MESSAGE = &H20 'pszMessage used. Tooltip main message.
LIM_ICON = &H40 'hicon used. Can use default icons e.g. IDI_HAND. Loaded from .hInst.
LIM_NOTIFY = &H80 'hwndNotify used. NOTE: Must be set to receive notifications. Automatic finding of parent broken.
LIM_TIMEOUT = &H100 'iTimeout used. Default timeout=10000.
LIM_TIPWIDTH = &H200 'cxTipWidth used. Default 500px.
End Enum
Code:
'Values for LIMITINPUTSTRUCT.dwFlags
Public Enum LI_Flags
LIF_INCLUDEFILTER = &H0 'Default: pszFilter specifies what to include.
LIF_EXCLUDEFILTER = &H1 'pszFilter specifies what to exclude.
LIF_CATEGORYFILTER = &H2 'pszFilter uses LICF_* categories, not a string of chars.
LIF_WARNINGBELOW = &H0 'Default: Tooltip below.
LIF_WARNINGABOVE = &H4 'Tooltip above.
LIF_WARNINGCENTERED = &H8 'Tooltip centered.
LIF_WARNINGOFF = &H10 'Disable tooltip.
LIF_FORCEUPPERCASE = &H20 'Makes chars uppercase.
LIF_FORCELOWERCASE = &H40 'Makes chars lowercase. (This and forceupper mutually exclusive)
LIF_MESSAGEBEEP = &H0 'Default: System default beep played.
LIF_SILENT = &H80 'No beep.
LIF_NOTIFYONBADCHAR = &H100 'Send WM_NOTIFY LIN_NOTIFYBADCHAR. NOTE: Must set LIM_NOTIFY flag and .hwndNotify member.
LIF_HIDETIPONVALID = &H200 'Timeout tooltip early if valid char entered.
LIF_PASTESKIP = &H0 'Default: Paste any allowed characters, skip disallowed.
LIF_PASTESTOP = &H400 'Paste until first disallowed character encountered.
LIF_PASTECANCEL = &H800 'Cancel paste entirely if any disallowed character.
LIF_KEEPCLIPBOARD = &H1000 'If not set, modifies clipboard to what was pasted after paste flags executed.
End Enum
Code:
'Filters support CT_TYPE1 categories:
Public Const LICF_UPPER = &H1
Public Const LICF_LOWER = &H2
Public Const LICF_DIGIT = &H4
Public Const LICF_SPACE = &H8
Public Const LICF_PUNCT = &H10 'Punctuation
Public Const LICF_CNTRL = &H20 'Control characters
Public Const LICF_BLANK = &H40
Public Const LICF_XDIGIT = &H80 'Hexadecimal values, 0-9 and A-F.
Public Const LICF_ALPHA = &H100 'Any CT_TYPE1 linguistic character. Includes non-Latin alphabets.
'Custom categories
Public Const LICF_BINARYDIGIT = &H10000
Public Const LICF_OCTALDIGIT = &H20000 'Base 8; 0-7.
Public Const LICF_ATOZUPPER = &H100000 'ASCII A to Z
Public Const LICF_ATOZLOWER = &H200000 'ASCII a to z
Public Const LICF_ATOZ = (LICF_ATOZUPPER Or LICF_ATOZLOWER)
Advanced
There's a couple flags for advanced options. LIF_NOTIFYONBADCHAR will send hWnd specified by the .hwndNotify member a LIN_BADCHAR notification code in a WM_NOTIFY message. You must subclass the specified hWnd to receive the message (on Windows 10, it will not automatically send them to the parent, but directly to the provided hWnd. That automatic behavior may work on earlier versions, but manually specifying it works on all). From there it has it's own NM structure to copy:
Code:
Public Type NMLIBADCHAR
hdr As NMHDR
wParam As Long 'WM_CHAR wParam (Char code)
lParam As Long 'WM_CHAR lParam (see MSDN for details)
End Type
There's also special handling for WM_PASTE operations built in. The default behavior is to paste whatever characters from the clipboard are allowed, then set the contents of the clipboard to the filtered result. You can change that behavior to only pasting up until the first disallowed character with the LIF_PASTESTOP flag, or to cancel the paste entirely with LIF_PASTECANCEL.
Callbacks
I didn't implement this option in the demo because I don't see a lot of utility for it, but you can specify LPSTR_TEXTCALLBACK for the text fields, and I_ICONCALLBACK for the icon field, and the control will send a LIN_GETDISPINFO message for the tooltip text and LIN_GETFILTERINFO for the filter. I'm not going to detail it, but it works exactly like LVN_GETDISPINFO callbacks for the ListView control, and there's plenty of documentation for that. The constants and structure are included in the Demo if you did want to explore this.
Sample Project
The demo pictured at the top of this post implements a wide array of features, including subclassing for the bad character notifications, but also includes a simple 'Set to numbers only' to show how simple calls to this function can be:
Code:
Dim tli As LIMITINPUTSTRUCT
tli.cbSize = Len(tli)
tli.dwMask = LIM_FILTER Or LIM_FLAGS
tli.dwFlags = LIF_CATEGORYFILTER Or LIF_WARNINGOFF
tli.pszFilter = LICF_DIGIT
SHLimitInputEditWithFlags Text1.hWnd, tli
And that's it! Enjoy this undocumented treasure from the Windows API.
IMPORTANT: This is an undocumented, internal API, with all the issues that involves. There may be small variations in functionality between Windows versions, stability is not guaranteed, and it may be removed at any time from future versions, or have it's ordinal changed.