Table Monitor - a real-time monitor for Hollywood tables
Posted: Wed May 20, 2026 8:46 pm
Since it can sometimes be difficult to keep track of complex table structures, I built a debugging tool that displays a table in a dedicated window.
And since I think it might be helpful to others as well, I'm posting it here.
The functions should be able to be integrated into any Hollywood program that has the ability to open new windows. If you save the script under the name “TableMonitor v1.0.hws”, it will be included as follows:
@INCLUDE "TableMonitor v1.0.hws"
You can then display an existing table by calling p_TableMonitorDisplayCreate():
idtable=p_TableMonitorDisplayCreate(Table, "Title")
The arguments Table and “Title” are the table name and the window title. The return value idtable is needed if you want to close the monitor window:
p_TableMonitorDisplayClose(idtable)
You can also simply close the window with the mouse.
If necessary, additional arguments not listed here control the position, size, color, and font size. You can find them in the function description and in the sample script.
In the open window, you will then see the table contents in a format similar to that generated by SerializeTable(). Initially, I had also used this function, but since it is not suitable for all table structures, I built a corresponding Hollywood function as a replacement. If you prefer to use SerializeTable() if possible (because it is faster), you can control this with #MDUSESERIALIZETABLE (at the beginning of the script).
If the displayed table contents are larger than the window, you can scroll the window contents using the arrow keys; PgUp and PgDn also work, and Esc resets the position.
TableMonitor v1.0.hws:
The sample script below doesn’t do much of practical use, but its tables include all the structures I could think of, and you can see how the whole thing works. If anything is missing or has bugs, please let me know.
TableMonitor-Test v1.0.hws:
And since I think it might be helpful to others as well, I'm posting it here.
The functions should be able to be integrated into any Hollywood program that has the ability to open new windows. If you save the script under the name “TableMonitor v1.0.hws”, it will be included as follows:
@INCLUDE "TableMonitor v1.0.hws"
You can then display an existing table by calling p_TableMonitorDisplayCreate():
idtable=p_TableMonitorDisplayCreate(Table, "Title")
The arguments Table and “Title” are the table name and the window title. The return value idtable is needed if you want to close the monitor window:
p_TableMonitorDisplayClose(idtable)
You can also simply close the window with the mouse.
If necessary, additional arguments not listed here control the position, size, color, and font size. You can find them in the function description and in the sample script.
In the open window, you will then see the table contents in a format similar to that generated by SerializeTable(). Initially, I had also used this function, but since it is not suitable for all table structures, I built a corresponding Hollywood function as a replacement. If you prefer to use SerializeTable() if possible (because it is faster), you can control this with #MDUSESERIALIZETABLE (at the beginning of the script).
If the displayed table contents are larger than the window, you can scroll the window contents using the arrow keys; PgUp and PgDn also work, and Esc resets the position.
TableMonitor v1.0.hws:
Code: Select all
Const #MDUSESERIALIZETABLE=False
/****************************************************************
** Tabellenmonitor-Fenster erzeugen
** Diese Funktion wird vom zu testenden Programm aufgerufen. Das
** erzeugte Fenster kann durch p_TableMonitorDisplayClose()
** wieder geschlossen werden.
** Argumente:
* table anzuzeigende Tabelle (muß beim Aufruf bereits existieren)
* title$ Name der Tabelle für den Fenstertitel
* mdwidth |
* mdheight |
* mdxpos | Parameter für das Fenster
* mdypos |
* mdcolor |
** Rückgabewert:
Tabelle {mdid - id des Displays, mdivid - id des Intervalls}
*/
Function p_TableMonitorDisplayCreate(table, title$, mdwidth, mdheight, mdxpos, mdypos, mdcolor, fontsize, fontcolor)
Local mdivid
If IsNil(mdwidth) Then mdwidth=400
If IsNil(mdheight) Then mdheight=300
If IsNil(mdxpos) Then mdxpos=20
If IsNil(mdypos) Then mdypos=GetAttribute(#DISPLAY, 1, #ATTRYPOS)
If IsNil(mdcolor) Then mdcolor=0x222240
If IsNil(fontcolor) Then fontcolor=#WHITE
If IsNil(fontsize) Then fontsize=15
mdid=CreateDisplay(Nil, {Width=mdwidth, Height=mdheight, X=mdxpos, Y=mdypos, Color=mdcolor, Layers=False,
Title="Table Monitor: "..title$, Active=False, Sizeable=True,
Hidden=False, NoLiveResize=True})
OpenDisplay(mdid)
Local xypos={x=2, y=2}
SetMargins(xypos.x, 1e6, True)
mdivid=SetInterval(Nil, p_TableMonitorIntervalDisplayUpdate, 50, {mdid=mdid, ttable=table, xypos=xypos, fontsize=fontsize, fontcolor=fontcolor})
; Handler zum Verschieben der Anzeigeposition mit den Cursortasten:
InstallEventHandler({OnKeyDown=p_TableMonitorEventPhysKey}, {mdid=mdid, ttable=table, xypos=xypos})
; Handler zum Schließen dieses Monitor-Displays. Als Userdata wird dieselbe Tabelle wie beim
; Rückgabewert übergeben, damit p_TableMonitorDisplayClose direkt damit aufgerufen werden kann:
InstallEventHandler({CloseWindow=p_TableMonitorEventCloseWindow},{mdid=mdid, mdivid=mdivid})
; Handler für Änderung der Fenstergröße:
InstallEventHandler({SizeWindow=p_TableMonitorEventSizeWindow}, {mdid=mdid, xypos=xypos})
SelectDisplay(1)
Return({mdid=mdid, mdivid=mdivid})
EndFunction ;p_TableMonitorDisplayCreate
/****************************************************************
** Tabellenmonitor-Fenster schließen
** Argumente:
* idTable - Tabelle {id des Displays, id des Intervalls}
*/
Function p_TableMonitorDisplayClose(idTable)
gDebugExtrasum=Nil
If Not IsNil(idTable)
If HaveObject(#INTERVAL,idTable.mdivid)
ClearInterval(idTable.mdivid)
EndIf
EndIf
If IsNil(idTable) Then Return
If Not HaveObject(#DISPLAY,idTable.mdid) Then Return
SelectDisplay(idTable.mdid,True)
InstallEventHandler({CloseWindow=0}) ; Handler für Monitor-Display entfernen
SelectDisplay(1)
FreeDisplay(idTable.mdid)
EndFunction ;p_TableMonitorDisplayClose
/****************************************************************
** Event SizeWindow: Änderung der Fenstergröße
*/
Function p_TableMonitorEventSizeWindow(msg)
SelectDisplay(msg.userdata.mdid, True)
SetMargins(msg.userdata.xypos.x, 1e6, True)
SelectDisplay(1, True)
EndFunction ;p_TableMonitorEventSizeWindow
/****************************************************************
** Intervall: Monitor-Variablenanzeigen aktualisieren
*/
Function p_TableMonitorIntervalDisplayUpdate(msg)
If Not HaveObject(#DISPLAY,msg.userdata.mdid) Then Return
SelectDisplay(msg.userdata.mdid, True)
Local mdfontname$=GetAttribute(#DISPLAY, msg.userdata.mdid, #ATTRFONTNAME)
Local mdfontsize=GetAttribute(#DISPLAY, msg.userdata.mdid, #ATTRFONTSIZE)
Local mdfontcolor=GetFontColor()
; Workaround für interne Fonts (#SANS, #SERIF und #MONOSPACE):
Local internalFontnames={
["Bitstream Vera Sans"]=":::default_sans",
["Bitstream Vera Serif"]=":::default_serif",
["Bitstream Vera Sans Mono"]=":::default_mono"}
If RawGet(internalFontnames, mdfontname$) Then mdfontname$=internalFontnames[mdfontname$]
SetFont(#MONOSPACE, msg.userdata.fontsize)
SetFontColor(msg.userdata.fontcolor)
BeginRefresh() ; ---↓
Cls()
Locate(msg.userdata.xypos.x, msg.userdata.xypos.y)
Local tblerror=False
If HaveItem(msg.userdata, "ttable")
If GetType(msg.userdata.ttable)<>#TABLE
NPrint("Is no table")
tblerror=True
EndIf
Else
; Hinweis: nachträgliches Löschen der Tabelle wird hier
; nicht erkannt, die Tabelle bleibt in der Datenstruktur
; des TableMonitors erhalten.
NPrint("Table does not exist")
tblerror=True
EndIf
If Not tblerror
Local err, st$
If #MDUSESERIALIZETABLE
Local smode=GetSerializeMode()
SetSerializeMode(#SERIALIZEMODE_HOLLYWOOD)
err, st$= ?SerializeTable(msg.userdata.ttable)
If err<>#ERR_NONE
SetSerializeMode(#SERIALIZEMODE_NAMED)
/* #SERIALIZEMODE_NAMED funktioniert auch mit String-Indexen, die
Leerzeichen enthalten, aber nicht mit gemischten (num/string) Indexen */
err, st$= ?SerializeTable(msg.userdata.ttable)
If err<>#ERR_NONE
st$=p_SerializeMixedTable(msg.userdata.ttable)
EndIf
EndIf
SetSerializeMode(smode) ; zurück auf vorherigen Wert
Else
st$=p_SerializeMixedTable(msg.userdata.ttable)
EndIf
st$=ReplaceStr(ReplaceStr(st$, "[", "[["), "]", "]]")
st$=ReplaceStr(st$, "\09"," ") ; Tabulatoren weg, weil NPrint sie als Leerzeilen ausgibt
NPrint(st$)
EndIf
EndRefresh() ; ---↑
SetFont(mdfontname$, mdfontsize, {Engine=#FONTENGINE_INBUILT})
SetFontColor(mdfontcolor)
SelectDisplay(1, True)
EndFunction ;p_TableMonitorIntervalDisplayUpdate
/****************************************************************
** Tabelle als String ausgeben, die sich mit SerializeTable nicht
** ausgeben läßt, weil sie gemischte Indexe hat
** Argumente:
* t - anzuzeigende Tabelle
* rc - Rekursionstiefe
*/
Function p_SerializeMixedTable(t, rc)
If IsNil(rc) Then rc=0
Local st$=""
Local indent=""
For Local i=1 To rc
indent=indent.." "
Next
If rc>500 Then Return("{} (Recursion stopped: too many subtables.)\n")
st$=st$.."{\n"
ForEach(t, Function(a, b)
Local aquotes$=""
If GetType(a)=#STRING Then aquotes$="\""
If GetType(b)=#TABLE
st$=st$..indent.." "..aquotes$..a..aquotes$..": "..p_SerializeMixedTable(b, rc+1)
Else
Local bquotes$=""
If GetType(b)=#STRING Then bquotes$="\""
st$=st$.." "..indent..aquotes$.. ToString(a)..aquotes$..": "..bquotes$..ToString(b)..bquotes$.."\n"
EndIf
EndFunction
)
st$=st$..indent.."}\n"
Return(st$)
EndFunction ; p_SerializeMixedTable
/****************************************************************
** Event CloseWindow: Reaktion auf Schließen des Fensters
*/
Function p_TableMonitorEventCloseWindow(msg)
p_TableMonitorDisplayClose(msg.UserData)
EndFunction ; p_EventCloseWindow
/****************************************************************
** Event OnKeyDown: Reaktion auf Tastendruck
*/
Function p_TableMonitorEventPhysKey(msg)
SelectDisplay(msg.userdata.mdid, True)
Local mdheight=GetAttribute(#DISPLAY, msg.userdata.mdid, #ATTRHEIGHT)
Local mdfontsize=GetAttribute(#DISPLAY, msg.userdata.mdid, #ATTRFONTSIZE)
Switch (msg.action)
Case "OnKeyDown":
Local mstep=5
Local msgkey=msg.key
Switch msgkey
Case "ESC":
msg.userdata.xypos.x=2
msg.userdata.xypos.y=2
Case "DOWN":
msg.userdata.xypos.y=msg.userdata.xypos.y-mstep
Case "UP":
msg.userdata.xypos.y=msg.userdata.xypos.y+mstep
Case "LEFT":
msg.userdata.xypos.x=msg.userdata.xypos.x+mstep
; SetMargins(msg.userdata.xypos.x, 1e6, True)
Case "RIGHT":
msg.userdata.xypos.x=msg.userdata.xypos.x-mstep
; SetMargins(msg.userdata.xypos.x, 1e6, True)
Case "PAGEUP":
msg.userdata.xypos.y=msg.userdata.xypos.y+mdheight-2*mdfontsize
Case "PAGEDOWN":
msg.userdata.xypos.y=msg.userdata.xypos.y-mdheight+2*mdfontsize
EndSwitch
SetMargins(msg.userdata.xypos.x, 1e6, True)
EndSwitch
SelectDisplay(1, True)
EndFunction ; p_TableMonitorEventPhysKeyTableMonitor-Test v1.0.hws:
Code: Select all
@INCLUDE "TableMonitor v1.0.hws"
SetFont(#SANS,30)
NPrint("\nTableMonitor test\n")
NPrint("open four windows")
testTable1={"Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt"..
" ut labore et dolore magna aliquyam erat, sed diam voluptua. ", "bb", 0, {0.0}}
idTable1=p_TableMonitorDisplayCreate(testTable1, "test1", 400, 250, 50, #TOP, 0x22aa22)
Sleep(300)
testTable2={"A","B", 0, c="c", subtable={0}}
idTable2=p_TableMonitorDisplayCreate(testTable2, "test2", 400, 250, 75, 300, 0x222288)
Sleep(300)
testTable3={"A","B", 0, c="c", ["string index with spaces"]="idxmspace" ,
subtable={"subtable content",{"sub2table content"}},
Function() EndFunction, ["function"]=Function() EndFunction, datetime=""}
idTable3=p_TableMonitorDisplayCreate(testTable3, "test3 (mixed index types)", 400, 250, 100, 600, 0xeebb33, 18, #BLACK)
Sleep(300)
idTable4=p_TableMonitorDisplayCreate(5, "test4 (will close in 4 seconds)")
; close fourth example window ("no table") after four seconds:
iidt={}
iidt[0]=SetInterval(Nil, Function()
NPrint("close the fourth window")
p_TableMonitorDisplayClose(idTable4)
ClearInterval(iidt[0]) ; only once
iidt=Nil
EndFunction, 4000)
; make some changes in the tables:
SetInterval(Nil, Function() testTable1[0]=UnrightStr(testTable1[0],1)..LeftStr(testTable1[0],1)
testTable1[2]=testTable1[2]+1
testTable1[3][0]=testTable1[3][0]+#PI
EndFunction, 300)
SetInterval(Nil, Function() testTable2[2]=testTable2[2]+1
Local x=testTable2[0]
testTable2[0]=testTable2[1]
testTable2[1]=x
testTable2.c=Chr(48+Rnd(75))
testTable2.subtable[0]=Rnd(10)
EndFunction, 800)
SetInterval(Nil, Function() testTable3.datetime=GetDate(#DATELOCAL) EndFunction, 200)
; output text in the main window to check if the font settings are unmodified:
iidm=SetInterval(Nil, Function() Cls Locate(2,2) NPrint(GetDate(#DATELOCAL)) EndFunction, 7000)
SetTimeout(Nil, Function() ChangeInterval(iidm, 1000) EndFunction, 8000)
NPrint("starting main loop")
Repeat
WaitEvent()
Forever