I have a form that I open multiple instances of. In there I use a SQL query to read some data from a database (Absolute Database in this case). I then fill out a few variables with data and also use the query for other operations during the lifetime of the form.
The problem is that when I open more than one instance of the form, the data change on the old forms to values from the last opened form.
I create the query in the form when it's opened so I thought that they wouldn't share the same data.
I don't use auto-created forms.
How can avoid this from happening?
** Edit - Added some code **
I create the form as follows:
procedure TfrmMain.OpenFormsFunction(Sender: TObject);
var
Resultater: TfrmregResultat;
begin
// Some code for checking if forms are already open
// Locate record in table to be used as paramdata in the query in the newly opened form
DataX.tStevneHead.Locate('StevneNummer;GrenType', VarArrayOf([StevneNummer, GrenType), []);
Resultater := TfrmRegResultat.Create(nil);
Resultater.Show; // OnClose action for the form is caFree
end;
Snip from the form's code
// decleared in implementation
Dato: TDate;
GrenType: string;
GrenRunder: integer;
MaxPoeng: integer;
procedure TfrmRegResultat.FormShow(Sender: TObject);
begin
//-- qStevneHead is a TABSQuery component on the form
//-- DataX is the name of my DataModule
with qStevneHead do
begin
Close;
SQL.Clear;
SQL.Text := 'SELECT * FROM StevneHead WHERE GrenType = :aGrenType AND StevneNummer = aStevneNummer';
ParamByName('aGrenType').AsString := DataX.tStevneHead.FieldByName('GrenType').Value;
ParamByName('aStevneNummer').AsString := DataX.tStevneHead.FieldByName('StevneNummer').Value;
Open;
end;
GrenRunder := qStevneHead.FieldByName('AntallRunder').Value;
Dato := qStevneHead.FieldByName('Dato').AsDateTime;
GrenType := qStevneHead.FieldByName('GrenType').Value;
MaxPoeng := qStevneHead.FieldByName('MaxPoeng').Value;
// More code to init stringgrid and other stuff on the form
end;
The problem seems to be that you're using global variables. Change them to be fields of the form and your problem should be solved. IOW, now you have
implementation
var
Dato: TDate;
GrenType: string;
GrenRunder: integer;
MaxPoeng: integer;
Change it to
type
TfrmRegResultat = class(...)
...
private
Dato: TDate;
GrenType: string;
GrenRunder: integer;
MaxPoeng: integer;
...
end;
If you need to access the values from some other part of the program (ie in the main form) then make them public propertyes or fields and access them via the currently active form variable.
Related
A HelpNDoc file allows you to provide a text value for the Description property. This field is used by search engines when crawling the HTML help.
I have added topics to my help over the years and some of these descriptions need updating. In my case I needed to replace all instances of Midweek Editor with Meeting Editor.
How can this be done since there is no built-in way to update the Description property in bulk.
This can be done by writing a script and using the HelpNDoc API. These scripts can be built and run with the Script Editor. The object we need to use is HndTopics.
The HndTopics object includes some useful methods:
GetTopicDescription
SetTopicDescription
These can be used in combination with the Pascal functions Pos / StringReplace.
var
// Current topic ID
aTopicId, aTopicDesc, aTopicDescNew: string;
begin
try
// Get first topic
aTopicId := HndTopics.GetTopicFirst();
// Loop through all topics
while aTopicId <> '' do
begin
// Does this topic description include the phrase?
aTopicDesc := HndTopics.GetTopicDescription(aTopicId);
if (pos('Midweek Editor', aTopicDesc) <> 0) then
begin
aTopicDescNew := StringReplace(aTopicDesc, 'Midweek Editor', 'Meeting Editor', [rfReplaceAll]);
HndTopics.SetTopicDescription(aTopicId, aTopicDescNew);
Print('Old: ' + aTopicDesc);
Print('New: ' + aTopicDescNew);
end;
// Get next topic
aTopicId := HndTopics.GetTopicNext(aTopicId);
end;
finally
end;
end.
There are 2 tables linked by master-detail. When adding a new value to the detail table, the foreign key selected from the master table is not bound.
The M-D connection itself is performed on the form using two Dblookupcombobox and DataSource, ADOQuery for each, respectively.
enter image description here
Using the [ + ] buttons, new values are added that are not present in the combobox. But the problems start at the second [ + ] (aka detail), when creating a new line, you need it to bind the foreign key from the previous LookUpComboBox (Master). Button code of the second button [+]:
begin
Form4.ADOQuery1.SQL.Clear;
Form4.ADOQuery1.SQL.Add('Select City from City WHERE City='+#39+Form5.DBEdit1.Text+#39); //checking for duplicates
Form4.ADOQuery1.Open;
if Form4.ADOQuery1.IsEmpty then
begin
Form4.Query_city.FieldByName('City').AsString := Form5.DBEdit1.Text; //The PROBLEM is SOMEWHERE HERE! It Adds a new value without binding the foreign key
Form4.Query_city.Open;
Form4.Query_city.Post;
MessageBox(Handle, 'New data entered','Adding a new value',MB_ICONINFORMATION);
end
else
begin
Form4.Query_spec.Cancel;
Form4.ADOQuery1.Cancel;
MessageBox(Handle,PChar(''+Form5.DBEdit1.text+' already on the list!'),'Error',MB_ICONWARNING);
end;
end;
The new value is written to DBEdit1. It has a corresponding binding to tables.
So How i can insert field with with the corresponding foreign key?
You are making this unneccessarily difficult because of the way your
code is structured. Try something like this instead:
Open ADOQuery1, SELECTing its entire contents, with e.g.
procedure TForm4.OpenCitiesTable;
begin
ADOQuery1.SQL.Clear;
ADOQuery1.SQL.Add('Select City from City');
ADOQuery1.Open;
end;
and leave it open while the user does whatever operations lead to wanting to add a new city.
Then, when the users wants to add a city, call this procedure, e.g. supplying the City
value from Form5.DBEdit1.Text.
procedure TForm4.AddCity(ACity : String);
begin
ACity := Trim(ACity); // remove any leading or trailing blanks
// Do any formatting checks on ACity here, re.g convert it to Proper case
// check for adding a duplicate
if ADOQuery1.Locate('City', ACity, []) then begin
ShowMessageFmt('%s is already in the City table', [ACity]);
Exit;
end;
try
ADOQuery1.Insert;
ADOQuery1.FieldByName('City').AsString := ACity;
finally
ADOQuery1.Post;
// at this point you might want to refresh the contents of whatever source
// you are populating Form5.DBEdit1.Text from
end;
end;
I assume you can adjust the code relating to TForm4.Query_spec yourself;
Btw, you might want to consider using a TDBLookUpComboBox instead of your DBEdit1.
This is the error I get when I run the code below. I'm not too sure what's wrong any help is appreciated
procedure TFSearchMember.btnSearchClick(Sender: TObject);
var buttonSelected:integer;
WhereTextSelection,WhereFieldSelection:string;
begin
WhereFieldSelection:=cboWhereField.Text;
WhereTextSelection:=txtWhere.Text;
adoQuery1.Parameters[0].Value:=WhereTextSelection;
adoQuery1.Open;
ADOQuery1.Requery;
txtWhere.Text:='';
cboWhereField.Text:='';
if ADOQuery1.RecordCount=0 then
begin
buttonSelected:=MessageDlg('Record not found.', mtError, [mbOK],0);
if buttonSelected = mrOK then
Exit;
end;
I also have this in the ADOQuery SQL property
Although your question doesn't say so, I'd suspect that you're trying to allow the user to both select the field to use in the WHERE (from cboWhereField) and the value (from txtWhere). If that's the case, you have to update the SQL first before you can assign the parameter, as both are changing.
Something like this should work (in a basic sense):
procedure TFSearchMember.btnSearchClick(Sender: TObject);
const
SQLBase = 'SELECT * FROM tblMembers WHERE %s = :SearchValue';
begin
if AdoQuery1.Active then
AdoQuery1.Close;
AdoQuery1.SQL.Text := Format(SQLBase, [cboWhereField.Text]);
AdoQuery1.Parameters.ParamByName('SearchValue').Value := txtWhere.Text;
AdoQuery1.Open;
txtWhere.Text:='';
cboWhereField.Text:='';
if not ADOQuery1.IsEmpty then
begin
// No need for if test here, as you're only offering one value.
// MessageDlg can't return anything other than mrOK
MessageDlg('Record not found.', mtError, [mbOk], 0);
Exit;
end;
// Your other code
end;
I assume that not all code is shown, because it seems that you try to create dynamic SQL where the "WhereFieldSelection" can be changed to whatever is shown in the combobox. If you change the SQL.Text property (e.g. to use "Forename") of the ADOQuery AFTER the parameter value is set, the value of the parameter is lost, and you will have to reset it. Also, the Requery, is not necessary, as Ken White mentioned.
I have 2 tables related by ID on MS SQL. Delphi-interface looks like 2 DBGrids. By selecting one record from the top table, the bottom table shows all the records with this ID. DBGrid is connected to a stored procedure (TMSStoredProc) that simply displays all records with a given ID. Top table AfterScroll event:
Bottom_table_SP.ParamByName('#ID').AsInteger := Top_table_SP.FieldByName('ID').AsInteger;
Bottom_table_SP.Active := False;
Bottom_table_SP.Active := True;
Everything's very simple, and it works. But while I scroll fast top table, the whole form starts to blink - size of top-table and bottom-table changes in the millisecond. Does anyone know how to handle this kind of problem?
Since you mention SQL Server, I'm guessing you're using the ADO components (TADOStoredProcedure) here, so the normal MasterFields and MasterSource properties aren't available. You still have the basic functionality for other things, so you should use TDataSet.DisableControls and EnableControls to avoid this flickering:
Bottom_table_SP.DisableControls;
try
Bottom_table_SP.Active := False;
Bottom_table_SP.ParamByName('#ID').AsInteger := Top_table_SP.FieldByName('ID').AsInteger;
Bottom_table_SP.Active := True;
finally
Bottom_table_SP.EnableControls;
end;
You might find Disabling and Enabling Data Display useful to explain.
I have a scenario in which I have to export data of around 500,000 records from sql table to be used in Delphi application. The data is to be loaded into a packed record. Is there a method in which i can use the BCP to write data file similar to that of writing the records to file.
As of now I am loading the data using this psudo code.
// Assign the data file generated from BCP to the TextFile object.
AssignFile(losDataFile, loslFileName);
Reset(losDataFile);
while not EOD(losDataFile) do
begin
// Read from the data file until we encounter the End of File
ReadLn(losDataFile, loslDataString);
// Use the string list comma text to strip the fields
loclTempSlist.CommaText := loslDataString;
// Load the record from the items of the string list.
DummyRec.Name := loclTempSList[0];
DummyRec.Mapped = loclTempSList[1] = 'Y';
end;
For convenience i have listed the type of Dummy rec below
TDummyRec = packed record
Name : string[255];
Mapped : Boolean;
end;
So, my question is, instead of exporting the data to a text file, will it be possible to export the data to binary so that i can read from the file directly using the record type?
like
loclFileStream := TFileStream.Create('xxxxxx.dat', fmOpenRead or fmShareDenyNone);
while loclFileStream.Position < loclFileStream.Size do
begin
// Read from the binary file
loclFileStream.Read(losDummyData, SizeOf(TDummyRec));
//- -------- Do wat ever i want.
end;
I don't have much experience on using the BCP. Please help me with this.
Thanks
Terminator...
In your record, a string[255] will create a fixed-size Ansi string (i.e. a so-called shortstring). This type is clearly deprecated, and should not be used in your code.
It will be an awful waste of space to save it directly, using a TFileStream (even if it will work). Each record will store 256 bytes for each Name.
And using a string[255] (i.e. a so-called shortstring) will make an hidden conversion to a string for most access to it. So it is not the best option, IMHO.
My advice is to use a dynamic array then serialize / unserialize it with our Open Source classes. For your storage, you can use a dynamic array. Works from Delphi 5 up to XE2. And you'll be able to use a string in the record:
TDummyRec = packed record
Name : string; // native Delphi string (no shortstring)
Mapped : Boolean;
end;
Edit after OP's comment:
BCP is just a command-line tool meant to export a lot of rows into a SQL table. So IMHO BCP is not the good candidate for your purpose.
You seems to need to import a lot of rows from a SQL table.
In this case:
Using shortstring will be in all case a waste of memory, so you'll get faster out of memory than with using a good string;
You can try our Open Source classes to retrieve all data rows one by one, then populate your records using this data: see SynDB classes - it is lighter than ADO; Then you'll be able to retrieve the record data one by one, then use our record serialization functions to create some binary content - or try a dedicated faster engine like our SynBigTable;
There are some articles about using directly the OleDB feature used by BCP from Delphi code in here - it is in french, but you can use google to translate it and here for fast bulk copy; full source code included.
You want to read a SQL-table into a record, I have no idea why you are working with the archaic AssignFile.
You should really use a TADOQuery (or suitable variant) for you database.
Put a sensible SQL-query in it; something like:
SELECT field1, field2, field3 FROM tablename WHERE .....
When in doubt you can use:
SELECT * FROM tablename
Which will select all fields from the table.
The following code will walk through all the records and all the fields and save them in a variants and save that in a FileStream.
function NewFile(Filename: string): TFileStream;
begin
Result:= TFileStream.Create(Filename, fmOpenWrite);
end;
function SaveQueryToFileStream(AFile: TFileStream; AQuery: TADOQuery): boolean;
const
Success = true;
Failure = false;
UniqueFilePrefix = 'MyCustomFileTypeId';
BufSize = 4096;
var
Value: variant;
Writer: TWriter;
FieldCount: integer;
c: integer;
RowCount: integer;
begin
Result:= Success;
try
if not(AQuery.Active) then AQuery.Open
FieldCount:= AQuery.Fields.Count;
Writer:= TWriter.Create(AFile, BufSize);
try
Writer.WriteString(UniqueFilePrefix)
//Write the record info first
Writer.WriteInteger(FieldCount);
//Write the number of rows
RowCount:= AQuery.RecordCount;
WriteInteger(RowCount);
AQuery.First;
while not(AQuery.eof) do begin
for c:= 0 to FieldCount -1 do begin
Value:= AQuery.Fields[c].Value;
Writer.WriteVariant(Value);
end; {for c}
AQuery.Next;
end; {while}
except
Result:= failure;
end;
finally
Writer.Free;
end;
end;