Delphi Designer, Is component inherited? - delphi

I'm writing a custom component editor, essentially similar to TActionList editor in that it allows creation of sub components.
Editor has buttons to add/delete components.
Now, I'd like to find out if selected component is inherited, so that I can disable the delete button.
I haven't found any such member in IDesigner or related interfaces.
If I just go ahead and delete by Designer.DeleteSelection(True); then I get an exception:
Selection contains a component, xxx, introduced in an
ancestor and cannot be deleted.
Which is not too bad, but I'd prefer to disable the delete button in the first place.

Ok, so apparently there is a way, by checking the TComponent.ComponentState for csAncestor, so:
csAncestor in ComponentState
And it's documented:
csAncestor - The component was introduced in an ancestor form. Only set if csDesigning is also set.

Related

Move some of the methods into a different file?

I have a lot of OnClick and similar events in my main form. It's hard to navigate between them where they all are in Unit1.pas.
I moved them to a different pas file and added this in my Unit1.pas:
{$INCLUDE Menu.pas}
But now the Delphi wants to remove OnClick events. So it doesn't work properly.
How do I split my code into different files?
You can't move a Form's event handlers to an .inc file, the IDE won't know how to handle that. Event handlers assigned at design-time need to be locatable and editable, and that means they must be in the same source file as the Form itself.
What you can do instead is move the event handlers to a TDataModule that is created before, and is used by, the Form in question. As long as the DataModule's unit is included in the Form's uses clause in the Form's interface section, the IDE should allow you to assign the event handlers at design-time. If you use the Object Inspector to create new event handlers, you will have to move them to the DataModule manually and re-assign the events accordingly.
Otherwise, all you can really do is refactor your code to reduce the amount of code in each event handler, or even reduce the number of event handlers used. And then you can use the Code Editor's code folding feature to hide the remaining code so you don't see it but it still exists to the IDE and compiler.

Hide properties and events in new component

I created in Lazarus a new component based on TPaintBox. Now in Object Inspector I have all Properties and Events which belong to this base component (TPaintBox).
My question is: can I hide chosen Properties and Events for my component?
For example I would like to leave visible only Width and Height properties.
Can you help me?
Once a property/event has been published, it cannot be un-published.
However, it can be hidden from the Object Inspector, at least (it is still accessible to code).
After your design-time code has registered the component with the IDE, it can then:
in Delphi, call UnlistPublishedProperty() from the DesignIntf unit.
in Lazarus, call RegisterPropertyEditor() from the PropEdits unit to register the THiddenPropertyEditor class for the property/event (see Hide Properties (UnlistPublishedProperty) in the Lazarus forum).
Not sure about Lazarus, but in Delphi TPaintBox is a lightweight descendant of TGraphicControl. The majority of its declaration is just publishing properties. I don't know what your component is doing, but it might be easier to derive it directly from TGraphicControl and duplicate the TPaintBox code wherever it actually is needed. Then you can publish only the properties you want. Note that you still have those properties declared published in TControl and TComponent.
No, you can't hide (unpublish) published properties.
In Delphi most objects are based on a parent classes with all the same properties, but mostly hidden.
So while you can't hide exposed properties you can usually achieve what you want by basing your class on the TCustomxxx instead.
Sadly, TPaintbox is an exception. It is descended from TGraphicControl, but that in turn is descended from TControl which already has a number of published properties, including AlignWithMargins, CustomHint and several others, and that in turn is descended from TComponent which has Name and Tag published. To be fair, you need name for sure, and Tag is not a problem I would think.
If you just had to go back to TGraphicControl, that is not too bad. Just one member and a couple of routines to copy. But to go back to TComponent, which is what you would need to do to hide a number of properties is not really viable.

How to add a property to TTabSheet such that it can be used at design time with TPageControl

I would like to add "MyProperty" to TTabSheet and work with it at design time. However if I subclass it I fear I will need to also subclass TPageControl, since it internally creates/manages the TTabSheets.
I believe this would require duplicating the TTabSheet management code to reference TMyTabSheet instead of TTabSheet, since it will need to invoke TMyTabSheet.create. This feels like something I will regret when a new version of Delphi updates TPageControl and I forget to update TMyPageControl.
I am also researching "class helpers" as another option.
Does anyone have advice on how to best accomplish this?
Thanks!
Type Helpers are merely compile-time sugar, they would cause no effect over the already compiled code, that stock VCL is together with TPageControl and TForm. That is a dead-end.
However you have a slight misconception here that TPageControl... internally creates... the TTabSheets. Indeed, when you create the sheets by the means of TPageControl itself, like right-clicking it and selecting "New Tab" that si what happens. But when you create the living form object out of DFM file (or DFM resource in your compiled EXE) that is TForm itself that creates ALL the components, including both page control and its tabsheets.
Just see my answer at How to efficiently let a `ParentFont = False` child control to use same font name as parent? - that would show you how far it is about the owner - TForm, not about TPageControl or other parent components when it comes about selecting specific classes for the actual tabs or other elements..
So you are free to pursue subclassing tabsheets only.
Create the TMyTabsheet = class(TTabSheet) component
Create and install into IDE the design-time package that would introduce new subclassed tab sheet to Delphi Form Editor
In your form put the stock TPageControl and create all the needed tabs regular way
in IDE form Editor right-click over the form free space and in the menu do the "View As Text Alt-F12" command - you would see the text content of your form's DFM file
in those DFM sources find your tab sheets and change their stock TTabSheet class to be your derived sub-class
right-click the text editor and choose "View As Form Alt-F12" command
If all was done correct then Delphi would recreate the form with your new-class sheets now. Find your new properties in Object Inspector and change them.
Now switch to .Pas sources of your form and find the declarations of those tab sheets and change their type too. That is only required if you would have to access your new properties from the Delphi sources. If not you can leave their declaration as TTabSheet in pas-file as your class is direct descendant from it. You may leave those declarations as they were - but then you would have to add RegisterClass(TMyTabSheet); call into the very initialization section at the bottom of your unit, so when the form would construct itself out of the DFM it would be able to find the class implementation by the name. If you would change the declaration (at least one of those) then your form would automagically call all needed RegisterClass before streaming out of DFM. Choose any option you like.
Optionally and later, extend your design-time package to find and hijack IDE Form Editor's right-click menu for TPageControl and add "New My Subclassed Tab" command there. Just to avoid manual post-factum DFM editing. if you would do it often
This feels like something I will regret when a new version of Delphi updates TPageControl
After you created and tuned the form and saved it into DFM - it would be TForm that creates all the components out of the saved DFM-data, and that includes your tabs too. Unless very improbable event EMBT would kill the whole VCL streaming (made back in Delphi 1 in 1995) and redesign it from scratch (killing all the compatibility with existing Delphi sources at once), there should be no problem with forward compatibility with specifying your class in DFM. It is just the standard way VCL is designed - to get specific component types from the DFM.

