I have presented SmartBinding in a couple of posts. The purpose of SmartBinding was to bind data containers together. Often one container would be something that could display and/or interact with its data, while the other could be a container holding data elsewhere, like the result from a query, or a list of objects or a record or something else. SmartBinding also introduced data proxies, which made it very easy to separate visual design and functionality from hardcore data manipulation/retrieval and storage.
In new release of kbmMW, SmartEvent has been added. The purpose of SmartEvent is primarily to separate user interaction from business logic. In other words a super version of the old hardwired event handling.
Someone might say… action lists… they are the answer. Yes, but no. TActionList is still a hardwired fairly simple way to operate events. SmartEvent is much more generic than that.
Starting to dig into it
What makes SmartEvent tick?
Well.. it is a publish/subscribe based notification framework. I was not as such aware about it, but Jim McKeeth recently told me that I had essentially “Delphinized” the way Apples ObjectiveC operates. I don’t know if that is to be understood as a good or a bad thing ;)…. ObjectiveC has an absolutely horrible syntax IMO.
However…. I’m sure the designers have had the same objective as I… finding ways to separate view from control and model.
So what means publish/subscribe based notification? It means that you define which parts of your code have interest in something and makes them subscribe for a “happening”… an event notification. In good old days (BSE – Before introduction of SmartEvent’s) that would be your ….OnSomething eventhandler.
In modern days (ASE – After introduction of Smart Event’s) it would basically be any method you have in your code.
So you could have a method like this:
procedure TForm1.ShowSomeMessage(const AMessage:string);
which could subscribe for every intent to show messages. I’ll show in a moment how.
But there is no fun in subscribing for anything if nothing or nobody is offering it… (I know all about it… having subscribed for fun, long-drinks with umbrellas on the beach of my own tropical island, and an endless amount of money…. and still not found the sponsors for it).
So we need to be able to offer information to the subscribers, and we need a mechanism that makes it possible to indicate what type of offering one is making, and thus only subscribers that have an interest in that particular offering, receives it.
The key thing here is the term “subject”. A subject is a sentence of . (dot) separated words.
For example one could make a subject called: SHOW.ERROR or SHOW.INFO or SHOW.ICECREAM or SHOW.FOOD.BANANA etc.
Basically you define what the subject should be. It doesn’t even have to have dot’s in it. But usually its a good idea to make it hierarchical, in the sense that you have starting with logical generic context words, and ending with more specialised words that match the topic at hand.
When you then have a situation where you want to notify potential subscribers about that you have an error to show, you simply send a notification with the subject “SHOW.ERROR”.
So now we know what a subject is, we need to learn how we subscribe to that subject.
In the former case, we want the method ShowSomeMessage to be called everytime someone publishes a SHOW.ERROR notification
That is very easily defined:
TForm1 = class... ... [kbmMW_Event('SHOW.ERROR')] procedure TForm1.ShowSomeMessage(const AMessage:string); ...
And it is as easy to send the notification:
... Event.Notify('SHOW.ERROR','Some error happened'); ...
We just need to do one thing to make it all work… tell the SmartEvent framework about your form instance is interested in subscribing for stuff.
So somewhere (perhaps in your TForm1 constructor), you will have this statement:
... Event.Subscribe(self); ...
where self is the form instance itself. You can also reference another instance of a class on which methods has been tagged with the kbmMW_Event attribute.
The moment the Notify command is sent, the method ShowSomeMessage is called.
A use case
There are many scenarios, in which SmartEvent will make live easier for you. One of them is if you have an application with a couple of main form/datamodules, and a host of frames or other forms, you want to display and remove during the use of the application.
Previously you would either have to make sure each of the forms directly referenced the main form or data module units in which you have your business code and/or data available, or you would have to write plenty of event handlers to make it possible to do IOC… basically to hook up external code in your main datamodule/form to your event handlers.
In both cases you would have to write allot of code, and for each small extra feature that would need interaction from other parts of your application, you would manually have to link the stuff together somehow. You would typically start to limit in which units you place your code, to minimize the complexity and the number of unit uses clauses ending up in a royal spaghetti mess (with meatballs).
SmartEvent makes this extremely easy for you, since any part of your code, can choose to subscribe for happenings, and any other parts of your code can choose to publish notifications where needed.
Since you simply write Event.Subscribe(yourform/frame/datamodule/otherclassinstance) when you need that instance to participate in handling notifications, it is very easy to plug in new functionality without the rest of your code needing to know about it’s implementation.
Obviously, you might want to close down a form/frame etc. when you no longer want to use it… It could for example be a form displaying a list of grocery items that the user can select between. The form with the list will then publish a notification with a wish for getting a relevant list of grocery items to display, and when the user selects an item, publish a notification about which item has been selected, and then close down the form.
Before closing down the form, simply write:
... Event.Unsubscribe(yourform/frame/datamodule/otherclassinstance); ...
Then the form will no longer be notified, which makes sense because you are about to destroy it.
But what about….
Showing other messages?
Remember the procedure was named ShowSomeMessage, so it is intended not only to show error messages… but presumably any messages. But since it only subscribes for SHOW.ERROR, it will not react on for example SHOW.INFO.
This is easy to remedy:
Change the subscription to
TForm1 = class... ... [kbmMW_Event('SHOW.>')] procedure TForm1.ShowSomeMessage(const AMessage:string); ...
Now we are using a wildcard. We have told the framework, that ShowSomeMessage should be called everytime anybody publishes a notification starting with SHOW.
The > indicates that all parts (each word between dots are called parts) starting at the place of the > are to be considered a match.
One can also use * as a wildcard for a specific part. In our SHOW.ERROR vs SHOW.INFO sample, a subscription for SHOW.* and SHOW.> would essentially result in the same. But SHOW.> would also match SHOW.FOOD.BANANA, which SHOW.* would not match with since there are extra parts.
Simply add the additional arguments to the Notify call. Methods subscribing will receive as many arguments as possible that match. If there is a mismatch between types of data, an exception will be raised. Like notifying a method that only accepts an integer, with a string.
Notify directly accepts until 9 arguments. If you have more you can use the version of Notify that takes an array TValue.
If there are no arguments to provide, you can just publish without an argument:
You can directly let specific parts of your code subscribe like this:
Event.Subscribe('SOMETHING.>', procedure(const AContext:IkbmMWEventContext) begin Log.Debug('Global SOMETHING.> subject:'+AContext.Notification.Subject); end).NamedAs('Sub1').Activate; Event.Subscribe('SOMETHING.MORE.>', procedure(const AContext:IkbmMWEventContext) begin Log.Debug('Global SOMETHING.MORE> subject:'+AContext.Notification.Subject); end).NamedAs('Sub2').Synchronized.Activate;
In the above code, we have even named each of the subscriptions (optional). You can use the name to unsubscribe later on if you need to, alternatively use the IkbmMWEventSubscriber instance returned from the Event.Subscribe method for later unsubscription.
Further we have in the subscription for SOMETHING.MORE.> requested the subscription to be executed synchronously, which means that it will be running in the main VCL/Firemonkey GUI thread. This is important if the code needs to update the GUI somehow, and since you, as the developer, knows exactly what your event handling code is doing, you can easily specify for the framework, that its GUI sensitive.
Then the framework will automatically make sure it is executed accordingly.
Sometimes (often) you will want to publish a notification with the expectancy of getting some data back. Like in our grocery list example further up. The frame/form publishes a notification about the with to receive a list of groceries.
You would make a method available somewhere in your application, that can produce a grocery list. In this example by returning a TkbmMemTable instance with the items:
... [kbmMW_Event('REQUEST.GROCERYLIST')] function RequestGroceryList:TkbmMemTable; ...
The frame/form containing the list would publish the wish for the grocery list like this:
Event.Notification('REQUEST.GROCERYLIST') .WantSubscribers .WantResults .WhenNotified( procedure(const AContext:IkbmMWEventContext) var mt:TkbmMemTable; begin mt:=Use.AsMyObject<TkbmMemTable>(AContext.Result); if mt<>nil then ShowList(mt); end) .Send;
We are now using a slightly different variant of the Notify method. One that allows chaining different bits and pieces together to augment the published notification with additional information.
In this situation, we ask the framework to ensure that we get informed about all the subscribers reacting to our notification, and also that we want the results from each of them.
The code in WhenNotified is run each time a subscriber event is done running, which then makes the memory table instance available for the list. The ShowList method (or the frame/form) is now the owner of the memory table instance (because we used Use.AsMyObject to get the instance from Result). If we used Use.AsObject instead, then the framework would automatically clean up the memory the moment Result goes out of scope.
But multiple subscribers (or none) could have heard our plea for a grocery list? Yes. that is very much possible. In the above case, the list will be shown for each of the subscribers that returned data to us, which may not be the result we wanted.
We probably only wanted the first if multiple subscribers responded.
In such case we use WhenDone instead of WhenNotified:
Event.Notification('REQUEST.GROCERYLIST') .WantSubscribers .WantResults .WhenDone( procedure(const AContext:IkbmMWEventContext) var mt:TkbmMemTable; begin mt:=Use.AsMyObject<TkbmMemTable>(AContext.Result); if mt<>nil then ShowList(mt); end) .Send;
Then it will only be called once… and only the first result being provided will be used.
However if you would like to see all the results, then do like this:
Event.Notification('REQUEST.GROCERYLIST') .WantSubscribers .WantResults .WhenDone( procedure (const AContext:IkbmMWEventContext) var lst:TList<variant>; i:integer; mt:TkbmMemTable; begin Log.Debug('Done notifying '+inttostr(AContext.Notification.NotifiedCount)+' about '+AContext.Notification.Subject); Log.Debug(' Number of results: '+inttostr(AContext.Notification.ResultCount)); lst:=AContext.Notification.Results.BeginRead; try for i:=0 to lst.Count-1 do begin mt:=Use.AsObject<TkbmMemTable>(lst.Items[i]); if mt<>nil then Log.Debug(' Result '+inttostr(i)+': Number of records: '+inttostr(mt.RecordCount)); end; finally AContext.Notification.Results.EndRead; end; end) .Send;
Main VCL/Firemonkey GUI thread?
I touched it a bit before where one of the code based subscription’s used the Synchronized method to indicate that the event handler should be run within the main GUI thread, probably because it needs to update the GUI.
The kbmMW_Event attribute can also state that requirement:
TForm1 = class... ... [kbmMW_Event('SHOW.ERROR',[mweoSync])] procedure TForm1.ShowSomeMessage(const AMessage:string); ...
Then ShowSomeMessage will always be run in the scope of the main GUI thread which makes it safe to update the GUI.
What about the WhenDone or WhenNotified notification augmentations? What if the code in them should be run synchronized?
Simple… just add the .Synchronized augmentation to the notification:
Event.Notification('REQUEST.GROCERYLIST') .Synchronized .WantSubscribers .WantResults .WhenDone(...) .Send;
At first this may seem more difficult than just writing an eventhandler. And for simple applications you may very well be fine without kbmMW SmartEvent. But the more complex application is getting, the more cumbersome it gets to manage all the different dependencies, and interactions between forms, data and code.
By combining kbmMW SmartBind and kbmMW SmartEvent you have the complete solution to split your application into a number of selfcontained bits and pieces, who only needs to know about a list of subjects they can use, and perhaps some names of some data they can bind to.
Plugging the form/frame in and out will then be extremely simple.