Let imagine that you have a form with 40 textboxes and 25 of them require input in Russian language and the rest of them require input in any other language (English, for example). In addition, these textboxes reside on the form in random order without grouping by language (yes, you can group them, but it is not always possible). Your user will do two things:
Windows API has a group of functions related to keyboard stuffs. Everything you might want to do with your keyboard can be found in this group, so, it is logical to explore the power of API to solve our problem. The necessary functions are ActivateKeyboardLayout, GetKeyboardLayout, GetKeyboardLayoutName. After researching these functions I have decided to write a separate class instead of bury API calls in GUI object. At least, it is more OOP way to go and a better maintainability. Listing 1 shows the code for LanguageSwitcher class.
Listing 1. LanguageSwitcher class code
************************************************** *-- Class: languageswitcher (c:\projects\controls\lngswth\lngswth.vcx) *-- ParentClass: label *-- BaseClass: label * DEFINE CLASS languageswitcher AS label FontBold = .F. FontItalic = .F. FontSize = 7 Alignment = 2 Caption = "Language Switcher" Enabled = .T. Height = 12 Visible = .F. Width = 92 BackColor = RGB(255,255,0) Name = "languageswitcher" *-- Protected -- Holds the handles to installed layout handles with their * abbreviated names. PROTECTED akbdhandles[1,1] *-- Protected -- Holds info about known layouts PROTECTED alayout[1] *-- Protected -- Holds DLL's declaration PROTECTED PROCEDURE declaredll * Program....: LANGUAGESWITCHER.DECLAREDLL * Version....: 1.0 * Author.....: ** Vladimir Shevchenko ** * Date.......: April 20, 1998 * Notice.....: Copyright (c) 1998 ** VSN Consulting **, All Rights * Reserved. * Compiler...: Visual FoxPro 05.00.00.0415 for Windows * Abstract...: Declares all necessary API calls * Input......: None * Output.....: None * Changes....: declare integer ActivateKeyboardLayout in Win32API integer, integer declare integer GetKeyboardLayout in Win32API long declare integer GetKeyboardLayoutList in Win32API integer, string @ declare GetKeyboardLayoutName in Win32API string @ ENDPROC *-- Protected -- Retrieves handles to all installed keyboard layouts PROTECTED PROCEDURE retrieveinstalledlayouthandles * Program....: LANGUAGESWITCHER.RETRIEVEINSTALLEDLAYOUTHANDLES * Version....: 1.0 * Author.....: ** Vladimir Shevchenko ** * Date.......: April 20, 1998 * Notice.....: Copyright (c) 1998 ** VSN Consulting **, All Rights * Reserved. * Compiler...: Visual FoxPro 05.00.00.0415 for Windows * Abstract...: Retrieve all installed keyboard layout's handles and * store them * Input......: None * Output.....: Fills class property aKBDHandles with handle values * Changes....: local lnLayoutCount, lcbuffer, lnBufLen, ix, iPos, iPos1,; lnSavedLayout,lcoldEXACT ******************************************************************** ** VS ** 21.04.98 Save SET Exact setting and turn it off ******************************************************************** lcoldExact = SET("EXACT") SET EXACT OFF ******************************************************************** ** VS ** 20.04.98 Retrieve list of installed layouts, so we have to * prepare buffer * for receiving info about layout's handles ******************************************************************** lcBuffer = space(100) ******************************************************************** ** VS ** 20.04.98 because returned values are DWORD otherwise VFP * will bombs out ******************************************************************** lnBuflen = len(lcBuffer)/4 && lnLayoutCount = GetKeyboardLayoutList(lnBuflen,@lcBuffer) ******************************************************************** ** VS ** 21.04.98 Save current layout handle ******************************************************************** lnSavedLayout = Getkeyboardlayout(0) ******************************************************************** ** VS ** 20.04.98 Now, parse the list by 4 bytes (DWORD) and fill an * array aKBDHandles ******************************************************************** dimension this.aKBDHandles[lnLayoutCount,2] FOR ix = 0 to lnLayoutCount-1 iPos = (ix *4)+1 iPos1 = ix+1 ******************************************************************** ** VS ** 20.04.98 Get the handle to the layout ******************************************************************** this.aKBDHandles[iPos1,1] =; this.ConvertDWORDtoDEC(substr(lcBuffer,iPos,4)) ******************************************************************** ** VS ** 20.04.98 Retrieve corresponding layout name ******************************************************************** this.aKBDHandles[iPos1,2] = ; this.RetrieveLayoutName(this.aKBDHandles[iPos1,1]) ENDFOR &&* ix = 0 to lnLayoutCount-1 ******************************************************************** ** VS ** 21.04.98 Restore original layout ******************************************************************** =activatekeyboardlayout(lnSavedLayout,0) ******************************************************************** ** VS ** 21.04.98 Restore EXACT setting ******************************************************************** SET exact &lcoldEXACT ENDPROC *-- Protected -- Converts DWORD values to Decimal values PROTECTED PROCEDURE convertdwordtodec * Program....: LANGUAGESWITCHER.CONVERTDWORDTODEC * Version....: 1.0 * Author.....: ** Vladimir Shevchenko ** * Date.......: April 20, 1998 * Notice.....: Copyright (c) 1998 ** VSN Consulting **, All Rights * Reserved. * Compiler...: Visual FoxPro 05.00.00.0415 for Windows * Abstract...: Converts DWORD values to plain Decimal * Input......: tcSTRtoCNV - DWORD value passed as string * Output.....: Decimal value or .NULL. if passed value is not DWORD * Changes....: lparameters tcSTRtoCNV if len(tcSTRtoCNV) # 4 return .null. else return asc(substr(tcSTRtoCNV,2))*256+asc(substr(tcSTRtoCNV,1))+; asc(substr(tcSTRtoCNV,4))*16777216+asc(substr(tcSTRtoCNV,3))*65536 endif ENDPROC *-- Protected -- Fills an array with known values for layout's abbreviation * and numbers. PROTECTED PROCEDURE fillknowkbdlayoutinfo * Program....: LANGUAGESWITCHER.FILLKNOWKBDLAYOUTINFO * Version....: 1.0 * Author.....: ** Vladimir Shevchenko ** * Date.......: April 20, 1998 * Notice.....: Copyright (c) 1998 ** VSN Consulting **, All Rights * Reserved. * Compiler...: Visual FoxPro 05.00.00.0415 for Windows * Abstract...: Fill an array with known values of keyboard layout inffo * Input......: None * Output.....: Filled array aLayout * Changes....: *************************************************************************** ** VS ** 20.04.98 Initially, Windows 95 and higher supports 33 primary * layout *************************************************************************** dimension this.aLayout[33] this.aLayout[1] = [09 EN English] this.aLayout[2] = [1C SQ Africaans] this.aLayout[3] = [1D SV Sveden] this.aLayout[4] = [1A HR Croatia] this.aLayout[5] = [22 UK Ukraina] this.aLayout[6] = [0C FR France] this.aLayout[7] = [0B FI Finnland] this.aLayout[8] = [1F TR Turkey] this.aLayout[9] = [1A SR Serbia] this.aLayout[10] = [1B SK Slovakia] this.aLayout[11] = [24 SL Slovenia] this.aLayout[12] = [07 DE Germany] this.aLayout[13] = [14 NO Norway] this.aLayout[14] = [26 LV Latvia] this.aLayout[15] = [03 CA Catalan] this.aLayout[16] = [27 LT Litva] this.aLayout[17] = [18 RO Romania] this.aLayout[18] = [15 PL Poland] this.aLayout[19] = [16 PT Portugese] this.aLayout[20] = [10 IT Italia] this.aLayout[21] = [0F IS Islandia] this.aLayout[22] = [0A ES Spain] this.aLayout[23] = [09 BA Indonesian] this.aLayout[24] = [06 DA Dutch] this.aLayout[25] = [2D EU Bascks] this.aLayout[26] = [0E HU Hungary] this.aLayout[27] = [23 BE Belorussia] this.aLayout[28] = [13 NI Netherlands] this.aLayout[29] = [02 BG Bulgaria] this.aLayout[30] = [08 GR Greece] this.aLayout[31] = [05 CZ Czchehoslovakia] this.aLayout[32] = [19 RU Russia] this.aLayout[33] = [25 ET Estonia] ENDPROC *-- Protected -- Returns layout name which corresponds to given handle PROTECTED PROCEDURE retrievelayoutname * Program....: LANGUAGESWITCHER.RETRIEVELAYOUTNAME * Version....: 1.0 * Author.....: ** Vladimir Shevchenko ** * Date.......: April 20, 1998 * Notice.....: Copyright (c) 1998 ** VSN Consulting **, All Rights * Reserved. * Compiler...: Visual FoxPro 05.00.00.0415 for Windows * Abstract...: Get the layout name * Input......: tnKBDHandle - handle to a keyboard layout * Output.....: Corresponding description string from aLayout * property * Changes....: lparameter tnKBDHandle local lcLayoutName, lcPrimeLayout,ix, lcBuffer *************************************************************************** ** VS ** 21.04.98 Get the layout name corresponding to passed handle *************************************************************************** lcBuffer = space(50) =ActivateKeyboardlayout(tnKBDHandle,0) =Getkeyboardlayoutname(@lcbuffer) *************************************************************************** ** VS ** 21.04.98 Extract primary layout number and find info about layout * in aLayout propery *************************************************************************** lcPrimeLayout = right(alltrim(lcBuffer),3) *************************************************************************** ** VS ** 21.04.98 Cut off the trailing CHR(0) *************************************************************************** lcPrimeLayout = left(lcPrimeLayout,2) ix = ascan(this.aLayout,lcPrimeLayout) *************************************************************************** ** VS ** 27.04.98 Return layout abbreviated name from layout array *************************************************************************** return iif(ix > 0,substr(this.aLayout[ix],4,2),[UNKNOWN]) ENDPROC *-- Public -- Switches layout to given layout PROCEDURE switchlayout * Program....: LANGUAGESWITCHER.SWITCHLAYOUT * Version....: 1.0 * Author.....: ** Vladimir Shevchenko ** * Date.......: April 21, 1998 * Notice.....: Copyright (c) 1998 ** VSN Consulting **, All Rights * Reserved. * Compiler...: Visual FoxPro 05.00.00.0415 for Windows * Abstract...: Switches layout to the given layout * Input......: tcLName - abbreviated name of layout (like EN, RU * etc) * Output.....: None * Changes....: lparameters tcLName *************************************************************************** ** VS ** 21.04.98 First, check if given layout is loaded then just activate * him *************************************************************************** ix = ascan(this.aKBDHandles, alltrim(tcLname)) if ix > 0 *************************************************************************** ** VS ** 21.04.98 OK, switch layout *************************************************************************** =activatekeyboardlayout(this.aKBDHandles[ix/2,1],0) endif ENDPROC PROCEDURE Destroy *************************************************************************** ** VS ** 20.04.98 If Application object is presented, then clear dll there *************************************************************************** if type([goApp.Name]) # [C] clear dlls endif ENDPROC PROCEDURE Init * Program....: LANGUAGESWITCHER * Version....: 1.0 * Author.....: ** Vladimir Shevchenko ** * Date.......: April 20, 1998 * Notice.....: Copyright (c) 1998 ** VSN Consulting **, All Rights * Reserved. * Compiler...: Visual FoxPro 05.00.00.0415 for Windows * Abstract...: Switch the keyboard layouts on the fly. It is very * useful in international apps * Changes....: *************************************************************************** *-* VS ** 20.04.98 If goApp object is not presented, then declare dlls * directly * Usually, I place all declaration in application object's * method *************************************************************************** if type([goApp.Name]) # [C] this.DeclareDLL() endif *************************************************************************** ** VS ** 20.04.98 Now move this object off the screen to avoid unnecessary * *headache... *************************************************************************** this.Left = -3000 *************************************************************************** ** VS ** 20.04.98 Fill the arrary property aLayouts with all known values * of keyboard layouts. *************************************************************************** this.FillKnowKBDLayoutInfo() *************************************************************************** ** VS ** 20.04.98 Next thing to do is to fill out array property with * values of installed keyboard layouts handles. *************************************************************************** this.RetrieveInstalledLayoutHandles() ENDPROC ENDDEFINE * *-- EndDefine: languageswitcher **************************************************
If you have a language that is not found in FillKnowKBDLayout method (for example, Chineese or Japan languages, sorry, I do not have East enabled Windows on my computer) you might want to add these languages to the list. Here is how to do this:
Using LanguageSwitcher class
Now we should create GUI classes that will use newly developed class. You will need to create three classes that allows to enter information via keyboard: Textbox, Editbox and Combobox. In this article I will show how to subclass only the Textbox class because you can proceed with an other classes similarly. Listing 2 shows the code for Textbox class with new functionality.
Listing 2. SelfSwitchedTextbox class code
************************************************** *-- Class: selfswitchedtextbox (c:\projects\controls\lngswth\lngswth.vcx) *-- ParentClass: textbox *-- BaseClass: textbox * DEFINE CLASS selfswitchedtextbox AS textbox Height = 23 Width = 100 *-- PUBLIC -- indicates whether switch keyboard layout to desired lswitchlayout = .T. Name = "selfswitchedtextbox" *-- PUBLIC -- Language that mostly used for key in. cinputlanguage = .F. PROCEDURE GotFocus * Program....: SELFSWITCHEDTEXTBOX.GOTFOCUS * Version....: 1.0 * Author.....: ** Vladimir Shevchenko ** * Date.......: April 27, 1998 * Notice.....: Copyright (c) 1998 ** VSN Consulting **, All Rights * Reserved. * Compiler...: Visual FoxPro 05.00.00.0415 for Windows * Abstract...: Switch keyboard layout to desired (if necessary) * Input......: NONE * Output.....: NONE * Changes....: *************************************************************************** ** VS ** 27.04.98 Check if we should switch layout on entry * Then, we should check if layout name is defined *************************************************************************** if this.lSwitchLayout if type([this.cInputLanguage]) = [C] *************************************************************************** ** VS ** 27.04.98 Alright, now we call Switcher object *************************************************************************** ThisForm.LanguageSwitcher.Switchlayout(upper(this.cInputLanguage)) endif endif ENDPROC ENDDEFINE * *-- EndDefine: selfswitchedtextbox **************************************************
goApp.LanguageSwitcher.SwitchLayout(upper(this.cInputLanguage))
Conclusion
Multilanguage input can seriously degrade the performance of user's input and can damage any convenience with using your application, so, you should decide yourself about how to improve it. I do hope that this article helps you in your development and will ease life of your users. All codes are placed in Public Domain, however, please keep the Copyright notices AS IS. I have uploaded ZIP file that contains a class library with all necessary classes and a test form to fiddle.