A Diet Hook On Steroids

Version: 1.01.02 - last update: Tuesday, March 18, 2014, 22:49:00

Previous Chapter Neat Solutions Home (TOC) Next Chapter


This thread is about how to start your own hook project. In this first chapter I will show you my own ProjectHook base only. Later we will talk about some ugly bugs, how to work around them, and how design your Hook Classes best.

Intro

I did not start using ProjectHooks with all of my projects before the VFP team gave them Activate() and Deactivate() hook methods. Working on a whole bunch of projects simultaneously, most of them with a little different set of settings, made maintaining the correct environment for developing, testing and building a real nightmare. VFP's ProjectHooks matured over time and finally we got the 'full-blown' version we are still using today.

At that time I started equipping all my different projects with ProjectHooks. Their major task was the change management of development settings. As we all know, every activation (event) of a VFP Project raises the ProjectHook's Activate() event. Thus, this is the best place to adjust VFP's development environment accordingly.

How to Stay Flexible

Of course, I began thinking about how to keep things flexible enough to let me change all needed variant settings (and other stuff) on the fly. For example, hardcoding a sequence of SET PATH TO …. ADDITIVE statements into each ProjectHook over and over again certainly is not a good idea. That's why I implemented a loop that scanned the Project's Files collection to create a search path from all files' paths automatically for me. Alas! Sometimes that was to much of a good thing and I found myself again hardcoding a list of SET PATH statements for one or two "very special" Projects.

First Working Result

I ended up with a really cool default ProjectHook (at least I thought that it was 'cool') that had a whole bunch of valuable features which could be configured using property based switches that, in turn, got their values from external configuration files. In fact, a lot of these switches I integrated only to suppress some behavior I've implemented before.

Sometimes a good object oriented programming style seems to be simply incompatible with the requirements you have to fulfill.

The Configuration Dilemma

Finally I got all switches and methods into place. All was up and running, until… I had to create a new "very special" Project needing a "very special" ProjectHook. I must admit, I had to consult my own (sparse) documentation hoping to find a path through my homegrown jungle of methods and switches leading me to the place where I had to implement my desired behavior. Since then, it was clear in my mind that my approach was a one-way street into a fatal program morass.
That time I started studying the VFPX ProjectHookX project which had/has a very clean and cool design. But I had not enough spare time to refactor my own Hooks that way. Thus, I decided to temporarily work without a ProjectHook "re-using" VFP's Environment Manager instead…

A Stroke of Luck

Before I started adding ProjectHooks to every Project, I was using VFP's Environment Manager pretty extensively (you may have seen some screen shots of it in one of my other sections:-). What I like most about the Environment Manager is its ease of use.

If we only could combine ProjectHook functionality (signaling an active project change) with Environment Manager functionality (configuring the development environment)!

But wait, WE CAN, don't we?!

The Solution

After a short stop at VFP's XSOURCES I came up with a super-slim ProjectHook that calls into the EnvMgr.App to set an EnvironmentSet defined there.

The Environment Manager API

  • One neat thing is that the Environment Manager can be run as a stand alone application (without the hosting TaskPane). Thus, it can be called directly, and, in addition to that, it can be run in a silent, GUI-less mode!
  • Another option that can be used is passing in the name of the Environment Manager driving table. If we do not want to use the default one, we are allowed to pass in a different table name instead. This opens up the world of team working where we would store only one centralized EnvMgr driving table to be used by the Environment Managers off all developers.
  • Last but not least we can pass a parameter to tell the Environment Manager to add a menu bar to VFP's Tools menu from where we can start it directly without the need of launching VFP's TaskPane.App first!

I ran some tests and, it works like a charm!

The Code

Because I have not enough time to upload my ProjectHook's VCX files I put them into a PRG listed below. I believe that you will not have any problems to convert the code back into a local VCX-based class to run some test on your own.

Define Class fq_minimal_hook As ProjectHook

	cLookupId = ""
	lAddMenu = .T.
	lActivateEnabled = .T.

	Name = "fq_minimal_hook"

	Procedure Init
		If Empty(This.cLookupId)
			This.cLookupId = Juststem(_VFP.ActiveProject.Name)
		EndIf
		If This.lAddMenu
			*\\ Place a new MenuBar on VFP's TOOLS menu (below the "Toolbox" Bar)
			Do (Home(0) + "EnvMgr.App") With "-m"
			This.lAddMenu = .F.
			*//
		Endif
	Endproc

	Procedure Activate
		*\\ Run EnvMgrStart.Prg in EnvMgr.App passing in the name of 
		*\\	the project which should match the environment name
		*\\	stored in EnvMgr.Setname
		If This.lActivateEnabled
			Do (Home(0) + "EnvMgr.App") With This.cLookupId, .T.
			_Screen.Caption = ;
				"FoxQuill Development Environment V9.0 - [Environment -> "+;
				Proper(This.cLookupId)+"]"
		Endif
		*//
	Endproc

	Procedure QueryModifyFile
		Lparameters toFile As VisualFoxpro.IFoxPrjFile, tcClassName As String
		If JustStem(toFile.Name) == "__pjxhookactivationswitch" && must be lower case!
			This.lActivateEnabled = Not This.lActivateEnabled
			Wait Window Nowait "Activation behaviour " + ;
				Iif(This.lactivateenabled, "ENABLED", "DISABLED") + ;
				" for " + JustFName(_VFP.ActiveProject.Name) ;
				Timeout 2
			*\\ do not open the dummy file!
			Nodefault
			*//
		Endif
	Endproc

