Delegates and Single-cast and Multicast Events

Delegates, single-cast events, and multicast events are names for language constructs that you have been using for quite some time now.

Although there are some differences between C# and Delphi events, delegates in all three languages — C#, Delphi for Win32, and Delphi for .NET — are simply methods that are called in response to an event.

Single-cast events are events that call only one event handler in response to themselves. They are used by the VCL. Delphi for Win32 only supports single-cast events.

Multicast events are events that can call multiple event handlers. Windows.Forms controls use multicast events. Delphi for .NET language supports both single-cast and multicast events.

Delegates and Single-Cast Events in Delphi for Win32

A delegate in Delphi is a procedure of object like, for instance, the TNotifyEvent and TMouseEvent types, which looks like this:

type TNotifyEvent = procedure (Sender: TObject) of object; type TMouseEvent = procedure (Sender: TObject; Button: TMouseButton;   Shift: TShiftState; X, Y: Integer) of object; 

Single-cast events are created with the read and write reserved words:

FOnClick: TNotifyEvent; ... property OnClick: TNotifyEvent read FOnClick write FOnClick;

Now it's time to see how to create and use a custom delegate in Delphi for Win32. We are going to derive a new component from TGraphicControl, draw randomly colored rectangles on the component's surface, and fire a custom event of type TSelectColor when the user clicks on the component, as shown in Figure 30-3.

image from book
Figure 30-3: Using a custom delegate in Delphi for Win32

Besides the Sender parameter, which tells us the component that called the method, the TSelectColor delegate has another parameter, SelectedColor, which tells us what color is selected. Here's the TSelectColor delegate:

type   TSelectColor = procedure(Sender: TObject;     SelectedColor: TColor) of object;

The TGraphicControl descendant, the TColorMix component, needs to override two methods of the TGraphicControl to do its job: Paint and MouseUp. It needs to override the Paint method to paint on the component's surface and it needs to override the TGraphicControl's MouseUp method to fire the custom OnSelectColor event when the user releases a mouse button over the component.

Listing 30-12 shows the source of the TColorMix component and the source of the main form that uses the component.

Listing 30-12: Using a custom delegate in Delphi for Win32

image from book
unit Unit1; interface uses   Windows, Messages, SysUtils, Variants, Classes,   Graphics, Controls, Forms, Dialogs; type   TSelectColor = procedure(Sender: TObject;     SelectedColor: TColor) of object;   TColorMix = class(TGraphicControl)   private     FOnSelectColor: TSelectColor;   public     constructor Create(AOwner: TComponent); override;     procedure Paint; override;     procedure MouseUp(Button: TMouseButton;       Shift: TShiftState; X, Y: Integer); override;     property OnSelectColor: TSelectColor read       FOnSelectColor write FOnSelectColor;   end;   TMainForm = class(TForm)     procedure FormCreate(Sender: TObject);   private     { Private declarations }     Mix: TColorMix;   public     { Public declarations }     procedure SelectColorDelegate(Sender: TObject;       SelectedColor: TColor);   end; var   MainForm: TMainForm; implementation {$R *.dfm} constructor TColorMix.Create(AOwner: TComponent); begin   inherited;   Align := alClient;           // automatically fill the form end; { Paint 100 rectangles on the component's surface } procedure TColorMix.Paint; var   i, j: Integer;   rcWidth: Integer;   rcHeight: Integer; begin   inherited;   rcWidth := Width div 10;   rcHeight := Height div 10;   // paint random rects   for i := 0 to 10 do     for j := 0 to 10 do     begin       Canvas.Brush.Color := RGB(Random(255),         Random(255), Random(255));       Canvas.Rectangle(i * rcWidth, j * rcHeight,          (i + 1)* rcWidth, (j + 1) * rcHeight);     end; end; procedure TColorMix.MouseUp(Button: TMouseButton;   Shift: TShiftState; X, Y: Integer); var   selectedColor: TColor; begin   inherited;   if Assigned(FOnSelectColor) then   begin     selectedColor := Canvas.Pixels[X, Y];     // Fire the OnSelectColor event, and pass Self as the Sender     // parameter and the selected color as the SelectedColor param.     FOnSelectColor(Self, selectedColor);   end; end; // -------------------------------------------------------- procedure TMainForm.FormCreate(Sender: TObject); begin   // display the TColorMix component on the form   Mix := TColorMix.Create(Self);   Mix.Parent := Self;   Mix.OnSelectColor := SelectColorDelegate; end; // this is called by the OnSelectColor event procedure TMainForm.SelectColorDelegate(Sender: TObject;   SelectedColor: TColor); var   Frm: TForm; begin   // if user didn't click on border (black)   if SelectedColor <> clBlack then   begin     Frm := TForm.Create(Self);     try       Frm.Caption := 'Selected Color';       Frm.Position := poScreenCenter;       Frm.Color := SelectedColor;       Frm.ShowModal;     finally       Frm.Free;     end;   end; end; end.
image from book