Using classes in dwsunit

1- Why is it necessary to name the methods of a class in a dwsunit this way?
dwsUnitClasses'ClassName''MethodName'Eval(Info: TProgramInfo;
var ExtObject: TObject)
2- Is there a link that must be done between the method defined in a dwsunit and its code?
coz it is not working with me. I took the demo attached with the DWscript (Custom Classes) and made something that looks like it but it is just not functioning.
No it's not necessary, that's just the format the IDE uses for auto-generated event names.
Usually you define script classes, their methods in the structure view, then you double-click their OnEval (or other) event in the properties editor, the IDE will create a method with a name like the above and you can implement there.
If you declare the methods manually, then you also have to attach their events manually, but it's not different than what you end up doing for a button click event (and where you end up with a Button1Click method).
Apart from the implementation code itself, most of the declarations in DWScript can happen at design-time, and are stored in the DFM. The Structure View is convenient for that, but you can also use the property editor only (it was even more convenient in older Delphi versions, when the structure view was sticking to design-time structures, rather than being reused for source structure).

Delphi 2009 creates my components in wrong order

Three components, working together:
* CompA, a TComponent descendant, a mastermind component knowing many things and tying things together
* CompB, a TComponent descendant, mines some data from it's CompA and crunches it. Can amongst other things feed CompC with data to present
- Has a published property of type CompA
* CompC, a TComponent descendant, a TFrame descendant drawing surface that can be set at designtime to use a CompB as data provider
- Has a published property of type CompA
- Has a published property of type CompB
I think I remember having read, even though I cannot state where, that Delphi's streaming engine reads all components from the .dfm and builds a dependency graph. This graph is then used to create all components in correct order. For the listed components it should be CompA first (since it uses none of the other ones), then the CompB (it uses CompA and must be created after) and lastly the CompC since it has properties of both the other component types.
This does not happen. CompC is created before CompB. If i rearrange the order in the .dfm file using a text editor it works. The property values are not used in any constructors, only in the Loaded procedures. But truly there must be a way to make it work no matter the order of components in the dfm?
I've been banging my head against the wall for two days straight now, I need somebody to tell me which keyword I forgot or what error in design I have.
I suspect your fault is you're trying to access other objects properties on setters for sibling pointers, forgetting that at dfm loading stage --runtime-- you can't be sure pointers to other components your component depends on are yet valid because it is possible that other component is not yet created. This works this way since Delphi 1.
Because of this, you usually deffer the reading of other component's state (for example) to your overridden Loaded method.
When the streaming system loads a form or data module from its form file, it first constructs the form component by calling its constructor, then reads its property values from the form file. After reading all the property values for all the components, the streaming system calls the Loaded methods of each component in the order the components were created. This gives the components a chance to initialize any data that depends on the values of other components or other parts of itself.
Note: All references to sibling components are resolved by the time Loaded is called. Loaded is the first place that sibling pointers can be used after being streamed in.
Because of this, usually on a setter method for a sibling pointer property you usually perform a check of this type:
procedure TMyComponent.SetDataSource(Value: TDataSource);
begin
FDataSource := Value;
//streaming in stage
if not (csLoading in ComponentState) then
ReadDataSourceProperties;
end;
procedure TMyComponent.Loaded;
begin
ReadDataSourceProperties;
end;
Take a look at the VCL source, you'll find hundreds of examples of this.
If your components are that much dependent on creation order, you are always going to be in trouble relying on the streaming mechanism. Just one addition or removal of a(n other) component on the form/datamodule can throw your order out of whack.
To ensure proper creation order, you'd be better off creating them at run time. Just note that when you create components at run-time the Loaded method will not be called. You will either have to do it yourself or move the code to some init method that you call after you create your components.
You can right click a form/datamodule and select the "Creation order" item. It will allow you to select the creation order of "non visual" components. Visual ones should follow the tab order, but I am not really sure about that.
Update: I was wrong about the tab order, but it looks the visual controls are streamed to the .dfm in Z-order. If the controls are instantiated following the order they are in the .dfm, you can use Edit -> Bring to front/send to back (or the Control menu in the form context menu) to change the z order. As long as the controls do not overlap you should be enough free to change it.

Resources