Failing to update a table - delphi

I have a boolean field in cxGrid (represented as a checkbox inside the grid).
I am trying to do an update of the very same record when user sets the checkbox as checked.
I tried this:
procedure TDataModule3.ABSTable2BeforePost(DataSet: TDataSet);
begin
if DataModule3.ABSTable2.FieldByName('DONE').AsBoolean = True then
with Datamodule3.ABSQuery4 do begin
Datamodule3.ABSQuery4.Close;
Datamodule3.ABSQuery4.SQL.Clear;
Datamodule3.ABSQuery4.SQL.Text :='UPDATE MYTABLE SET USER=:a1 where TW_ID =:a2';
Datamodule3.ABSQuery4.Params.ParamByName('a1').AsString := MainForm.AdvOfficeStatusBar1.Panels[3].Text ;
Datamodule3.ABSQuery4.Params.ParamByName('a2').AsInteger := Datamodule3.ABSTable2.FieldByName('TW_ID').AsInteger;
Datamodule3.ABSQuery4.ExecSQL;
end;
end;
I get :
First chance exception at $00B6E33F. Exception class $C0000005 with message 'access violation at 0x00b6e33f: read of address 0x000003a0'. Process Project1.exe (4916)
What am I doing wrong ?
Edit: I tried the suggestion.
/
if ABSTable2.FieldByName('Done').AsBoolean = True then
begin
ABSQuery4.Close;
ABSQuery4.SQL.Clear;
ABSQuery4.SQL.Text :='UPDATE mytable SET user=:a1 where TW_ID=:a2 ';
ABSQuery4.Params.ParamByName('a1').AsString := ABSQuery1.FieldByName('USER').asString ;
ABSQuery4.Params.ParamByName('a2').AsInteger := ABSTable2.FieldByName('TW_ID').AsInteger;
ABSQuery4.ExecSQL;
end;
I have removed the reference to the main form and added the query syntax as the AdvOfficeStatusBar1.Panels[3].Text gets its data from it.
Now I get: Absolute Engine error : The table is locked.

Remove the DataModule3 from the beginning of all of the lines of code.
You're using an instance variable inside the methods of a class. If the instance is named anything other than DataModule3 or DataModule3 hasn't been created, your code will fail.
The proper way to write the code would be (see the NOTE below):
procedure TDataModule3.ABSTable2BeforePost(DataSet: TDataSet);
begin
if Self.ABSTable2.FieldByName('DONE').AsBoolean = True then
begin
Self.ABSQuery4.Close;
Self.ABSQuery4.SQL.Clear;
Self.ABSQuery4.SQL.Text :='UPDATE MYTABLE SET USER=:a1 where TW_ID =:a2';
// See NOTE below
Self.ABSQuery4.Params.ParamByName('a1').AsString := MainForm.AdvOfficeStatusBar1.Panels[3].Text ;
Self.ABSQuery4.Params.ParamByName('a2').AsInteger := Self.ABSTable2.FieldByName('TW_ID').AsInteger;
ABSQuery4.ExecSQL;
end;
end;
NOTES
The Self is optional. You could write each without the Self. and it would work equally as well in this case. Self refers to the current instance of the object, rather than a specific named instance like DataModule3 does.
You should find a way to remove the reference to MainForm and a visual component by adding an instance variable or property to the datamodule class instead that you can refer to in your code. Hard-coding in the MainForm can cause the same kind of problems that hard-coding in DataModule3 - it's a specific instance name rather than just being the current instance of something. If you rename your MainForm to something else, your code won't compile. If you replace MainForm with a different form, but there's another MainForm in scope that hasn't been created yet, your code will also crash.

Related

Create a TDataModule only once

