Development system with methods providing visual form inheritance6002867Abstract A visual development system is described which allows a user to derive forms from other "ancestor" forms, inheriting their components, properties, and code as a starting point for one's own forms. During system operation, the user selects an "Inherit" menu choice for indicating to the system that the form to be created inherits from an existing ancestor form. Ancestor forms can be any forms already contained in an existing project or in an "object repository" provided by the system. Form inheritance allows the user to create a library of standard form templates, either within a single application or across an entire suite of applications. Any changes made to the ancestor form immediately appear in the descendant forms. Further, the use can customize each form type so derived while still retaining the ability to modify the standard form and have those changes reflected in the derived forms. Claims What is claimed is: Description COPYRIGHT NOTICE
______________________________________
Application
Creates a new project containing a form, a unit,
and a .DPR, or
provides a way for the user to select a template.
Automation Object
Creates a .pas file which contains an Automation
Object template.
Component Creates a new component using the Component
Expert.
Data Module
Creates a new Data Module.
DLL Creates a new DLL project.
Form Creates and adds a blank form to the current
project, or enables the
user to select a form template.
Text Creates a new ASCII text file.
Thread Object
Creates a new Thread Object.
Unit Creates and adds a new unit to the current project.
______________________________________
Upon the user selecting "Project1" tab 415 from the New Items dialog 410, the system displays Project page 423, as shown in FIG. 4C. If a project is open, the Project page 423 represents the currently active project. Further, the tab 415 displays the title for the project. The current project page contains all the forms of the project. From here, the user can create an inherited form from any existing project form. To add a new form to the current project, on the other hand, the user selects (i.e., "clicks") a "Form" icon 417, from "New" page 413 shown in FIG. 4B. As a result of this user operation, the system now displays a new form, Form2, as shown at 430 in FIG. 4D. In the system of the present invention, the user can derive forms from other "ancestor" forms, inheriting their components, properties, and code as a starting point for one's own forms. As shown in FIG. 4C, the user can select "Inherit" choice 427 for indicating to the system that the form to be created inherits from an existing ancestor form. Ancestor forms can be any forms already contained in an existing project or in the object repository. Form inheritance allows the user to create a library of standard form templates, either within a single application or across an entire suite of applications. Any changes made to the ancestor form immediately appear in the descendant forms. Further, the use can customize each form type so derived while still retaining the ability to modify the standard form and have those changes reflected in the derived forms. The "object repository" serves as a means for sharing and reusing forms and projects. In an exemplary embodiment, the repository itself is implemented as a text file containing references to forms, projects, and other objects. By adding forms, dialog boxes, and data modules to the object repository, the user makes these objects available to other projects. For example, the user can have all of his or her projects use the same about box by placing a reference to a particular about box in the object repository. When the user creates a new form, the user has the option of either starting with a blank form or starting from an already-designed form type. When the user starts from an already-designed type, he or she can either copy that form, inherit from it, or use it. When a form inherits from another form, the system creates a reference to the ancestor form and only generates additional code for adding components and event handlers. If several forms in a project are inherited from the same ancestor, they share the inherited code. FIG. 5A illustrates an ancestor form, Form1 (501), together with a descendant form, Form2 (511). Note that since Form2 inherits from Form1 it contains essentially identical properties and event handlers as those of Form1, except that Form2 includes a different name (as each object in the system is uniquely identified within a particular scope of objects). In FIG. 5A, Form2 has been moved by the user to a new location (i.e., change its position (e.g., top and left) properties), so that it can be viewed simultaneously with Form1. Also illustrated in the figure is the selection by the user of screen buttons 520 of Form1 and "dragging" these buttons, as indicated by direction arrow 521, to a new location 525. FIG. 5B illustrates completion of the drag operation--that is, at the point when the user drops the screen buttons 520 at the new location 525. Since Form2 (shown at 511a) is a descendant of Form1 (shown at 501a), the above-described movement of screen buttons or controls 520 on Form1 is, in turn, propagated to respective objects on Form2, screen buttons 530. Specifically, without further user intervention, screen buttons 530 automatically move from position 531 to new position 535, thereby completing propagation of changes from the ancestor to the descendant. Similarly, resizing ancestor Form1 (shown at 501b) to a new size, as indicated at 540 in FIG. 5C, immediately propagates a new size to descendant Form2 (shown at 511b), as indicated at 541. In this manner, the user can change other properties of ancestor Form1 and have those property changes propagate to all descendant forms without further intervention on the part of the user. Typically, at some point the user will want to further customize descendant forms. In FIG. 6A, for instance, the user has added a group box component 601 to the descendant Form2 (shown at 511c). Now the descendant is modified, with no effect on the ancestor (shown unchanged at 501b). Still further, the user can customize inherited components on the descendant form, without effect on the ancestor form's corresponding components. As shown particularly in FIG. 6B, buttons 530 (now 530a) are moved to a new location on Form2 (now shown as form 511d), as indicated by movement arrow 611. At the same time, however, corresponding screen buttons 520 on the ancestor Form1 (shown unchanged at 501b) are unaffected. In this instance, the user has "overridden" the property values inherited from the ancestor by the descendant form. When particular properties have been overridden with new values, further changes to those property values at the ancestor will not propagate to the descendant. If, for instance, screen buttons 520 are moved to a new location on Form1, such a movement will not effect the position of descendant screen buttons 530a, as propagation of the ancestor's property values is blocked by the overriding which occurred at the descendant. More particularly in this example, since movement of the buttons in the descendant was limited to horizontal movement, the "top" (vertical) position property of the descendants is still inherited from corresponding objects of the ancestor. FIG. 6C illustrates movement (as indicated by arrow 631) of ancestor screen buttons 520 (now 520a) to a new horizontal location (i.e., change "left" property). Note, however, that such a movement does not effect the horizontal position of the descendant screen buttons (530a). As also shown in FIG. 6C, the user has placed a new screen button, Button1 (541), on the descendant Form2, with no effect on the ancestor Form1. As illustrated in FIG. 6D, vertical movement of screen buttons 520 (shown as 520b) a certain distance upward (indicated by arrow 621) propagates new "top" property values to corresponding screen buttons 530 (shown at 530b), by a corresponding amount (movement arrow 631). Button1 (541) is unaffected, however. Finally, FIG. 6E illustrates that change of the font property of screen button 521 of Form1 propagates that property value change to corresponding descendant screen button 531, as that property value has not been overridden by the user. All told, property values of an ancestor propagate to descendants, so long as those corresponding property values of the descendant objects have not been overridden (i.e., customized by the user). For purposes of form inheritance, this propagation also applies to event handlers (i.e., propagation of an event handler from ancestor to descendant) in an automatic fashion, since event handlers (through the method pointer mechanism) can also be treated as properties. Form inheritance for event handlers (code) is perhaps best illustrated by way of example. FIG. 7A illustrates event handling code 710 (in code editor 701) for the "help" button (from screen buttons 520). As shown at 710, the event handler includes code for launching "WinHelp" (i.e., the standard help system provided by Microsoft Windows). As shown by the following class definition and instance declaration for Form1, Form1 (class) includes an event handler, HelpBtnClick.
______________________________________
TForm1 = class(TForm)
OKBtn: TButton;
CancelBtn: TButton;
HelpBtn: TButton;
procedure HelpBtnClick(sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1; { declare instance of class TForm1 }
______________________________________
Note that the class definition for Form2, shown at 720 in FIG. 7B, does not include an event handler declared for the help button. Since the Form2 class inherits from the Form1 class (as indicated at 723 in FIG. 7B), the Form2 class definition need not include a declaration for the event handler. In other words, the functionality is instead inherited from the ancestor class (Form1 class) which, in turn, implements the event handler. In a manner analogous to that shown for overriding property values, one can override event handlers of descendants. This is illustrated in FIG. 7C. By double clicking on the "help" button of Form2 (from screen buttons 530), the user invokes a new event handler for the descendant help button, as illustrated at 730. The system of the present invention introduces the key word "inherited" (731) into the event handler for indicating that it is to also perform the method steps implemented by the ancestor's corresponding event handler. In the example shown in FIG. 7C, the event handler 730 would first launch WinHelp, as a result of execution of the inherited handler, and then would proceed to execute any method steps added to the descendant event handler, such as the "beep" command shown at 733. As shown in FIG. 7D at 741, the ancestor's handler code can be moved to position it after the descendant's code, so that the ancestor's method steps execute after the method steps of the descendant or child. Finally, as illustrated in FIG. 7E at 751, the ancestor's handler code can be removed entirely (e.g., by either deleting or "commenting" it out). In such a case, only method steps for the descendant event handler execute. Here, the "inherited" reserved word instructs the system to call the event handler in the ancestor which has the same name. FIG. 7F illustrates an event handler 761 for the "OK" button of Form2. In this instance, an ancestor event handler has not yet been defined (i.e., Form1 does not include an event handler for its OK button). Nevertheless, the event handler 761 includes the "inherited" reserve word, shown at 771. This allows the descendant to automatically pickup any changes to corresponding event handlers of the ancestor, without having to recode the descendant. The foregoing forms were simplified so that the general operation of form inheritance of the present invention could be demonstrated without distraction from forms having numerous elements. In typical practice, however, users will tend to use form inheritance for creating and managing complicated forms. It is for this particular use that form inheritance greatly simplifies the task of application development. FIG. 8A illustrates a scenario where the user has created a standard corporate Form 810 having standard elements (e.g., company name plus bitmap logo). This represents a company-standard form for a hypothetical company, Global Dive Supply. Form 820 inherits from Form 810. Accordingly, Form 820 displays the company name and logo set forth in the ancestor Form 810. Additionally, Form 820 includes new components 821 added by the user. The components 821 provide standard searching/filtering techniques for the company data (e.g., customer list). The components 821 include all the logic (method steps) for performing the searching/filtering functionality. Form 830 and Form 835 inherit from Form 820. The two forms provide two different presentations of that data. Form 830 provides a single record view presentation; Form 835 provides a grid or table view presentation. Since these new components are being added to the descendant Form 820, they do not affect the ancestor Form 810. Since Form 820 inherits from Form 810, the Forms 830, 835 inherit indirectly from Form 810. Accordingly, the Forms 830, 835 inherit the company name and logo (from ancestor 810). Additionally, these forms inherit the components added by their immediate ancestor form--that is, components 821 of immediate ancestor Form 820. Each of the Forms 830, 835 can, in turn, add additional components without affecting the ancestors. As thus illustrated, form inheritance can continue to an arbitrary level, with each level adding its own particular objects for inheritance by subsequent levels. Although each form displays a separate bitmap image, the system stores only a single copy once in the program. That single copy is stored with the ancestor form (810). With each successive child, only the differential which is necessary is stored. FIG. 8B demonstrates the flexibility afforded by form inheritance for the corporate form. Suppose the company decides to change its name to "Universal Dive Supply." As illustrated by the base Form 810 (shown as 810a), all forms can be updated with the new company name by simply changing a single ancestor form. By the user simply editing this text object in the base form, all descendant forms are automatically updated, without further intervention on the part of the user. In other words, by simply editing the ancestor, the change is automatically propagated to all descendants. Internal Operation A. Overview FIG. 9 is a block diagram 900 illustrating internal operation of visual form inheritance in accordance with the present invention. Ancestor form 910 represents an existing form from which one or more descendant forms are derived. Descendant form 920, for instance, descends from ancestor form 910. Internal to the system, each of the forms 910, 920 represents a class managed by the system. In particular, ancestor form 910 represents an instance created from a form class (for the ancestor); descendant form 920 represents an instance from the form class (i.e., descendant class which inherits from the ancestor class). Conceptually, the relationship between a descendant and its ancestor is maintained via "update managers." The relationship between the descendant form 920 and the ancestor 910, for instance, is maintained by update manager 940. The relationship between the ancestor form 910 and its ancestor, in turn, is maintained by update manager 930. As the ancestor 910 is a base form (i.e., a base class, with no ancestor), the update manager 930 is, in effect, connected to or shorted to "ground" for the ancestor link (shown at 938), thereby indicating to the system that there is no further ancestor. Whenever a form is modified, a "modified" method fires at the update manager. When the ancestor form 910 is modified, for instance, a "modified" method is invoked in conjunction with the update manager 930. In a similar manner, when the descendant form 920 is modified, a corresponding "modified" method fires or is invoked in conjunction with the update manager 940. This mechanism is implemented through the system's forms designer. Whenever a form is modified, the forms designer, in turn, invokes an appropriate "modified" method for alerting the appropriate update manager that a form has been modified. A given update manager, in turn, invokes a corresponding "update" method for notifying the descendant's update manager that a modification has occurred. In other words, as part of invoking the "modified" method, the system invokes the associated "update" method. When a modified method of an object is invoked, therefore, the object filters "up" and updates "down." In particular, invocation of the modified method fires the "filter" method of the update manager which then proceeds to "filter" the relationship. Also, when the modified method of an ancestor object fires, it will invoke update methods for all of the descendants. When a "modify" method of a descendant fires, it will filter itself with respect to its ancestor and then update all of its children. Each update manager itself is an instance of an update object and may be viewed as being "owned" by a particular form. For example, the update manager instance 940 is owned by descendant form 920. In this manner, different instances of update managers are linked together in a manner which parallels the inheritance hierarchy of the forms. For every ancestor/descendant relationship which exists in the system, therefore, there exists an update manager for managing their state, in essence "sitting" between the two forms. Each update manager itself maintains pointers to an ancestor form and a descendant form. In the case of ancestor form 910, the corresponding update manager (manager 930) stores a null or Pascal "nil" pointer for the ancestor form. Additionally, each update manager stores a pointer to the next update manager, thereby linking together the update manager objects. At a high level, each update manager represents a generic mechanism. When ancestor form 910 is modified, for instance, the update manager 930 filters against the ancestor of form 910. Since no ancestor in fact exists (i.e., ancestor link or pointer is nil), no filtering is done. On the other hand, the change to the ancestor form 910 causes the update manager 930 to invoke its update linkage (935), for alerting update manager 940. In essence, the update manager 930 propagates the changes which occurred at the ancestor form 910 to all child (descendant) update managers. For the example shown in FIG. 9, this entails updating a single child update manager 940. The update to the update manager 940 can, in turn, propagate to other descendants, via this update linkage mechanism. In addition to the update relationship or linkage which exists between managers, another relationship is operating--a filter relationship--as indicated by filter 950. In the face of changes arriving from update manager 930 (as a result of changes from ancestor form 910), update manager 940 applies filter 950 for filtering property values arriving from the ancestor, based on the changes it sees have occurred in the descendant (i.e., property values which have been overridden at the descendant form 920). Suppose, for instance, that descendant form 910 has changed (i.e., overridden) the following properties: caption, left, top, and "OnClick" method. In this case, the filter 950 will "filter" changes from the ancestor update manager (930), so as to exclude these properties (i.e., properties which the descendant has overridden). In other words, the filter 950 excludes or filters those properties which have changed in the descendant, so that such properties are excluded from the update received from the ancestor update manager 930. In this fashion, properties of the ancestor which have been overridden by the descendant are not propagated from the update manager 930 to the update manager 940. In an exemplary embodiment, a filter (i.e., list of properties) in effect goes up one level, but it does not propagate up several levels. An update, on the other hand, can propagate property changes to several levels down (to the extent that any given property has not been filtered at a particular level). Recall that method pointers are also treated as properties. Accordingly, modification of an event handler (i.e., attaching code or modifying code at a particular form) is treated as if a particular property has changed. For the filter 950, for instance, the code which was added to the "OnClick" event handler for the descendant form 920 serves as an override for that method. Here, the OnClick event handler forms one of the properties listed at the filter 950. Accordingly, the corresponding OnClick event handler from the ancestor form 910 will not execute at the descendant form 920, as it has instead been overridden (and, in effect, filtered as a "method pointer" property). Since method pointers are treated as property values in the system of the present invention, changes to event handlers can be treated in a fairly generic manner--that is, in a similar manner to how changes to properties are handled. Because method pointers are instance specific (i.e., according to particular methods in memory on a per instance basis), however, some additional treatment is required. It is otherwise treated as if it were a simple property value. In a preferred embodiment, as a method pointer is propagated, the system strips off the instance (i.e., "this" or "self" pointer) of the ancestor and replaces it with the instance of the propagated-to descendant form. This treatment of a method pointer is illustrated in FIG. 10. The figure illustrates a method pointer 1000 which comprises an instance ("this") pointer 1010 together with a code or function pointer 1020. When a method pointer, such as method pointer 1000, is propagated, the instance or "this" pointer is changed to point to the current form (object). The function or code pointer, on the other hand, is static and, thus, need not be changed. As indicated in FIG. 9, each update manager is associated with an update object tree--one update object for every component on the corresponding form. In FIG. 9, for instance, the update manager 930 is associated with update object tree 937. A given update object in essence "sits" between its component (of its form) and the corresponding component of the ancestor. The update objects themselves comprise one update object for the form together with one update object for each component on that form. An update manager, on the other hand, can be viewed as a manager of a collection of update objects. Changes which are propagated to an update manager are, in turn, delegated to the update objects controlled by that manager (except when delegating downward towards children). Each update object itself maintains a list of properties (including those which are filtered) for its component. When "update" or "filter" is invoked, the corresponding update manager delegates the processing to its update objects which are invoked recursively for carrying out the requisite processing. All told, each update object maintains information about what has changed (and what has not changed) for its corresponding component. With this design, the task of saving a descendant is simplified: only those properties which have changed (i.e., changed relative to the ancestor) need be saved. B. Implementation 1. Update Manager In an exemplary embodiment, the update manager may be constructed from an update manager class, as follows.
______________________________________
{ Update manager }
{ This is the public interface for the update manager }
TUpdateManager = class
private
FComponentUpdate: TUpdateObject;
FChildList: TList;
FComponent, FAncestor: TComponent;
FParent: TUpdateManager;
FMode: TUpdateMode;
FUpdateList: TList;
FOnUpdating: TNotifyEvent;
FOnUpdate: TNotifyEvent;
procedure Filter;
function GetHasDescendents: Boolean;
function GetIsDescendent: Boolean;
procedure UpdateChildren;
procedure SetChildAncestor(Child: TComponent);
procedure Updating(Component: TComponent);
public
constructor Create (AComponent, AAncestor: TComponent;
AAncestorManager: TUpdateManager);
destructor Destroy; override;
// Can the property be reverted (e.g. if it is already the parent's
// value or it is object and the object's properties need to be
// reverted individually).
function CanRevert(Instance: TPersistent; PropInfo: PPropInfo):
Boolean;
// Notification transmitted by VCL and forwarded by the form designer
// from the form. Lets the update object know when components are
// deleted so it doesn't hold on to dead pointers.
procedure Notification (AComponent: TComponent;
Operation: TOperation);
// Kicks off the update process. When a form is changed it calls
// Modified which will remove the properties that changed from the list
// of properties to copy from its ancestor when it changes and tell
// descendents copy the changed properties.
procedure Modified;
// Utility to see if a particular name is used by a descendent to avoid
// creating a naming conflict
function NameExists(const Name: string): Boolean;
// Revert a given property to its ancestor's value.
procedure Revert(Instance: TPersistent; PropInfo: PPropInfo);
// Forces the form to sync with the ancestor. This is called
// automatically when the ancestor is modified but should be done prior
// to streaming the descendent to ensure the stream written is
// accurate.
procedure Update;
// Notify any interested party when the updating is happening.
property OnUpdating: TNotifyEvent read FOnUpdating write
FOnUpdating property OnUpdate: TNotifyEvent read FOnUpdate write
FOnUpdate;
// The root component (i.e. form) that is being updated.
property Component: TComponent read FComponent;
// The ancestor component (i.e. form) for the Component
property Ancestor: TComponent read FAncestor;
// True if component has descendents loaded.
property HasDescendents: Boolean read GetHasDescendents;
// True if this component has an ancestor.
property IsDescendent: Boolean read GetIsDescendent;
end;
______________________________________
As shown, the private data members of the class are as follows. The first member, TUpdateObject, is the update object for the form. It represents the root of the tree of update objects. The second data member, FChildList, is a list of child update managers The next two data members, FComponent and FAncestor, represent the descendant and ancestor components, respectively. The FParent data member refers to the parent update object (which may be set to nil). FMode, the next parameter, indicates an update mode; it is used for internal housekeeping (e.g., for indicating "updating" or "filtering"). FUpdateList indicates those components which are being updated; accordingly, it is employed for internal housekeeping during updating operations. The next two data members, FOnUpdating and FOnUpdate, represent notification events. This provides the mechanism whereby the form designer can be notified that updating is occurring. The private procedures and functions are as follows. The "Filter" procedure filters an ancestor's properties, as previously described. The next two functions, GetHasDescendants and GetIsDescendant, represent "getter" functions--functions which return values stored by the class. The operation of each is self-evident from the respective function names. UpdateChildren procedure or method serves to propagate an update call to all children. The next procedure, SetChildAncestor, is employed as a callback procedure or method for allowing the system to easily track what children get created when a component is created. The last private procedure, Updating, is an internal housekeeping method which is invoked on a component which is about to be updated. This call adds a component to the update list and invokes the components own updating method. The public methods--the external interface--function as follows. The primary method or procedure which is invoked is Modified. This is invoked when the user has made some modification in the forms designer, such as a change to a component's property (e.g., moved the component to a new location). The Modified method is also called when a component is first created. Before a component can actually be created, the update manager confirms that the component being created has a unique name. The name is to be unique not only in the component but also all the descendants of that component. Therefore, the NameExists function confirms that a particular name does not lead to naming conflicts. In a corresponding manner, when a component is deleted, the Notification procedure is invoked for informing the update manager (so that pointers which dereference through the deleted component are no longer employed). For a descendant whose property value has been overridden, the system provides (for certain property types) the ability to "revert" to the property value of the ancestor. For managing this process, the class defines two methods: Revert and CanRevert. The latter returns a Boolean value indicating whether a property can be reverted. The former reverts a given property to its ancestor's respective value. Finally, the Update procedure or method is declared public, for allowing the system to force the form to synchronize with its ancestor. This is invoked, for instance, when a form is saved, for ensuring that the form has a valid copy of itself and that all ancestor values have been correctly propagated. The remaining public members are properties which indicate the internal state of the update manager. These provide a mechanism where properties of the update manager can be easily read. 2. Pairing and Pairings Before describing the actual Update method for the update manager, it is first helpful to review "pairing" and "pairings" which are represented by class definitions TPairing and TPairings, respectively. A pairing is one ancestor object and one descendent object, where the descendent represents the ansestor object in the descendent. This needs to be discovered since the objects do not point to their ancestors. If an ancestor is found with no corresponding descendent then the ancestor object is new and needs to be created on the descendent. If a descendent is found with no ancestor (and it is marked as having been created from an ancestor) then it was deleted in the ancestor. All other pairings are ignored. Pairings are created for each ancestor/dependent relation which arises. In the previous example of Form1 and Form2, for instance, a pairing exists representing the pairing of Form1 and Form2. Likewise, a pairing exists for related buttons as well. When Update is called, the system creates pairings for respective components of the ancestor and the descendant. For the pairing object, a component was created at the ancestor when a pairing object stores an ancestor value (which is not nil) but a component value which is nil. In other words, this represents a pairing having an ancestor but no descendant. During operation, the SetAncestor method of the TPairing class is invoked. The method is implemented as follows.
______________________________________
procedure TPairing.SetAncestor(Value: TComponent);
begin
FAncestor := Value;
Children.AddChildren(Value, True);
end;
______________________________________
As shown, the method invokes an AddChildren method call. The AddChildren method call adds all the children pairings. The TPairing class also does the work of actually deleting the components which the user has deleted. In particular, this is performed by a DeleteDeleted method, which may be constructed as follows.
______________________________________
procedure TPairing.DeleteDeleted;
var
I: Integer;
begin
if not Assigned(Ancestor) then
begin
Component.Free;
FComponent := nil;
Children.Clear;
end
else
for I := 0 to Children.Count - 1 do
Children [I]. DeleteDeleted;
end;
______________________________________
In operation, the method first tests whether an ancestor exists for the component. If not, the component is simply deleted; otherwise, the component's children are first deleted (by calling DeleteDeleted method on those children). The TPairing class is also responsible for creating an update object for valid pairings. The method may be constructed as follows.
______________________________________
procedure TPairing.CreateUpdateObjects (ParentObject: TUpdateObject);
var
I: Integer;
CurrentObject: TUpdateObject;
begin
CurrentObject := nil;
if New then
CurrentObject := TUpdateObject.Create (ParentObject,
Component, Ancestor,
nil, True)
else if Children.HasNew then
CurrentObject := ParentObject. FindChild (Component);
if Children.HasNew then
for I := 0 to Children.Count - 1 do
Children[I].CreateUpdateObjects(CurrentObject);
end;
______________________________________
In the instance of a new object, the method creates an update object. The process is repeated for children of that object, by invoking the CreateUpdateObjects method recursively for those children. 3. Update Manager's Update Method Returning to the description of the update manager, the Update method may be implemented as follows.
______________________________________
procedure TUpdateManager.Update;
var
Pairing: TPairing;
Stream: TStream;
Reader: TReader;
Writer: TWriter;
{ Nested methods - - - removed for clarity of description }
begin
if FMode <>umNone then Exit;
FMode := umUpdate;
FUpdateList.Clear;
try
try
if Assigned (FOnUpdating) then FOnUpdating (Self);
try
if Assigned(FAncestor) then
begin
Pairing := TPairing.Create(nil); { root pairing }
try
Pairing.Component := FComponent;
Pairing.Ancestor := .FAncestor;
DeleteDeleted;
InsertInserted;
CreateUpdateObjects;
finally
Pairing.Free;
end;
FComponentUpdate.Update (Self);
end;
UpdateChildren; { All children also update }
finally
if Assigned(FOnUpdate) then FOnUpdate (Self);
end;
finally
FMode := umNone;
end;
finally
CallUpdateds;
end;
end;
______________________________________
At the outset, the method tests a mode flag, for preventing reentry. If the method does not terminate, the flag is set to "update." Next, the method clears the update list. The "OnUpdating" event is triggered. Actual updating occurs only if an ancestor exists from which to update. A root pairing is created by invoking the Create method of the TPairing class, passing a nil value (i.e., no parent). This is followed by passing FComponent and FAncestor to the pairing. This is followed by deleting the deleted (DeleteDeleted) and inserting the inserted (InsertInserted). These two calls are in turn followed by the CreateUpdateObjects call, which creates any update objects which need to be created. Finally at this point, the pairing structure is freed. The method now updates children, which actually performs the update work. The InsertInserted method itself may be implemented as follows.
______________________________________
procedure InsertInserted;
begin
Stream := nil;
Reader := nil;
Writer := nil;
try
DoInsertInserted(TComponent (FComponentUpdate.FObject),
Pairing);
FixupComponents;
finally
EndStream;
end;
end;
______________________________________
As shown, the method calls into DoInsertInserted, which is recursive in nature. It may be constructed as follows.
______________________________________
procedure DoInsertInserted (AParent: TComponent; Pairing: TPairing);
var
I: Integer;
begin
with Pairing do
begin
if Assigned(Ancestor) and not Assigned(Component) then
begin
New := True;
Component := CreateFromAncestor (Aparent, Ancestor);
end;
for I := 0 to Children.Count - 1 do
DoInsertInserted(Component, Children[I]);
end;
end;
______________________________________
The DoInsertInserted method traverses the pairing for determining which components need to be created. If an ancestor exists but not a component, then the pairing is new. In such a case, the method creates the component from the ancestor. The specific method call which performs this task, CreateFrom Ancestor, may be constructed as follows.
______________________________________
function CreateFromAncestor(Parent, Ancestor: TComponent):
TComponent; var
ComponentOwner: TComponent;
begin
BeginStream;
Writer.Position := 0
Writer.WriteComponent(Ancestor);
Writer.FlushBuffer;
Reader.Position := 0
Reader.FlushBuffer;
Reader.Parent := THack(Parent).GetChildParent;
Updating(Parent);
ComponentOwner := THack(Parent).GetChildOwner;
if not Assigned(ComponentOwner) then
ComponentOwner := FComponent;
Result := TComponentClass(Ancestor.ClassType).
Create(ComponentOwner); try
THack(Result).SetAncestor(True);
Reader.ReadComponent(Result);
THack(Result).GetChildren(SetChildAncestor);
except
Result.Free;
raise;
end;
end;
______________________________________
At the outset, the method calls BeginStream, for ensuring that a stream exists together with a "reader" and a "writer." Both the reader and the writer are provided by the Visual Component Library (VCL) of Boriand's Delphi. To create a new component, the system streams out the ancestor to a writer and then streaming it back in using a reader. In other words, a persistent image is streamed out from the ancestor, followed by streaming in a persistent image which now serves as the descendant. As shown, the CreateFromAncestor method invokes two TComponent methods: the GetChildParent and GetChildOwner methods. In effect, these provide a mechanism for asking the component to indicate its parent and owner (properties). In turn, the method then proceeds to create the component using these values. As this occurs, the method determines the component's children (by calling a GetChildren method), and updates the pairings accordingly. When the CreateFromAncestor method completes, the system returns to the DoInsertInserted method. At this point, the method is invoked recursively for any children. The result is that all new components which were added to any ancestor are correctly propagated to descendants. The Update method of the update manager concludes by invoking two final methods: FixupComponents and EndStream. Both may be constructed as follows.
______________________________________
procedure FixupComponents;
begin
if Reader <>nil then Reader.FixupReferences;
end;
procedure EndStream;
begin
if Reader <>nil then Reader.EndReferences;
Reader.Free;
Writer.Free;
Stream.Free;
end;
______________________________________
The FixupComponents method is an internal housekeeping routine which makes sure that all the pointers are fixed up (which were read in with the stream). Finally, the EndStream method destroys all streams which were created in the process (of update). 4. Update Object (a) Class Definition An update object, which maintains synchronization between two objects, is created from an TUpdateObject class. In an exemplary embodiment, this class may be constructed as follows.
______________________________________
{ TUpdateObject }
{ An update object maintains two objects in sync.
It first compares both objects properties.
Properties that are the same are maintained in a
list for later updating. When the ancestor changes the Update method
is called and all properties in the list that have changed in the
ancestor
are copied to the descendent. If the descendent changes, Filter is
called deleting any properties that are no longer the same as the
ancestors since it would no longer inherit the value from the ancestor.
TUpdateObject = class
private
FOwner: TUpdateObject;
FObject, FAncestor: TPersistent;
FObjectOwner, FAncestorOwner: TComponent;
FPropList: TList;
FChildList: TList;
FUpdateFiler: TUpdateFiler;
FIsComponent: Boolean;
FIsCollection: Boolean;
FUpdateCollection: Boolean;
FUpdateOrder: Boolean;
FOrder: Integer;
FPropInfo: PPropInfo;
FCompare: Boolean;
constructor Create(AOwner: TUpdateObject; AObject, AAncestor:
TPersistent;
APropInfo: PPropInfo; Compare: Boolean);
destructor Destroy; override;
procedure AddChild(Component: TComponent);
function CanRevert(Instance: TPersistent; PropInfo: PPropInfo;
var Continue: Boolean) : Boolean;
function GetAncestorMethod(PropInfo: PPropInfo): TMethod;
function GetAncestorPointer(Value: Pointer) : Pointer;
function GetAncestorReference(PropInfo: PPropInfo): Pointer;
function FindChild(Component: TComponent): TUpdateObject;
function FindChildProp (APropInfo: PPropInfo): TUpdateObject;
procedure ComponentDelete(AComponent: TComponent);
procedure Filter;
procedure FilterOrder;
function Revert(Instance: TPersistent; PropInfo: PPropInfo): Boolean;
procedure Update (UpdateManager: TUpdateManager);
procedure ValidateObjects;
end;
______________________________________
The data members are as follows. FOwner is the owning update object. For the root, this is set to nil. For all children objects, the value points to the immediate ancestor or parent update object. The next two data members, FObject and FAncestor, correspond to the object and ancestor (components). The next two data members, FObjectOwner and FAncestorOwner, represent the FObject and FAncestor (components) for the owner. The FPropList data member stores the property list. The FChildList is a list of all children. The FUpdateFiler is a utility object used for updating. The next two data members, FIsComponent and FIsCollection, are simple Boolean members indicating whether the object is a component or a "collection," respectively. FUpdateCollection and FUpdateOrder, similarly, are Booleans indicating whether the system should update the collection (if the object is a collection) and update order (i.e., creation order), respectively. FOrder is an integer data member indicating the creation order for the object. The FPropInfo data member references property information. For a font object, for instance, the FPropInfo data member allows the system to determine property information for the font. Finally, the FCompare data member is a Boolean indicating whether the system should perform comparison operations (during creation of an object). After declaring a constructor (Create) and destructor (Destroy), the class defines the following methods. AddChild is a housekeeping method used during creation for adding children. The next method, CanRevert, provides the implementation for the "can revert" feature previously described. The next three methods, GetAncestorMethod, GetAncestorPointer, and GetAncestorReference, are internal housekeeping methods which perform the previously-described method pointer fixup. The FindChild method finds the child update object for the component (associated with the particular update object instance). The FindChildProp method finds an update object for a PropInfo data member. The ComponentDelete method is employed by notification methods for ensuring that invalid pointers are not employed. The Filter method performs the filtering (i.e., filtering of properties from the ancestor). The FilterOrder method determines whether a descendant has overridden the (creation) order. The Revert method provides the actual implementation for reverting back (to an ancestor's state). The Update method performs the actual work of updating; it represents the work horse routine for the update manager. Finally, the ValidateObjects method is an internal housekeeping method which insures that PropInfo data members point to real objects. (b) Create Method The following description will focus on those methods which are helpful for understanding operation of an update object. The Create method, which creates all descendant update objects, may be implemented as follows.
______________________________________
constructor TUpdateObject.Create (AOwner: TUpdateObject; AObject,
AAncestor: TPersistent; APropInfo: PPropInfo; Compare: Boolean);
procedure AddNestedObjects;
var
PropInfo: PPropInfo;
ORef, ARef: TObject;
I: Integer;
begin
for I := 0 to FPropList.Count - 1 do
begin
PropInfo := FPropList[I];
if PropInfo.PropType.Kind = tkClass then
begin
ORef := TObject (GetOrdProp (AObject, PropInfo));
if (ORef <>nil) and not (ORef is TComponent) and
(ORef is TPersistent) then
begin
ARef := TObject (GetOrdProp(AAncestor, PropInfo));
TUpdateObject.Create(Self, TPersistent (ORef),
TPersistent (ARef),
PropInfo, Compare);
end;
end;
end;
end;
begin
FObject := AObject;
FAncestor := AAncestor;
FPropList := TList.Create;
FChildList := TList.Create;
FOwner := AOwner;
FCompare := Compare;
FUpdateOrder := True;
FUpdateCollection := True;
FPropInfo := APropInfo;
if FOwner <>nil then FOwner.FChildList.Add(Self);
FPropList.Count := GetTypeData(AObject.ClassInfo) A .PropCount;
GetPropInfos (AObject.ClassInfo, PPropList(FPropList.List));
FIsComponent := AObject is TComponent;
FIsCollection := AObject is TCollection;
if FIsComponent then
begin
FObjectOwner := TComponent(FObject).Owner;
if FObjectOwner = nil then FObjectOwner :=
TComponent(FObject);
FAncestorOwner := TComponent(FAncestor).Owner;
if FAncestorOwner = nil then FAncestorOwner :=
TComponent(FAncestor);
end;
FUpdateFiler := TUpdateFiler.Create(Self, Compare);
AddNestedObjects;
Filter;
if FIsComponent then THack (FObject).GetChildren (AddChild);
FilterOrder;
FCompare := True;
end;
______________________________________
An update object may have two types of children: (1) pointers to components that it owns and (2) pointers to nested properties (e.g., fonts, pens, brushes, and the like). The Create method or constructor creates the various lists and fills in the class data members described above. The constructor also determines whether the object is a component or a collection. If the object is a component, the constructor will determine the owner object and owner ancestor object. The constructor adds nested objects, by invoking a nested procedure, AddNestedObjects, shown above. Finally, the constructor filters itself (based on the object) and then adds any children. The order of the children is filtered, by invoking FilterOrder. The call to GetChildren creates an update object for each child. (c) Filter Method The filter method may be implemented as follows.
______________________________________
procedure TUpdateObject.Filter;
var
I: Integer;
PropInfo: PPropInfo;
begin
ValidateObjects;
for I := FPropList.Count - 1 downto 0 do
begin
PropInfo := FPropList[I];
if (PropInfo .GetProc <> nil) and (PropInfo .SetProc <> nil) then
if FCompare and IsStoredProp (FAncestor, PropInfo) then
.sup. case PropInfo .PropType .Kind of
tkInteger, tkChar, tkWChar, tkEnumeration, tkSet:
if GetOrdProp (FObject, PropInfo)
= GetOrdProp (FAncestor, PropInfo) then
Continue;
tkFloat:
if GetFloatProp (FObject, PropInfo)
= GetFloatProp (FAncestor, PropInfo) then
Continue;
tkString, tkLString:
if GetStrProp (FObject, PropInfo)
= GetStrProp (FAncestor, PropInfo) then
Continue;
tkMethod:
if MethodsEqual (GetMethodProp (FObject, PropInfo),
GetAncestorMethod(PropInfo)) then
Continue;
tkClass:
if (FindChildProp (PropInfo) <> nil) or
(Pointer(GetOrdProp(FObject, PropInfo))
= GetAncestorReference(PropInfo)) then
Continue;
.sup. end
else
.sup. if PropInfo .PropType .Kind in [tkInteger, tkChar, tkWChar,
tkEnumeration, tkSet, tkFloat, tkString, tkLString, tkMethod,
tkClass] then
Continue;
FPropList.Delete (I);
end;
FilterOrder;
if FIsCollection and FUpdateCollection and FCompare then
FUpdateCollection := CollectionsEqual (TCollection(FObject),
TCollection (FAncestor));
if FCompare then FUpdateFiler.Filter;
for I := 0 to FChildList.Count - 1 do
TUpdateObject (FChildList[I]).Filter;
end;
______________________________________
At the outset, the Filter method validates its objects, for making sure the method is referencing real objects. Using the runtime type information (RTTI), the method iterates through all of the properties and compares the property of the ancestor with the current component of the update object. If the property values differ, the method deletes the property from the property list. The method then filters the order. If the object is a collection, the method will perform a collection comparison. Thereafter, the method invokes the Filter method for each of its children. (d) Update Method The Update method is similar in structure is similar to that of the Filter method. The Filter method will, however, delete properties, whereas the update method will copy them. In an exemplary embodiment, the Update method may be constructed as follows.
______________________________________
procedure TUpdateObject.Update(UpdateManager: TUpdateManager);
var
I: Integer;
PropInfo: PPropInfo;
IValue: Integer;
PValue: Pointer;
FValue: Extended;
SValue: string;
Child: TUpdateObject;
procedure UpdateOrder;
var
I, J: Integer;
ChildObjects: TChildUpdateObjects;
Descendent, Ancestor: TUpdateObject;
begin
if FIsComponent then
begin
ChildObjects := TChildUpdateObjects.Create (Self,
TComponent (FObject),
TComponent (FAncestor));
try
J := 0;
for I := 0 to ChildObjects.DescendentCount - 1 do
begin
Descendent := ChildObjects.Descendents [I];
if Descendent <> nil then
begin
Ancestor := ChildObjects.Ancestors[J];
if Ancestor <> Descendent then
Ancestor.FOrder := I
else
Ancestor.FOrder := -1;
Inc (J);
end;
end;
for I := 0 to ChildObjects.AncestorCount - 1 do
begin
Ancestor := ChildObjects.Ancestors[I];
if (Ancestor <> nil) and Ancestor.FUpdateOrder
and (Ancestor.FOrder <> -1) then
THack(FObject).SetChildOrder (TComponent
(Ancestor.FObject), Ancestor.FOrder);
end;
finally
ChildObjects.Free;
end;
end;
end;
begin
ValidateObjects;
if FIsComponent then UpdateManager.Updating(TComponent(FObject));
for I := 0 to FPropList.Count - 1 do
begin
PropInfo := FPropList[I];
if IsStoredProp(FAncestor, PropInfo) then
case PropInfo .PropType .Kind of
tkInteger, tkChar, tkWChar, tkEnumeration, tkSet:
begin
IValue := GetOrdProp(FAncestor, PropInfo);
if IValue <> GetOrdProp(FObject, PropInfo) then
SetOrdProp (FObject, PropInfo, IValue);
end;
tkFloat:
begin
FValue := GetFloatProp(FAncestor, PropInfo);
if FValue <> GetFloatProp(FObject, PropInfo) then
SetFloatProp(FObject, PropInfo, FValue);
end;
tkString, tkLString:
begin
SValue := GetStrProp(FAncestor, PropInfo);
if SValue <> GetStrProp(FObject, PropInfo) then
SetStrProp (Fobject, PropInfo, SValue);
end;
tkMethod:
if FIsComponent and
not MethodsEqual (GetMethodProp (FObject,
PropInfo), GetAncestorMethod(PropInfo)) then
SetMethodProp (FObject, PropInfo,
GetAncestorMethod(PropInfo));
tkClass:
begin
Child : = FindChildProp(PropInfo);
if Child <> nil then
Child.Update (UpdateManager)
else
begin
PValue := GetAncestorReference(PropInfo);
if PValue <> Pointer(GetOrdProp(FObject, PropInfo)) then
SetOrdProp (Fobject, PropInfo, Longint(PValue));
end;
end;
end;
end;
FUpdateFiler.Update;
UpdateOrder;
if FIsCollection and FUpdateCollection then
TCollection(FObject).Assign(TCollection(FAncestor));
for I := FChildList.Count - 1 downto 0 do
with TUpdateObject(FChildList[I]) do
if FPropInfo = nil then Update(UpdateManager);
end;
______________________________________
As shown, the method includes a nested procedure: UpdateOrder. The steps of the Update method itself are as follows. After validating objects and notifying the update manager of an update, the method enters a case statement which switches on the property type (using runtime type information). For each case arm, the method requests the value for the ancestor property and then compares it against the value for the object. The property of the object is set to that of the ancestor, unless it has been overridden. Thereafter, the method invokes the UpdateFiler update method for updating non-type info properties. This is followed by updating the order of components. If the object is a collection, the method assigns the ancestor collection into the collection of the object. Finally, the method instructs the update objects of all the children to update themselves. Appended herewith as Appendix A are source listings in Object Pascal providing further description of the present invention. A suitable compiler/linker for Object Pascal is provided by the abovementioned Delphi.TM., available from Borland International, Inc. of Scotts Valley, Calif. While the invention is described in some detail with specific reference to a single preferred embodiment and certain alternatives, there is no intent to limit the invention to that particular embodiment or those specific alternatives. Thus, the true scope of the present invention is not limited to any one of the foregoing exemplary embodiments but is instead defined by the appended claims. ##SPC1##
|
Same subclass Same class Consider this |
||||||||||
