Delphi: How do i know what my property editor is editing? - delphi

i have a property editor (descendant of TPropertyEditor) that is used to edit a property.
When it comes time to edit my property, how do i know what property of what object i'm editing? If i'm going to edit a property, i have to know what property i'm editing.
i've been pulling my hair out, sifting through the Delphi help, the online help, and the TPropertyEditor and descendant source code, and i can't find the answer.
i expected something like:
TPropertyEditor = class(...)
public
procedure Initialize(TheObject: TObject; ThePropertyName: string);
end;
As far as i can tell, my property editor is created, and i will be told to "Edit", and i just have to divine what property they wanted me to edit.
From the help:
Editing the property as a whole
You can optionally provide a dialog
box in which the user can visually
edit a property. The most common use
of property editors is for properties
that are themselves classes. An
example is the Font property, for
which the user can open a font dialog
box to choose all the attributes of
the font at once.
To provide a
whole-property editor dialog box,
override the property-editor class’s
Edit method.
Edit methods use the same
Get and Set methods used in writing
GetValue and SetValue methods. In
fact, an Edit method calls both a Get
method and a Set method. Because the
editor is type-specific, there is
usually no need to convert the
property values to strings. The editor
generally deals with the value “as
retrieved.”
When the user clicks the ‘...’ button
next to the property or double-clicks
the value column, the Object Inspector
calls the property editor’s Edit
method.
Within your implementation of
the Edit method, follow these steps:
Construct the editor you are using
for the property.
Read the current
value and assign it to the property
using a Get method.
When the user
selects a new value, assign that value
to the property using a Set method.
Destroy the editor.
Answer
It's tucked away, and not documented, but i found out how. The property i'm editing that i edit:
TheCurrentValue := TMyPropertyThing(Pointer(GetOrdValue));
Now that i have the value, i can edit it. If i want to replace the property with some other object:
SetOrdValue(Longint(TheNewValue));
The full code:
Create a property editor that descends from TClassProperty:
TMyPropertyEditor = class(TClassProperty)
public
procedure Edit; override;
function GetAttributes: TPropertyAttributes; override;
end;
First is the housekeeping, telling Delphi's object inspector that my property editor will display a dialog box, this will make a "..." appear next to the property:
function TMyPropertyEditor.GetAttributes: TPropertyAttributes;
begin
//We show a dialog, make Object Inspector show "..."
Result := [paDialog];
end;
Next is the actual work. When the user clicks the "..." button, the object inspector calls my Edit method. The trick that i was missing is that i call my GetOrdValue method. Even though my property isn't an ordinal, you still use it, and just cast the resulting thing to an object:
procedure TMyPropertyEditor.Edit;
var
OldThing: TMyPersistentThing;
NewThing: TMyPersistentThing;
begin
//Call the property's getter, and return the "object" i'm editing:
OldThing:= TMyPersistentThing(Pointer(GetOrdValue));
//now that i have the thing i'm editing, do stuff to "edit" it
DoSomeEditing(OldThing);
//i don't have to, but if i want to replace the property with a new object
//i can call the setter:
NewThing := SomeVariant(OldThing);
SetOrdValue(Longint(NewThing));
end;

If I understand your question right, you're wondering how you're supposed to actually find the value you need to be editing, especially if the object in question contains more than one of them. The answer is that the IDE sets that up for you and the property editor comes "preloaded" by the time Edit is called. TPropertyEditor comes with a bunch of GetValue methods that your Edit function can use to retrieve the value. Or if it's not one of those types, (if it's an object descended from TPersistent, for example,) then you can call GetOrdValue and cast the result to a TPersistent.
Also, you might want to check out TJvPersistentPropertyEditor in the JvDsgnEditors unit of the JVCL to use as a base class. It provides some of the functionality for you.
BTW if you really need it, you can use the GetName method, which will give you the name of the property, but you usually shouldn't have to. And be careful if you're inheriting from something other than TPropertyEditor itself, as GetName is virtual and can be overridden.

