delphi3000.com - the free delphi knowledge platform
delphi3000.com - the free delphi knowledge platform
487 Users Online NOW
Have a look at your member-status

connecting people's knowledge


  - Recent ArticlesRSS feed for Recent Articles on delphi3000.com
  - List of All Articles
  - Top Viewed Articles
  - Articles (+Attachem.)
  - Articles Of Interest
  - Categories
  - Top Uploader
  - Search
  - Index

  - My Home
  - Submit an Article
  - My Articles
  - My Personal Data
  - My Bookmarks
  - Activities
  - Login/Logout

  - Sign Up
  - Why Sign Up
  - Newsletter

  - Press
  - Advertise

  - Contact
  - Feedback





Community
Borland
ClubeDelphi
Dr. Bob
UK-BUG
Delphi Meetings
Planeta Delphi



Loremo - the 1.5 liter car coming in 2009




Startblatt.de






Share this article with friendsShare this article with friends
Rate this articleRate this article - to keep the quality of delphi3000.com !
Comment this article or read through previous comments (8)


Reducing Source Code Complexity in your applicationGo to William Egge's websiteComponent available for this articleFormat this article printer-friendly!Bookmark function is only available for registered users!
Using a MessageCenter to link your application systems together
Product:
Delphi 3.x (or higher)
Category:
OO-related
Skill Level:
Scoring:
Last Update:
03/07/2002
Search Keys:
delphi delphi3000 article borland vcl code-snippet mediator messy linking notification message messagecenter center broadcast notify
Times Scored:
11
Visits:
5858
Uploader: William Egge
Company: Eggcentric
Reference: N/A
Component Download: http://www.eggcentric.com/download/MCDemo.zip
 
Question/Problem/Abstract:
Have you ever written an application where things have to know when things happen, such as when an object gets freed then you need to update some UI screen or remove some depency. Or in the case of a paint program where when a mode change requires a cursor change, buttons to enable or disable or push down... if something gets deleted then you have to do this and that etc... I have a solution that will keep your code clean of linking code.
Answer:



This uses Delphi's built in Messaging in TObject.
Whats in here:
Explaining on the concept.
Example implementation.
Source code of the MessageCenter is listed at the end.
Download full demo.

There are times when you write an application that turns into a linking nightmare when your system needs to react to certain conditions.  Examples are Mode changing in a paint program requires cursor changes, an object being updated needs to update some UI element or disable and enable controls, when an object gets freed you need to remove dependencies.  In other words there are side effects that you need to happen as a result of something changing in your application. Coding these side effects can produce some nasty code that is like a big spider web.

The solution to the problem is to use a "Message Center". I have created a easy to use MessageCenter class that uses the built in messaging capablity already built into TObject.  Source code is at the end of this artical.

1. Concept of the message center
The concept is simple, you have a central "hub" that receives maybe all actions that happen in your program.  Certain parts of your program need to change when these events happen.  Instead of hard coding these "reactions" into your code, you send the message of the event to the message center in a record structure.  Anything that needs to react or change based on the event is registered with and notified by the MessageCenter.

2. Example Implementation

This app is an image editor where you can have multiple images opened at once.
Each Image is opened in a Form class of TForm_ImageEdit.
A graphical list of buttons are listed at the top of the main form, there is one button per opened image and a picture of the image is drawn on the surface of the button.  Users can click the button and active the form for that image.

The rule of the system is
A button should be added when a new form is added.
The button should remove when the form is removed.
The button should push down when the editor form becomes active.

First define the MessageID and the record for the message.

const
  MID_ImageEdit = 14936;

type
  TMID_ImageEdit = packed record
    MessageID: Cardinal;  // This is required field for Dispatching
    Action: (aDestroyed, aActivated);
    ImageEdit: TForm_ImageEdit;
  end;

Then within the TForm_ImageEdit Broadcast the messages...

procedure TForm_ImageEdit.FormDestroy(Sender: TObject);
var
  M: TMID_ImageEdit;
