Douglas,
Thanks. Plenty of food for thought in your post too!
Regards,
Narinder Chandi,
ToolBox Systems Ltd.
--
Kirk has explained how the “form controller” approach works and it is
essentially the same as selector methods. In the past, I avoided that
approach, as I’ll touch on below, but have embraced it fully, starting with
V11.
IIRC, that approach to programming arose in the late 90’s. My impression at
the time was that it was part of the movement in the 4D community to try to
bring OO coding principles into 4D and I saw that as trying to bludgeon a
square peg into a round hole. It is unfortunate that 4D, to this day, is
not an OO language but I hold out hope. Another motivation for that
selector codes was that 4D method names were limited to 15 characters. That
was a significant impediment and I accepted that selector methods were a
way to get around that. My dislike for selector methods was that they
violated the “a method should have one clearly defined purpose” dictum and,
if you looked code quality using the McCabe score, the decision count was
sky high.
In practical terms, it was very hard to track down what routines were
calling what other routines because the “Find in design” feature in 4D was
glacially slow in those versions. Another issue I ran into was that code
was so intertwined that it could be very hard to decipher. I referred to
that approach as “loopy swoopy” and, actually had one incident where I was
called upon to fix a bug in a body of code that had been written by the
previous programmer and, after studying it for some hours, had to admit to
the client that I needed to rewrite the code because I could not figure out
how the code worked. That was a very painful experience (bruised ego!) and
it reinforced my position against using that approach.
With the release of V11, 4D’s Find in design feature was vastly improved,
fortunately so it became much easier to find where routines are used. By
2007, I had also been programmatically creating wrapper routines for Object
Tools objects for about a decade. That allowed me to move away from process
variables and debugging code was vastly simplified - I could put a break
point in a setter or a getter, run the code, and the debugger would pop
open. With those improvements, I was able to now use the form controller
approach and I embraced it fully.
Kirk did a good presentation at the last Summit and I’d advise you to get a
copy of it from him. My approach differs somewhat from how Kirk handles
things. IIRC, Kirk’s code reacts to form events. I’ve taken a different
approach which completely avoids coupling. This code pattern, reacting to
the appropriate events, is used in each active object on the form;
C_LONGINT($formEvent_L)
$formEvent_L:=Form event
Case of
: ($formEvent_L=On Data Change)
PROC_FormEventValues_Assign (OBJ_GPBN ("$h_")->)
MARKUP_FC (OBJ_GPBN ("$h_")->)
End case
PROC_FormEventValues_Assign contains this code:
PROC_FocusObjectPtr_Set ($h_;OBJ_FocusPtr_Return )
PROC_FocusObjectName_Set ($h_;OBJ_FocusName_Return )
PROC_CurrentObjectPtr_Set ($h_;OBJ_CurrentPtr_Return )
PROC_CurrentObjectName_Set ($h_;OBJ_CurrentName_Return )
PROC_FormEvent_Set ($h_;Form event)
PROC_gTablePtr_Set ($h_;gTablePtr_Get )
PROC_CurrentFormPage_Set ($h_;FORM Get current page)
PROC_CurrentFormTable_Set ($h_;Current form table)
PROC_FunctionName_Set ($h_;”")
PROC_ObjectState_Set ($h_;”")
PROC_FormEventValues_Assign
The OBJ_methods are wrappers for the underlying 4D function and gTablePtr
is a tell that this is a Foundation-based system.
This code is using an Object Tools object but newer code uses a C_Object.
I use that code in every object method rather than the form method because
I ran into issues with how the tab control behaves under certain
conditions. My approach requires more code but it does give me absolute
control over form activity and, as I mentioned, helps decouple my _FC.
The object is the only parameter passed to the form controller and it uses
Case statement and code as shown in this (cleaned up) sample code:
C_LONGINT($h_;$1)
$h_:=$1
//C_POINTER($currentObject_P)
//$currentObject_P:=PROC_CurrentObjectPtr_Get ($h_)
C_TEXT($currentObjectName_T)
$currentObjectName_T:=PROC_CurrentObjectName_Get ($h_)
C_POINTER($focusObject_P)
$focusObject_P:=PROC_FocusObjectPtr_Get ($h_)
C_LONGINT($formEvent_L)
$formEvent_L:=PROC_FormEvent_Get ($h_)
Case of
: ($formEvent_L=On Activate)
//NOP
: ($formEvent_L=On Deactivate)
//NOP
: ($formEvent_L=On Mouse Enter)
C_TEX
Case of
: (OBJ_GPBN ($currentObjectName_T)=(->[Proposals]Bill_And_Hold))
If (Not(USER_CanSetBillAndHold_Get ($h_)))
PROPOSAL_ToolTip_OM (OBJ_GPBN ($currentObjectName_T);"")
End if
: (PROP_IsSelAddDeleteButton (0;<>pNil;$currentObjectName_T))
If ([Proposals]Bill_And_Hold)
PROPOSAL_ToolTip_OM (OBJ_GPBN ($currentObjectName_T);"")
End if
: ($currentObjectName_T="PROP_Selected_OptnDiscountRateT")
OBJ_GPBN ("PROP_Selected_OptnDiscountRateT")->:="Options Discount:
"+String([Proposals]F030_OptionsDiscountRate*100)+"%"
End case
$h_:=PROC_FormEventValues_Clear ($h_)
: ($formEvent_L=On Mouse Leave)
Case of
: ($currentObjectName_T="PROP_Selected_OptnDiscountRateT")
OBJ_GPBN ("PROP_Selected_OptnDiscountRateT")->:=""
End case
$h_:=PROC_FormEventValues_Clear ($h_)
Else
Case of
: ($formEvent_L=On Clicked)
Case of
: ($focusObject_P=(OBJ_GPBN ("PREF_ShowLineNumberscb")))
PrefsPut ("PREF_ShowLineNumberscb";String(OBJ_GPBN
("PREF_ShowLineNumberscb")->=1))
: ($focusObject_P=(->bAccept))
gDirtyRec_Set (True)
: ($currentObjectName_T="@installBy@")
: ($formEvent_L=On Outside Call)
: ($formEvent_L=On Unload)
: ($formEvent_L=On Load)
End case
End case
End case
In order to reduce coupling, there are very few process variables and the
vast majority of the data is passed in the Object. That’s even easier with
C_Object and dotted notation.
Working with this approach, I do wish that QCP was still available but, as
you’ve detailed, it’s no longer available. It its stead, I’ve come to learn
to use the Find function and to use Ctrl/Cmd-L to navigate through the
code.
This approach does result in large methods which would be catastrophic if a
method were to be corrupted. That seems to be a thing of the past,
fortunately, but that situation can be avoided by frequent backups or using
source code repository.
If you were an advocate of the selector method approach, you’ll take to the
form controller. As Kirk mentions, it’s a very useful approach and I’ve
overcome my conceptual concerns by viewing the FC approach as a being a
flexible dispatcher method so I’ve convinced myself that I’m not violating
the “a method should have one clearly defined purpose” guideline.
--
Douglas von Roeder
**********************************************************************
4D Internet Users Group (4D iNUG)
Archive: http://lists.4d.com/archives.html
Options: https://lists.4d.com/mailman/options/4d_tech
Unsub: mailto:[email protected]
**********************************************************************