Enddefine

Some Final Code Comments

The code in the QueryModifyFile() method supports enabling/disabling of the ProjectHook's Activate() behavior. Just add (e.g.) a TXT-file to your project with the name __PjxActivationSwitch.txt (don't forget to exclude it). Double-clicking the file in the Project Manager toggles the state of the lActivateEnabled property which in turn controls execution within the Activate() method.

My latest ProjectHook versions do not have any properties that control behavioral aspects any longer! Thus, there is no need to create and maintain external configuration files that must be parsed and evaluated at runtime. Behavior gets loaded dynamically like implemented in the VFPX project. Environment handling is delegated to VFP's Environment Manager completely.

Inner Workings of the EnvMgr

The Environment Manager uses a two-level lookup internally to locate your EnvironmentSet. In the first place it searches the EnvMgr.Lookupid column of its driving table. This column contains the unique IDs of the EnvironmentSets (amongst other things). The IDs are Sys(2015) generated values that are not difficult to unearth (I will show you in a moment how to spot them). If the EnvMgr cannot locate your ID value it searches the EnvMgr.Setname column next. The Setname column holds the name you gave the EnvironmentSet when adding it in the Environment Manager. This is the name the ProjectHook will pass to the EnvMgr, too.

How does the ProjectHook knows what name you gave to your EnvironmentSet?

Convention over Configuration…

… is a well-known slogan today. And it is a good one, believe me. I've learned it the hard way! Either you do some configuration which is time-consuming (= bad), or you keep with the convention and, in our case, use the same name for your EnvironmentSet and your Project (which doesn't cost you even a single second = good). This is what the ProjectHook does. It keeps with the (unwritten) convention that both, the EnvironmentSet and the Project File share the same name if they belong to the same Project: UPPER(ALLTRIM(Envmgr.Setname) == JUSTSTEM(_VFP.ActiveProject.Name). This denoted by the orange lines in the the screenshot below.

Need More Speed?

From the above follows that you have to pass in an existing Sys(2015)value to avoid the second Locate. Okay, let's lose some time configuring a high-speed ProjectHook ;-) Let's say you set up a VFP Project called AppSpy.Pjx, and that you set up an EnvironmentSet for it, too. First, you have to create a subclass of the ProjectHook. Let's name it AppSpyHook. Now, you have to find out what unique ID the EnvMgr had given to your EnvironmentSet to assign that value to the cLookupID property at design-time. This is easily done as you can see on the screenshot below. Open the TaskPane Manager, select the Environment Manager, then hover with your mouse over your EnvironmentSet entry. You will see the lookup key displayed in VFP's status bar. This is the key value you have to assign to the cLookupId property in your ProjectHook's class code!

PjxHookEnvMgrLookupID

You may create and use the following on key label to save some handwriting / typing:

On Key Label f8 _cliptext = Substr(_vfp.StatusBar, At("WITH", _vfp.StatusBar)+6,10)

Press F8 when you see the ID value in VFP's StatusBar. After pasting the key value back into your code editor window your class header should now look like this:

Define Class fq_minimal_hook As ProjectHook
	clookupid = "_3P90VWP8P"
	laddmenu = .T.
	lactivateenabled = .T.
	Name = "fq_minimal_hook"
	Procedure Init
		If Empty(This.clookupid)

Save your class and assign it to your AppSpy Project Manager. Next time you open your AppSpy Project the AppSpyHook will call the EnvMgr sending the "_3P90VWP8P" key. BTW: This frees you from using the same EnvironmentSet name there. Maybe you want to use a more meaningful, or totally different key value now.

This "faster" version of your ProjectHook does not run noticeably faster! The only difference between this "high speed" ProjectHook and our first implementation is that it took much longer to implement (and you had to know much more details). Decide for yourself which way you want to take: the classic configuration oriented, or the promising convention based approach…

The End

I hope that you found this entry chapter useful. Please let me know if you have encountered any side effects while playing with this tiny ProjectHook. Next time we will talk about constellations where a Hook's Activate() isn't raised, why you should not check-in a hook-enabled project into Microsoft's Team Foundation Server (TFS) and other weird things.

To wet your appetite…

ProjectHooks are loosely coupled with their (parental) Project objects only. You may exchange them on the fly, thereby changing the whole Project behavior dynamically without the need to close and re-open the Project Manager first. Have you ever asked yourself what happens to your ProjectHook instances (that are 'linked' to your Project objects) when you issue a CLEAR ALL in VFP's command window? Simply put a Wait Window "I'm dying!" in your ProjectHook's Destroy() method, instantiate your class, assign the reference to any open Project Manager instance, and finally issue a CLEAR ALL. What do you see and what does that mean?

Stay tuned, enjoy!


Previous Chapter Neat Solutions Home (TOC) Next Chapter

No comments:

Post a Comment