I'm trying to build a procedure that creates a TDataModule in Application as its parent.
The problem is, the second time I call the procedure, the dm parameter still nil. I expect something diferent of nil considering that it was created before.
Here is the code I'm trying:
procedure UseDataModule(dm : TDataModule; cClass:TcomponentClass);
begin
if dm = nil then
cClass.Create(Application);
end;
There are some requirements I want for this procedure:
The given TDataModule should be created once
It must to be created by procedure because I want to use It sometimes, that's why I don't put it in auto-create forms
Its parent will be always Application
Try changing your code to this:
procedure UseDataModule(var dm : TDataModule; cClass:TcomponentClass);
// the `var` qualifier is to allow the value of `dm` to be retained
// after `UseDataModule` exits, otherwise the Created instance will be discarded
// and you will have a memory leak
begin
if dm = nil then
dm := cClass.Create(Application);
end;
Imo, it would be better to code UseDataModule as a function, but that is largely a matter of taste. Note also that you could write if notAssigned(dm) instead of if dm = Nil.
I gather from your comment that you have decided to use the following code instead of my initial suggestion:
procedure UseDataModule(var dm : TDataModule; cClass:TcomponentClass);
begin
if dm = nil then begin
dm := cClass.Create(Application) as TDataModule;
end;
end;
which seems fine to me.
Another option is to use the same code used to autocreate forms with a check to see if it is already created.
// Create data module if it doesn't already exist
if DM = nil then Application.CreateForm(TDM, DM);

Setting dynamicly a property of an object containing datetime

