Sunday, February 17, 2008, 8:19:00 PM
Windows internally has a neat feature which is the ability to make a text box automatically pick up file system paths and the URL history and assign it to a textbox as a dynamic drop down. You've undoubtedly seen this behavior all over the place in Windows itself (like the Explorer file selection boxes) or in other applications.
Here's what this should look like in a custom application:
As you type path information the behavior shows a drop down of all the available files and directories below the input box.
The process of doing the above is pretty simple from a Windows perspective - there's an API call that extends an input control with the required functionality to show the drop down of files.
The API function that handles this is:
HWND hwndEdit, DWORD dwFlags
You pass an hWnd handle to a window or control and a flag that defines the behavior of the auto complete dropdown.
In FoxPro code this looks like this:
DECLARE SHAutoComplete IN "shlwapi.dll" LONG hwndEdit, LONG dwFlags
with the following constants applied:
#DEFINE SHACF_AUTOAPPEND_FORCE_OFF 0x80000000
#DEFINE SHACF_AUTOAPPEND_FORCE_ON 0x40000000
#DEFINE SHACF_AUTOSUGGEST_FORCE_OFF 0x20000000
#DEFINE SHACF_AUTOSUGGEST_FORCE_ON 0x10000000
#DEFINE SHACF_DEFAULT 0x0
#DEFINE SHACF_FILESYSTEM 0x1
#DEFINE SHACF_URLHISTORY 0x2
#DEFINE SHACF_URLMRU 0x4
#DEFINE SHACF_URLALL SHACF_URLHISTORY +SHACF_URLMRU
Seems easy enough, right?
Only one problem: VFP controls don't have an HWnd property (not even an internal one) so there's no way to attach this behavior to native controls.
Another Tack: ActiveX Controls
The alternative is that you can use an ActiveX text control and use it to apply this functionality. You can use the RichText Control from the common control library for example and sure enough that works. If you want to do this all you have to do is:
- Add a RichTextControl onto the page
- Add the following code:
DECLARE SHAutoComplete IN"shlwapi.dll" LONG hwndEdit, LONG dwFlags
SHAutoComplete(this.txtProjectFile_Rtf.Hwnd,268435457) && File Autosuggest
And that's it. It works, but if you're after a clean UI experience you'll probably be a little disappointed:
You can see that the RichTextBox shows in 3D (Win98 style) formatting. Although there are a few options (Appearance and Borderstyle) that you can play around with none of them let you get a themed look that matches your typical FoxPro form interface - the control looks out of place.
Yet, if you look closely on the first screenshot (which is also running in FoxPro) you'll notice that the textbox actually DOES look correct. There's some extra code involved in making that happen.
The way that code works is by placing a TextBox control underneath the rich text control, changing its style to flat and no borders then overlaying the rich text control over the textbox so that only its borders show with the Rich Textbox overlaid on top and acting as the 'real' input control.
Reusable Code? FoxPro and ActiveX Hell
You can do this manually but it's a bit of work everytime you want a file input box, so I got to thinking that it'd be easier to create something reusable since it's a fairly common scenario.
My first preference would have been to build a TextBox extender. Rather than creating a new UI class I figured an extender would be perfect so that you wouldn't have to add a new control to the page. The idea was that you'd pass in a TextBox control and the code would dynamically add the rich text box, use BindEvent to capture changes and transfer them back to the textbox. In other words it would fake input with the RTF control but otherwise leave the old control completely intact.
Sounds easy right? But there ended up being a number of problems with this approach. The worst of which is that that Appearance and BorderStyle cannot be set past control initialization on the RichText control. Assigning these values didn't work to change the control's display - even if I create custom (PRG based) subclass of the RichText control and assign the values as defaults. In the end the only way I could make this work was by requiring the RTF control to be on the form with the proper formatting applied which is lame. There were a few other problems too - the extender approach required keeping track of the TextBox and the Control and that in turn caused form unload errors due to cyclic references. I managed to get around this by binding all possible Form unloadnig events (QueryUnload, Unload, Release, Destroy) but there ended up being a bunch of associated code.
In the end I decided that this wasn't worth the effort - the requirement for the extra RTF control on the page pretty much defeats the purpose so I decided to just create a visual subclass of the RTF control to provide this functionality.
The end result is a simple wwAutoComplete Control that can be just dropped onto the form. The control code looks like this:
*-- Class: wwautocomplete (c:\wwapps\wwhelp\wwdialogs.vcx)
*-- ParentClass: olecontrol
*-- BaseClass: olecontrol
*-- Time Stamp: 02/17/08 06:08:01 PM
*-- OLEObject = C:\Windows\system32\richtx32.ocx
DEFINE CLASS wwautocomplete AS olecontrol
Height = 22
Width = 300
BorderStyle = 0
Appearance = 0
OleClass = "RichText.RichTextCtrl.1"
*-- High level modes: FILE, URL, URLMRU
cautocompletemode = "FILE"
*-- Top margin of the RTF control inside of the textbox. Use this to account for lack of margins in the RTF control.
ntopmargin = 4
Name = "wwautocomplete"
LOCAL lnFlags, loRtf
lnFlags = this.cAutoCompleteMode
LOCAL loRtf as RichText.RichTextCtrl.1
loRtf = this
IF VARTYPE(lnFlags) = "C"
lnFlags = UPPER(lnFlags)
CASE lnFlags = "FILE"
lnFlags = 268435457
CASE lnFlags = "URL"
lnFlags = 268435458
CASE lnFlags = "URLMRU"
lnFlags = 268435460
lnFlags = 268435457
loRtf.Parent.AddObject(loRtf.Name + "_RTF","TextBox")
LOCAL loTextBox as TextBox
loTextBox = EVALUATE("loRtf.Parent." + loRtf.Name + "_RTF")
loTextBox.Left = loRtf.Left
loTextBox.Top = loRtf.Top
loTextBox.Height = loRtf.Height
loTextBox.Width = loRtf.Width
loTextBox.TabStop = .F.
loRtf.Left = loRtf.Left + 2
loRtf.Width = loRtf.Width - 3
loRtf.Top = loRtf.Top + this.nTopMargin
loRtf.Height = loRtf.Height - this.nTopMargin - 1
loTextBox.Visible = .t.
DECLARE SHAutoComplete IN "shlwapi.dll" INTEGER hwndEdit, INTEGER dwFlags
SHAutoComplete(loRTF.Hwnd, lnFlags )
loRtf = null
loTextBox = null
*-- EndDefine: wwautocomplete
With this control in place you can simply drag the control onto the surface and set the cAutoComplete mode, which defaults to file. It will do the rest and give you the formatting as shown in the first screen shot above.
This is fine if you're starting from scratch. Unfortunately, if you need to replace a TextBox control you'll have to make sure you search for all .Value assignments and replace them with the .Text property of the RichText control. You can't even implement a Value property of your own - Visual FoxPro doesn't allow that on OleControl even though it doesn't have a Value property. Grrr...