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

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.

Related

Delphi manipulate state of Dataset

Is there a way to modify the state of my dataset (TTAble) before any delete or edit or insert?
I try to use Table1.State := dsXXX. the compiler tell me can't modify this properties.
The property State (inherited from class TDataSet) is read-only. You are not supposed to manipulate it directly.
To delete/edit/insert a record use the respective methods Delete/Edit/Insert. They change value of State as designed.
Presumably (as you don't provide any information on what you are doing or what you have tried), you are asking to modify State because you get an error message like
DataSet not in edit mode.
That's because data manipulation can only be done after the dataset has been opened by calling the Open method.
You can do this but be careful that, by what you're doing, you aren't subverting the TDataSet's state model.
You need to declare a descendant class of the TDataSet type you are using. E.g.
type TmyTTable = Class(TTable);
Then by casting your dataset to that type, you can use SetTempState and RestoreState on it:
SaveState := Table1.State;
try
TmyTTable(Table1).SetTempState();
// do something
finally
TmyTTable(Table1).RestoreState(SaveState);
end;
You should really look at the source of SetTempState in DB.Pas, though. And proceed with extreme caution - using SetTempState is asking for trouble. SetTempState is used in several places in DBClient.Pas, fwiw.

Is there any way to dynamically cast the item type of a generics collection in Delphi?

Unlike the case with common objects, it is impossible to directly assign generics of different related types in Delphi as follows:
Possible (normal objects):
var
var_1 : TObject;
var_2 : MyTObjectSubClass;
var_1 := var_2; //Works
Not possible (generics):
var
var_1 : TList<TObject>;
var_2 : TList<MyTObjectSubClass>;
var_1 := var_2; //Does not compile
It is possible to use casts to accomplish this though, as follows:
var
var_1 : TList<TObject>;
var_2 : TList<MyTObjectSubClass>;
var_1 := TList<TObject>(var_2); //Works
This creates the need to be able to dynamically cast generics (i.e. to dynamically parameterize their generic type specification) somehow, but I have not been able to find a way to do this, so my question is: Is this in any way possible?
I'm indeed aware of the covariance/contravariance problems related to this, but in some cases it would indeed both be useful and "correct" to do such a thing.
One example of such a situation is the current code I'm writing for generic streaming of Delphi objects over a TStream, where the receiving end knows the exact type of the object that is incoming over the stream, e.g. TList<MyTObjectSubClass>. This type information is extracted by means of RTTI though (from a provided target variable to which the loaded object should be written), so I cannot explicitly mention the exact generics type in my stream-loading code in advance, but rather have to detect it by means of RTTI (which is possible, although somewhat hacky) and then write it to a target variable that I only at that run-time point will know the exact type of.
Thus, the load-object-from-stream code has to be fully generic, and thus, it would need to dynamically cast an existing TList<TObject> variable (which is defined explicitly in the code) to the exact type of TList<MyTObjectSubClass> (which I at that point have just learned about, through the use of RTTI), in order to be able to pass this object loaded from the stream to its final destination variable.
So again, is there ANY way whatsoever to accomplish this, or is it on the contrary actually completely impossible to assign to a not-in-advance-known generics collections using generic code (i.e. code that does not explicitly have some kind of "if [type of xxx is TList<TMyObject1>] then ... else if [type of xxx is TList<TMyObject2>] then ... else ..." test, containing explicit mentions of every single generics type that should be supported by it)?
PS.
The generics type of the stream-loaded object obviously already exists somewhere in the program (since it is concluded by means of RTTI on the target variable that the stream-loaded object should be written to), so I'm not asking about full run-time dynamic creation of generics types, but rather just about how to be able to dynamically pick the right one of those generics types already defined at compile-time in the program, and then cast a variable to that type.
EDIT:
By request from #RemyLebeau , here comes some more example code from my application, from its stream-loading function:
var
source_stream : TStream;
field_to_process : TRttiField;
field_type : TRttiType;
loaded_value : TValue;
temp_int : integer;
//...
//The fields of any object given to the streaming function are
//enumerated and sorted here
//...
//Then, for each field (provided in field_to_process),
//the following is done:
case field_to_process.FieldType.TypeKind of
//...
tkInteger:
begin
source_stream.ReadBufferData(temp_int);
loaded_value := TValue.From(temp_int);
end;
tkString,
tkLString,
tkWString,
tkUString:
begin
source_stream.ReadBufferData(noof_raw_bytes_in_string_data);
SetLength(raw_byte_buf, noof_raw_bytes_in_string_data + 4);
source_stream.ReadBuffer(raw_byte_buf[0], noof_raw_bytes_in_string_data);
temp_str := used_string_encoding.GetString(raw_byte_buf, 0, noof_raw_bytes_in_string_data);
loaded_value := TValue.From(temp_str);
end;
tkClass:
begin
is_generics_collection_containing_TObject_descendants := <does some hacky detection here>; //Thanks Remy :-)
if is_generics_collection_containing_TObject_descendants then
begin
<magic code goes here that loads data from the stream into the currently processed field, whose type has been detected to be of some specific generics collection type>
end;
end;
//...
end;
field_to_process.SetValue(self, loaded_value);
That should hopefully give a somewhat better overview of my problem. The superfluous code for strings and integers are just for context, by showing how some simple types are handled.
For more info about the (necessarily) "hacky detection" mentioned in the code, please see this question. After doing that, I will know the exact type of the generics collection and its subitems, for example TList<TSomeTObjectDescendant>.
So, as you hopefully can see now, the question is about the <magic code goes here that loads data from the stream into the currently processed field, whose type has been detected to be of some specific generics collection type> part. How can it be implemented?
NOTE: My problem is not to understand how to serialize/deserialize contents of an enumerable through a stream (which can of course be done by simply iterating over the items in the enumerable and then recursing the stream saving/loading code for each of them, where the number of items is given first of all in the stream). The problem is rather how to create generic code that will be able to recreate/populate any kind of generics collection of TObject descentants, whose type you only get to know at runtime, and then to finally get this into the object field that was originally enumerated by RTTI at the beginning of the stream-loading code. As an example, assume that the processed field has the type TList<TSomeTObjectDescendant>, and that you can easily load its subobjects from the stream using a call like function load_list_TSomeTObjectDescendant_subitems(input_stream : TStream) : array of TSomeTObjectDescendant. How could I then get these subitems into the TList<TSomeTObjectDescendant> field?
Type-casts and variable declarations are parsed at compile-time (though is and as casts are executed at runtime based on compiler-provided RTTI). The type being casted to, and the type of the variable being assigned to, must be known to the compiler. So what you are asking for is simply not possible with Generics. Not the way you have described it, anyway.

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.

How to cast OleVariant to IDispatch derived?

I bring today another question that is burning my head,
I do import a DAO 3.6 type library to my delphi 7, and I start to see many interesting intefaces so I face on intriguing question.
Every time the class Fields appears on property of another class, they have the right definition, I mean, he is defined as Fields, but in Index class, in the parts where he describes all fields participants of his structure, the property fields appears not as Fields, but as OleVariant.
Look at the diference from TableDefs, that have Fields property to and compare to Index definition:
_TableDef = interface(_DAO)
...
property Fields: Fields read Get_Fields;
...
end;
_Index = interface(_DAO)
...
property Fields: OleVariant read Get_Fields write Set_Fields;
...
end;
The question is, is there a way to cast that Fields that appears like OleVariant type to be casted on Fields interface type?
I´m very gratefull for all that every help me here in StackOverflow
If I understand your question correctly, you are asking how to convert a variant to an IDispatch. Do that like this:
IDispatch(V)
In your case I think you have another type, Fields that derives from IDispatch. You can get hold of that like this:
IDispatch(V) as Fields

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

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.

Resources