begin
  with M do
  begin
    M.MessageID:= MID_ImageEdit;
    M.Action:= aClosed;
    M.ImageEdit:= Self;
  end;
  GetMessageCenter.BroadcastMessage(Self, M);
end;

procedure TForm_ImageEdit.FormActivate(Sender: TObject);
var
  M: TMID_ImageEdit;
begin
  with M do
  begin
    M.MessageID:= MID_ImageEdit;
    M.Action:= aActivated;
    M.ImageEdit:= Self;
  end;
  GetMessageCenter.BroadcastMessage(Self, M);
end;

Now to edit the main form

At some point in your main form when you create the Image Editor, add this code
after creation:

F:= TForm_ImageEdit.Create(Self);
// Listen to messages
GetMessageCenter.AttachListner(Self, F);

// Next few lines will add the button for the new form at the top of the main window.
.
.
.

This way the Main form will receive messages from the ImageEditor window.

So now Add this MessageHandler to your main form:
Create this method to receive messages of type MID_IMageEdit:

    procedure ImageEditorWindowChanged(var Msg: TMID_ImageEdit); message
        MID_ImageEdit;


And implement it in this way

procedure TForm_NMLDA.ImageEditorWindowChanged(var Msg: TMID_ImageEdit);
begin
  case Msg.Action of
    aDestroyed:
    begin
      ImageEditorClosed(Msg.ImageEdit);
      GetMessageCenter.DetachListner(Self, Msg.ImageEdit);
    end;
    aActivated: EditorFocused(Msg.ImageEdit);
  end;
end;

ImageEditorClosed method will remove the button from the main form
EditorFocused will push down the button associated with the ImageEditor.

-------------------------
Thats all, you have low coupling and you may attach as many listners as you like.

This concept has a lot of potential and it will make your complex apps very simple and maintainable.

Here is the code:

=================================
unit MessageCenter;
{
  William Egge public@eggcentric.com
  Created Feb - 28, 2002
  You can modify this code however you wish and use it in commercial apps.  But
    it would be cool if you told me if you decided to use this code in an app.

  The goal is to provide an easy way to handle notifications between objects
  in your system without messy coding.  The goal was to keep coding to a minimum
  to accomplish this. That is why I chose to use Delphi's built in
  Message dispatching.
  This unit/class is intended to be a central spot for messages to get dispatched,
    every object in the system can use the global GetMessageCenter function.
  You may also create your own isolated MessageCenter by creating your own
    instance of TMessageCenter.. for example if you had a large subsystem and
    you feel it would be more effecient to have its own message center.

  The goal is to capture messages from certain "Source" objects.

  Doc:
    procedure BroadcastMessage(MessageSource: TObject; var Message);
      The message "Message" will be sent to all objects who called AttachListner
      for the MessageSource.
      If no objects have ever called AttachListner then nothing will happen and
      the code will not blow up :-).  Notice that there is no registration for
      a MessageSource, this is because the MessageSource registration happens
      automatically when a listner registers itself for a sender.
      (keeping external code simpler)

    procedure AttachListner(Listner, MessageSource: TObject);
      This simply tells the MessageCenter that you want to receive messages from
      MessageSource.

    procedure DetachListner(Listner, MessageSource: TObject);
      This removes the Listner so it does not receive messages from MessageSource.

  Technique for usage with interfaces:
    If your program is interface based then its not possible to pass a
    MessageSource but it IS possible to pass an object listner if it is being
    done from within the object wanting to "listen" (using "self").
    To solve the problem of not being able to pass a MessageSource, you can
    add 2 methods to your Sender interface definition,
    AttachListner(Listner: TObject) and DetachListner(Listner: TObject).
    Internally within those methods your interfaced object can call the
    MessageCenter and pass its object pointer "Self".

  Info:
    Performance and speed were #1 so...

    MessageSources are sorted and are searched using a binary search so that
    a higher number of MessageSources should not really effect runtime performance.
    The only performance penalty for this is on adding a new MessageSource because
    it has to do an insert rather than an add, this causes all memory to be shifted
    to make room for the new element.  The benifit is fast message dispatching.

    There is no check for duplicate MesssageListners per Sender, this would have
    slowed things down and this coding is usefull only when you have bugs.  And
    hoping you prevent bugs, you do not have to pay for this penalty when your
    code has no bugs.
}