A property editor keeps the information about which objects and properties it's editing in the private FPropList variable. The IDE fills that in by calling your editor's SetPropEntry method. You're then generally supposed to call the various methods of TPropertyEditor to find out the properties' values.
It's not really supposed to matter which property you were asked to edit. Your property editor edits properties of a particular type. For in-place editing, your editor provides an implementation of SetValue that translates the string from the Object Inspector into a value of the proper type for the property, and then you call the appropriate Set function, such as SetOrdValue or SetIntfValue. For whole-property editing, the IDE won't call SetValue. Instead, it will call Edit, and you're expected to call GetOrdValue or GetIntfValue (for example) yourself, since your editor already knows what type of property it's designed to edit.
Remember that property editors, in general, can edit multiple properties simultaneously. The name of the property will be the same for all of them, but the type of component they belong to may vary, and thus so may their getters and setters. Call your property editor's GetName method to find out the name. To get the objects, call GetComponent for each index from 0 to PropCount - 1. (Be careful; there's no range checking in those Get functions.) You can also check whether a specific object is in the list by calling HasInstance. The GetPropInfo method will tell you the PPropInfo pointer for the first property, but I don't think that will necessarily be equal to the pointers of all the other properties. Aside from that, you don't get direct access to the PPropInfo data, but again, it really shouldn't matter. If you think you need that for your editor, you're probably doing something wrong; post a new question with more specific information about your underlying task.

Property editors only care about (and are registered for) the type of the property, not the specific property itself.

Related

How To: Assigning a value to a sub property of an indexed default property via "setter"

