Here you are, Chris. I would've finished a lot sooner, but I have a day job, had to contend with walking a few miles to work every day in the wake of a recent snowstorm, among other things.
I tried to emulate as closely as possible to the way VO 2.8 does this. Like the functions in XSharp.RT.Debugger, it has to be either inserted within the code or called from a menu option. As long-time VO 2.8 users know, however, the feature seems to use a kind of hybrid control with the properties of both a listview and treeview. Since I didn't have that, I settled on a treeview instead. And since I can't include the actual form here, you need only construct one yourself with the two PROTECTs as listed in the class declaration for ViewObjects. One or more GLOBALs, or any combination of objects or arrays to be inspected, are included in an array designated as uExtra argument in ViewObjects.
Code: Select all
LOCAL oViewObjects AS ViewObjects
...
oViewObjects:=ViewObjects{,,,{<arrayOrObject1,arrayOrObject2,...}}
oViewObjects:Show(SHOWCENTERED)
Early on I figured that I'd face the problem of a stack overflow error if I constructed a complete tree, particularly if there were circular references, so I put in safeguards prevent that. As in VO, a user double-clicks a node to reveal its content, but the tree adds items only incrementally, revealing only what the user intends to see. That way, the tree indicates the presence of any array elements or object attributes one step in advance, with the [+] designation beside the node as unexpanded.
I tested it as extensively as I could, and hope that you find use with it.
Code: Select all
CLASS ViewObjects INHERIT DATADIALOG
PROTECT oDCtv AS TREEVIEW
PROTECT oCCbtnExit AS PUSHBUTTON
//{{%UC%}} USER CODE STARTS HERE (do NOT remove this line)
PROTECT aObjectsInTree AS ARRAY
PROTECT nStopAdding AS BYTE
METHOD Init(oWindow,iCtlID,oServer,uExtra) CLASS ViewObjects
SELF:PreInit(oWindow,iCtlID,oServer,uExtra)
SUPER:Init(oWindow,ResourceID{"ViewObjects",_GetInst()},iCtlID)
oDCtv := TreeView{SELF,ResourceID{VIEWOBJECTS_TV,_GetInst()}}
oDCtv:HyperLabel := HyperLabel{#tv,"View Objects",NULL_STRING,NULL_STRING}
oCCbtnExit := PushButton{SELF,ResourceID{VIEWOBJECTS_BTNEXIT,_GetInst()}}
oCCbtnExit:HyperLabel := HyperLabel{#btnExit,"Exit",NULL_STRING,NULL_STRING}
SELF:Caption := "View Objects"
SELF:HyperLabel := HyperLabel{#ViewObjects,"View Objects",NULL_STRING,NULL_STRING}
IF !IsNil(oServer)
SELF:Use(oServer)
ENDIF
SELF:PostInit(oWindow,iCtlID,oServer,uExtra)
RETURN SELF
METHOD PostInit(oWindow,iCtlID,oServer,uExtra) CLASS ViewObjects
LOCAL y AS BYTE
SELF:aObjectsInTree:={}
SELF:nStopAdding:=0
FOR y=1 UPTO ALen(uExtra)
IF IsObject(uExtra[y])
SELF:doObject(uExtra[y],#ROOT)
ELSE
SELF:doArray(uExtra[y],#ROOT)
ENDIF
NEXT
RETURN NIL
METHOD doArray(aArray,sParentName) CLASS ViewObjects
LOCAL sName AS SYMBOL
LOCAL y AS BYTE
LOCAL oTVI AS TreeViewItem
sName:=String2Symbol(PadL(SELF:oDCtv:ItemCount,3,"0"))
oTVI:=TreeViewItem{sName,"array",aArray}
SELF:oDCtv:AddItem(sParentName,oTVI)
IF SELF:nStopAdding<3
SELF:nStopAdding:=SELF:nStopAdding+1
FOR y=1 UPTO ALen(aArray)
SELF:doAttribute(sName,"["+NTrim(y)+"]",aArray[y],4)
NEXT
SELF:nStopAdding:=SELF:nStopAdding-1
ENDIF
RETURN NIL
METHOD doAttribute(sParentName,cName,uValue,dwlInfo) CLASS ViewObjects
LOCAL sName AS SYMBOL
LOCAL cExp AS STRING
LOCAL y AS BYTE
LOCAL oTVI AS TreeViewItem
sName:=String2Symbol(PadL(SELF:oDCtv:ItemCount,3,"0"))
IF IsNil(uValue)
IF IsArray(uValue)
oTVI:=TreeViewItem{sName,cName+" NULL_ARRAY ",uValue}
ELSEIF IsString(uValue)
oTVI:=TreeViewItem{sName,cName+" NULL_STRING ",uValue}
ELSEIF IsObject(uValue)
oTVI:=TreeViewItem{sName,cName+" NULL_OBJECT ",uValue}
ELSE
oTVI:=TreeViewItem{sName,cName+" nil ",uValue}
ENDIF
SELF:oDCtv:AddItem(sParentName,oTVI)
ELSEIF IsLogic(uValue)
oTVI:=TreeViewItem{sName,cName+" logic "+LTOC(uValue),uValue}
SELF:oDCtv:AddItem(sParentName,oTVI)
ELSEIF IsSymbol(uValue)
oTVI:=TreeViewItem{sName,cName+" symbol "+Symbol2String(uValue),uValue}
SELF:oDCtv:AddItem(sParentName,oTVI)
ELSEIF IsFloat(uValue)
oTVI:=TreeViewItem{sName,cName+" float "+NTrim(uValue),uValue}
SELF:oDCtv:AddItem(sParentName,oTVI)
ELSEIF IsLong(uValue)
oTVI:=TreeViewItem{sName,cName+" long "+NTrim(uValue),uValue}
SELF:oDCtv:AddItem(sParentName,oTVI)
ELSEIF IsNumeric(uValue)
oTVI:=TreeViewItem{sName,cName+" numeric "+NTrim(uValue),uValue}
SELF:oDCtv:AddItem(sParentName,oTVI)
ELSEIF IsDate(uValue)
oTVI:=TreeViewItem{sName,cName+" date "+DToC(uValue),uValue}
SELF:oDCtv:AddItem(sParentName,oTVI)
ELSEIF IsString(uValue)
oTVI:=TreeViewItem{sName,cName+" string "+CHR(34)+uValue+CHR(34),uValue}
SELF:oDCtv:AddItem(sParentName,oTVI)
ELSEIF IsArray(uValue)
oTVI:=TreeViewItem{sName,cName+" array",uValue}
SELF:oDCtv:AddItem(sParentName,oTVI)
IF SELF:nStopAdding<3
SELF:nStopAdding:=SELF:nStopAdding+1
FOR y=1 UPTO ALen(uValue)
SELF:doAttribute(sName,"["+NTrim(y)+"]",uValue[y],4)
NEXT
SELF:nStopAdding:=SELF:nStopAdding-1
ENDIF
ELSEIF IsObject(uValue)
cExp:=cName+" class "+Symbol2String(ClassName(uValue))
IF AScan(SELF:aObjectsInTree,uValue)>0
oTVI:=TreeViewItem{sName,cName+" class "+Symbol2String(ClassName(uValue))+" (circular reference)",uValue}
SELF:oDCtv:AddItem(sParentName,oTVI)
ELSE
SELF:doObject(uValue,sParentName)
ENDIF
ELSE // IF IsPtr(uValue)
oTVI:=TreeViewItem{sName,cName+" Other, assuming Pointer",uValue}
SELF:oDCtv:AddItem(sParentName,oTVI)
ENDIF
RETURN NIL
METHOD doObject(oObject,sParentName) CLASS ViewObjects
LOCAL sName,sAttrib AS SYMBOL
LOCAL y AS BYTE
LOCAL cExp AS STRING
LOCAL oTVI AS TreeViewItem
LOCAL aVarList:=IvarList(oObject)
cExp:="CLASS "+Symbol2String(ClassName(oObject))
sName:=String2Symbol(PadL(SELF:oDCtv:ItemCount,3,"0"))
AAdd(SELF:aObjectsInTree,oObject)
oTVI:=TreeViewItem{sName,cExp,oObject}
SELF:oDCtv:AddItem(sParentName,oTVI)
FOR y=1 UPTO ALen(aVarList)
sAttrib:=aVarList[y]
SELF:doAttribute(sName,Symbol2String(sAttrib),IVarGet(oObject,sAttrib),IVarGetInfo(oObject,sAttrib))
NEXT
RETURN NIL
METHOD TreeViewMouseButtonDoubleClick(oTreeViewMouseEvent) CLASS ViewObjects
LOCAL sName,sAttrib AS SYMBOL
LOCAL y AS BYTE
LOCAL uValue AS USUAL
LOCAL oTVI,oTVI2,oTVI3 AS TreeViewItem
LOCAL aVarList AS ARRAY
LOCAL oControl AS Control
oControl:=IF(oTreeViewMouseEvent==NULL_OBJECT,NULL_OBJECT,oTreeViewMouseEvent:Control)
SUPER:TreeViewMouseButtonDoubleClick(oTreeViewMouseEvent)
oTVI:=SELF:oDCtv:GetSelectedItem()
sName:=oTVI:NameSym
uValue:=oTVI:Value
IF(!IsArray(uValue).AND.!IsObject(uValue)).OR.IsObject(uValue).AND.AScan(SELF:aObjectsInTree,uValue)>0
RETURN NIL
ENDIF
oTVI2:=SELF:oDCtv:GetFirstChildItem(oTVI)
IF!oTVI2==NULL_OBJECT
WHILE!oTVI2==NULL_OBJECT
IF IsArray(oTVI2:VALUE).OR.IsObject(oTVI2:VALUE).AND.AScan(SELF:aObjectsInTree,oTVI2:VALUE)>0
oTVI3:=SELF:oDCtv:GetFirstChildItem(oTVI2)
WHILE!oTVI3==NULL_OBJECT
IF IsArray(oTVI3:VALUE).AND.SELF:oDCtv:GetFirstChildItem(oTVI3:NameSym)==NULL_OBJECT
FOR y=1 UPTO ALen(oTVI3:VALUE)
SELF:doAttribute(oTVI3:NameSym,"["+NTrim(y)+"]",oTVI3:VALUE[y],4)
NEXT
ELSEIF IsObject(oTVI3:VALUE)
aVarList:=IvarList(oTVI3:VALUE)
FOR y=1 UPTO ALen(aVarList)
sAttrib:=aVarList[y]
SELF:doAttribute(oTVI3:NameSym,Symbol2String(sAttrib),IVarGet(oTVI3:Value,sAttrib),IVarGetInfo(oTVI3:Value,sAttrib))
NEXT
ENDIF
oTVI3:=SELF:oDCtv:GetNextSiblingItem(oTVI3)
ENDDO
ENDIF
oTVI2:=SELF:oDCtv:GetNextSiblingItem(oTVI2)
ENDDO
ELSEIF IsArray(uValue).AND.ALen(uValue)>0
IF SELF:nStopAdding<3
SELF:nStopAdding:=SELF:nStopAdding+1
FOR y=1 UPTO ALen(uValue)
SELF:doAttribute(sName,"["+NTrim(y)+"]",uValue[y],4)
NEXT
SELF:nStopAdding:=SELF:nStopAdding-1
ENDIF
ENDIF
RETURN NIL
METHOD btnExit( ) CLASS ViewObjects
SELF:EndWindow()
RETURN NIL