|By Bruce Armstrong, Demetrios Tsakiris||
|August 5, 2005 10:00 AM EDT||
What we needed to do was implement spell checking in the rich edit fields in our application (see Figures 1 and 2). To do that, we got a license for the Sentry Spelling Checker Engine from Wintertree Software (www.wintertree-software.com). The utility is easily implemented and works quite well on standard Rich Edit controls.
However, the PowerBuilder Rich Edit control is an OEM version of an old third-party control that was popular before Microsoft introduced its Rich Edit control to the common controls. As a result, the messages and functions it supports are completely different from those that the Sentry utility is trying to use to interact with the control.
Our first approach to dealing with this was to extend the PowerBuilder Rich Edit control so that it mimicked the events and functions of the Microsoft Rich Edit control. However, we soon realized that the amount of work required to do this was rapidly approaching the same level of effort needed to replace the PowerBuilder Rich Edit control with the Microsoft version. We also realized that there were other potential advantages to implementing the Microsoft version instead, not the least of which would be support for a recent version of the RTF standard.
Implementation - u_richedit
The first step was to take a page from Regan Sizer (author of the Common Controls chapter of the SAMs PowerBuilder 9: Advanced Client/Server Development book and a couple of related excerpts here in PBDJ) and create a custom visual user object based on the Microsoft Rich Edit control (see Figure 3).
You'll want to refer to the MSDN reference page on its Rich Edit control as we discuss the implementation. See http://msdn.microsoft.com/library/default.asp?url=/
The "About Rich Edit Controls" link on that page takes us to another page that indicates the DLL and class name we need to reference to implement the control. We've referenced the RICHED20.DLL file so we can support up through version 3.0 of the control. The most recent version of the control (as of this writing) is 4.1 and would require a reference to the MSFTEDIT.DLL file instead. However, that version of the control is only supported on Windows XP SP1 and later. Version 3.0 is supported on all operating systems since Window 98. Since we can't guarantee that all of our users will be on XP SP1 or later, we chose to implement a slightly older version of the control to ensure compatibility on all clients. Since version 2.0, the control offers two class names: one for Unicode and another for ANSI. Since our application is still PowerBuilder 9 (ANSI)-based, we're using the ANSI class (RichEdit20A).
Finally, the "Rich Edit Control Styles" link on that page takes us to another page that indicates the various style settings available that we need to implement through the Style property of the visual user object. The styles we are particularly concerned about are:
- ES_LEFT: left paragraph alignment
- ES_SUNKEN: gives the control a sunken border
- ES_MULTILINE: otherwise the control defaults to support for only a single line
- ES_WANTRETURNS: so the user entering carriage returns will result in a returns character being added to the text in the control
- ES_NOHIDESEL: so the selected text in the control is still highlighted when focus moves off the control.
The values for those and other constants we'll be using are provided in the include files (*.h) provided in the Windows SDK, particularly WinUser.H and RichEdit.H. The values in those files are provided in a hexadecimal format that we'll need to convert to decimal and combine (see Table 1).
u_richedit Instance Variables
There are a large number of instance variables declared in the user object; all but one of them are constants from the Windows API used to interact with the control. For those of you with some C++ experience, this is essentially the way that we incorporated an include file with defined constants. What this let us do was refer to various messages and settings in our code by the names used in the Windows API (e.g., PFA_LEFT, PFA_RIGHT, PFA_CENTER, and PFA_JUSTIFY for paragraph justification), which will make the PowerScript code more understandable. The one additional instance variable was a string variable (rtftext) used to capture data coming from a callback, which we'll discuss a bit later.
u_richedit Local External Functions
The user object also has a large number of local external functions declared on it. Many of them are aliases for the SendMessage Windows API function. Most of the interaction we'll be having with the control will be by sending a certain Windows message to the control using that function. The SendMessage function takes four arguments, the first one being the handle of the window or control we're sending the message to and the second one being the numeric value of the message we're sending. The third and fourth arguments vary depending on which message we're sending. We could have declared a number of overloaded versions of the SendMessage function without using aliases, but opted to use aliases to make the PowerScript code more understandable.
When working with Windows API function calls, we occasionally have to work with arguments that are pointers or structures used as arguments that contain pointers. That requires some memory manipulation before or after the call, and we've declared a number of Windows API functions as local external functions to assist us with that (LocalAlloc, LocalFree, RtlMoveMemory, GlobalAlloc, GlobalLock, GlobalUnlock, and GlobalFree). To do copy and paste operations with RTF data, we also declared a number of Windows API functions for working with the clipboard (OpenClipboard, RegisterClipboardFormat, GetClipboardData, SetClipboardData, and CloseClipboard). A few Windows API function declarations were added to support the use of the ChooseFont dialog (ChooseFont, GetDC, GetDeviceCaps, and MulDiv).
One of the Windows messages we'll be sending to the control (EM_STREAMOUT - used to get the RTF formatted data out of the control) requires the address of a callback routine to return the data. Since PowerBuilder doesn't support callbacks, we've written a custom DLL (which we'll be discussing in Part 2) to provide that callback. We have to load the DLL and get the address of that callback, so we've declared a couple of Windows API function calls to help with that (LoadLibrary, GetProcAddress, and FreeLibrary). Finally, the callback needs to know how to send the data back to the control, so there's also a local external function declaration for that custom DLL to provide the handle to the control (SetControl).
- Where Are RIA Technologies Headed in 2008?
- PowerBuilder History - How Did It Evolve?
- Creation and Consumption of Web Services with PowerBuilder
- Cloud People: A Who's Who of Cloud Computing
- DDDW Tips and Tricks
- Cloud Expo 2011 East To Attract 10,000 Delegates and 200 Exhibitors
- Working with SOA & Web Services in PowerBuilder
- Dynamically Creating DataWindow Objects
- Cloud Expo, Inc. Announces Cloud Expo 2011 New York Venue
- OLE - Extending the Capabilities of PowerBuilder