I have a class which contains the profile information of a project of mine, with loads and loads of information, its called PROFILE. To have an easy access to all the different properties of this profile I have an indexed default property. This default property(TCHANNELLIST) is a record containig again a few properties as well as another record(TCHANNELPARAMETER). The property CHANNEL is the default property (indexed) of the default property TCHANNELLIST.
Now I do have a problem when constructing the setter of these properties. (To clearify: the read function is not the problem! Please don't bother except the solution can be found in it).
The Question: How do I construct the Property/procedure/function to get the following code running
MyProfile[i][j].Name := 'Thanks_for_the_help';
Since more is more here is the structure of my records I have used. I am also willing to change the general structure if there is a better way, so I am open for suggestions.
TChannelParameter = record
// each channel gets one record for itself
public
channelType : TKanalTyp;
display_number : Integer;
Name : string;
// and a few other but you will get the idea...
end;
TChannelList = record
private
FChannelparameter_List : array of TChannelParameter ;
function GetChannelParameter(Index: Integer): TChannelParameter ;
procedure SetChannelParameter(Index: Integer); //Here I need some help
public
property Channal_GlobalNumber[index: Integer]: TChannelParameter read GetChannelParameter write SetChannelParameter; //Here I need some help
end;
To be honest I just don't have an idea (and I cant find any help online) to get that line of code running. To Read everything is not a problem but to write stuff into the "subitem" of an indexed default property is a riddle to me. Here it does not matter if I use this
A_Channel_list[i].name := 'aName';
or
MyProfile[i][j].name := 'aName';
Both setters are till now not constructed! Since I lack the basic knowledge to do so! (further I did not include the class since the handling should be the same)
If I get one running the other one should not be a problem anymore. Maybe somebody knows that this kind of operation however is not possible, please also let me know this! I will then reconstruct my class and records.
For what you are trying to achieve you don't even need Channal_GlobalNumber property to be writable. Having it readable would be enough provided that your TChannelParameter object is class type instead record type that you have now.
You see if you declare your TChannelParameter as class your Channal_GlobalNumber property will return you a reference (pointer) to that object so you can then access any of its fields/properties like if you would have variable referencing such object.
This means that you could then be changing name property/field of individual channels simply by using:
A_Channel_list[i].name := 'aName';
So why doesn't this work when your TChannelParameter is of record type. As Uwe Raabe wrote in his comment your indexed property of record type won't return you the original record from your array but instead a copy of it. Therefore making any hanges on it would not change the original record from your array but instead copy.
EDIT: Don't forget that if you change your TChannelParameter object to class type you will have to write special routines for creating such object when you are changing size of your FChannelparameter_List array as in such case this array is array of poiters to TChannelParameter classes.
Now if you want to realy avoid this and use records only you could write multiple indexed properties in TChannelList object one for each field of TChannelParameter record. So then you can use these properties getter or setter method to Access items in your array.
Unfortunately I can't write you a code example righht now since I'm not on my development computer.

TPageControl: how to prevent changing Style property

I want to prevent a descendant of TPageControl from having a Style property except tsTabs.
At first glance I tried to override SetStyle, but it is declared private in the base class. anyone have ideas?
TPageControl does not natively support what you are asking for. To accomplish what you are asking for, you would have to either:
have your component re-declare the Style property with new getter/setter methods, and then have the setter exit without doing anything. However, the inherited Style property will still technically be accessible at runtime if the user of the component really wants to access it.
derive your component from TCustomTabControl instead of TPageControl, and re-implement everything that TPageControl does, but without promoting the protected Style property. However, the inherited Style property will still technically be accessible at runtime if the user of the component really wants to access it.
have your component override the virtual CreateParams() method and force the TCS_TABS window style in the TCreateParams.Style field. The Style property will still have whatever value the user assigns (which will also affect the behavior of the TabPosition property), but at least the underlying window will always behave as if the tsTabs style were being used.
use a detouring library to hook TCustomTabControl.SetStyle() directly at runtime and make it return without doing anything.

Published persistent property - Should I use FMyPersistent.Assign(Value) or FMyPersistent:= Value?

When I'm building a custom component, I may implement published persistent properties. For example...
type
TMyComponent = class(TComponent)
private
FMyPersistent: TMyPersistent;
...
public
...
published
property MyPersistent: TMyPersistent read FMyPersistent write SetMyPersistent;
...
end;
Note that the procedure SetMyPersistent is not here yet, that's where the next step comes in. I right-click this object and select "Complete Class at Cursor" (or Shift + Control + C) to invoke the code completion. When it automatically creates this property setter, it automatically puts the assignment code in...
procedure TMyComponent.SetMyPersistent(const Value: TMyPersistent);
begin
FMyPersistent := Value;
end;
Now it's nice that it went ahead and completed this assignment for me. However, in normal cases, I've always grown accustomed to using...
procedure TMyComponent.SetMyPersistent(const Value: TMyPersistent);
begin
FMyPersistent.Assign(Value);
end;
In cases where the property is a type such as String or Integer, then a direct assignment is the proper way to do it. But when implementing a published property of a TPersistent, isn't it the correct method using TPersistent.Assign?
What's the essential difference between using these two assignment mechanisms? Because if using TPersistent.Assign is the appropriate thing to do, then the code completion has a slight flaw - that is, assuming that FMyPersistent := Value is considered "wrong".
Call Assign. That's why you have a property setter in the first place. If you were to directly overwrite the field, you wouldn't need a setter. Overwriting it would leak the original object you created in the constructor. You'd also notice access violations in the IDE when you modified the property in the Object Inspector.
Code completion puts the same code in every setter it creates. For properties that have additional work to do before ultimately storing the value in a field, so the field-storage statement is correct. The IDE doesn't know what you really want.
The question you should be asking yourself is - who owns the objects involved? If your component creates and owns FMyPersistent then use FMyPersistent.Assign(Value) to copy values from Value into FPersistent. If FMyPersistent merely points at an external object that someone else owns then use FMyPersistent := Value instead.

Date/Time Picker as Property Editor in a TCollectionItem descendant

I'm writing a component which requires properties of type Date, Time, and Date/Time. I would like these properties to be visible in the Object Inspector, with an option to use a popup property editor.
I have tried TDate as a published property, and this gives me the results I need for just Date alone. However I need the same thing for TTime and TDateTime but they don't come with a property editor, and in fact it won't even accept any value I type in there either.
I have found the TDateTimeProperty which can be used as a property editor, or so I understand anyway. I have done the necessary implementation when registering this component. This property I need to apply it to is actually a TCollectionItem descendant, not necessarily a part of the component but within it.
This is how I'm registering it...
RegisterComponents('My Page', [TMyComponent]);
RegisterPropertyEditor(TypeInfo(TDateTime), TMyCollectionItem, 'MyPropName', TDateTimeProperty);
Although this compiles, when I install it, there is no property editor on this property. I have tried using my component's class name in place of TMyCollectionItem but same issue.
What am I doing wrong here to show this property editor?
You don't need to register the built-in property editors for TDateTime, TDate and TTime. They are already registered. That's why your attempts to register them have no impact.
The built-in property editors for these types simply convert between the underlying floating point value and a string representation. They don't implement date time pickers or anything like that.
You say:
However I need the same thing for TTime and TDateTime but they don't come with a property editor, and in fact it won't even accept any value I type in there either.
That is in fact incorrect. They do come with property editors. They are the same built-in property editors that you named in your question. And they do accept values. They don't accept the values you provided because you provided invalid values.
If you want to register a property editor that does provide a visual date time picker, then you will have to write the property editor yourself.

Passing parameters to a delphi TFrame

To avoid singletons and global variables I'd like to be able to pass parameters to a TFrame component. However since a TFrame normally is included on form at design time it is only possible to use the default constructor.
The parent form can of course set some properties in the OnCreate callback after the TFrame has been created. However this does not ensure that a property is not forgotten, and the dependencies are not as clear as using a constructor.
A nice way would be if it was possible to register a factory for creating components while the dfm file is being read. Then the required parameters could be passed to the TFrame constructor when created by the factory. Is there a way of accomplishing this?
Or does anyone have a better solution on how to pass parameters to a TFrame?
All components, including descendants of TFrame, need to be able to be constructed using the constructor inherited from TComponent. Otherwise, they can't be used properly at design time. If the restriction of design-time use is acceptable to you, then you could override that constructor and raise an exception. That would prevent the component from being placed on a form at design time. Simply provide some other constructor that requires other parameters.
Because of the design-time requirement, all components need to be able to exist with some or all of their properties still at their default values. That doesn't mean the components have to do useful things while they're in that state, but they do need to be able to stay in that state indefinitely. It should be OK, for example, to place a component on a form, save the form, and close Delphi, with the intention of resuming the form-designing at a later time. The component should allow itself to be saved and restored, even if all its properties haven't been set up for final use yet.
My preferred option is to enforce the component's rules only at run time. Check that all the properties are set to sensible values before you allow them to be used. You can use assertions to enforce the proper use of your components. Consumers of your classes will learn very quickly if they haven't finished setting up your components on their forms.
I would normally add a public, non-virtual "Initialise" or (Initialize to Americans) procedure which requires all parameters to be provided. This will then set the properties.
Make the properties protected or private if possible, so the only way they can be set is from calling Initialise(AFoo, ABar : integer).
Then in TFormXXX.FormCreate or TformXXX.Create, have:
inherited;
Initialise(foo, bar);
could you create/registercomponent your own tFrame component and
place that on the form - it's create could have anything passed to it.
If a factory could provide the parameters that you need, why don't you just override the default constructor for your frame, and ask the factory-class for parameters?
I usually make my own constructor. I don't like to create frames at designtime anyway.
a) a frame can be created dynamically when required and destroyed when not needed
b) give the frame a public property with either the parameter data type or a data structure and pass the values to the form through the property.
Example:
TAddress - a class to hold the usual elements of an address.
TAddressFra - a frame with the visual controls to display the address
populate an instance of TAddress with values
create an instance of TAddressFra
assign the TAddressFra.address property with the TAddress instance
use the procedure setAddress(o_address : TAddress) to assign the values of the TAddress attributes to the corresponding visual components on the TAddressFra

Resources