.NET Tips & Tricks: Dispatcher

There are situations when you want to exchange data between different objects in NAV, and there is no simple way to do it. One of those, that a friend stumbled upon a couple of days ago goes like this: you have a page which shows a subpage, that is linked to a factbox. Depending on the situation in the lines, you the factbox shows content from one table or another.

Your instincts may yell “ProviderID” at this moment, but there are some problems with it. ProviderID is used to set the link between different part controls on the same page. But if the link from the provider control results in no record being selected in the target part control, then the OnAfterGetRecord trigger in it does not fire, and you cannot update the content.

Another example may be this: depending on the line selected in the lines part, you may want to show either of two factboxes. Imagine – on a sales order, if you are on a line that sells a resource, you want to show the Resource factbox; and if you are on a line that sells an item, you want to show the Item factbox. You get the gist. There are not many ways you can achieve this.

As a matter of fact, when helping my colleague, I couldn’t think of any.

Except for an old trick which, unfortunately, does not work at all in the RTC. In the good old days of the Classic client, when heroes roamed the Earth, you could pass the CurrForm as a reference onto a function in a subform, which would then store the reference to the main form in its own Form variable. Then, when something happened in the lines, and you wanted to let the master page know, you called the function on the stored reference. It worked like charm. However, in NAV 2013 (and possibly 2009 – can’t bother to check) you cannot assign one page variable to another. Furthermore, the CurrPage variable has page ID in some funny range, and does not even correspond to the actual page number. All in all, no way you can pass, or retain, a reference to a page object, let alone the current page reference.

So, how do you have two, three, or more pages (or page parts, for that matter) talk to each other to pass some information when needed, in all those situations when ProviderID simply doesn’t get the job done (I could bore you to death listing those situations)?

.NET interoperability, what else.

If a page reference cannot be shared, some other reference can: a reference to an instance of a .NET object. So, imagine you had an object that has a method, and an event. You call the method, and the method raises the event. If multiple objects use the same reference of the object, the event is raised in multiple objects in NAV.

So, imagine this code:

If your imagination fails you, I’ve provided the download. Just click here and use the objects. I promise, they don’t bite.

I’ll give you two use cases here.

1. Calling a method in a master page from a subpage

In a master page (e.g. Sales Order) and a subpage (e.g. Sales Order Subform) declare a global DotNet variable of subtype EventManager.Dispatcher. In the subpage, declare a function SetDispatcher that receives EventManager.Dispatcher as a parameter. In this function assign the parameter object to the global variable of type Dispatcher. Then instantiate (construct) the Dispatcher in the OnOpenPage trigger of the master page, and call the SetDispatcher method on the subpage, passing this object to it. Make sure that the Dispatcher object on the master page has the WithEvents set to Yes.

When you need to call the master page from the subpage, simply call the Dispatcher.Dispatch method, and the event will fire on the master page.

You can use the Source, Target, and Data parameters on the Dispatch method to pass any data to event subscribers. Source should somehow indicate who is sending the data, target should indicate which (of possible many) subscriber should respond to the event, and Data should contain, well – give it a guess Smile All of them are of type System.Object, so you can pass just about anything.

2. Dispatching data to multiple independent objects across the application

This is more of a tipsy and tricksy scenario than of a real-life one, but still, if you ever feel an urge to go bananas, use this trick.

Create one single-instance codeunit that has one global variable of type Dispatcher. Have one global function GetDispatcher that receives a parameter of type Dispatcher by reference. Within this method, check if the Dispatcher variable of the codeunit is null, and if it is, then first instantiate it. Then assign this global variable to the by reference parameter.

Then, from whichever object is interested in whoever is dispatching whatever (my late English teacher, may she rest in peace, would just love this sentence Smile) you call this GetDispatcher method to get the reference to this global dispatcher that rules them all. In each object interested in the chatter, have the Dispatcher variable subscribe to the events, so you get the OnDispatch event in that object.

Then, whenever an object feels like tweeting some news to the crowd, call the Dispatcher.Dispatch method from there. In each OnDispatch event write code that listens only to those dispatch events (source) that the target is interested in.

Sit back, grab a bag of popcorn, and watch the show. Simple, clean, efficient, and most important of all – it gets the work done.

Please share your thoughts about this trick. If you love it, or hate it, or couldn’t care less, I’d still like to know. And if you put this to practical use, please just let the world know by leaving a comment.