Delegates and Multicast Events in Delphi for .NET

Multicast events can be implemented with great ease in Delphi. The only thing that you have to do is use the reserved words add and remove, instead of read and write:

property OnClick: TNotifyEvent    add FCustomOnClick remove FCustomOnClick;

The above OnClick event is from a custom button class, displayed in Listing 30-13. In order to create a multicast version of the OnClick event, you have to override the TButton's Click procedure and, instead of the inherited behavior, call FCustomOnClick. Figure 30-4 shows what happens when you click on a TMultiClickButton.

image from book
Figure 30-4: Using a TMultiClickButton

Listing 30-13: A TButton descendant with a multicast OnClick event

image from book
unit Unit1; interface uses   Windows, Messages, SysUtils, Variants, Classes, Graphics,   Controls, Forms, Dialogs, System.ComponentModel,   Borland.Vcl.StdCtrls, Borland.Vcl.XPMan; type   TMultiClickButton = class(TButton)   private     FCustomOnClick: TNotifyEvent;   public     procedure Click; override;   published     property OnClick: TNotifyEvent       add FCustomOnClick remove FCustomOnClick;   end;   TMainForm = class(TForm)     XPManifest: TXPManifest;     procedure FormCreate(Sender: TObject);   private     { Private declarations }     mcb: TMultiClickButton;   public     { Public declarations }     procedure ChangeCaption(Sender: TObject);     procedure ShowMyMessage(Sender: TObject);   end; var   MainForm: TMainForm; implementation {$R *.nfm} procedure TMultiClickButton.Click; begin   if Assigned(FCustomOnClick) then FCustomOnClick(Self); end; // ---------------------------------------------------------------- procedure TMainForm.FormCreate(Sender: TObject); begin   mcb := TMultiClickButton.Create(Self);   mcb.Parent := Self;   mcb.SetBounds(10, 10, 200, 25);   mcb.Caption := 'Click to call two handlers!';   // add TWO methods to the OnClick event   Include(mcb.OnClick, ChangeCaption);   Include(mcb.OnClick, ShowMyMessage); end; // another sample method procedure TMainForm.ChangeCaption(Sender: TObject); begin   Caption := 'Hello from method #1!'; end; // another sample method procedure TMainForm.ShowMyMessage(Sender: TObject); begin   MessageDlg('Hello from method #2!', mtInformation, [mbOK], 0); end; end.
image from book

Delegates and Events in C#

Delegates can be used to anonymously call methods and to implement events in C# classes. To create a delegate in C# (procedure of object equivalent), you have to use the following syntax:

public delegate ReturnType DelegateName(Parameters)

To use a delegate, you then have to create a method with the same return type and the same parameter list. When you have both the delegate and a method with the appropriate return type and parameter list, you can call the method through the delegate, using the following syntax:

delegate InstanceName = new delegate(MethodName);  delegate(MethodParameters);

Listing 30-14 illustrates how to use the above syntax to declare, instantiate, and call a method through a delegate.

Listing 30-14: Using delegates