interface
uses
  Classes, SysUtils;

type
  TMessageCenter = class
  private
    FSenders: TList;
    FBroadcastBuffers: TList;
    function FindSenderList(Sender: TObject; var Index: Integer): TList;
  public
    constructor Create;
    destructor Destroy; override;
    procedure BroadcastMessage(MessageSource: TObject; var Message);
    procedure AttachListner(Listner, MessageSource: TObject);
    procedure DetachListner(Listner, MessageSource: TObject);
  end;

// Shared for the entire application
function GetMessageCenter: TMessageCenter;

implementation
var
  GMessageCenter: TMessageCenter;
  ShuttingDown: Boolean = False;

function GetMessageCenter: TMessageCenter;
begin
  if GMessageCenter = nil then
  begin
    if ShuttingDown then
      raise Exception.Create('Shutting down, do not call GetMessageCenter during shutdown.');
    GMessageCenter:= TMessageCenter.Create;
  end;

  Result:= GMessageCenter;
end;

{ TMessageCenter }

procedure TMessageCenter.AttachListner(Listner, MessageSource: TObject);
var
  L: TList;
  Index: Integer;
begin
  L:= FindSenderList(MessageSource, Index);
  if L = nil then
  begin
    L:= TList.Create;
    L.Add(MessageSource);
    L.Add(Listner);
    FSenders.Insert(Index, L);
  end
  else
    L.Add(Listner);
end;

procedure TMessageCenter.BroadcastMessage(MessageSource: TObject; var Message);
var
  L, Buffer: TList;
  I: Integer;
  Index: Integer;
  Obj: TObject;
begin
  L:= FindSenderList(MessageSource, Index);
  if L <> nil then
  begin
    // Use a buffer because objects may detach or add during the broadcast
    // Broadcast can be recursive.  Only broadcast to objects that existed
    // before the broadcast and not new added ones.  But do not broadcast to
    // objects that are deleted during a broadcast.
    Buffer:= TList.Create;
    try
      FBroadcastBuffers.Add(Buffer);
      try
        for I:= 0 to L.Count-1 do
          Buffer.Add(L[I]);

        // skip 1st element because it is the MessageSender
        for I:= 1 to Buffer.Count-1 do
        begin
          Obj:= Buffer[I];
          // Check for nil because items in the buffer are set to nil when they are removed
          if Obj <> nil then
            Obj.Dispatch(Message);
        end;
      finally
        FBroadcastBuffers.Delete(FBroadcastBuffers.Count-1);
      end;
    finally
      Buffer.Free;
    end;
  end;
end;

constructor TMessageCenter.Create;
begin
  inherited;
  FSenders:= TList.Create;
  FBroadcastBuffers:= TList.Create;
end;

destructor TMessageCenter.Destroy;
var
  I: Integer;
begin
  for I:= 0 to FSenders.Count-1 do
    TList(FSenders[I]).Free;
  FSenders.Free;
  FBroadcastBuffers.Free;
  inherited;
end;

procedure TMessageCenter.DetachListner(Listner, MessageSource: TObject);
var
  L: TList;
  I, J: Integer;
  Index: Integer;
begin
  L:= FindSenderList(MessageSource, Index);
  if L <> nil then
  begin
    for I:= L.Count-1 downto 1 do
      if L[I] = Listner then
        L.Delete(I);

    if L.Count = 1 then
    begin
      FSenders.Remove(L);
      L.Free;
    end;

    // Remove from Broadcast buffers
    for I:= 0 to FBroadcastBuffers.Count-1 do
    begin
      L:= FBroadcastBuffers[I];
      if L[0] = MessageSource then
        for J:= 1 to L.Count-1 do
          if L[J] = Listner then
            L[J]:= nil;
    end;
  end;