26 thoughts on “.NET Tips & Tricks: Dispatcher”

  1. Hi Vjeko,

    Do I see any practical use of this in a real life scenario? O YES Sir!

    Now I really can’t wait any longer to get to next week to get ‘some’ of your knowledge being used in our scenario 😛

    Thanks for sharing yet again a great .Net Interoperability tip. One would wonder why Microsoft hasn’t built this functionality in standard NAV yet.

  2. Great trick, Vjeko.

    I think you cannot but show this to the attendants of next weeks .Net Interoperabilty training in NL I guess you also discussed this at this weeks training in BE. 😉

    b rg
    Luc

    1. @Luc: yes, I will definitely show this next week. But last week I didn’t show this to the BE attendants, as I didn’t create this thing yet.

  3. Amazing post Vjeko and a great blog in general 🙂 Really great for us detached developers who can only rely on ourselves and google, always learning new stuff, thanks and keep it up!

  4. Hi.

    Please can you Show Code-samples from NAV.
    I am not able to implkement your Code in a Page, SubPage Construct.

    Thanks.

    1. @everybody: I see a lot of interest for this, so I will provide some examples and hints.
      @Dave: I’ll try to comment the C# code. I probably won’t explain the very basics of everything in the code, but may post a blog post where I teach all NAV folks what the cryptic C# stuff does. I only need to get half an hour of spare time and I hope it happens in this geological era 🙁

  5. Hey Vjeko, thanks for this great trick. I have tried it, and the principle works. The subpages can communicate with the main page. But… The main page wont update.
    Imaginge the subpages have filters. If in subpage1 a record is clicked (onaftergetcurrentrecord), subpage2 has to update its filters, to show corresponding values. I can get the main page to notice the selected record has changed in Subpage1, but Subpage2 doesnt update. Update(FALSE) doesnt work. The F5 button does, but thats exactly what i want to avoid 🙂

    Any ideas?

    1. @joost: have you tried CurrPage.ACTIVATE(TRUE) instead of CurrPage.UPDATE(FALSE). Basically, ACTIVATE(TRUE) will force the page to refresh, and you absolutely must do it. There is no reason for the page to refresh just by itself, and the only way to properly do it from the code is to call ACTIVATE(TRUE).

  6. Yes, that works on the main page, but it forces the subpages in a loop. If a page is activated (CurrPage.Activate(TRUE), then it comes in the OnAfterGetCurrentRecord trigger on the subpage, wich fires the event on the mainpage, wich activates the OnAfterGetCurrentRecordtrigger on the subpage again.
    Maybe my idea is wrong: i want the dispatcher to fire when a record is clicked, not when its retrieved from the database.

  7. I got it working now 🙂 . Turns out that when loading a subpage, the OnAfterGetCurrentRecord is hit only once.
    This is what i did:
    In the OnOpenPage trigger of the Main Page:
    IF ISNULL(gDispatcher) THEN BEGIN
    gDispatcher := gDispatcher.Dispatcher;
    CurrPage.Step.PAGE.GetDispatcher(gDispatcher); //Subpage1
    CurrPage.MailNotification.PAGE.GetDispatcher(gDispatcher); //Subpage2
    END;

    In the OnDispatch trigger on the Main Page:
    CurrPage.ACTIVATE(TRUE);

    In the function ‘GetDispatcher(Dispatcher :Dotnet “EventManager.Dispatcher”) of the Subpages:
    globalDispatcher := Dispatcher. //DO NOT CONSTRUCT THE DISPATCHER… Thats the mistake i made.

    //Depending on where you want the dispatcher to fire, you call the Dispatcher.Dispatch method. In my case:
    In the OnAfterGetCurrentRecord trigger on the Subpages:

    IF NOT globalVariableOfRec.FIND THEN
    globalVariableOfRec := Rec
    ELSE IF (globalVariableOfRec.Code Code) THEN BEGIN //Primary key than rec.primary key
    gDispatcher.Dispatch(”,”,”); //Event Fires
    globalVariableOfRec := Rec;
    END;

    //This prevents the page to go into a loop.
    //When loading the page, the globalVariableOfRec is not filled. That is, the first time the OnAfterGetCurrentRecord
    //is triggered, the event will not fire. After that, each time you click on a record, the OnAfterGetCurrentRecord is
    //triggered, but this time, the globalVariableOfRec is found. If the primary key is other than the found
    //globalVariableOfRec, the event will fire.

    This works like a charm. Downside is that the whole page is refreshed, wich means is visually notable.

    Thanks a bunch, Vjeko! Im sure i will use this concept alot in our program.

    1. Hi Vjeko and Joost,

      Is there a way to also do this by declaring/calling directly .NET classes from within nav objectos (between page parts) using NAV2015 ?
      I’m asking this, because I would like to avoid installing DLL’s in all PC’s.

      Thanks and Best Regards,
      /ASF

  8. This trick is amazing!!!! very simple but powerful. I also found that your blog is very helpful and it has a lot of good stuff 🙂

    This is my first time visiting your site and I definitely coming back to learn more from you!

  9. The current download of the DLL gives an error.
    How to deal with it :
    In Visiual Studio, generate a new DLL.
    Sample : depending on the line selected in the lines part, you may want to show it on a sales order on demand

    OnAfterGetCurrRecord() Subpage
    ———————-
    IF Rec.GET(Rec.”Document Type”, “Document No.”,”Line No.”) THEN BEGIN
    gEventManager.Dispatch(CurrPage.OBJECTID,PAGE::”Sales Order”,Rec.GETPOSITION);
    END;

    OnOpenPage() MainPage
    ————
    gEventManager := gEventManager.Dispatcher;
    CurrPage.SalesLines.PAGE.SetDispatcher(gEventManager);

    gEventManager::OnDispatch MainPage
    ————————-
    gRecSalesLine.SETPOSITION(FORMAT(data));
    gRecSalesLine.SETRECFILTER;
    CurrPage.UPDATE;

    Thank you Vjeko,
    Warm Regards from Newminds

  10. Dear Vjeko, the solution works fine, also in Nav2015.
    Only in one occasion the dispatcher event on the main page is not triggered.
    No error. I puzzeled a lot, but haven’t found the answer/solution yet.
    Do you have any idea ? (security issue perhaps).
    Newminds

      1. Dear Vjeko.

        The problem that we’ve found earlier (May 11 2015) was caused by not linking to the new Microsoft.Dynamics.Framework.UI.Extensibility.dll

        So when you make an add-on. You’ll need to make sure that the project is linked to the right NAV Installation version.
        And I find it weird we don’t get an error message, like: “Add-on isn’t supported on this NAV installation” or something.

        Only problem with this is, we need to make the same add-ons with different version linking. Do you’ve an idea how we can organise this?

        1. @Vincent: There is no need to reference to UI.Extensibility. That is mandatory on Client AddIns. But this case is a simple .Net Assembly, used serverside.
          But if you need a Client AddIn, you don´t have to use the newest version, they are downwards compatible.

Leave a Reply