Вы находитесь на странице: 1из 6

Print Article

Seite 1 von 6

Issue Date: FoxTalk March 1998

Understanding the CommonDialog Control


John V. Petersen

This month, John continues to attack the CommonDialog control head-on.


In the January 1998 issue of FoxTalk (see "Creating and Obtaining a Printer Device Context"), I discussed a long-standing problem facing Visual FoxPro Developers -- the need to create a printer device context -- and showed how it was solved by using the CommonDialog ActiveX control. To review, controls like the RichText Edit Box must have a valid printer device context to which to refer in order to print their contents. In the Visual Basic Development System, this is a snap due to the existence of a global Printer object, which takes care of the details of establishing a printer device context. While the VFP Programming System doesn't have a native Printer object, VFP can make use of the CommonDialog control to create a valid printer device context. As I'll show, the CommonDialog control is quite flexible. This month, I'll focus on how the control works, and next month, I'll show how to augment its functionality via a subclass. What exactly does the CommonDialog control do? As the name implies, this control provides an interface to several user dialog boxes present in the Windows OS. These dialog boxes include File Open, File Save, Printer, Font, Color, and Help. By invoking the appropriate method, each of these dialog boxes can be displayed to the user. To get things started, follow these steps to conduct some simple tests (these steps assume that you've already registered the Microsoft CommonDialog control in VFP in the Controls tab of the VFP Options dialog box): 1. Create a form and name it frmCD. 2. Drop a CommonDialog control on the form. Name the control oleCD. 3. In the Command window, issue Do Form frmCD. Now, in order to display the File Open dialog box, issue this code in the Command window: ?FrmCD.oleCD.ShowOpen()

You'll see the standard File Open dialog box appear. However, it's not exactly the same one you get when issuing a GetFile() function within VFP. This, then, is a good time to discuss how working with the CommonDialog control differs from the native VFP functions that correspond to these dialog boxes. How do the CommonDialog methods differ from VFP functions? If you pressed the Open button, your first observation might have been that nothing was returned by the ShowOpen() method. Instead, the name of the selected file is stored to the Filename property of the CommonDialog control. The VFP function corresponding to the ShowOpen() method is GetFile(). When GetFile() is issued, the filename of the selected file is returned. Another difference is the appearance of the dialog boxes themselves. Figure 1 and Figure 2 show the dialog boxes when the ShowOpen() method and the GetFile() functions, respectively, are issued. It should be noted that these dialog boxes aren't the same -- the difference rests with the attributes for the dialog box that have been selected. VFP makes certain assumptions and exposes a few of the characteristics that can be altered by passing the GetFile() function various parameters. As I'll show later in the article, many more dialog box characteristics are exposed in the CommonDialog control. This is one reason why a developer would elect to use the CommonDialog control in lieu of native VFP Commands. Figure 1. Dialog box when the ShowOpen() function is used.

Figure 2. Dialog box when the GetFile() function is used.

As you've seen, when a file is selected after the ShowOpen() method is invoked, the FileName property of the CommonDialog control is updated. Other dialog boxes have similar behavior. For example, the Color dialog box, which is displayed by invoking the ShowColor() method, updates the Color property. The ShowFont() method, which displays the Font dialog box, updates the various font properties of the CommonDialog control.

http://foxtalknewsletter.com/ME2/Audiences/Segments/Publications/Print.asp?Module=... 07.02.06

Print Article

Seite 2 von 6

What happens when the Cancel button is pressed? This is another area where the native VFP functions differ from the CommonDialog control. When the Cancel button is pressed in the dialog box produced by GetFile(), an empty string is returned, signifying that a file wasn't selected. With the CommonDialog control, the behavior observed will depend on the setting of the CancelError property of the CommonDialog control. (Note: This property is accessed by right-clicking on the control, not through the VFP properties window.) If CancelError is set to .F., which is the default, an error condition won't occur if the Cancel button is pressed. At first, this might seem like a good thing. Unfortunately, if CancelError is set to .F., there's no way to detect whether the user pressed the Cancel button. Consider this scenario: 1. The user selects a file and thus updates the FileName property (of the CommonDialog control). 2. The user thinks about selecting another file, but instead decides to bail on the entire action. 3. The user presses the Cancel button. What's the value of the FileName property after the user presses the Cancel button? The FileName property still holds the name of the last file the user selected. Since the CancelError property is set to .F., there's no way to trap the user selecting the Cancel button. Therefore, to properly account for the user pressing the Cancel button, you'll need to allow an error to occur by setting the CancelError property to .T., and then trap for the error when it occurs. The following example, available in the accompanying Download file , uses in-line error trapping to get the job done. (Later in the article, I'll show how you can extend the base CommonDialog class to do the work for you.) * 02peter1.prg Local lcErr,llErr * Save the current global error handler lcErr = On("error") llErr = .F. && Establish our error variable do form FRMCD On Error llErr = .T. && Set our in-line error trap frmcd.oleCD.ShowOpen() && Invoke the File Open Dialog If llErr && The user pressed the Cancel Button MessageBox("You pressed the Cancel Button") Else MessageBox("You selected " + frmcd.oleCD.Filename) Endif On Error &lcErr && Restore the global error handler Return

How do you customize the dialog boxes? In the January 1998 issue, I discussed how to set the Flags property of the CommonDialog control in order to create a printer device context when the Print dialog box is displayed via a call to the ShowPrinter() method. Just about every attribute of the dialog boxes in the CommonDialog control is customizable by altering the Flags property. Continuing with the File Open dialog box as our primary example, I'll run through a few examples of changing the Flags property and noting the effects. Before going further, though, it's important to note the Help associated with the CommonDialog control. This file can be accessed by first selecting the control in the Form's Designer, and then clicking the right mousekey and selecting the Help option. One of the most important features of the Help file is that it documents all of the hexadecimal values that correspond to the customizable attributes of the various controls. For example, the Help file states that, to allow multiple selection of files, the Flags property must be set to the cdlOFNAllowMultiselect constant, which has a value of &H200. The bad news is that hexadecimal values in the Help file are expressed in a manner that Visual Basic -- not VFP -- can use. The good news is that, beginning with version 5.0, VFP can use hexadecimal values directly, and the VB hexadecimal values can be used by VFP with a slight modification. A value of &H200 in VB corresponds to a value of 0x200 in VFP. If you need to use decimal values, you'll need to convert the hex number to decimal. If you're not used to doing this conversion in your head, don't worry. Just issue this code in the Command window: ? 0x200

The decimal equivalent -- 512 -- will be echoed to the VFP screen. In continuing with our example, giving the File Open dialog box the ability to select multiple files entails the following modification to our code:

* 02peter2.prg Local lcErr,llErr * save the current global error handler lcErr = On("error") llErr = .F. && Establish our error variable

http://foxtalknewsletter.com/ME2/Audiences/Segments/Publications/Print.asp?Module=... 07.02.06

Print Article

Seite 3 von 6

do form FRMCD On Error llErr = .T. && Set our in-line error trap frmcd.oleCD.Filename = "*.*" && Default to all files frmcd.oleCD.Flags = 0x200 && Set the multiselect flag frmcd.oleCD.ShowOpen() && Invoke the File Open Dialog If llErr && The user pressed the Cancel Button MessageBox("You pressed the Cancel Button") Else MessageBox("You selected " + frmcd.oleCD.Filename) Endif On Error &lcErr && Restore the global error handler Return

Figure 3 illustrates the modified look of the File Open dialog box when you can select multiple files. Figure 3. The File Open dialog box.

Finally, suppose you'd like to make use of both the cdlOFNAllowMultiselect constant and the cdlOFNExplorer constant, which produces an Explorer-like interface for the File Open dialog box. Just as different options are added together in the MessageBox function, so too are options added together in the Flags property. To illustrate, simply swap the preceding Flag property assignment with this code: * se the multiselect flag and Explorer Display frmcd.oleCD.Flags = 0x200+0x80000

Figure 4 illustrates the modified look. Figure 4.

Handling CommonDialog errors In Visual FoxPro, which object owns the error? That's easy: whichever object's Error() event fires. If anybody gives you this response, you should immediately take his or her laptop and send it to David Letterman so he can throw it off a five-story building! Just kidding <g>. Of course the object that causes the error also has its Error() Event fired. The key in all of this is identifying which object caused the error. Let's review the code that was contained in the Click() method of a CommandButton: Local lcErr,llErr * Save the current global error handler lcErr = On("error") llErr = .F. && Establish our error variable * Set our in-line error trap On Error llErr = .T. * Invoke the File Open dialog box frmcd.oleCD.ShowOpen() If llErr && The user pressed the Cancel Button MessageBox("You pressed the Cancel Button") Else MessageBox("You selected " + frmcd.oleCD.Filename) Endif On Error &lcErr && Restore the global error handler Return

If the CommonDialog control raised the error, should its Error() event fire? To answer this question, it's important to look at how errors manifest themselves in an OO environment. Consider the concept of messaging: Messaging is the sole means by which objects communicate. Figure 5 shows a messaging diagram. Figure 5. A messaging diagram.

http://foxtalknewsletter.com/ME2/Audiences/Segments/Publications/Print.asp?Module=... 07.02.06

Print Article

Seite 4 von 6

First, the CommandButton sends the ShowOpen() message to the CommonDialog control. The CommonDialog control, in turn, processes the ShowOpen() message by invoking its ShowOpen() method. When this occurs, the standard File Open dialog box is presented to the user. If the user presses the Cancel button -- providing the CancelError property of the CommonDialog is set to .T. -- the CommonDialog control will raise an error. This action is carried out by the CommonDialog control sending a message to the CommandButton to fire its Error() event. If our goal is to create a subclass that is as fully encapsulated as possible, we've already encountered a major roadblock when it comes to handling errors. Managing a CommonDialog subclass will be difficult without a plan that will facilitate the CommonDialog control handling errors that the control itself raises. Given this, we now have the following design criteria for this new subclass of the CommonDialog control: It must be able to recognize and handle errors it raises -- it can't rely on other objects to handle errors, as this would bring coupling to an unacceptable level. (For a nice overview of coupling, refer to Jefferey Donnici's "Best Practices" column in the January 1998 issue of FoxTalk.) Coupling refers to how much two modules are interconnected. These modules can either be class or blocks of code. The extent to which coupling occurs in a system is usually an indication of how complex the system is to maintain. A sample form, CDIALOG.SCX, and an updated OLECLASSES class library are also included in the accompanying Download file . Wrapper methods to the rescue! While studying the messaging, it became apparent to me that the object that sends the ShowOpen() message to the CommonDialog control is the object that will recognize the error. What if the CommonDialog control sent the ShowOpen() message to itself? Figure 6 illustrates how the messaging changes. Figure 6.

In order to help you better understand the FileOpen() message, I've outfitted the CommonDialog control subclass with a FileOpen() method. The code for that method is shown in Listing 1. Listing 1. This code is contained in the new FileOpen() method of the CommonDialog control subclass. lParameters cExtensions,cTitle Local cRetVal cRetVal = Space(0) This.SetFilter(cExtensions) This.SetTitle(cTitle) This.ShowOpen() If This.lError * cancel was pressed If This.aerrarray[1,6] = 32755 */ Reset error This.ResetError() cRetVal = Space(0) Endif Else cRetVal = This.FileName This.UpdateHistoryArray() Endif Return cRetVal

Most of the elements in the code block shown in Listing 1 will be discussed later in the article. At this point, it's important to focus on the fact that the CommonDialog control can handle its own errors. To facilitate its own error handling, the code shown in Listing 2 has been added to the Error() method. Listing 2. This code is contained in the Error() Event method. LPARAMETERS nError, cMethod, nLine Aerror(This.aErrArray) This.lError = .T. Return

To better facilitate the recognition of an error, the CommonDialog subclass has been outfitted with two new properties: 1. lError -- Protected Boolean property to indicate if an error condition has occurred.

http://foxtalknewsletter.com/ME2/Audiences/Segments/Publications/Print.asp?Module=... 07.02.06

Print Article

Seite 5 von 6

2. AErrArray -- Protected array property to hold contents of Aerror(). By now, it should be apparent that the goal of the FileOpen() method is to provide the advantages of the GetFile() function and, at the same time, retain the flexibility and power of the CommonDialog control. By the way, in order to avoid confusion, I elected to name the custom method FileOpen() rather than GetFile(). This way, it's clear when the discussion is focused on the GetFile() method as opposed to the FileOpen() method. However, when you implement the class, you might find it easier to rename the method GetFile(). How far can we take this? Simply put, the sky's the limit! One feature that's nice about the GetFile() function is that when a file is chosen, the name of the file, along with the path, is returned by the function. This is one feature that should definitely be included in our custom FileOpen() method. Just as an empty string is returned when the user presses the Cancel button in the GetFile() function, so too will the same behavior occur in the FileOpen() method. A quick word on the SetFilter() and SetTitle() protected methods The responsibility of the SetFilter() and SetTitle() methods is to process the file filter and dialog title parameters, respectively. Why not perform the parameter checking in the FileOpen() method? The reason for breaking this functionality into separate methods is based upon the concept of cohesion. Cohesion deals with how much sense a module makes, whether it's a class or a method. Good OO design requires that a method be concentrated on a particular task. Tasks such as validating parameters, while important, aren't central to the process of obtaining and returning a filename. For more information on the concept of cohesion, refer again to Jefferey Donnici's "Best Practices" column in the January 1998 issue of FoxTalk. Tracking the history of opened files Word, Excel, and most other Windows applications have the capacity for remembering the names of files that have been opened. Typically, the last four files that have been opened will appear at the bottom of the File menu. This would be a nice feature to add to our CommonDialog subclass. Four new members, three properties, and a method are required to facilitate this new feature:

n aFileHist -- A protected array property that tracks the files previously opened. The number of files tracked is specified n nFileCount -- A public integer property that specifies how many previously opened files are tracked. The default value n lTrackHistory -- A public Boolean property that specifies whether a history of previously opened files is to be tracked. n GetFileHistory() -- A public method that provides an interface to the aFileHist property. This method accepts an array
parameter and will return .T. if the aFileHist array contains any files. The default value of this property is .T.. of this property is 4. by the nFileCount property.

Figure 7 illustrates a sample form that highlights the functionality of our new CommonDialog subclass. Figure 7. Sample form of a CommonDialog subclass.

The following code in the Click() method of the CommandButton does all the work: Local Array aFileHistory[1] Local x This.Parent.Text1.Value = ; This.Parent.oleCD.GetFile("Tables (*.dbf)|*.dbf",; "Please Select a Table to Open") If This.Parent.oleCD.GetFileHistory(@aFileHistory) This.Parent.List1.Clear For x = 1 To Alen(aFileHistory) This.Parent.List1.AddItem(aFileHistory[x]) EndFor Endif Return

Conclusion The CommonDialog control is one of those ActiveX controls that requires you to roll up your sleeves and devote some time to experimentation in order to understand how it works. As I've shown, the dialog boxes of the CommonDialog control are very customizable and provide a nice alternative to the native VFP functions when more control is required. This article has only reached the tip of the iceberg in regard to how the power of the CommonDialog control can be harnessed. It's my hope that you've now begun the task of devising your own additions to the oleCommonDialog subclass. Because the CommonDialog control is so rich in features, it might be a bit overwhelming to include options for obtaining fonts, saving files, getting colors, and putting Help files in one large subclass. You might find it easier to create a number of

http://foxtalknewsletter.com/ME2/Audiences/Segments/Publications/Print.asp?Module=... 07.02.06

Print Article

Seite 6 von 6

subclasses. Next month, I'll begin concentrating on automation. In the April 1998 issue, I'll explore some of the basics that are common to all applications that expose themselves as automation servers -- their object models. Whether it's Word, Excel, Internet Explorer, or Visio, you'll find many similarities. Once you understand how the object models in these products are composed, you'll find it easier to get up and running when you need to work with a new product.

http://foxtalknewsletter.com/ME2/Audiences/Segments/Publications/Print.asp?Module=... 07.02.06

Вам также может понравиться