end;

function TMessageCenter.FindSenderList(Sender: TObject;
  var Index: Integer): TList;
  function ComparePointers(P1, P2: Pointer): Integer;
  begin
    if LongWord(P1) < LongWord(P2) then
      Result:= -1
    else if LongWord(P1) > LongWord(P2) then
      Result:= 1
    else
      Result:= 0;
  end;
var
  L, H, I, C: Integer;
begin
  Result:= nil;
  L:= 0;
  H:= FSenders.Count - 1;
  while L <= H do
  begin
    I:= (L + H) shr 1;
    C:= ComparePointers(TList(FSenders[I])[0], Sender);
    if C < 0 then
      L:= I + 1
    else
    begin
      H:= I - 1;
      if C = 0 then
      begin
        Result:= FSenders[I];
        L:= I;
      end;
    end;
  end;
  Index := L;
end;

initialization
finalization
  ShuttingDown:= True;
  FreeAndNil(GMessageCenter);

end.





Please rate this article!
Skill level:
BeginnerExpert

Useful:
No!Very!

Overall rating:
PoorExcellent



Comments to this article
Write a new comment
good idea
    Jorge Abel Ayala Marentes (Mar 4 2002 9:00PM)

you could also use the Observer pattern
Respond

RE: good idea
William Egge (Mar 7 2002 2:04AM)

Actually this is an implementation of an observer pattern except that it uses Delphi's built in message dispatching and also maintains the relationships. This means your objects can be free of maintaining observers and free of looping through them. Also any object can be "observed" without altering or needing them to decend from some base class.
Respond

Is it possible to get a Demo
    Akhila J (Mar 4 2002 4:29PM)

Is it possible to get a demo program to use the above mentioned
code.  It will be very easy to follow.

Thanks and Best Regards
Jayan Chandrasekhar
Respond

RE: Is it possible to get a Demo
William Egge (Mar 5 2002 7:02PM)

Yes, I would be happy to make one. What features would you like it to have. Are there any problems you would want me to approach and solve?
Respond

RE: RE: Is it possible to get a Demo
Jayan Chandrasekhar (Mar 5 2002 8:52PM)

Hi William,

I would like to have the demo on how to use MessageCenter in our applications.  A simple demo ( preferably non-database )
which shows all the features, methods and use of Message center will be be good enough.

Best Regards,

Jayan Chandrasekhar
Respond

RE: RE: RE: Is it possible to get a Demo
William Egge (Mar 7 2002 1:59AM)

I created a demo that shows all features. You can download it from the component link. If you have further questions, I can change the demo to include more examples of usage.
Respond

why not...
    EberSys (Mar 3 2002 7:30PM)

mmm... why not simply use Delphi TActionList, that's a very efficient way of keeping code centralized and has some other cool features (as enabling/disabling the control that they're associated to, assign the bitmap image to the associated control(s), etc)

I've noticed that many delphi programmers don't use this component, and is actually one that really helps to organize all the source code

salu2
EberSys
Respond

RE: why not...
Sean Dockery (Mar 15 2002 7:31PM)

The problem with using TActionList is that you have to take everything underneath TActionList with it.  Sometimes that isn't practical for systems which require a messaging with a small footprint.

For example, if you have business objects which use TActionList instead of the TMessageCenter component, you suddenly have to worry about linking or deploying half of the VCL with your code.

Respond














 
Sign up to consume product discounts for Bronze memberships !

read more


  Visit our Sponsor

 

  Community Ad of
D. Souchard
 
   














 







     
  Copyright © 2000 - 2007 delphi3000.com - All rights reserved. Terms of use. || Privacy
delphi3000.com is a service by bluestep.com IT-Services GmbH (Vienna)