Tl;Dr
A wrapper class for the message loop, finally as an include script to handle events of dialogs with event handlers instead of switch / if else for easier coding and better to read code.
Download
Version | Date | Changes |
---|---|---|
v2.32 (4.2 KB) | 2024-07-24 | Fixed a bug in AddEventHandler() that prevented adding a hanlder for multiple controls at once (changed instanceof to typeof) |
v2.31 (4.2 KB) | 2024-07-17 | Fixed two bugs in AddEventHandler() and DictionaryLength() (forgot to use 'var' in loops) |
v2.3 (4.3 KB) | 2024-07-17 | AddEventHandler() now takes either a string or a string array for eventSenderName to attach one controlhandler to one event for several controls at once |
v2.2 (2.2 KB) | 2024-07-16 | - ![]() ![]() - Introduced LoopContext variable, which is passed to every eventhandler from scope of starting the loop. Add it to the callback signature if needed - fixed bug in ListHandlers() |
v2.1 (2.2 KB) | 2024-07-15 | Added WatchDir() and WatchTab() for convenience |
v2.0 (2.1 KB) | 2024-07-13 | Inital Version of the include script |
Testscript (4.4 KB) | 2024-07-17 | Testcommand package to test the wrapper. Not regularily updated! |
MsgLoopHandler is a wrapper class in an include script to handle the evaluation of the DOpus message loop to make coding custom dialogs easier and to produce better readable code.
You simply have to register an event handler for a specified control and a certain event. It also allows a catchall handler for any event fired by a certain control. The event handler then gets called with a reference to the current msg object for further processing.
This allows a simpler and easier to read and maintainable control flow than evaluating the message loop with a branching switch / if else setup.
Usage
Add this to the beginning of your script (only in DOpus 13, for older versions you need to copy the MsgLoopHandler class to every script where you need it).
@include inc_MsgLoopHandler.js
//Setup your dialog
var dlg = scriptCmdData.func.Dlg;
dlg.window = source;
dlg.template = "Dialog";
dlg.title = "MsgLoopHandlerTest";
dlg.want_resize = true; //do that when you want to register resize events
dlg.detach = true;
dlg.Create();
var loopContext = { myValue : 1 };
//MsgLoopHandler(Dialog, DialogClosedCallback, LoopStepCallback, DebugLog, ListHandlersOnStart)
var msgLoopHandler = new MsgLoopHandler(dlg, loopContext, OnDialogClosed, null, true, true);
msgLoopHandler.AddEventHandler("button1", "click", Button1Click);
//Add eventhandler for check1 click event as anonymous function
msgLoopHandler.AddEventHandler("check1", "click", function(dialog, msg){DOpus.Output("checkbox1 was clicked");});
//Add eventhandler for listview1 selection changed event as Function Pointer (defining the function somewhere else)
msgLoopHandler.AddEventHandler("listview1", "selchange", EventHandler);
//Adding a catchall eventhandler for combo1, being called for any event of this control
msgLoopHandler.AddEventHandler("combo1", "*", CatchAll);
//add one eventhandler for one event for multiple controls at once
msgLoopHandler.AddEventHandler(["button1", "check1", "listview1"], "click", MultiHandler);
/*
//Setting up a timer
//Instead of this
dlg.SetTimer(5000, "timer1");
msgLoopHandler.AddEventHandler("timer1", "timer", Timer1Tick);//Event always has to be "timer"
//do that
*/
msgLoopHandler.SetTimer(5000, "timer1", Timer1Tick);
//Run the loop and react to events
msgLoopHandler.Loop();
}
//add loopContext to your signature if you need to access it
function Button1Click(dialog, msg, loopContext)
{
DOpus.Output("myValue = " + loopContext.myValue);
loopContext.myValue *= 2;
dialog.control("static1").Title = loopContext.myValue;
}
function EventHandler(dialog, msg)
{
DOpus.Output("another eventhandler");
}
function Timer1Tick(dialog, msg)
{
DOpus.Output("Timer ticked");
}
function CatchAll(dialog, msg)
{
DOpus.Output("Catchall handler, event = " + msg.event);
}
function OnDialogClosed(dlg)
{
DOpus.Output("The dialog '" + dlg.title + "' has been closed.");
}
inc_MsgLoopHandler.js Code
// MsgLoopHandler
// (c) 2024 Felix.Froemel
// This is an include file script for Directory Opus.
// See https://www.gpsoft.com.au/endpoints/redirect.php?page=scripts for development information.
// Called by Directory Opus to initialize the include file script
function OnInitIncludeFile(initData)
{
var versionDate = "2024-07-24";
initData.version = "2.32";
initData.name = "MessageLoopHandler";
initData.desc = "Wrapper class to handle the DOpus Message Loop in event driven way. v" + initData.version + " ("+ versionDate + ")";
initData.copyright = "(c) 2024 Felix.Froemel";
initData.url = "https://resource.dopus.com/t/msgloophandler-an-event-driven-wrapper-for-the-dopus-message-loop-as-include-script/51781";
initData.min_version = "13.0";
initData.shared = true;
}
//Wrapper class to handle the DOpus Message Loop in an event driven way.
//Call with the dialog, and optional callback for each loop step, debbugging, and listing handlers on start
function MsgLoopHandler(dlg, loopContext, dialogClosedCallback, loopStepCallback, debugLog, listHandlersOnStart)
{
this.Dialog = dlg;
this.EventHandlers = [];
this.LoopContext = loopContext || { };
this.DialogClosedCallback = dialogClosedCallback || function(dlg) { };
this.LoopStepCallback = loopStepCallback || function (msg) { };
this.ListHandlersOnStart = listHandlersOnStart || false;
this.DebugLog = debugLog || false;
//Run the Messageloop, wait for Messageloop result
this.Loop = function()
{
if (this.ListHandlersOnStart)
this.ListHandlers();
var msg;
do
{
msg = this.Dialog.GetMsg();
if (!msg.result)
{
if (this.DebugLog)
this.Log("Msgloop: the dialog '" + this.Dialog.title + "' has been closed.");
this.DialogClosedCallback(this.Dialog);
break;
}
if (this.DebugLog)
{
var evnt = msg.event || "no event";
var val = String(msg.value || "no value");
var ctrl = msg.control || "no control";
var logMsg = "Msgloop: Event = " + evnt + "\tValue = " + val + "\tControl = " + ctrl;
this.Log(logMsg);
}
//Call the actual event processing
this.LoopStep(msg);
}
while (msg);
};
//Actual Messageloop event processing
this.LoopStep = function (dlgMsg)
{
var controlName = dlgMsg.control;
var event = dlgMsg.event;
var eventHandler = this.EventHandlers[controlName];
if (eventHandler)
{
var eventCallback = eventHandler[event];
if (eventCallback)
eventCallback(this.Dialog, dlgMsg, this.LoopContext);
else //check if catchall event handler is present
{
var catchAllEventHandler = eventHandler["*"];
if (catchAllEventHandler)
catchAllEventHandler(this.Dialog, dlgMsg, this.LoopContext);
}
}
this.LoopStepCallback(dlgMsg);
};
//Add event handler callback for events
//Checks the type of eventSenderName for either being string or array to allow adding multiple controls with the same handler as array
//Callback Signature:
//EventHandler(dialog, msg) or
//EventHandler(dialog, msg, loopContext)
//set eventtype to "*" for catching all events of this control
/*
Structure of EventHandlerDict is controlnames are keys to a list of mappings eventname -> eventhandler
[
ControlA : [Event1, EventHandler1],
[Event2, EventHandler2]
ControlB : [Event3, EventHandler3]
...
]
*/
this.AddEventHandler = function(eventSenderName, eventType, eventCallback)
{
if(typeof eventSenderName === "string")
this.AddEventHandlerInternal(eventSenderName, eventType, eventCallback);
else if(eventSenderName instanceof Array) //adding multiple controls with the same event handler
{
for(var senderIndex in eventSenderName)
this.AddEventHandlerInternal(eventSenderName[senderIndex], eventType, eventCallback);
}
};
//Used for actually adding the handler
this.AddEventHandlerInternal = function(eventSenderName, eventType, eventCallback)
{
if (eventSenderName in this.EventHandlers) //add to already existing entry
{
var handler = this.EventHandlers[eventSenderName];
if (!(eventType in handler))//just add once
handler[eventType] = eventCallback;
else
this.Log("Already registered event '" + eventType + "' for sender '" + eventSenderName + "'. Skipping", true);
}
else //create new element in list for eventSender and register
{
var handler = {};
handler[eventType] = eventCallback;
this.EventHandlers[eventSenderName] = handler;
}
};
//Set a timer in the dialog and add to eventhandlers
this.SetTimer = function (timeoutMs, name, elapsedCallback)
{
this.AddEventHandler(name, "timer", elapsedCallback);
this.Dialog.SetTimer(timeoutMs, name);
};
//Watch directory for changes
this.WatchDir = function(id, path, flags, eventCallback)
{
this.AddEventHandler(id, "dirchange", eventCallback);
this.Dialog.WatchDir(id, path, flags);
};
//Watch tab for changes
this.WatchTab = function(tab, events, id, eventCallback)
{
this.AddEventHandler(id, "tab", eventCallback);
this.Dialog.WatchTab(tab, events, id);
};
//List all the handlers that are registered in the scriptlog
this.ListHandlers = function ()
{
this.Log(this.DictionaryLength(this.EventHandlers) + " registered Event Handlers");
this.PrintDict(this.EventHandlers);
};
//Get element count in dictionary
this.DictionaryLength = function(dict)
{
var length = 0;
for(var keys in dict)
length++;
return length;
};
//Print all keys/dubkeys from dictionary to script log
this.PrintDict = function (dict)
{
for (var key in dict)
for (var subKey in dict[key])
this.Log("\t" + key + " => " + subKey);
};
//Log events
this.Log = function(msg, e) {
DOpus.Output(String(msg), e || false);
};
}
Background
I really enjoy the powerfully possibility of being able create custom dialogs for script addins/buttons. But having more complex dialogs leaded to horrible to read and maintain if else code. So I started this code some years ago in DOpus 12. I haven't had the time in the past years and wasn't very active in the forum in this time. But upgrading to v13 some months ago and finding the time scratch the surface I was very happy of the new ability of finally being able to include scripts in other scripts. I had different versions of this wrapper in many different scripts and buttons in different versions and thus it was hard to maintain and to continue coding. So now I finally had the time and success finalizing the code as an include script. I hope you enjoy using this piece of code, maybe even updating your old scripts.
I tried to keep it as simple as possible and removed a lot of code of older versions. But I didn't test every case so please let me know if you find any bug or have an idea how to improve.
Old way to achieve the same as the code above
var msg;
do
{
msg = dlg.GetMsg();
if (!msg.result)
{
DOpus.Output("Dialog closed");
break;
}
var ctrl = msg.control;
var evnt = msg.event;
if(control == "button1")
{
if(evnt = "click")
{
//...
}
//else if other event
}
else if(ctrl == "timer1" && evnt == "timer")
{
//...
}
else if(ctrl == "listview1")
{
if(evnt == "selchange")
{
//...
}
}
}
while (msg);