Delphi manipulate state of Dataset - delphi

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.

Related

How to pass a list of objects to a function, which expects a list of objects which implement an interface?

tl;dr:
Trying to pass a list of objects to a function, which expects list of objects implementing an interface. Said objects implement that interface. Compiler will not allow it.
Looking for an alternative workaround or my mistake.
The setup
I did not use the actual code, and that should not matter IMO. Seems like a conceptional problem to me.
The class TObjectWithInterface implements the interface ISomeInterface and extends the class TCustomInterfacedObject, which simply works around the reference counting of IInterface as given in the documentation.
TObjectWithInterface = class(TCustomInterfacedObject, ISomeInterface)
If have a procedure which takes a list of objects implementing that interface:
procedure SomeFunction(List: TList<ISomeInterface>)
The issue
Inside of a function of TObjectWithInterface, I try to call that function, using a list of objects of TObjectWithInterface:
procedure TObjectWithInterface.DoTheStuff(ListOfObjects: TList<TObjectWithInterface>)
begin
// ...
SomeFunction(ListOfObjects); // <-- Compiler error: Incompatible types
// ...
end;
The compiler tells me the following:
E2010: Incompatible types: 'System.Generics.Collections.TList' and 'System.Generics.Collections.TList'
The dumb workaround
I really dislike my current workaround, which consists of creating a new list and type casting each TObjectWithInterface to ISomeInterface:
procedure TObjectWithInterface.DoTheStuff(ListOfObjects: TList<TObjectWithInterface>)
var
ListOfInterfaceObjects: TList<ISomeInterface>;
begin
// ...
ListOfInterfaceObjects := TList<ISomeInterface>.Create;
for var Object in ListOfObjects do
ListOfInterfaceObjects.Add(Objects as ISomeInterface);
SomeFunction(ListOfInterfaceObjects)
// ...
end;
This seems very hacky to me. I may have done something stupid, or do not understand something correctly, as this is the first time, that I am trying to use Interfaces in Delphi. Please don't be mad.
Either way, I hope someone can point out my mistake or, if this is a language limitation, has an alternative workaround.
Your "workaround" to copy the list of objects into a separate list of interfaces is actually the correct and proper solution. You can't use a TList<X> where a TList<Y> is expected, they are different and unrelated types. Just because you are disabling reference counting on the interfaces doesn't change the memory layout of the objects in relation to their interfaces, so you still have to pass around proper memory pointers, which means performing necessary conversions from one to the other.

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.

How can i work RegisterClass() to units

Base from the anwser How to eliminate variables... i get and accepted it works great when i have all this components and make the actions like a button1.click from the main form...
But i use to make the actions from units... so
When i click a button i great a procedure DoMaths(Sender: TObject);
procedure Tform1.DoMaths(Sender: TObject);
begin
if TButton1(Sender).hint := 'Make the standard Package' then
do_Maths_standard_package;
end;
the do_Maths_standard_package is in unit ComplexMaths.
is the procedure do_Maths_standard_package form unit ComplexMaths it calls some components form Form1... like Form1.label1 etc...
So when i call the RegisterClass(TLabel) and erase the Tlabel from the type it gives an error that it cant find the Label1...
Please can someone help me so not to do the hole program from the start...
Thank you..
You might be able to reference your components like this:
TLabel(Form1.FindComponent('Label1')).Caption := '...';
TCheckBox(Form1.FindComponent('CheckBox12')).Checked := False;
But it's really a pain...
I think you have two options.
1) you can assign each component a unique numeric ID.
And save it into .Tag property.
Just like u use to generate and bind IDs in .HelpContext properties.
Then to get the control by number you would enumerate Form.Controls and get the one with proper Tag value.
The problem would be to have two separate ID lists, in PAS files and DFM files, in sync. Mistyping would be hard to notice. Especially since you have no constants in DFM but only "magic numbers".
2) Set .Name property and use iMan Biglari's recipe - FindComponent by name.
The question is if you can have .Name but not variable. Since no one answers - just try and see.
To my experience - with Delphi 5, hopefully D7 is mostly the same - you can just delete the variable.
If you made wrong declaration of variable, then Delphi editor would notice and ask to correct it.
If you have variable without DFM object, Delphi would notice it and ask to remove it.
But if there is DFM object without corresponding variable, then Delphi editor is oblivious. Maybe it thinks that the object is inherited or whatever.
But if you did not declare it at all it does not mind.
However, since you deleted Names, it looks like it is not possible to you for some reason.
In both cases you would have to cache value if some procedure makes a lot of accesses to some control. And maybe even across the procedures. In effect yu would manually restore those variables at least for most used controls.