I have a bunch of different methods for creating, calling and setting properties of different objects using Delphi's RTTI. But now I have come to an error where setting a TDateTime triggers an error like: "Cannot convert variant into double". Google doesn't help when searching for this error.
So far, I'm defining an object of any type, for example:
TExample = class
private
FDateField : TDateTime;
published
property DateField : TDateTime read FDateField write FDateField;
end;
I'm then putting this object in a TObjectList, and then looping some internal logic that's not really relevant to the problem. But when I come to the DateField property, it triggers the error. I'm trying to set it like this:
objPropValue := '12/02/2018 12:25:00';
objPropName := 'DateField';
if IsPublishedProp(parameterObject, objPropName) then
begin
SetPropValue(parameterObject, objPropName, objPropValue); <- doesn't work on DateField
end;
This is only a hardcoded example, the objPropValue and Name are set in a loop and can be of any other type. I tried different formatting as well, but I can't seem to find the correct way to do this.
Despite the error message, what you are trying to do here is assign a string to a date, which you just can't do. If instead you did this
objPropValue := '12/02/2018 12:25:00';
objPropName := 'DateField';
if IsPublishedProp(parameterObject, objPropName) then
begin
SetPropValue(parameterObject, objPropName, StrToDateTime(objPropValue));
end;
it would work fine. That is just for illustration, of course. If objPropValue is a variant (which you don't show) you could use
objPropValue := StrToDateTime('12/02/2018 12:25:00');
objPropName := 'DateField';
if IsPublishedProp(parameterObject, objPropName) then
begin
SetPropValue(parameterObject, objPropName, objPropValue);
end;
instead.

How to set a record field as 'Procedure of object' before an object exists so that it can run

Very un-snappy title I know.
I have a series of text lines that I need to perform certain operations on in a certain order. I have come up with a means of doing this by defining the following record structure:
TProcessOrderRecord = record
RecordTypes: TByteSet;
InitialiseProcedure: TPreScanProc;
ProcessProcedure: TProcessRecord;
FinaliseProcedure: TEndScanProc;
end;
AProcessOrderArray = array of TProcessOrderRecord;
Initialise tends to call a constructor which will fill a field in the host object.
Process will be a procedure on the object which will be called for each text line that matches one of the record types in RecordTypes.
Finalise will tend to call the destructor and possibly do any checks when it knows that the full set of records has been processed.
The means of processing this array is quite straightforward:
procedure TImport.ScanTransferFile;
var
i: integer;
lArrayToProcess: AProcessOrderArray;
begin
lArrayToProcess := SetUpProcessingOrder(NLPGApp.ImportType);
for i := low(lArrayToProcess) to high(lArrayToProcess) do
begin
ProcessRecordType(lArrayToProcess[i].RecordTypes, lArrayToProcess[i].InitialiseProcedure, lArrayToProcess[i].ProcessProcedure, lArrayToProcess[i].FinaliseProcedure);
end;
end;
procedure TImport.ProcessRecordType(const RecordTypesToFind: TByteSet; PreScanProcedure: TPreScanProc; OnFindRecord: TProcessRecord; OnCompleteScan: TEndScanProc);
var
lLineOfText: string;
lIntegerRecordID: byte;
begin
if Assigned(PreScanProcedure) then PreScanProcedure;
try
if assigned(OnFindRecord) then
begin
Reader.GoToStartOfFile;
while not Reader.EndOfFile do
begin
lLineOfText := Reader.ReadLine;
lIntegerRecordID := StrToIntDef(GetRecordID(lLineOfText), 0);
if lIntegerRecordID in RecordTypesToFind then
begin
try
OnFindRecord(lLineOfText);
except
on E: MyAppException do
begin
// either raise to exit or log and carry on
end;
end;
end;
end;
end;
finally
// OnCompleteScan usually contains calls to destructors, so ensure it's called
if Assigned(OnCompleteScan) then OnCompleteScan;
end;
end;
My problem is that I want to define a record as such:
RecordTypes = [10]
InitialiseProcedure = ProcToCreateFMyObj
ProcessProcedure = FMyObj.do
FinaliseProcedure = ProcToFreeFMyObj
This compiles fine, however when ProcessProcedure is called, as FMyObj was nil when the ProcessProcedure is set, the instance of TMyObj is nil even though FMyObj is now set. Is there any clean way to get the record to point to the instance of FMyObj at the time of calling rather than at the time of first assignment?
At present I have resorted to having 'caller' methods on the host object which can then call the FMyObj instance when needed, but this is creating quite a bloated object with lots of single-line methods.
Edit to clarify/complicate the problem
Sometimes one instance of FObj can handle more than one types of record (usually if they have a master-detail relationship). In this case, InitialiseProcedure of the first record type will create FObj, FinaliseProcedure of the second record will free FObj and each record's ProcessProcedure can reference different procedures of FObj (do1 and do2).
At present I have resorted to having 'caller' methods on the host object which can then call the FMyObj instance when needed, but this is creating quite a bloated object with lots of single-line methods.
That is the right solution. Since the instance is not available at the point of initialisation you have no alternative.
When you use of object you are defining something called a method pointer. When you assign to a variable of method pointer type, the instance is captured at the point of assignment. There is no mechanism for the instance associated with a method pointer to be dynamically resolved. The only way to achieve that is to use runtime delegation, which is what you are currently doing. As is so often the case, another layer of indirection is used to solve a problem!
Your record that contains a number of methods looks awfully like an interface. I suspect that the most elegant solution will involve an interface. Perhaps at the point of calling you can call a function that returns an interface. And that function will using the value of FMyObj at the time of calling to locate the appropriate interface.
Yes it is possible to make additional runtime initialization of your record:
var
A: TProcessOrderRecord;
begin
..
TMethod(A.ProcessProcedure).Data:= FMyObj;
..
end;
though I would prefer a different solution, like the one you already use.

Access violation when affecting a value to variable?

The code below is written in unit2 ( form2), it calls the values entered in the email and password boxes ( in form1 ), yesterday the code was working perfectly, i made some changes and now : This code doesn't work, it raises an Access Violation error when i click the Button COMMENCER:
procedure TForm2.Btn_commencerClick(Sender: TObject);
begin
email := form1.ed_Email.Text;// <----- LOOK HERE
password := form1.Ed_typedpass.Text; // <-----AND HERE
MD5 := GetMD5;
MD5.Init;
MD5.Update(TByteDynArray(RawByteString(password)), Length(password));
password := LowerCase(MD5.AsString);
etc.......
But this code works :
email := 'myemail#yahoo.com';
password := 'mypass';
MD5 := GetMD5;
MD5.Init;
etc etc......
The question :
Why ?
Where are you creating your form1 object? Sounds like it haven't initialized before you access it and therefore you get AV.
Your second code works, because you don't have to initialize string variables before accessing or assigning values to them and you are assigning them directly, not through the form1 variable.
But breakpoint to email := form1.ed_Email.Text; and look if form1 is nil or not.
i think you have to create the form1 (as i think its available form in your case)..and you may have closed and freed the form so, your
email := form1.ed_Email.Text;
is giving AV, as form1 doesnt exist(as its freed now) ,so u cannot have the ed_Email.Text value.
make sure your not closing the form1 (freeing) before pressing Btn_commencer
You can always check if the form has been created first to avoid access violations.
if assigned(Form1) then
begin
// assignments
end;

Delphi getting value of form components properties

I am implementing a Boilerplate feature - allow users to Change descriptions of some components - like TLabels - at run time.
e.g.
TFooClass = Class ( TBaseClass)
Label : Tlabel;
...
End;
Var FooClass : TFooClass;
...
At design time, the value Label's caption property is say - 'First Name', when the
application is run, there is a feature that allows the user to change the caption
value to say 'Other Name'. Once this is changed, the caption for the label for
the class instance of FooClass is updated immediately.
The problem now is if the user for whatever reason wants to revert back to the design
time value of say 'First Name' , it seems impossible.
I can use the RTTIContext methods and all that but I at the end of the day, it seems
to require the instance of the class for me to change the value and since this
has already being changed - I seem to to have hit a brick wall getting around it.
My question is this - is there a way using the old RTTI methods or the new RTTIContext
stuff to the property of a class' member without instantiating the class - i.e. getting
the property from the ClassType definition.
This is code snippet of my attempt at doing that :
c : TRttiContext;
z : TRttiInstanceType;
w : TRttiProperty;
Aform : Tform;
....
Begin
.....
Aform := Tform(FooClass);
for vCount := 0 to AForm.ComponentCount-1 do begin
vDummyComponent := AForm.Components[vCount];
if IsPublishedProp(vDummyComponent,'Caption') then begin
c := TRttiContext.Create;
try
z := (c.GetType(vDummyComponent.ClassInfo) as TRttiInstanceType);
w := z.GetProperty('Caption');
if w <> nil then
Values[vOffset, 1] := w.GetValue(vDummyComponent.ClassType).AsString
.....
.....
....
....
I am getting all sorts of errors and any help will be greatly appreciated.
The RTTI System does not provide what you are after. Type information is currently only determined at compile time. Initial form values are set at Runtime using the DFM resource. You can change values in a DFM Resource in a compiled application because it's evaluated at runtime.
Parse and use the DFM Resource where it is stored, or make a copy of the original value at runtime. Possibly at the point of initial change to reduce your memory footprint.
Masons Suggestion of using TDictionary<string, TValue> is what I would use. I would be careful of storing this information in a database as keeping it in sync could become a real maintenance nightmare.
Sounds like what you're trying to do is get the value of a certain property as defined in the DFM. This can't be done using RTTI, since RTTI is based on inspecting the structure of an object as specified by its class definition. The DFM isn't part of the class definition; it's a property list that gets applied to the objects after they've been created from the class definitions.
If you want to get the values of the properties of a form's controls, you'll probably have to cache them somewhere. Try putting something in the form's OnCreate that runs through all the controls and uses RTTI to populate a TDictionary<string, TValue> with the values of all the properties. Then you can look them up later on when you need them.
If what you are trying to achieve it to restore the value that was set at design-time (i.e. that one that is saved in the DFM), I'd use InitInheritedComponent as a starting point.
It's possible to get the content of the DFM at runtime. Could be a pain to parse though.
Check also InternalReadComponentRes.
Both routine can be found in the classes unit.
Well - I solved the problem. The trick is basically instantiating another instance of the form like so :
procedure ShowBoilerPlate(AForm : TForm; ASaveAllowed : Boolean);
var
vCount : Integer;
vDesignTimeForm : TForm;
vDesignTimeComp : TComponent;
vDesignTimeValue : String;
vCurrentValue : String;
begin
....
....
vDesignTimeForm := TFormClass(FindClass(AForm.ClassName)).Create(AForm.Owner);
try
// Now I have two instances of the form - I also need to have at least one
// overloaded constructor defined for the base class of the forms that will allow for
// boilerplating. If you call the default Constructor - no boilerplating
// is done. If you call the overloaded constructor, then, boilerplating is done.
// Bottom line, I can have two instances AForm - with boilerplated values and
// vDesignForm without boilerplated values.
for vCount := 0 to AForm.ComponentCount-1 do begin
vDummyComponent := AForm.Components[vCount];
if Supports (vDummyComponent,IdoGUIMetaData,iGetGUICaption) then begin
RecordCount := RecordCount + 1;
Values[vOffset, 0] := vDummyComponent.Name;
if IsPublishedProp(vDummyComponent,'Caption') then begin
vDesignTimeComp := vDesignTimeForm.FindComponent(vDummyComponent.Name);
if vDesignTimeComp <> nil then begin
// get Design time values here
vDesignTimeValue := GetPropValue(vDesignTimeComp,'Caption');
end;
// get current boilerplated value here
vCurrentValue := GetPropValue(vDummyComponent,'Caption');
end;
vOffset := RecordCount;;
end;
end;
finally
FreeAndNil(vDesignTimeForm);
end;
end;
Anyway - thank you all for all your advice.

Resources