Chapter 1
Project Manager Improvements
The Project Manager is the key tool for building executables and the primary tool Visual FoxPro developers use to modify the source code in applications they create. Any change the Fox Team can work into the product increasing productivity or making it a better experience has a high impact on Visual FoxPro developers.
The Project Manager has been tweaked over the last few releases to make day-to-day development a little easier. We think you will find the tweaks in this version a big help when you develop your applications. This chapter covers the changes to the Project Manager and the projecthook object, which you can use to respond to the interaction you have with the Project Manager. We also discuss some of the enhancements with respect to building your application using the Project Manager and the BUILD command. If you are looking for information on creating setups and deploying applications check out Chapter 15, “Setup and Deployment.”
Generating message logs during project build
Prior to Visual FoxPro 9, the build process (whether you used the Build button in the Project Manager, the BUILD command, or the project object’s Build method) did not write out the error log until it was done. This meant if the build failed (stopped by Visual FoxPro or by the developer canceling), the error log was not written, and was not available to be reviewed. Typically you cancel the build when the build process presents an error or a missing file and you realize what the error is and how you want to correct it. The problem is, by the time you open the source code, you forget the details and have no way to get the details without rebuilding. In Visual FoxPro 9, the build process generates the log when the process starts and adds to it as errors and warnings are encountered.
This is a big deal if you have large projects and you hit an error you know you need to fix before testing the build. At the time the error displays, you can cancel the build process and review the error log to gather the details, make the fix in the source, and start the long build process again.
This enhancement gets even better. If the Debug Output window is open (see Figure 1), the same error messages are output to this window along with a complete log of the build process. An entry is output to the Debug Output window as each file/module/method compiles. Additional entries are made as the files/modules/methods are analyzed for dependencies (see Listing 1 for an example of one class library’s log output).
The error log file starts fresh each time a build is started. If no errors are recorded during the build process, the error log file is deleted; otherwise, the process would leave an empty error log.
The Debug Output window is not cleared before each build; the new information is appended to the Debug Output window. This allows you to review, compare, and contrast one build with the next. You can also save the logged output in the Debug Output windows to a file using the Save As option in the Debug Output window’s shortcut menu. (Save As is not a new feature, just a hint of how you can leverage this logged information.)
Build Project: Visual Class Library wlcutils
Module: cplatform_access
Module: cfullosname_access
Module: cosversion_access
Module: cservicepack_access
Module: cshortservicepack_access
Module: ccompleteosinformation_access
Module: cbasicosinformation_access
Module: release
Module: getappversion
Module: getappfileinformation
Module: cappnametosearch_assign
Module: cappdirectory_assign
Module: init
Module: release
Analyzing:
Module: CPLATFORM_ACCESS
Module: CFULLOSNAME_ACCESS
Module: COSVERSION_ACCESS
Module: CSERVICEPACK_ACCESS
Module: CSHORTSERVICEPACK_ACCESS
Module: CCOMPLETEOSINFORMATION_ACCESS
Module: CBASICOSINFORMATION_ACCESS
Module: RELEASE
Analyzing:
Module: GETAPPVERSION
Module: GETAPPFILEINFORMATION
Module: CAPPNAMETOSEARCH_ASSIGN
Module: CAPPDIRECTORY_ASSIGN
Module: INIT
Module: RELEASE
The COMPILE command lists the modules compiled in the Debug Output window the same way Build does.
Change the Project Manager font
The Visual FoxPro Project Manager look and feel has remained virtually unchanged in the last several versions. Visual FoxPro 9 introduces the ability to change the item list (treeview) and file description font (not the page tabs, or any of the command buttons). A change to the font in the Project Manager is hardly a big bang change, but it is our opinion that anything enhancing usability and accessibility is an improvement in productivity for someone visually challenged or for developers running at very high resolutions on smaller monitors.
You can define the default font for all projects by setting
the font on the IDE page of
the Tools | Options dialog. The process of making the setting is not overly
intuitive: first,
set the window type to
Change the Project Manager font for the open project by
right-clicking the Project Manager and using the Font… option on the shortcut
menu (see Figure 3). The Font…
menu item is not available if you use the shortcut menu from the Project
Manager’s item
list; you need to right-click outside of the project item list. This feature is
not available for a docked project.
If you choose an italic style for the font, the Project Manager displays the file names, categories, and file descriptions in italics, but selecting the bold style has no impact. This is because the main file for the project displays in bold. The font attributes for the project are stored in the FoxUser resource file in a PRJUI record where the Name column contains the project file name (without the extension).
Additional Project Manager shortcut menu items
There are a number of changes to the Project Manager shortcut menus. You see three different shortcut menus. The first when the Project Manager is docked, the second in the item list of undocked/collapsed dropdown tabs (and in tear-off tabs), and the third when the Project Manager is undocked without being collapsed.
The majority of the changes affect developers who dock their projects to a toolbar. The additional items on the shortcut menu for a docked project (Figure 4) include:
· Close: closes the project.
·
Add Project to Source Control…: opens the add to
source control dialog, and is only included on the menu if the active source
control provider is selected on the
Tools | Options dialog Projects page.
· Errors…: displays the error log file from the last build, and is only enabled if the last build produced and recorded errors.
· Refresh: updates the list of files in the project, and new classes in class libraries.
· Clean Up Project: packs the deleted records from the project file (PJX).
The captions for some items in the shortcut menu for the collapsed or docked Project Manager item list (found on the dropdown tabs and tear-off tabs) has changed; in addition, Rename File moved to a different position in the list. There is no additional functionality (see Figure 5).
We already detailed the Font… option in the “Change the Project Manager font” section earlier in this chapter. This option is the only new shortcut menu option added and is only available when the Project Manager is not docked and not collapsed.
Modifying a class library from the Project Manager
You can now double-click a visual class library in the Project Manager item list or click the Modify command button to open the class library in the Class Browser. Prior to Visual FoxPro 9, nothing happened when you double-clicked a class library; the Modify command button was disabled when a class library was selected in the item list.
If you don’t like the new behavior, you can prevent it by issuing NODEFAULT in the QueryModifyFile event of projecthook (see Listing 2). You can also intercept the QueryModifyFile event and run your own class library tool (see Listing 3).
The Developer Downloads for this chapter, available from www.hentzenwerke.com, include two projecthook classes (phkStopClassBrowser and phkRunOldFashionClassHackingTool) in the wn9ProjectHookDemos.vcx.
LPARAMETERS
toFile, tcClassName
* Only for class libraries
IF LOWER(JUSTEXT(toFile.Name)) = "vcx"
* Only when no class is selected
IF EMPTY(tcClassName)
* Provide behavior found in VFP 8 and earlier
NODEFAULT
ENDIF
ENDIF
RETURN
LPARAMETERS toFile, tcClassName
#DEFINE IDYES 6 && Yes button pressed
#DEFINE IDNO 7 && No button pressed
* Only for class
libraries
IF
LOWER(JUSTEXT(toFile.Name)) = "vcx"
* Only when no class is selected
IF EMPTY(tcClassName)
lnAnswer = IDNO
* Offer choice if this option is enabled
IF this.lOfferChoiceOfClassBrowser
lnAnswer = MESSAGEBOX("Would you
prefer to open " + ;
toFile.Name + ;
" in VFP
Class Browser even though" + ;
"you have
the old-fashioned BROWSE "+ ;
"hacking
option enabled via " + ;
"the
projecthook?", ;
4+32, ;
"Modify
Class Library")
ENDIF
IF lnAnswer = IDYES
* No additional functionality, rather
"documenting"
* that the normal behavior is desired
in this situation
RETURN .T.
ELSE
* Run your favorite class library tool
from
* White Light Computing
* DO hackcx4.exe WITH toFile.Name
IF USED("curHack")
USE IN
(SELECT("curHack"))
ENDIF
USE (toFile.Name) IN 0 SHARED ALIAS
curHack
SELECT curHack
BROWSE LAST FONT "Tahoma",
12 NAME goOldHack NOWAIT
NODEFAULT
ENDIF
ENDIF
ENDIF
RETURN
The key to working with
the class library functionality of the Project Manager is to check the
parameters passed to the QueryModifyFile method. You can determine whether a
class library was passed by evaluating the extension of the file parameter, and
checking whether the class name parameter is empty. (If it’s a VCX and the
class name parameter is not empty, you are modifying a specific class of the
class library.)
In addition to
intercepting, stopping, and altering the behavior of double-clicking the class
library, you can allow the Class Browser to open after you alter the normal
behavior by returning .T. (also the default return value) from the
QueryModifyFile event method. This could be a problem if you open the class
library via a USE...EXCLUSIVE in your own tool because the Class Browser
expects to open the the class library when it starts. If you want to open the
class library in your tool and the Class Browser, make sure to open it via
USE...SHARED.
ProjectHook SCCInit and SCCDestroy methods
Since the introduction of ProjectHooks in Visual FoxPro 6, developers have tried to integrate with source code control when opening a project and again when closing a project. According to Microsoft, when a project is opened, the developer wants to check out specific files from source code control, and when a project is closed, some or all the checked-out files should be checked in. You can’t use the Init and Destroy methods of the ProjectHook (which fires when project is opened and closed, respectively) because the link to the source code control provider is not established in Init, and is broken in the Destroy. The file object’s CheckIn, CheckOut, GetLastestVersion, AddToSCC, RemoveFromSCC, and UndoCheckOut methods only work once the project is opened and displayed in the Project Manager.
In Visual FoxPro 9, Microsoft provides two new projecthook events. The SCCInit event fires after the link to source code control is established, but before the project is activated. The order of projecthook events when opening the project is:
1. Init
2. SCCInit
3. Activate
The SCCDestroy event fires before the link to source code control is broken, and before the projecthook is destroyed. The order of events when closing a project is:
1. Deactivate
2. SCCDestroy
3. Destroy
The following sections explore some of the things you might do with these new events.
Check out files
Microsoft added the new events in response to requests from developers for the ability to check out all the files needed when opening the project. We’re not big fans of the integrated source code control functionality and prefer to use the source code control client user interface to check files out as needed. But checking out everything when opening a project sounded interesting and like a potential time saver, so it is the first feature we tried.
The Developer Downloads for this chapter, available from www.hentzenwerke.com, include projecthook classes (phkSCCCheckOutAllFiles and phkSCCCheckOutFilesOnce) in the wn9ProjectHookDemos.vcx that demonstrate using the SCCInit event to check out files when opening the project. The sample code provided for this section of the chapter will not work unless you have a source code control application like Visual Source Safe installed and accessible.
The code in Listing 4 is found in the CheckOut method of the phkSCCCheckoutAllFiles class. The CheckOut method is called from the SCCInit event method. This follows the best practice of never using an event method to hold the primary code for the desired behavior. In addition, it avoids the problem of losing code if we compile this class in VFP 8 or earlier. Because the SCCInit event is not part of VFP 8, any code in SCCInit disappears during the next compile in that version, and is lost, even if you reopen the class in Visual FoxPro 9. Code in a custom method is not deleted; only the code in the event methods is lost (which is a lot easier to fix if all it does is call the custom method).
* phkSCCCheckoutAllFiles::CheckOut()
#DEFINE cnFILECOLUMN 1
#DEFINE cnFILESTATUS 2
* File Object SCCStatus Property (available in FoxPro.h)
#DEFINE SCCFILE_NOTCONTROLLED 0 && Not source controlled
#DEFINE SCCFILE_NOTCHECKEDOUT 1 && Controlled but not checked out to anyone
#DEFINE SCCFILE_CHECKEDOUTCU 2 && Checked out to the current user only
#DEFINE SCCFILE_CHECKEDOUTOU 3 && Checked out to someone else
#DEFINE SCCFILE_MERGECONFLICT 4 && Has a merge conflict
#DEFINE SCCFILE_MERGE 5 && Has been merged without conflict
#DEFINE SCCFILE_CHECKEDOUTMU 6 && Checked out to multiple users
LOCAL loProject, ;
loFile, ;
llCheckoutStatus
loProject = _vfp.ActiveProject
IF EMPTY(loProject.SCCProvider)
* Source Code Control not used for this project
DEBUGOUT "No source code control provider to check out files"
ELSE
FOR EACH loFile IN loProject.Files
IF loFile.SCCStatus = SCCFILE_NOTCHECKEDOUT
* Attempt to check out file...
WAIT WINDOW "Checking out file: " + ;
TRANSFORM(loFile.Name) + ;
"..." NOWAIT
llCheckoutStatus = loFile.CheckOut()
this.nFilesCheckedOut = this.nFilesCheckedOut + 1
DIMENSION this.aFiles[this.nFilesCheckedOut, 2]
this.aFiles[this.nFilesCheckedOut, cnFILECOLUMN] = loFile.Name
this.aFiles[this.nFilesCheckedOut, cnFILESTATUS] = llCheckoutStatus
DEBUGOUT loFile.Name + ;
IIF(llCheckoutStatus, SPACE(0), " not") + ;
" checked out - " + ;
TRANSFORM(DATETIME())
ELSE
* Nothing to do
ENDIF
ENDFOR
ENDIF
RETURN
The code first checks to see if the project is using integrated source code control. If so, it loops through the Files collection and checks to find files not currently checked out. This is accomplished by evaluating the SCCStatus property for each file. If the file is not checked out, it attempts to check it out by calling the file object’s CheckOut method. The CheckOut method returns True if the file was successfully checked out. It logs the process by recording the file name and check out status to an array property called aFiles and displays a message in the Debug Output window.
The drawback of this approach is the Check Out Files dialog (see Figure 6) is presented for every file not already checked out when this method runs. The current file is selected for check-out in the dialog, so all you have to do is click the OK button and the checkout process is attempted. Having to click OK or Cancel for each of the files in a project is tedious. You can select all the files you want to check out the first time the dialog is presented. This avoids the dialog being presented for each of the files individually. Still canceling all the files you do not want checked out will reduce your productivity.
If you want to see the Check Out Files dialog once, you can use the code in Listing 5, found in the CheckOut method (called from the SCCInit event method) of the phkSCCCheckoutFilesOnce class. The disadvantage of this approach is you lose the individual file logging functionality, so you will not be able to determine if a certain file failed to check out. We think you will find this trade-off well worth it and more productive. If all files are already checked out, you get a message “Project error. There are no files to check out.” This is the same message you get when using the Project menu to check out files and no files need to be checked out.
LOCAL loProject, ;
loFile
loProject = _vfp.ActiveProject
IF EMPTY(loProject.SCCProvider)
* Source Code Control not used for this project
DEBUGOUT "No source code control provider to check out files"
ELSE
loFile = loProject.Files[1]
loFile.CheckOut()
ENDIF
RETURN
The advantage of this
approach is you can cancel the Check Out Files dialog and move on to the task
of development if you do not want to check any files out. Alternatively, you
can select the files when you open the individual source files (the same dialog
appears). If you use the check out process when opening a project, you ensure
that you have the current versions of the files. Otherwise, one of your
teammates could grab a file before you get a chance to edit it.
You might ask why we did not write the reverse code to check the files into source code control via the SCCDestroy event method. We feel the check-in process requires a commitment from the developer that the code is solid and tested and ready to be integrated with the rest of the code in the project before it is checked into source control. This is not something we can control programmatically; therefore we did not implement it in our sample classes. We open and close projects frequently and do not want files checked in just because we closed the project.
Get latest version of files
The biggest advantage we saw with the SCCInit event when we first read about it was the ability to get the latest version of the files from source code control. We frequently need to make sure we have the latest version of source code other members of the team might be changing. Automating this when the project is opened could save us the extra step of making the manual request.
The Developer Downloads for this chapter, available from www.hentzenwerke.com, include projecthook classes (phkSCCGetLatestAllFiles and phkGetLatestFilesOnce in wn9ProjectHookDemos.vcx) that demonstrate how the SCCInit event can help you get the latest version of source code files when opening a project.
Unfortunately, like the CheckOut example, calling GetLatestVersion leads to dialogs appearing. With this in mind, we created two projecthooks to address the issue the same way we did with the CheckOut process. Listing 6 shows you how to get the latest version of all files with individual logging, and Listing 7 shows you how to present the Get Latest Version dialog once, but give up logging.
* Code to get the latest version of all files that are
* not already checked out.
#DEFINE cnFILECOLUMN 1
#DEFINE cnFILESTATUS 2
* File Object SCCStatus Property (available in FoxPro.h)
#DEFINE SCCFILE_NOTCONTROLLED 0 && Not source controlled
#DEFINE SCCFILE_NOTCHECKEDOUT 1 && Controlled but not checked out to anyone
#DEFINE SCCFILE_CHECKEDOUTCU 2 && Checked out to the current user only
#DEFINE SCCFILE_CHECKEDOUTOU 3 && Checked out to someone else
#DEFINE SCCFILE_MERGECONFLICT 4 && Has a merge conflict
#DEFINE SCCFILE_MERGE 5 && Has been merged without conflict
#DEFINE SCCFILE_CHECKEDOUTMU 6 && Checked out to multiple users
LOCAL loProject, ;
loFile, ;
llGetLatestStatus
loProject = _vfp.ActiveProject
IF EMPTY(loProject.SCCProvider)
* Source Code Control not used for this project
DEBUGOUT "No source code control provider to get latest version of files"
ELSE
FOR EACH loFile IN loProject.Files
IF INLIST(loFile.SCCStatus, SCCFILE_NOTCONTROLLED, ;
SCCFILE_CHECKEDOUTCU, ;
SCCFILE_MERGECONFLICT)
* Nothing to do, either not in SCC, or already checked out,
* or there is a merge conflict (highly unlikely with VFP
* source code and integrated source code control).
ELSE
* Attempt to get the latest version of file...
WAIT WINDOW "Getting latest version of file: " + ;
TRANSFORM(loFile.Name) + ;
"..." NOWAIT
llGetLatestStatus = loFile.GetLatestVersion()
this.nFilesUpdated = this.nFilesUpdated + 1
DIMENSION this.aFiles[this.nFilesUpdated, 2]
this.aFiles[this.nFilesUpdated, cnFILECOLUMN] = loFile.Name
this.aFiles[this.nFilesUpdated, cnFILESTATUS] = llGetLatestStatus
DEBUGOUT loFile.Name + ;
IIF(llGetLatestStatus, SPACE(0), " not") + ;
" updated - " + ;
TRANSFORM(DATETIME())
ENDIF
ENDFOR
ENDIF
RETURN
LOCAL loProject, ;
loFile
loProject = _vfp.ActiveProject
IF EMPTY(loProject.SCCProvider)
* Source Code Control not used for this project
DEBUGOUT "No source code control provider to update " + ;
"latest version of files."
ELSE
loFile = loProject.Files[1]
loFile.GetLatestVersion()
ENDIF
RETURN
The code is very
similar to the code used to demonstrate the check out process. The big
difference is the checks made to the SCCStatus property to ensure you do not
get the latest version of a file you already have checked out and possibly
changed. The other difference, of course, is calling the GetLatestVersion
method of the file object to tell the source code control provider to copy the
latest version to your project folder.
Log source code control state
The final example for the SCCInit and SCCDestroy actually uses both events. In this example, we create a log of the source code control status for each file in the project. This log is sent to a text file, and can later be imported into a DBF given that the output is created as a comma delimited string.
The Developer Downloads for this chapter, available from www.hentzenwerke.com, include a projecthook class (phkSCCListState in wn9ProjectHookDemos.vcx) that demonstrates how you can list the status to a log file.
The ListStatus method (see Listing 8) can be found in the phkSCCListStatus class. This method is called from both the SCCInit and SCCDestroy. The method accepts one parameter allowing you to determine which of the two methods called it. The method collects details about each file in the project and outputs them to a text file specified by the cListStatusFileName property. The text file is always stored in the Visual FoxPro temporary files folder; if it already exists, ListStatus adds the new information rather than overwriting it.
LPARAMETERS tcTiming
LOCAL loProject, ;
lcStatusOutput, ;
lcFileName
loProject = _vfp.ActiveProject
lnFiles = loProject.Files.Count
lcStatusOutput = REPLICATE("=", 60) + ;
CHR(13)+CHR(10) + ;
tcTiming + " - " + loProject.Name + ;
CHR(13)+CHR(10)
lcStatusOutput = lcStatusOutput + ;
TRANSFORM(DATETIME()) + ;
" - " + ;
loProject.SCCProvider + ;
REPLICATE(CHR(13)+CHR(10),2)
FOR lnI = 1 TO lnFiles
lcSCCText = SPACE(0)
DO CASE
CASE loProject.Files[lnI].SCCStatus = 0;
AND this.lListCheckedOutOnly = .F.
lcSCCText = "File is not source controlled."
CASE loProject.Files[lnI].SCCStatus = 1;
AND this.lListCheckedOutOnly = .F.
lcSCCText = "File is in source control but is not checked out."
CASE loProject.Files[lnI].SCCStatus = 2
lcSCCText = "File is checked out to the current user."
CASE loProject.Files[lnI].SCCStatus = 3
lcSCCText = "File is checked out to someone other than “ + ;
“the current user."
CASE loProject.Files[lnI].SCCStatus = 4
lcSCCText = "File has a merge conflict."
CASE loProject.Files[lnI].SCCStatus = 5
lcSCCText = "File has been merged without conflict."
CASE loProject.Files[lnI].SCCStatus = 6
lcSCCText = "File is checked out to multiple users."
OTHERWISE
IF this.lListCheckedOutOnly = .F.
lcSCCText = "Invalid SCC Status."
ENDIF
ENDCASE
IF EMPTY(lcSCCText)
* Skip the output since it is filtered out
ELSE
lcStatusOutput = lcStatusOutput + ;
loProject.Files[lnI].Name + ;
", " + ;
loProject.Files[lnI].Type + ;
", " + ;
TRANSFORM(loProject.Files[lnI].SCCStatus) + ;
", " + ;
lcSCCText + ;
", " + ;
TRANSFORM(loProject.Files[lnI].LastModified) + ;
CHR(13)+CHR(10)
ENDIF
ENDFOR
lcStatusOutput = lcStatusOutput + CHR(13)+CHR(10)
lcFileName = this.cListStatusFilename
STRTOFILE(lcStatusOutput, lcFileName, .T.)
WAIT WINDOW "SCCStatus saved to:" + lcFileName NOWAIT
RETURN
This information
collected allows you to review the status of the files over time, almost like
an audit file indicating when files were in use by another developer, and
changes you made to the state of source code control when you check files in
and out.
Naturally this
information is easier to analyze if it is in a DBF file. We include a method
(called ImportStatus) in the projecthook to create a cursor and populate it
with information from the text file. Once the details are loaded, you can write
SQL queries to see how often a particular file was checked out by you or
another developer, which files were never checked out, and which files in the
project are not under source code control.
Summary
The changes to the Project Manager address some long-time enhancement requests (like the shortcut menus for docked projects), correct some usability issues (with the item list fonts and class libraries opening in the Class Browser), and eliminate some shortcomings with the source code control integration. We think you will agree there is a little something for everyone in the changes made with the Project Manager and projecthooks.
Updates and corrections for this chapter can be found on Hentzenwerke’s website, www.hentzenwerke.com. Click “Catalog” and navigate to the page for this book.