A form in Visual Basic is represented as a special type of class that includes hooks into the Windows graphical user interface (GUI) technology. The gory details of the hooks remain suppressed within the Visual Basic run-time library. What the Visual Basic programmer has access to is a special type of class module officially referred to as a Form module. A Form module is associated with a particular window that can contain controls (children) such as buttons and list boxes. Both the window and its controls raise events in response to user interaction. Visual Basic auto-magically subscribes to events simply by allowing the programmer to stub out event handler subroutines made readily available in the Object drop-down list box in the Form class module editor. This means of event handling, which by the way should be considered an implementation of an Event Service design pattern, has been the cornerstone technology advantage of programming Windows GUI applications in Visual Basic.
The purpose of this sample is to create a localized Windows application. The typical approach to localizing an application calls for replacing string literals with variable references to strings that are obtained from a table. The form that would display the text usually follows one of two approaches: It incorporates some auto-proportioning algorithm to resize the form and its controls, and makes font changes appropriately to accommodate the language text; or it determines a static proportion that can accommodate all anticipated languages the application must display. Both approaches eventually fall short of an ideal solution mainly because there is such a great disparity between the number of characters required to represent the words that form a given phrase. For example, a phrase in German that requires 100 characters might require only one character in Japanese.
A third, less practiced alternative exists, which would be to maintain a completely separate form for each language. By using this approach, you can customize the display to the exact dimensions required. The important issue with this alternative is that you must figure out how to get all forms to publish identical events that a single subscriber can handle. Hence the behavior is written once and remains consistent across multiple language implementations that can be provided at any point. You would also want to package each language version in its own satellite DLL that is interchangeable in the Windows application.
What better way to implement this third alternative than by taking advantage of the ActiveX DLL technology available in Visual Basic? Unfortunately, Form classes must be private in an ActiveX project, making them inaccessible from a client application. My solution to this problem is to create a public ActiveX class that encapsulates a Form object and delegates through appropriate method calls such as IForm.Show and IForm.Hide. In addition, this class must be able to notify a subscriber of events that would normally be handled in the Form class. This is accomplished by creating the interface-based implementation solution of the Event Service design pattern that publishes the events captured in the private Form object defined in the ActiveX DLL.
To clearly demonstrate the benefits of this approach, I've created a simulated online banking window that allows you to proceed in English or in Spanish by the click of a button. Based on your selection, you get the Security form in English or in Spanish. (See Figure 13-2.) Notice that despite the difference in language, pressing the OK and Cancel buttons produces identical behavior in both windows. Under the hood, this application consists of the following components.
The code in the following discussion highlights the participants of the Event Service design pattern that contribute to the successful implementation of a localized satellite Forms DLL. For a full source code disclosure, refer to the companion CD.
The following components are compiled in FormLib.tlb and defined using IDL rather than Visual Basic. (Appendix B explains how to define interfaces in IDL directly and the reasons for doing so.)
Figure 13-2. Sample application localized Security login window.
interface IForm : IDispatch { [id(0x60030000)] HRESULT Show( [in, optional, defaultvalue(0)] FormModalityConstants Modal, [in, optional] VARIANT OwnerForm); [id(0x60030001)] HRESULT Hide(); [id(0x60030002)] HRESULT Advise( [in] IFormEvents* frmEvt, [in, optional] BSTR Notify, [out, retval] long* ); [id(0x60030003)] HRESULT UnAdvise([in] long Cookie); }; |
interface IFormEvents : IDispatch { [id(0x60030000), vararg] HRESULT OnEvent( [in] BSTR Evt, [in, out] SAFEARRAY(VARIANT)* Params); }; |
This next component is defined in both FormsEnglish.DLL and FormsSpanish.DLL:
' FormLogin Class Implements FormLib.IForm ' Form frmLogin related variables Private m_frmLogin As frmLogin Private WithEvents cmdOK As CommandButton Private WithEvents cmdCancel As CommandButton ' IFormEvents Private m_FormEvents As IFormEvents ' IForm interface implementation Private Function IForm_Advise(ByVal frmEvt As _ FormLib.IFormEvents, _ Optional ByVal Notify _ As String) As Long Set m_FormEvents = frmEvt End Function ' Form frmLogin event handlers that publish Form events to ' an IFormEvents object via its OnEvent method ' Private Sub cmdCancel_Click() ' Publish cmdCancel button click event If Not m_FormEvents Is Nothing Then Call m_FormEvents.OnEvent("cmdCancel.Click") End If End Sub Private Sub cmdOK_Click() ' Publish cmdOK button click event If Not m_FormEvents Is Nothing Then Call m_FormEvents.OnEvent("cmdOK.Click", _ m_frmLogin.txtUserName, _ m_frmLogin.txtPassword) End If End Sub |
This final component is defined in Banker.exe:
' FormEventsImpl Class Implements FormLib.IFormEvents ' Implementation of IFormEvents.OnEvent Private Sub IFormEvents_OnEvent(ByVal Evt As String, _ ParamArray Params() _ As Variant) If Evt = "cmdOK.Click" Then MsgBox "Ok button clicked" & vbLf & _ "UserId = " & Params(0) & vbLf & _ "Password = " & Params(1) Else MsgBox "Cancel button clicked" End If End Sub |