Forms Messaging

From OpenPetra Wiki
Revision as of 20:43, 11 May 2022 by Pokorra (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Deprecated

This only was written for the Winforms Client, and does not apply to the Javascript client.

Overview

OpenPetra has a facility to send typed 'messages' between Forms on the client side. This Facility is called 'Forms Messaging'.

Forms Messaging is usually used in scenarios where an action in one Form should trigger an action in the other Form (e.g. data is saved in 'Form A' and 'Form B' needs to re-load that data and re-display it so it is refreshed). Example: The Partner Edit screen sends a Form Message if new Partner gets saved, and another Form Message if an existing Partners' data got saved. That Message could be listened for by arbitrary Forms and they could react appropriately.

Explanation of the Concept

There is always one 'Sending Form' and there are 0..n 'Listening Forms'.
Yes, zero 'Listening Forms' are allowed - it doesn't cause a failure - and the reason for that is that the 'Sending Form' cannot know at run time whether any of the Forms that would potentially be 'listening' for that particular 'message' are indeed instantiated at the time when the 'Sending Form' broadcasts the 'message'. Also, when a developer adds the 'broadcasting' of a 'Form Message' to a Form (and turns it into a 'Sending Form' by doing so) the developer doesn't need to implement the 'listening' for that 'Form Message' in any Form yet - no error will be caused at run time when the 'Forms Message' is 'broadcasted'. (Obviously nothing will happen in any other Form in that case, too!)


Details of the Implementation

Facilitating Classes

  • The Class file for the definition of the typed 'messages' is \csharp\ICT\Petra\Client\CommonForms\FormsMessaging.cs.
    • The 'Sending Form' creates an instance of the TFormsMessage Class and specifies the kind of typed 'message' using the TFormsMessageClassEnum in the Constructor of that Class.
  • The Class TFormsList found in C:\openpetraorg\trunk\csharp\ICT\Petra\Client\CommonForms\FormsList.cs has the public Method BroadcastFormMessage that is used by a 'Sending Form' to 'broadcast' a 'message'.

Forms Messaging is Synchronous

Forms Messaging is synchronous, that is, 'messages' are 'broadcasted' in a synchronous manner and if a 'Listening Form' reacts to a 'message' then the 'broadcasting of that message' stops until that Form has finished whatever processing it will do in response to the 'message', then the 'broadcasting of that message' will continue to the next form, and so on.

See also Dangers of Asynchronism!

How the 'broadcasting' Works Under The Hood

The Method TFormsList.GFormsList.BroadcastFormMessage iterates all currently opened Forms and checks whether any have got a ProcessFormsMessage Method by using .NET Reflection. If a Form instance does have that Method then TFormsList.GFormsList.BroadcastFormMessage runs that Method (synchronously) through .NET Reflection.

Caveats

  • A Form can 'listen' to any number of different 'Form Messages'!
  • A Form can 'broadcast' any number of different 'Form Messages' in any situation!
  • A Form can both be the 'sender' of and the 'listener' for Form Messages at the same time!
    • Check out the file \csharp\ICT\Petra\Client\MPartner\Gui\PartnerEdit.ManualCode.cs to see code of a Form that does both!


How-to: 'Broadcasting' of a 'Form Message'

This is best illustrated with a concrete example from OpenPetras' Partner Edit screen. Code for the 'sending' of the FormsMessage can be found in the 'SaveChanges' Method in \csharp\ICT\Petra\Client\MPartner\Gui\PartnerEdit.ManualCode.cs, inside the if (ReturnValue) code block at the end of the Method.

  • A Variable 'TFormsMessage BroadcastMessage;' is declared at the beginning of that code block.
  • An instance is created: BroadcastMessage = new TFormsMessage(TFormsMessageClassEnum.mcNewPartnerSaved, FCallerContext);.
    • The Argument 'AMessageClass' is required and defines the type of 'Form Message' that is being created (and which will be 'broadcasted' later), the Argument 'AMessageContext' is optional (it allows the 'listening Form(s)' the decision whether they want to act on the 'Forms Message' in a given 'context', or not).
      • If you want to 'broadcast' new types of 'Form Messages' then you will need to
        • create a new value for TFormsMessageClassEnum;
        • add a new Method 'SetMessageDataXXX' that can accept typed 'Message Data'.
  • Typed 'Message Data' is set by the following code: BroadcastMessage.SetMessageDataPartner(FPartnerKey,SharedTypes.PartnerClassStringToEnum(FPartnerClass),PartnerShortNameForBroadcast,FMainDS.PPartner[0].StatusCode);
    • That typed 'Message Data' is specific to the type of 'Form Message' that was specified when the TFormsMessage instance was created. In this case it specifies data that pertains to a specific Partner.
    • If you want to 'broadcast' new types of 'Form Messages' then you will need to call your new Method 'SetMessageDataXXX' and supply the pertaining typed 'Message Data'
  • The Form Message is 'broadcasted' through a call to TFormsList.GFormsList.BroadcastFormMessage(BroadcastMessage);.
    • You will need to add Forms that 'Listen' to the 'Forms Message' if anything should happen when this 'broadcast' is issued!

How-to: 'Listening' for a 'Form Message' and Reacting on it

Turning any Form into a Form that 'listens' for 'Form Messages' is easy: you only need to declare a public Method ProcessFormsMessage(TFormsMessage AFormsMessage) in the *ManualCode.cs file of the Form and do whatever processing is needed in there. Enclose it in a C# Region called 'Forms Messaging Interface Implementation'.

The suggestion is to copy an existing ProcessFormsMessage Method from another Form and then adapt it to your needs. That way the approaches for processing of Form messages will be kept similar and the XML Comments will match across different ProcessFormsMessage Method implementations.

Behaviour at Run-time

The ProcessFormsMessage Method will get called when any Form 'broadcasts' any Form Message (they do that by calling TFormsList.GFormsList.BroadcastFormMessage)! That Method therefore ought to inspect the MessageClass of the passed in TFormsMessage instance to determine if that Method should react on that particular Form Message. If it should do that then the Method will need to 'unpack' the data contained in that instance by casting its MessageObject to the appropriate Interface.

Example from the Partner Find screens' ManualCode.cs file:

FormsMessagePartner = (IFormsMessagePartnerInterface)AFormsMessage.MessageObject;

Implementation detail: The ProcessFormsMessage Method ought to return true or false, depending whether it reacted to the Forms Message, or not. This is so that the 'broadcaster' can be informed about if/how many 'listeners' reacted to its message. (This count is not used yet - but it could be useful at some point.)

Dangers of Asynchronism

'Forms Messaging' is synchronous. The recommendation is to not try to make elements of it synchronous by starting a Thread for 'broadcasting' in the 'Sending Form' or by starting a Thread inside the 'listening' Method of any 'Listening Form(s)'.

The reason for this recommendation is that OpenPetra is limited to only one DB call at any given time (because of an ADO.NET constraint and RDBMS ADO.NET implementation constraints). If Forms Messaging is made to be asynchronous then there is a great likelihood that this could create 'clashes' of DB calls as they will get executed in an asynchronous way.

If the actions that the 'broadcasting' of a 'Forms Message' trigger will not cause DB calls then one could try to make either the 'broadcasting' or the 'listening' asynchronous - but beware that this introduces complexity that should well be warranted! To conserve memory resources and for reasons of less execution time you should not start (a) new Thread(s) but get (a) new Thread(s) from the .NET Thread Pool in this case.