Adding the same Object twice to a TObjectDictionary frees the object

Look at this code:
dic:=TObjectDictionary<Integer, TObject>.Create([doOwnsValues]);
testObject:=TObject.Create;
dic.AddOrSetValue(1,testObject);
dic.AddOrSetValue(1,testObject);
The code
Creates a Dictionary that owns the contained values
Adds a value
Adds the same value again, using the same key
The surprising thing is that the object is freed when you add it the second time.
Is this intended behaviour? Or a bug in the Delphi libraries?
The documentation simply says "If the object is owned, when the entry is removed from the dictionary, the key and/or value is freed". So it seems a little odd to Free an object that I have just asked it to Add!
Is there any way to tell the TObjectDictionary to not do this? Currently, each time I add a value I have to check first if that Key-Value combination is already in the Dictionary.
Delphi 2010
[EDIT:
After reading all the comments:
My conclusions (for what they are worth)]
This seems to be the intended behaviour
There is no way of modifying this behaviour
Don't use TObjectDictionary (or any of the other similar classes) for anything other than the common "Add these objects to the container. Leave them there. Do some stuff. Free the container and all the objects you added" usage. If you are doing anything more complicated, it's better to manage the objects yourself.
The behaviour is poorly documented and you should read the source if you want to really know what's going on
[/EDIT]
TObjectDictionary<TKey,TValue> is in fact just a TDictionary<TKey,TValue> which has some extra code in the KeyNotify and ValueNotify methods:
procedure TObjectDictionary<TKey,TValue>.ValueNotify(const Value: TValue;
Action: TCollectionNotification);
begin
inherited;
if (Action = cnRemoved) and (doOwnsValues in FOwnerships) then
PObject(#Value)^.Free;
end;
This is, IMO, a rather simple minded approach, but in the ValueNotify method, it is impossible to tell for which key this is, so it simply frees the "old" value (there is no way to check if this value is set for the same key).
You can either write your own class (which is not trivial), deriving from TDictionary<K,V>, or simply not use doOwnsValues. You can also write a simple wrapper, e.g. TValueOwningDictionary<K,V> that uses TDictionary<K,V> to do the brunt of the work, but handles the ownership issues itself. I guess I would do the latter.
Thats because with reusing the key youre replacing the object and since the dictionary owns the object it frees the old one. Dictionary doesn't compare the value, only key, so it doesn't detect that the value (object) is same. Not a bug, as designed (IOW user error).
On second thought - perhaps the designer of the dict should have taken more care to have both doOwnsValues and AddOrSetValue()... one can argue both ways... I suggest you file it in QC, but I wouldn't hold my breath - it has been so now in at least two releases so it's unlikely to change.
This behaviour is by design and the design is sound.
Were the class to take responsibility for not freeing duplicates, it would have to iterate over the whole container every time a modification was made, both adding and removing. The iteration would check for any duplicate values and check accordingly.
It would be disasterous to impose this diabolical performance drain on all users of the class. If you wish to put duplicates in the list then you will have to come up with a bespoke lifetime management policy that suits your specific needs. In this case it is unreasonable to expect the general purpose container to support your particular usage pattern.
In the comments to this answer, and many of the others, it has been suggested that a better design would have been to test in AddOrSetValue whether or not the value being set was already assigned to the specified key. If so, then AddOrSetValue could return immediately.
I think it's clear to anyone that checking for duplicates in full generality is too expensive to contemplate. However, I contend that there are good design reasons why checking for duplicate K and V in AddOrSetValue would also be poor design.
Remember that TObjectDictionary<K,V> is derived from TDictionary<K,V>. For the more general class, comparing equality of V is potentially an expensive operation because we have no constraints on what V is, it being generic. So for TDictionary<K,V> there are performance reasons why we should not include the putative AddOrSetValue test.
It could be argued that we make a special exception for TObjectDictionary<K,V>. That would certainly be possible. It would require a little re-engineering of the coupling between the two classes, but it is quite feasible. But now you have a situation where TDictionary<K,V> and TObjectDictionary<K,V> have different semantics. This is a clear downside and must be weighed against the potential benefit from the AddOrSetValue test.
These generic container classes are so fundamental that design decisions have to take into account a huge spread of use cases, consistency considerations and so on. It is not, in my view, reasonable to consider TObjectDictionary<K,V>.AddOrSetValue in isolation.
Since the Delphi TDictionary implementation doesn't allow for more than one of the same keys you could check the excellent Generic collections library from Alex Ciobanu. It comes with a TMultiMap or for your case TObjectMultiMap that allows for multiple values per key.
Edit:
If you don't want multiple values per key, but rather want to avoid adding duplicates to the Dictionary then you can try TDistinctMultiMap or a TObjectDistinctMultiMap from the same Collections library.
So it seems a little odd to Free an object that I have just asked it to Add!
You didn't ask the dictionary to add - you called 'AddorSet', and since the key was already found, your call was a 'set', not an 'add'. Regardless, I see nothing odd here in terms of Delphi's behavior: In Delphi, objects are only object references, and there is no reference counting or ownership for simple objects.
Since in this case the dictionary owns the objects, it is doing exactly what it's supposed to do: "If the object is owned, when the entry is removed from the dictionary, the key and/or value is freed". You removed the value when you overwrote entry[1] - therefore the object referred to in 'testObject' is immediately deleted and your reference to 'testObject' is invalid.
Currently, each time I add a value I have to check first if it's already in the Dictionary.
Why is that? The behavior you described should only occur if you overwrite a previously used key with a reference to the same object.
Edit:
Perhaps there is something 'odd' after all - try this test code:
procedure testObjectList;
var ol:TObjectList;
o,o1:TObject;
begin
ol:=TObjectList.create;
ol.OwnsObjects:=true;//default behavior, not really necessary
try
o:=TObject.create;
ol.add(o);
ol[0]:=o;
showmessage(o.ClassName);//no av-although ol[0] is overwritten with o again, o is not deleted
o1:=TObject.create;
ol[0]:=o1;
showmessage(o.ClassName);//av - when o is overwritten with o1, o is deleted
finally
ol.free
end;
end;
This in spite of what it says in the (Delphi 7) help: "TObjectList controls the memory of its objects, freeing an object when its index is reassigned"
I think it is a bug.
I ran into it a week ago.
I use the TObjectDictionary for storing some real time telemetria datas, which are very often updated with new datas.
for example:
Type TTag = class
updatetime : TDateTime;
Value : string ;
end ;
TTagDictionary:= TObjectDictionary<string,TTag>.Create([doOwnsValues]);
procedure UpdateTags(key: string; newValue: String) ;
var
tag : TTag ;
begin
if TTagDictionary.TryGetValue(key,tag) then begin // update the stored tag
tag.Value = newValue ;
tag.updatetime := now ;
TTagDictionary.AddorSetValue(key,tag) ;
else begin
tag := TTag.Create ;
tag.updatetime := now ;
tag.Vluae := newValue ;
TTagDictionary.AddorSetValue(key,tag) ;
end ;
end ;
After several updates I ended up with some nasty access violations and with an dictionary full of freed objects.
It is a very poor designed container.
At update it need check if the new object is the same as the old only and then it must NOT free the object.

Delphi: what are the 8 mystery components on my form?

When I iterate of the controls on my form, I see those which I placed there at design time or run time. They are all of type TEdit, Tmemo, TComboBox, etc ...
However, there are always exactly eight which I do not recognize. I can skip over them, since they are not of a type which interests me, but I am curios.
I am guessing system controls like min/max/close. Their Name property is empty. Is there any way I can determine what type they are (without explicitly testing for every standard component derived from TWinControl) ?
I am curious - but not yellow ;-)
for i := 0 to Form1.ControlCount -1 do ...
Answer: use ClassName to find out. And i got TGrabHandle for all of them.
Thanks for all the help. +! all round
Use their ClassName to get an idea of wht they are when the Name property is empty.
TObject has the property ClassName; Try to access and interrogate this property for this components.
Paste the code block (for/while) that you are using for interrogate the form, for search components.
Regards

Resources