|[ Team LiB ]|
Recipe 12.5 Add an Item to the Startup Group
As part of your application, you would like to be able to allow users to add an application to the Startup menu so that your application will start up when Windows does. You just can't figure out how to put the information into the Startup group. Is there a way to communicate between Access and the Windows shell so you can do this?
This is a case where the old technology called DDE comes in handy. The Windows shell accepts commands using DDE that allow you to create and delete groups and items. You can also retrieve lists of existing groups and items within those groups. This solution explains most of the Windows shell's DDE interface.
To test out the DDE interface, load and run the form frmShell from 12-05.MDB. This form, shown in Figure 12-7, allows you to view groups and their items, create and delete groups and items, and display a particular group. It will decide whether to use the group/item or the folder/shortcut terminology after determining whether you are using the Windows 9x shell or the Windows NT/Windows 2000 Program Manager, respectively.
Once you select a group from the list on the left in Figure 12-7, the form will display the group's items in the list on the right. If you select the first item in the righthand list—the group itself—the form will display the information Windows stored about that group. Once you've selected a group in the righthand list box, you can click the Show button to have Windows display that group. The code attached to the Show button requests Windows to open the group window using style 3 (see Table 12-8 for a list of window styles). As described later, in the sidebar Switching Focus," Windows may grab the focus, depending on the previous state of the group window you've selected.
Select an item in the group (any row except the first in the righthand list box), and the form will display all the information that Windows stores about that item. Figure 12-8 shows frmShell with an item selected.
With either a group or an item selected, you can create or delete a group or an item. If you've selected a group, pressing the Delete button will instruct Windows to delete that group; if you've selected an item, Windows will delete that item. Regardless of what's selected, pressing the Create button will pop up a dialog asking whether you want to create a new item or a new group. Either choice will pop up the appropriate dialog requesting the necessary information.
The following sections describe how to use the sample forms in your own applications, and then explain most of the DDE interface to the Windows shell. Although more DDE options are available, the most useful tasks can be accomplished with the tools provided here.
126.96.36.199 Using the sample forms
To include the sample forms from 12-05.MDB in your own applications, follow these steps:
188.8.131.52 Using DDE with the Windows shell
The Windows shell supports two operations: you can either request information using the DDERequest function (Table 12-5 lists the DDERequest items) or execute actions using the DDEExecute subroutine (Table 12-6 lists the most useful subset of the shell's DDEExecute command-string interface). DDE conversations between Access and the shell involve three steps:
184.108.40.206 Retrieving information from the Windows shell
Table 12-5 describes the two groups of information you can request from Windows. The sample form, frmShell, uses both to fill its two list boxes.
To retrieve a list of groups from Windows using the Access DDERequest function, you must first initiate a conversation with the PROGMAN program on the PROGMAN topic, requesting information on the PROGMAN item; even if you use the undocumented "Folders" program name and "AppProperties" topic, it still expects you to request information on the PROGMAN item. The DDERequest call returns a carriage-return/line-feed (CR/LF) delimited string of group names. It's up to your code to pull apart the list of groups and place them into whatever data structure is most convenient for you. To simplify this task, you can use the acbPMGetGroups function in basShell. It accepts, as a parameter, a dynamic array to fill in with the list of groups. This function performs the DDERequest for you and calls the private CopyToArray function to break apart the returned stream of groups and fill the array you've sent it. It returns the number of items in the array. Its source code is:
Public Function acbPMGetGroups(avarGroups( ) As Variant) ' Fill a dynamic array with all the Program Manager groups. Dim lngChannel As Long Dim strGroups As String Dim intCount As Integer On Error GoTo HandleErr ' Most replacement shells will start PROGMAN for you if you attempt ' to start up a DDE conversation with it. That is, you won't need ' to Shell( ) PROGMAN if you're using a replacement shell. lngChannel = DDEInitiate("PROGMAN", "PROGMAN") strGroups = DDERequest(lngChannel, "PROGMAN") intCount = CopyToArray(strGroups, avarGroups( )) ExitHere: acbPMGetGroups = intCount On Error Resume Next DDETerminate lngChannel Err.Clear Exit Function HandleErr: MsgBox Err.Number & ": " & Err.Description, , "acbGetProgmanItems" Resume ExitHere End Function
To call this function from your own code, use code like this:
Dim avarGroups( ) as Variant Dim intCount as Integer intCount = acbPMGetGroups(avarGroups( )) ' If you want the list sorted, call acbSortArray, in basSortArray. acbSortArray avarGroups( )
To retrieve a list of items within a selected group, use the acbPMGetItems function, which works almost exactly as acbPMGetGroups does. This time, however, pass in a group name along with the dynamic array to be filled in; the function uses the group name as the topic, instead of PROGMAN (see Table 12-5). It calls the CopyToArray function to move the items into the dynamic array. You generally won't sort the array, however, unless you store the first item; this first item returns information about the group window itself. The rest of the rows contain information about the individual items. To use acbPMGetItems, you might use code like this:
Dim avarGroups( ) as Variant Dim avarItems( ) as Variant Dim intCount as Integer intCount = acbPMGetGroups(avarGroups( )) intCount = acbPMGetItems(avarGroups(0), avarItems( )) ' List all the item information for the specified group. For intI = 0 To intCount - 1 Debug.Print avarItems(intI) Next intI
220.127.116.11 Executing tasks
The Windows shell includes a command-string interface, which you can access via DDE, that allows you to execute tasks involving groups and items within those groups. Table 12-6 lists the functions addressed in this solution. Other commands are available (they're documented in the Windows SDK documentation), but they're not as useful for Access programmers.
In each case, you use the Access DDEExecute procedure to communicate with the shell. You must construct a string containing the function name, parentheses, and any arguments for the function. For example, to create a group from within Access, you can use code like this:
Dim intChannel as Integer intChannel = DDEInitiate("PROGMAN", "PROGMAN") DDEExecute intChannel, "[CreateGroup(My Group, MYGROUP.GRP)]"
The command string must be surrounded by square bracket delimiters (). Luckily, the Windows shell is far more relaxed about the use of embedded quotes than almost any other DDE-enabled application. For example, WinFax Pro's implementation of DDE requires quotes embedded in command strings you send to it; the Windows shell accepts embedded quotes but doesn't require them.
Some functions, such as AddItem, allow quite a few parameters, almost all of which can be left blank (see Table 12-7). To use the AddItem command to add a new item, you must first select a group in which to add the item. To do this, use the CreateGroup command, which creates a group if necessary or selects it if it already exists. The only required AddItem parameter is the command line. Note that both X- and Y-coordinates are necessary if you choose to specify coordinates for the icon. For example, to create a new icon to run C:\EDIT\MYEDIT.EXE with the description My Editor minimized in the My New Group group, use code like this (you'd normally include error-handling code, too):
Dim intChan As Integer intChan = DDEInitiate("PROGMAN", "PROGMAN") ' First select the group (or create it). DDEExecute intChan, "[CreateGroup(My New Group)]" ' Use commas to delimit parameters (even missing ones). DDEExecute intChan, "[AddItem(C:\EDIT\MYEDIT,My Editor,,,,,,1)]"
18.104.22.168 Using the wrapper procedures
To make your DDE programming simpler, the module basShell includes wrapper procedures that handle all the details for you. (Table 12-4 provides a description of each of the wrapper procedures; Table 12-9 lists the parameters.) The module also provides functions that handle each of the commands described in Table 12-6. In some cases (AddItem, for example), the wrapper functions don't allow you to specify all the possible parameters for the command string. If you find these wrapper functions too limiting, you can modify them so they allow you to pass in whatever parameters you like.
All the wrapper procedures (except acbPMShowMessages) in Table 12-9 perform the same set of steps to communicate with the Windows shell. To simplify the code and centralize error handling, those steps have been pulled into a single private procedure in basShell, DDEExecutePM, which is shown in the following code example:
Private Function DDEExecutePM(strCommand As String) As Boolean ' DDEExecute with the passed-in command. If it succeeds, ' return True. If it fails, return False. ' At this point, this function handles error messages itself. ' You could move this out of here to a higher level, if you ' want, by setting the SHOW_MESSAGES constant to False. Dim lngChannel As Long On Error GoTo HandleErr lngChannel = DDEInitiate("PROGMAN", "PROGMAN") DDEExecute lngChannel, strCommand DDEExecutePM = True ExitHere: On Error Resume Next DDETerminate lngChannel Err.Clear Exit Function HandleErr: If Not mfHideMessages Then MsgBox Err.Number & ": " & Err.Description, , "DDEExecutePM" End If DDEExecutePM = False Resume ExitHere End Function
Given a string to execute, this code initiates the DDE channel, uses DDEExecute to execute the command, and then terminates the connection. If all goes according to plan, the procedure returns a True value. If an error occurs, it displays a message box (unless you've used the acbPMShowMessages procedure to disable warning messages) and then returns False.
Table 12-9 lists the parameters for the wrapper procedures in basShell. Each of these procedures (except acbPMShowMessages) returns True if the function succeeded, or False if it failed. Unless you've called the acbPMShowMessages subroutine to disable messages, a message will appear before deleting a group or item or if any error occurs.
For example, to use the wrapper functions to add an icon to the My Group group that will run C:\EDIT\MYEDIT.EXE minimized with the description My Editor (as in the example that called AddItem directly), you could use code like this:
Dim fSuccess As Boolean ' Disable error messages. acbPMShowMessages False fSuccess = acbPMCreateItem("My Group", "My Editor", _ "C:\EDIT\MYEDIT.EXE", Null, True) If Not fSuccess Then MsgBox "Unable to create new item!"
This example also calls acbPMShowMessages to disable error messages from within acbCreateItem, so the code fragment itself can handle them.
For examples of each of the wrapper functions, check out the code in frmShell's module.
Though this solution covers a great deal more than the original question required, all the information here will be useful to Access programmers working with the DDE interface to the Windows shell.
The sample form, frmShell, is not only a good example of using DDE to converse with Windows, it's also a useful tool on its own. Because it allows you to see what's in each group without having to open and close each group's window, it's a quick and easy way to clean out your groups. Of course, some extra work would be required for it to be a really useful tool, but it's a good start.
In 16-bit applications, DDEInitiate returns a short integer (16-bit) handle. In Access 95 and later (and other 32-bit applications), this function returns a long integer (32-bit) handle. If you have existing code that uses DDE, you'll want to convert the variables containing the return values into long integers.
The Windows shell has an undocumented DDE application topic pair that is not supported by the original Program Manager or any of the major third-party shell substitutes: Folders AppProperties. This syntax seems to be just an alias for the regularly documented DDE interface, because the item name syntax and all the operations are identical in both cases.
This undocumented syntax can be of some benefit. If you are going to add the functionality to interact with the shell, you can use code like the following to determine if your user is running the Windows 9x shell:
Public Function acbNewShell ( ) as Boolean Dim lngChannel as Long On Error Resume Next lngChannel = DDEInitiate("Folders","AppProperties") acbNewShell = (lngChannel <> 0) DDETerminate lngChannel End Function
You'll notice that the example uses this function (as well as a public flag) to decide whether to call the various shell objects "groups" and "items" (as in the Windows NT Program Manager) or "folders" and "shortcuts" (as in the Windows 9x shell).
To shield you from the details of the DDE conversation and to isolate the DDE code in one routine, each of the command-string replacement functions calls the DDEExecutePM function. This makes the code neat and easy to understand, but it does have a potential disadvantage: calling DDEInitiate and DDETerminate every time you call a wrapper function adds substantial time and overhead to your application. If you make many calls to Window via DDE, you'll want to reconsider this design. For most applications, though, this shouldn't be a problem.
|[ Team LiB ]|