[ Team LiB ] |
Recipe 7.6 Passing Specialized Parameters to and from an EventProblemYou have implemented Recipe 7.5, but you want to allow an event listener to be able to cancel an action that raised a particular event. For example, if a class attempts to create a new directory, you want to be able to verify that the directory is being created in the correct location. If the directory is not being created in the correct location (perhaps an insecure location), you want to be able to prevent the directory's creation. SolutionUse a class derived from EventArgs as the second parameter to the event handler. In this example, we use CancelEventArgs, a class defined in the .NET Framework Class Library. The Solution for Recipe 7.5 has been modified to include an event that is raised before the Create method of the DirectoryInfoNotify object actually creates a new path. An object of type CancelEventArgs is passed to this new event to allow any listeners of this event to cancel the Create method action. The modified class is shown here with the modifications highlighted: using System; using System.ComponentModel; using System.IO; public class DirectoryInfoNotify { public DirectoryInfoNotify(string path) { internalDirInfo = new DirectoryInfo(path); } private DirectoryInfo internalDirInfo = null; public event CancelEventHandler BeforeCreate; public event EventHandler AfterCreate; public event EventHandler AfterCreateSubDir; public event EventHandler AfterDelete; public event EventHandler AfterMoveTo; protected virtual void OnBeforeCreate(CancelEventArgs e) { if (BeforeCreate != null) { BeforeCreate(this, e); } } protected virtual void OnAfterCreate( ) { if (AfterCreate != null) { AfterCreate(this, new EventArgs( )); } } protected virtual void OnAfterCreateSubDir( ) { if (AfterCreateSubDir != null) { AfterCreateSubDir(this, new EventArgs( )); } } protected virtual void OnAfterDelete( ) { if (AfterDelete != null) { AfterDelete(this, new EventArgs( )); } } protected virtual void OnAfterMoveTo( ) { if (AfterMoveTo != null) { AfterMoveTo(this, new EventArgs( )); } } // Event firing members public void Create( ) { CancelEventArgs args = new CancelEventArgs(false); OnBeforeCreate(args); if (!args.Cancel) { internalDirInfo.Create( ); OnAfterCreate( ); } } public DirectoryInfoNotify CreateSubdirectory(string path) { DirectoryInfo subDirInfo = internalDirInfo.CreateSubdirectory(path); OnAfterCreateSubDir( ); return (new DirectoryInfoNotify(subDirInfo.FullName)); } public void Delete(bool recursive) { internalDirInfo.Delete(recursive); OnAfterDelete( ); } public void Delete( ) { internalDirInfo.Delete( ); OnAfterDelete( ); } public void MoveTo(string destDirName) { internalDirInfo.MoveTo(destDirName); OnAfterMoveTo( ); } // Non-Event firing members public virtual string FullName { get {return (internalDirInfo.FullName);} } public string Name { get {return (internalDirInfo.Name);} } public DirectoryInfoNotify Parent { get {return (new DirectoryInfoNotify(internalDirInfo.Parent.FullName));} } public DirectoryInfoNotify Root { get {return (new DirectoryInfoNotify(internalDirInfo.Root.FullName));} } public override string ToString( ) { return (internalDirInfo.ToString( )); } } The DirectoryInfoObserver class contains each of the event listeners and is shown here with the modifications highlighted: public class DirectoryInfoObserver { public DirectoryInfoObserver( ) {} public void Register(DirectoryInfoNotify dirInfo) { dirInfo.BeforeCreate += new CancelEventHandler(BeforeCreateListener); dirInfo.AfterCreate += new EventHandler(AfterCreateListener); dirInfo.AfterCreateSubDir += new EventHandler(AfterCreateSubDirListener); dirInfo.AfterMoveTo += new EventHandler(AfterMoveToListener); dirInfo.AfterDelete += new EventHandler(AfterDeleteListener); } public void UnRegister(DirectoryInfoNotify dirInfo) { dirInfo.BeforeCreate -= new CancelEventHandler(BeforeCreateListener); dirInfo.AfterCreate -= new EventHandler(AfterCreateListener); dirInfo.AfterCreateSubDir -= new EventHandler(AfterCreateSubDirListener); dirInfo.AfterMoveTo -= new EventHandler(AfterMoveToListener); dirInfo.AfterDelete -= new EventHandler(AfterDeleteListener); } public void BeforeCreateListener(object sender, CancelEventArgs e) { if (!e.Cancel) { if (!((DirectoryInfoNotify)sender).Root.FullName.Equals(@"d:\")) { e.Cancel = true; } else { Console.WriteLine( "Notified BEFORE creation of directory--sender: " + ((DirectoryInfoNotify)sender).FullName); } } } public void AfterCreateListener(object sender, EventArgs e) { Console.WriteLine("Notified after creation of directory--sender: " + ((DirectoryInfoNotify)sender).FullName); } public void AfterCreateSubDirListener(object sender, EventArgs e) { Console.WriteLine("Notified after creation of SUB-directory--sender: " + ((DirectoryInfoNotify)sender).FullName); } public void AfterMoveToListener(object sender, EventArgs e) { Console.WriteLine("Notified of directory move--sender: " + ((DirectoryInfoNotify)sender).FullName); } public void AfterDeleteListener(object sender, EventArgs e) { Console.WriteLine("Notified of directory deletion--sender: " + ((DirectoryInfoNotify)sender).FullName); } } DiscussionThe code for the modified DirectoryInfoNotify class contains a new event called BeforeCreate, which is raised from the OnBeforeCreate method. The OnBeforeCreate method is initially called by the Create method immediately before calling the Create method of the wrapped DirectoryInfo object. This setup will allow the event listener for the BeforeCreate event to decide whether the directory creation operation should be cancelled. The DirectoryInfoObserver class contains a new method, BeforeCreateListener, which listens for the BeforeCreate event. In addition, the Register and UnRegister methods of this class contain logic to add this event to the list of events that will be listened for on any registered DirectoryInfoNotify objects. The OnBeforeCreate method of the DirectoryinfoNotify class is passed a parameter of a type called CancelEventArgs, which exists in the .NET FCL. This type derives from EventArgs and contains one useful property, called Cancel. This property will be used by the AfterCreateListener method of the DirectoryInfoObserver class to determine whether the Create method should be cancelled before it has a chance to create a new directory. The CancelEventArgs object will be created in a DirectoryInfoNotify object, and when the BeforeCreate event is raised, the CancelEventArgs object will be passed to the BeforeCreateListener method on the DirectoryInfoObserver object. This method will then determine whether the creation of the directory should proceed or be cancelled. The determination is made by comparing the root drive of the directory to see if it is anything but the D:\ drive; if so, the operation is cancelled. This prevents any registered DirectoryInfoNotify objects from creating a directory on any drive other than the D:\ drive. If multiple DirectoryInfoObserver objects are listening to the BeforeCreate event and one of those observer objects decides to cancel the operation, the entire operation is cancelled. In other words, the final handler to be called gets the power of veto. The same CancelEventArgs object is referenced by each observer as well as each object that raised the event. This allows us to read the value of the Cancel property on the returned CancelEventArgs object in the Create method of the DirectoryInfoNotify object. If this property returns true, the operation cannot proceed; otherwise, the operation is permitted. You are not confined to merely passing EventArgs objects or any of its subclasses found in the FCL; you can subclass the EventArgs class to create a specialized EventArgs type. This would be beneficial if the object passed in to the sender parameter of the event does not include all of the information that the XxxListener methods will need. For example, you could create the following specialized EventArgs class: public class UserEventArgs : EventArgs { public UserEventArgs(string userName) { this.userName = userName; } private string userName = ""; public string UserName { get {return (userName);} } } This class passes the name of the logged-on user to the XxxListener methods to allow them to determine whether the operation should continue based on that user's privileges. This is just one example of creating a specialized EventArgs class. You can create others to pass in whatever information your listeners need. See AlsoSee Recipe 7.5; see the "Event" keyword, "EventHandler Delegate," and "Handling and Raising Events" topics in the MSDN documentation. |
[ Team LiB ] |