I am trying to configure a FireDAC TFDQuery component so it fetches records on demand in batches of no more than 500, but I need it to report back what is the total record count for the query not just the number of fetched records. The FetchOptions are configured as follows:
FetchOptions.AssignedValues = [evMode, evRowsetSize, evRecordCountMode, evCursorKind, evAutoFetchAll]
FetchOptions.CursorKind = ckForwardOnly
FetchOptions.AutoFetchAll = afTruncate
FetchOptions.RecordCountMode = cmTotal
FetchOptions.RowSetSize = 500
This immediately returns all records in the table not just 500. I have tried setting RecsMax to 500, which works in limiting the fetched records, but RecordCount for the query shows only 500 not the total.
The FireDAC help file states that setting RecordCountMode to `cmTotal' causes FireDAC to issue
SELECT COUNT(*) FROM (original SQL command text).
Either there is a bug or I am doing something wrong!
I cannot see what other properties I can change. I am confused as to the relationship between RowSetSize and RecsMax and din't find the help file clarified.
I have tried playing with the properties of AutoFetchAll (Again confused as to this properties' purpose), but noting that is was set to afAll I set it to afTruncate to see if that would make a difference, but it didn't.
I have tested FetchOptions' fmOnDemand Mode with a FDTable component and a FDQuery component. Both with identical settings for FetchOptions ie RowSetSize=50. 425,000 rows in the dataset fetched over a network server.
FDTable performs as expected. It loads just 50 tuples and does so almost instantly. When pressing Ctrl+End to get to the end of the DBGrid display, it loads just 100 tuples. Whilst scrolling it rarely loads more than 100 tuples. Impact on memory negligible. But it is slow in scrolling.
FDQuery loads 50 tuples, but takes around 35 seconds to do so and consumes over 0.5GB of memory in the process. If you press Ctrl+Home to move to the end of the connected DBGrid it does so virtually instantly and in the process loads the entire table and consumes a further 700MB of memory.
I also experimented with CachedUpdates. The results above where with CachedUpdates off. When on, there was no impact at all on the performance of FDQuery (still poor), but for FDTable it resulted in it loading the entire table at start up, taking over half a minute to do so and consuming 1.2GBs of memory.
It looks like fmOnDemand mode is only practically usable with FDTable with CachedUpdates off and is not suitable for use with FDQuery at all.
The results of my tests using fmOnDemand with postgreSQL and MySQL are basically the same. With FDTable fmOnDemand only downloads what it needs limited to the RowSetSize. With a RowSetSize of 50 it initially downloads 50 tuples and no matter where you scroll to it never downloads more than 111 tuples (though doubtless that is dependent on the size of the connected DBGrid. If you disconnect the FDTable from a data source it initially downloads 50 tuples and if you then navigate to any record in the underlying table it downloads one tuple only and discards all other data.
FDQuery in fmOnDemand downloads only the initial 50 tuples when opened, but if you navigate by RecNo it downloads every tuple in between. I had rather hoped it would use LIMIT and OFFSET commands to get only records that were being requested.
To recreate the test for PostGre you need the following FireDAC components:
object FDConnectionPG: TFDConnection
Params.Strings = (
'Password='
'Server='
'Port='
'DriverID=PG')
ResourceOptions.AssignedValues = [rvAutoReconnect]
ResourceOptions.AutoReconnect = True
end
object FDQueryPG: TFDQuery
Connection = FDConnectionPG
FetchOptions.AssignedValues = [evMode, evRowsetSize]
end
object FDTable1: TFDTable
CachedUpdates = True
Connection = FDConnectionPG
FetchOptions.AssignedValues = [evMode, evRowsetSize, evRecordCountMode]
FetchOptions.RecordCountMode = cmFetched
end
If you wish to recreate it with MYSQL, you will basically need the same FireDAC components, but the FDConnectionneeds to be set as follows:
object FDConnectionMySql: TFDConnection
Params.Strings = (
'DriverID=MySQL'
'ResultMode=Use')
ResourceOptions.AssignedValues = [rvAutoReconnect]
ResourceOptions.AutoReconnect = True
end
You'll need an edit box, two buttons, a checkbox, a timer and a label and the following code:
procedure TfrmMain.Button1Click(Sender: TObject);
begin
if not FDQueryPG.IsEmpty then
begin
FDQueryPG.EmptyDataSet;
FDQueryPG.ClearDetails;
FDQueryPG.Close;
end;
if not FDTable1.IsEmpty then
begin
FDTAble1.EmptyDataSet;
FDTable1.ClearDetails;
FDTable1.Close;
end;
lFetched.Caption := 'Fetched 0';
lFetched.Update;
if cbTable.checked then
begin
FDTable1.TableName := '[TABLENAME]';
FDTable1.Open();
lFetched.Caption := 'Fetched '+ FDTable1.Table.Rows.Count.ToString;
end
else
begin
FDQueryPG.SQL.Text := 'Select * from [TABLENAME]';
FDQueryPG.open;
lFetched.Caption := 'Fetched '+ FDQueryPG.Table.Rows.Count.ToString;
end;
timer1.Enabled:=true;
end;
procedure TfrmMain.Button2Click(Sender: TObject);
begin
if cbTable.Checked then
FDTable1.RecNo := strToInt(Edit1.Text)
else
FDQueryPG.RecNo := strToInt(Edit1.Text);
end;
procedure TfrmMain.cbTableClick(Sender: TObject);
begin
timer1.Enabled := False;
end;
procedure TfrmMain.Timer1Timer(Sender: TObject);
begin
if cbTable.checked then
lFetched.Caption := 'Fetched '+ FDTable1.Table.Rows.Count.ToString
else
lFetched.Caption:='Fetched '+FDQueryPG.Table.Rows.Count.ToString;
lFetched.Update;
end;
Related
I am trying to have a loop where it will start at 100 and drop until it hits to a point where the while condition no longer holds true.
I started with
While Solar_Power_House_W_Solar_PER <= OneHundred AND BatChargePercent < OneHundred DO
State_Dis_Charge := false
FOR PLC_SetLoopChargeValue:= 100 TO 0 By -1 DO
ConvertoReal := INT_TO_LREAL(PLC_SetLoopChargeValue);
Divide := ConvertoReal DIV(100);
PLC_SetCharge := Divide;
PLC_Charge := 1500 * PLC_SetCharge;
RB_Charge := PLC_Charge;
Visual_RBPower := 1500 * PLC_SetCharge; (*Charge *)
END_FOR;
The problem I believe I have with this is that it cycles too fast so the condition never gets out of the while loop because it takes a while for the system to update so I thought of adding a delay portion:
While Solar_Power_House_W_Solar_PER <= OneHundred AND BatChargePercent < OneHundred DO
State_Dis_Charge := false;
wait(IN:=not wait.Q , PT:=T#50ms);
if Wait.Q Then
FOR PLC_SetLoopChargeValue:= 100 TO 0 By -1 DO
ConvertoReal := INT_TO_LREAL(PLC_SetLoopChargeValue);
Divide := ConvertoReal DIV(100);
PLC_SetCharge := Divide;
PLC_Charge := 1500 * PLC_SetCharge;
RB_Charge := PLC_Charge;
Visual_RBPower := 1500 * PLC_SetCharge; (*Charge *)
END_FOR;
END_IF;
END_WHILE;
How I think it should work is every 50ms 1 for loop should run. Currently nothing happens every 50ms.
You have to consider that WHILE and FOR are executed synchronously. It means blocking. It means that interpreter do not execute next line, until previous line is finished.
This means that "running to fast" cannot apply here. It does not matter how fast it runs, the execution of the lines will be always in order.
The only thing I would change and loop not from 100 to 0 but vice versa from 0 to 100, because I am not sure this backward will work fine. And then all you have to change:
ConvertoReal := INT_TO_LREAL(100 - PLC_SetLoopChargeValue);
You do now show all code it is VERY HARD to judge but if FOR loom is complete it totally make no sense. You calculate some variables but you do not use them there. You know that you cannot use them outside of your FOR loop, right? Because outside of your FOR loop those variable will be always same value of last loop.
In your second example your FOR loop, although it might work, you should not use timer to run the loop inside the loop. Because loops are synchronous and times async.
As I understand you task you do not need WHILE at all. With this approach your program execution of other parts will be blocked until 100%. That might take a while as I can see. So you have to use IF.
IF Solar_Power_House_W_Solar_PER <= OneHundred AND BatChargePercent < OneHundred DO
// ....
END_IF;
The difference is significant. With WHILE it will block your program till WHILE finish and other parts will not be executed for this long, in the same PLC cycle FOR might be executed so many times.
With IF if will run FOR one time per one PLC cycle and actualy doe snot change your logic.
If you would share your full code or at least parts where variables you have here are used so that the whole picture is visible, you might get a better help. Edit your post and I'll edit my comment.
With this answer im only adressing your issue with the for loop not being executed every 50ms.
The other answers why the while loop cant be exited are correct unless the variables Solar_Power_House_W_Solar_PER and BatChargePercent aren't changed in a parrellel thread.
I suggest wait is a TON function block. Please mind that names of FBs are case sensitive: wait.Q is possibly unequal Wait.Q. I think that is the main reason your for loop is not executed, because you check the output of another FB. Maybe check your declaration list for doubles with higher or lower cases.
Another possibility is, that your condition for the while loop isn't met at all and you didn't notice. In this case the for loop wouldn't be executed too of course.
I need help with this solution.
There is application with several forms. One of this forms need to be opened on selected monitor. For example:
Solution 1. OnCreate form cheks if there are more than one monitor used, and open on the last one. I try this code, but with no luck:
Application.CreateForm(TfrmDashboard, frmDashboard);
for I := 0 to Screen.MonitorCount -1 do
begin
// Checking Screen Position
ShowMessageFmt('%d, %d, %d, %d',
[Screen.Monitors[i].BoundsRect.Left,
Screen.Monitors[i].BoundsRect.Top,
Screen.Monitors[i].BoundsRect.Right,
Screen.Monitors[i].BoundsRect.Bottom]);
end;
if Screen.MonitorCount > 1 then
begin
frmDashboard.Top:= Screen.Monitors[i].BoundsRect.Top;
frmDashboard.Top:= Screen.Monitors[i].BoundsRect.Left;
end;
Solution 2. Form is dragged to the selected monitor and OnDestroy event Top and Left position are written to the INI file. Next time form opens on the same monitor and same position. I try this code, but also with no luck:
procedure TfrmDashboard.FormCreate(Sender: TObject);
var
ini: TIniFile;
begin
ini:= TIniFile.Create(extractfilepath(paramstr(0))+'Dashboard.ini');
Left:= StrToInt(ini.ReadString('Left', 'Form_Left', '0'));
Top:= StrToInt(ini.ReadString('Top', 'Form_Top', '0'));
ini.Free;
end;
procedure TfrmDashboard.FormDestroy(Sender: TObject);
var
ini: TIniFile;
begin
ini:= TIniFile.Create(extractfilepath(paramstr(0))+'Dashboard.ini');
ini.WriteString('Left', 'Form_Left', IntToStr(Left));
ini.WriteString('Top', 'Form_Top', IntToStr(Top));
ini.Free;
end;
frmDashboard.Top:= ...
frmDashboard.Top:= ...
This appears to be a simple copy paste error. You set Top both times. Presumably you mean:
frmDashboard.Top:= ...
frmDashboard.Left:= ...
This code makes the same mistake:
if Screen.MonitorCount > 1 then
begin
frmDashboard.Top:= Screen.Monitors[i].BoundsRect.Top;
frmDashboard.Top:= Screen.Monitors[i].BoundsRect.Left;
end;
Furthermore, it refers to i when it is ill-defined. The compiler will warn about this. I hope you have compiler warnings and hints enabled, and do take heed of them.
Your OnCreate event handler will raise an exception if the INI file contains invalid data. For instance, if the user edits the position values to be non-numeric, then StrToInt will raise an exception. Your program should be resilient to that.
Both the OnCreate and OnDestroy event handlers don't manage the lifetime of the INI file object correctly. If the INI file access fails, or the calls to StrToInt fail (see above) then you will leak the object. This is the pattern to be followed:
obj := TMyObject.Create;
try
// do things with obj
finally
obj.Free;
end;
I developed a procedure that receives a TStream; but the basic type, to allow the sending of all the types of stream heirs.
This procedure is intended to create one thread to each core, or multiple threads. Each thread will perform detailed analysis of stream data (read-only), and as Pascal classes are assigned by reference, and never by value, there will be a collision of threads, since the reading position is intercalará.
To fix this, I want the procedure do all the work to double the last TStream in memory, allocating it a new variable. This way I can duplicate the TStream in sufficient numbers so that each thread has a unique TStream. After the end of the very thread library memory.
Note: the procedure is within a DLL, the thread works.
Note 2: The goal is that the procedure to do all the necessary service, ie without the intervention of code that calls; You could easily pass an Array of TStream, rather than just a TStream. But I do not want it! The aim is that the service is provided entirely by the procedure.
Do you have any idea how to do this?
Thank you.
Addition:
I had a low-level idea, but my knowledge in Pascal is limited.
Identify the object's address in memory, and its size.
create a new address in memory with the same size as the original object.
copy the entire contents (raw) object to this new address.
I create a pointer to TStream that point to this new address in memory.
This would work, or is stupid?? If yes, how to operate? Example Please!
2º Addition:
Just as an example, suppose the program perform brute force attacks on encrypted streams (just an example, because it is not applicable):
Scene: A 30GB file in a CPU with 8 cores:
1º - TMemoryStream:
Create 8 TMemoryStream and copy the entire contents of the file for each of TMemoryStreams. This will result in 240GB RAM in use simultaneously. I consider this broken idea. In addition it would increase the processing time to the point of fastest not use multithreading. I would have to read the entire file into memory, and then loaded, begin to analyze it. Broke!
* A bad alternative to TMemoryStream is to copy the file slowly to TMemoryStream in lots of 100MB / core (800MB), not to occupy the memory. So each thread looks only 100MB, frees the memory until you complete the entire file. But the problem is that it would require Synchronize() function in DLL, which we know does not work out as I open question in Synchronize () DLL freezes without errors and crashes
2º - TFileStream:
This is worse in my opinion. See, I get a TStream, create 8 TFileStream and copy all the 30GB for each TFileStream. That sucks because occupy 240GB on disk, which is a high value, even to HDD. The read and write time (copy) in HD will make the implementation of multithreaded turns out to be more time consuming than a single thread. Broke!
Conclusion: The two approaches above require use synchronize() to queue each thread to read the file. Therefore, the threads are not operating simultaneously, even on a multicore CPU. I know that even if he could simultaneous access to the file (directly creating several TFileStream), the operating system still enfileiraria threads to read the file one at a time, because the HDD is not truly thread-safe, he can not read two data at the same time . This is a physical limitation of the HDD! However, the queuing management of OS is much more effective and decrease the latent bottleneck efficiently, unlike if I implement manually synchronize(). This justifies my idea to clone TStream, would leave with S.O. all the working to manage file access queue; without any intervention - and I know he will do it better than me.
Example
In the above example, I want 8 Threads analyze differently and simultaneously the same Stream, knowing that the threads do not know what kind of Stream provided, it can be a file Stream, a stream from the Internet, or even a small TStringStream . The main program will create only one Strean, and will with configuration parameters. A simple example:
TModeForceBrute = (M1, M2, M3, M4, M5...)
TModesFB = set of TModeForceBrute;
TService = record
stream: TStream;
modes: array of TModesFB;
end;
For example, it should be possible to analyze only the Stream M1, M2 only, or both [M1, M2]. The TModesFB composition changes the way the stream is analyzed.
Each item in the array "modes", which functions as a task list, will be processed by a different thread. An example of a task list (JSON representation):
{
Stream: MyTstream,
modes: [
[M1, m5],
[M1],
[M5, m2],
[M5, m2, m4, m3],
[M1, m1, m3]
]
}
Note: In analyzer [m1] + [m2] <> [m1, m2].
In Program:
function analysis(Task: TService; maxCores: integer): TMyResultType; external 'mydll.dll';
In DLL:
// Basic, simple and fasted Exemple! May contain syntax errors or logical.
function analysis(Task: TService; maxCores: integer): TMyResultType;
var
i, processors : integer;
begin
processors := getCPUCount();
if (maxCores < processors) and (maxCores > 0) then
processors := maxCores;
setlength (globalThreads, processors);
for i := 0 to processors - 1 do
// It is obvious that the counter modes in the original is not the same counter processors.
if i < length(Task.modes) then begin
globalThreads[i] := TAnalusysThread.create(true, Task.stream, Task.modes[i])
globalThreads[i].start();
end;
[...]
end;
Note: With a single thread the program works beautifully, with no known errors.
I want each thread to take care of a type of analysis, and I can not use Synchronize() in DLL. Understand? There is adequate and clean solution?
Cloning a stream is code like this:
streamdest:=TMemoryStream.create;
streamsrc.position:=0;
streamdest.copyfrom(streamdest);
streamsrc.position:=0;
streamdest.position:=0;
However doing things over DLL borders is hard, since the DLL has an own copy of libraries and library state. This is currently not recommended.
I'm answering my question, because I figured that no one had a really good solution. Perhaps because there is none!
So I adapted the idea of Marco van de Voort and Ken White, for a solution that works using TMemoryStream with partial load in memory batch 50MB, using TRTLCriticalSection for synchronization.
The solution also contains the same drawbacks mentioned in addition 2; are they:
Queuing access to HDD is the responsibility of my program and not of the operating system;
A single thread carries twice the same data in memory.
Depending on the processor speed, it may be that the thread analyze well the fast 50MB of memory; On the other hand, to load memory can be very slow. That would make the use of multiple threads are run sequentially, losing the advantage of using multithreaded, because every thread are congested access to the file, running sequentially as if they were a single thread.
So I consider this solution a dirty solution. But for now it works!
Below I give a simple example. This means that this adaptation may contain obvious errors of logic and / or syntax. But it is enough to demonstrate.
Using the same example of the issue, instead of passing a current to the "analysis" is passed a pointer to the process. This procedure is responsible for making the reading of the stream batch 50MB in sync.
Both DLL and Program:
TLotLoadStream = function (var toStm: TMemoryStream; lot, id: integer): int64 of object;
TModeForceBrute = (M1, M2, M3, M4, M5...)
TModesFB = set of TModeForceBrute;
TaskTService = record
reader: TLotLoadStream; {changes here <<<<<<< }
modes: array of TModesFB;
end;
In Program:
type
{ another code here }
TForm1 = class(TForm)
{ another code here }
CS : TRTLCriticalSection;
stream: TFileStream;
function MyReader(var toStm: TMemoryStream; lot: integer): int64 of object;
{ another code here }
end;
function analysis(Task: TService; maxCores: integer): TMyResultType; external 'mydll.dll';
{ another code here }
implementation
{ another code here }
function TForm1.MyReader(var toStm: TMemoryStream; lot: integer): int64 of object;
const
lotSize = (1024*1024) * 50; // 50MB
var
ler: int64;
begin
result := -1;
{
MUST BE PERFORMED PREVIOUSLY - FOR EXAMPLE IN TForm1.create()
InitCriticalSection (self.CriticalSection);
}
toStm.Clear;
ler := 0;
{ ENTERING IN CRITICAL SESSION }
EnterCriticalSection(self.CS);
{ POSITIONING IN LOT OF BEGIN}
self.streamSeek(lot * lotSize, soBeginning);
if (lot = 0) and (lotSize >= self.stream.size) then
ler := self.stream.size
else
if self.stream.Size >= (lotSize + (lot * lotSize)) THEN
ler := lotSize
else
ler := (self.stream.Size) - self.stream.Position; // stream inicia em 0?
{ COPYNG }
if (ler > 0) then
toStm.CopyFrom(self.stream, ler);
{ LEAVING THE CRITICAL SECTION }
LeaveCriticalSection(self.CS);
result := ler;
end;
In DLL:
{ another code here }
// Basic, simple and fasted Exemple! May contain syntax errors or logical.
function analysis(Task: TService; maxCores: integer): TMyResultType;
var
i, processors : integer;
begin
processors := getCPUCount();
if (maxCores < processors) and (maxCores > 0) then
processors := maxCores;
setlength (globalThreads, processors);
for i := 0 to processors - 1 do
// It is obvious that the counter modes in the original is not the same counter processors.
if i < length(Task.modes) then begin
globalThreads[i] := TAnalusysThread.create(true, Task.reader, Task.modes[i])
globalThreads[i].start();
end;
{ another code here }
end;
In DLL Thread Class:
type
{ another code here }
MyThreadAnalysis = class(TThread)
{ another code here }
reader: TLotLoadStream;
procedure Execute;
{ another code here }
end;
{ another code here }
implementation
{ another code here }
procedure MyThreadAnalysis.Execute;
var
Stream: TMemoryStream;
lot: integer;
{My analyzer already all written using buff, the job of rewriting it is too large, then it is so, two readings, two loads in memory, as I already mentioned in the question!}
buf: array[1..$F000] of byte; // 60K
begin
lot := 0;
Stream := TMemoryStream.Create;
self.reader(stream, lot);
while (assigned(Stream)) and (Stream <> nil) and (Stream.Size > 0) then begin
Stream.Seek(0, soBeginning);
{ 2º loading to memory buf }
while (Stream.Position < Stream.Size) do begin
n := Stream.read(buf, sizeof(buf));
{ MY CODE HERE }
end;
inc(lot);
self.reader(stream, lot, integer(Pchar(name)));
end;
end;
So as seen this is a stopgap solution. I still hope to find a clean solution that allows me to double the flow controller in such a way that access to data is the operating system's responsibility and not my program.
I am automating an open source program written in Delphi. From the main form, I am performing the following loop:
for i := 0 to analysisNames.Count - 1 do begin
currentAnalysisName := analysisNames[i];
analysisID := DatabaseModule.GetAnalysisIDForName(analysisNames[i]);
frmIIGraph.autoMode := true;
frmIIGraph.ShowModal();
end;
As you can see, it opens a form called frmIIGraph. Inside that form, I must open another form, which I do with the following code:
procedure TfrmIIGraph.FormActivate(Sender: TObject);
begin
if autoMode then begin
events := DatabaseModule.GetEvents(analysisID);
frmEventEdit.autoMode := true;
frmEventEdit.OpenDialog(events,0,analysisID);
frmEventEdit.ShowModal();
//frmEventEdit.Close;
SetFocus;
ModalResult := mrOK;
PostMessage(Self.Handle,wm_close,0,0);
end;
end;
The form opened from the above method is called frmEventEdit. Within that form I am running this code:
procedure TfrmEventEdit.FormActivate(Sender: TObject);
begin
if autoMode then begin
btnRTK_CalcClick(nil);
ModalResult := mrOK;
PostMessage(Self.Handle,wm_close,0,0);
end;
end;
The problem is that the PostMessage(Self.Handle,wm_close,0,0); in the latter code works fine and closes the form, resuming the code on the frmIIgraph at SetFocus;. However, the PostMessage(Self.Handle,wm_close,0,0); in the IIGraph form code, does not close the graph form, so that execution can resume on the main form, for the next iteration of the loop. You have to manually close the graph for it to proceed.
Any help is appreciated.
Your fundamental problem is that you have coded all your business logic in GUI code. So you are not able to execute the code that you want to execute without the convoluted code seen in the question.
If you want to solve your real problem you will deal with the root cause of your woes. You will separate the business logic and the GUI code. You will arrange for your business logic to be able to be executed in the absence of GUI.
If you don't want to solve your real problem, and wish to continue with this madness, you need to post a WM_CLOSE message to frmIIGraph.Handle in the OnDeactivate event handler for TfrmEventEdit. Presumably the one you post in TfrmIIGraph.FormActivate is getting consumed by the sub-form's message loop, or perhaps some call to ProcessMessages. But I cannot endorse this as a sane way to proceed.
im working on a project using delphi 7, The project is a maintenance project and im not the original coder of the project, i have a situation where i need to close a available form after it had been created through code under certain situations,The form is model
here is sample code of that
var
frmStratum : TfrmStratum;
begin
if not assigned(frmStratum) then myMainForm.OnExecute(PropAction);
end;
inside myMainForm.OnExecute(PropAction); i have
frmStratum := TfrmStratum.Create(Self, Self as IStratum,inttostr(m_surveyno),Module,m_stations,false);
now the procedure TfrmStratum.FormActivate of TfrmStratum i do lots of calucaltion and write to database
var
if (bMassStratumExport) AND (bDoneOne) then
begin
//write to database..
end;
now i have to do this atleast 20 times
that is
1. Create the form
2. onactivate do database writing
3. close TfrmStratum
since it is a modal form i cannot close if below from where i create it,so i wanted to close it onactivate as soon as the step 2 is done
now i have tried this
if (bMassStratumExport) AND (bDoneOne) AND NOT (bReadyToclose) then
begin
//do database writing
if bNowClo then frmStratum.close;
end
EDIT :(edited to make the question more clear)
Onactivate of the form(frmStratum) , i want to close the modal form (frmStratum),so i do this
procedure TfrmStratum.FormActivate(Sender: TObject);
begin
if (bMassStratumExport) AND (bDoneOne) AND NOT (bReadyToclose) then
begin
//do database writing
if bNowClo then self.close;// i need to close the form after after doing database write
end
end;
but the control while bugging goes to self.close but it doesnt close the form.
how to tackle this ?
In the past when I needed to close a form during activation I posted a message to myself instead of calling self.close.
PostMessage(Self.Handle, WM_CLOSE, 0, 0);
I tried to find my original source that pointed me in this direction but I could not find it.
PostMessage will return immediately and not wait for the message to be processed. Once the OnActivate function is finished and the message Delphi processing loop processes the message close will be called on your form.
Assuming its frmStatum, being invalid that's giving you the A/V exception
if bNowClo then self.close;