image from book
using System; namespace Wordware.Delegates {    public delegate void MathOperation(int value);    class DelegateUser    {       public static int x = 0;       public static void Add(int value)       {         x += value;       }       public static void Subtract(int value)       {         x -= value;       }       public static void Show()       {         Console.WriteLine("x = {0}", x);       }        [STAThread]       static void Main(string[] args)       {         MathOperation a = new MathOperation(Add);         a(100);         Show();        // 100         MathOperation b = new MathOperation(Subtract);         b(50);         Show();        // 50         Console.ReadLine();       }    } }
image from book

Now that you know the syntax involved in the creation and usage of a delegate, it's time to see how to implement events in C# classes.

Once you've created a delegate, you declare an event using the reserved word event:

public event DelegateType EventName;

Events in C# are fired like events in Delphi: you first check whether an event has event handlers assigned to it and then call the handlers:

if(Event != null)   Event(ParameterList);

Finally, you need to assign an event handler to an event, using the following syntax:

Object.Event += new DelegateType(EventHandler);

Listing 30-15 shows the MyFileReader class that can read text files. The class has four events: OnNoFile (error), OnOpen, OnReadLine, and OnClose. The OnReadLine event is the most complex and the most important event because it allows other classes to do whatever they want with the strings read from the file. In this example, the DelegateUser class's OnReadLine event handler only reads the interface part of a Delphi unit and displays it in the console window. The OnReadLine event handler of the FormUser class shows how to use the same MyFileReader class and read the file into a ListBox.

Figure 30-5 shows how the event handlers of the DelegateUser class work.

image from book
Figure 30-5: Reading a text file using delegates and events

Listing 30-15: Delegates and events in C#

image from book
using System; using System.IO; using System.Windows.Forms; namespace Wordware.Delegates {    /* three different delegates */    public delegate void Notification();    public delegate void ErrorNotification(string file);    public delegate void BreakEvent(string line, ref bool Break);    class MyFileReader    {       /* events */       public event Notification OnOpen;       public event Notification OnClose;       public event ErrorNotification OnNoFile;       public event BreakEvent OnReadLine;       /* this method fires all four events */       public void ReadFile(string FileName)       {         if(!File.Exists(FileName) && (OnNoFile != null))         {            OnNoFile(FileName);         return;       }       StreamReader sr = new StreamReader(FileName);       try       {         if(OnOpen != null)            OnOpen();         string line;         bool Break = false;         while((line = sr.ReadLine()) != null)         {            if(OnReadLine != null) {               OnReadLine(line, ref Break);            }            /* if Break == true, the caller wants to               stop reading from the file */            if(Break == true) return;         }       }       finally       {         sr.Close();         if(OnClose != null)            OnClose();       }    } } // MyFileReader class // -------------------------------------------------------------- class DelegateUser {    public static void OpenHandler()    {       Console.WriteLine("File opened...");       Console.WriteLine("--------------");    }    public static void CloseHandler()    {       Console.WriteLine("--------------");       Console.WriteLine("File closed...");    }    public static void ErrorHandler(string file)    {       Console.WriteLine("Cannot load file \"{0}\".", file);    }    public static void ReadInterfaceOnly(string line,       ref bool Break)    {       if(line == "implementation")         Break = true;       else         Console.WriteLine(line);    }     [STAThread]    static void Main(string[] args)    {       string fileName = @"C:\My Components\StringsCache.pas";       MyFileReader mfr = new MyFileReader();       /* Passing handlers to all four events */       mfr.OnOpen += new Notification(OpenHandler);       mfr.OnClose += new Notification(CloseHandler);       mfr.OnNoFile += new ErrorNotification(ErrorHandler);       mfr.OnReadLine += new BreakEvent(ReadInterfaceOnly);       // displays the file and fires the events       mfr.ReadFile(fileName);       // display the file on a form by using a       // different OnReadLine event handler       FormUser fu = new FormUser();       fu.DisplayFile();       Console.ReadLine();    } } // -------------------------------------------------------------- class FormUser {    private string fileName = @"C:\My Components\StringsCache.pas";    private Form f;    private ListBox fileList;    private MyFileReader fr;    public void ReadFileToList(string line, ref bool Break)    {       fileList.Items.Add(line);       // after the unit's name has been displayed,       // remove the display unit name handler; this       // works because the OnOpen event first assigns       // the DisplayUnitName handler, and then the       // main code assigns the ReadFileToList handler       // to the OnReadLine event       fr.OnReadLine -= new BreakEvent(DisplayUnitName);    }    public void OpenHandler()    {       // add another handler to the OnReadLine event;       // this handler needs to execute only once and its       // purpose is to display the unit name on the form         fr.OnReadLine += new BreakEvent(DisplayUnitName);       }       public void DisplayUnitName(string line, ref bool Break)       {         f.Text = line;       }       // display the file in a listbox       public void DisplayFile()       {         f = new Form();         try         {            fileList = new ListBox();            try            {               f.Controls.Add(fileList);               fileList.Dock = DockStyle.Fill;               fr = new MyFileReader();               // assign the handler that reads the unit name               fr.OnOpen += new Notification(OpenHandler);               // assign the main handler that reads the               // file into the list box               fr.OnReadLine += new                  BreakEvent(ReadFileToList);               fr.ReadFile(fileName);               f.ShowDialog();            }            finally            {               fileList.Dispose();            }         }         finally         {            f.Dispose();         }       }    } }
image from book

Inside Delphi 2006
Inside Delphi 2006 (Wordware Delphi Developers Library)
ISBN: 1598220039
EAN: 2147483647
Year: 2004
Pages: 212
Authors: Ivan Hladni

Similar book on Amazon © 2008-2017.
If you may any questions please contact us: