I are using Delphi Berlin 10.1
When I create a TFDQuery it dont seem to be able to access it in my code using Findcomponent.
creation
procedure TDataModule1.QuVerConAfterOpen(DataSet: TDataSet);
begin
QuVerCon.First;
while not QuVerCon.Eof do
begin
if QuVerCon.FieldByName('Table').AsString <> 'versioncontrol' then
begin
TFDQuery.Create(self).Name := 'Qu' + QuVerCon.FieldByName('Table').AsString;
with TFDQuery(FindComponent('Qu' + QuVerCon.FieldByName('Table').AsString)) do
begin
Connection := SqlConn;
Open('select * From ' + QuVerCon.FieldByName('Table').AsString);
end;
end;
QuVerCon.Next;
end;
FMMain.Filter1.FormatScrn(FMMain.Filter1);
end;
Disconnection
procedure TDataModule1.SqlConnBeforeDisconnect(Sender: TObject);
var
Tbl: TFDQuery;
begin
with QuVerCon do
begin
First;
if FieldByName('Table').AsString <> 'versioncontrol' then
begin
Tbl := TFDQuery(FindComponent('Qu' + FieldByName('Table').AsString));
if Assigned(Tbl) then
TFDQuery(FindComponent('Qu' + FieldByName('Table').AsString)).Free;
end;
Next;
end;
QuVerCon.Close;
end;
The code skips over "if Assigned(Tbl) then" as it cant find it
where are I going wrong?
Is the a way to show a message box with a list of components of the data module so I can check when is disappears?
Related
I am currently doing a school project, I am making a Credit Card machine. I need the 'Enter Button' to
run different code when it is clicked. The first click must get the card number from an edit ps... (I clear the edit once the card number has been retrieved), and the second click must get the pin from the same edit.
How would I do this?
procedure TfrmMainMenu.btbtnEnterClick(Sender: TObject);
var
sCvv,sPin:string;
begin
iCount2:=0;
sCardNumber:=lbledtCardInfo.Text;
if (Length(sCardNumber)<>16) AND (iCount2=0) then
begin
ShowMessage('Card number has to 16 digits,please try again!!');
end
else
begin
Inc(iCount2);
lbledtCardInfo.clear;
lbledtCardInfo.EditLabel.Caption:='Enter Pin' ;
btbtnEnter.Enabled:=false;
end; //if
if iCount2=2 then
begin
btbtnEnter.Enabled:=true;
sPin:=lbledtCardInfo.Text;
ShowMessage(sPin);//returns a blank
end;
You could try to do everything in a single event handler. There are several different ways to handle that. However, a different solution would be to use separate event handlers for each task, and then each task can assign a new handler for the next click to perform, eg:
procedure TfrmMainMenu.FormCreate(Sender: TObject);
begin
// you can set this at design-time if desired...
btbtnEnter.OnClick := GetCCNumber;
end;
procedure TfrmMainMenu.GetCCNumber(Sender: TObject);
begin
sCardNumber := lbledtCardInfo.Text;
if Length(sCardNumber) <> 16 then
begin
ShowMessage('Card number has to 16 digits,please try again!!');
Exit;
end;
lbledtCardInfo.Clear;
lbledtCardInfo.EditLabel.Caption := 'Enter Pin' ;
btbtnEnter.OnClick := GetCCPin;
end;
procedure TfrmMainMenu.GetCCPin(Sender: TObject);
var
sPin: string;
begin
sPin := lbledtCardInfo.Text;
if Length(sPin) <> 4 then
begin
ShowMessage('Card Pin has to 4 digits,please try again!!');
Exit;
end;
ShowMessage(sPin);
...
lbledtCardInfo.Clear;
lbledtCardInfo.EditLabel.Caption := 'Enter Number' ;
btbtnEnter.OnClick := GetCCNumber;
end;
A variation of this would be to create multiple buttons that overlap each other in the UI, and then you can toggle their Visible property back and forth as needed, eg:
procedure TfrmMainMenu.FormCreate(Sender: TObject);
begin
// you can set this at design-time if desired...
btbtnCCPinEnter.Visible := False;
btbtnCCNumEnter.Visible := True;
end;
procedure TfrmMainMenu.btbtnCCNumEnterClick(Sender: TObject);
begin
sCardNumber := lbledtCardInfo.Text;
if Length(sCardNumber) <> 16 then
begin
ShowMessage('Card number has to 16 digits,please try again!!');
Exit;
end;
lbledtCardInfo.Clear;
lbledtCardInfo.EditLabel.Caption := 'Enter Pin' ;
btbtnCCNumEnter.Visible := False;
btbtnCCPinEnter.Visible := True;
end;
procedure TfrmMainMenu.btbtnCCPinEnterClick(Sender: TObject);
var
sPin: string;
begin
sPin := lbledtCardInfo.Text;
if Length(sPin) <> 4 then
begin
ShowMessage('Card Pin has to 4 digits,please try again!!');
Exit;
end;
ShowMessage(sPin);
...
lbledtCardInfo.Clear;
lbledtCardInfo.EditLabel.Caption := 'Enter Number' ;
btbtnCCPinEnter.Visible := False;
btbtnCCNumEnter.Visible := True;
end;
Notice that you test iCount2 = 0 immediately after setting iCount2 := 0. Thus, that test will always be True. Furthermore, the later test iCount2 = 2 will always be False because the value starts at 0 and you only have one Inc in between.
Instead try the following.
Add two string fields FCardNumber and FPin to your form class:
private
FCardNumber: string;
FPin: string;
Also create an enumerated type TEntryStage = (esCardNumber, esPin) and add a field of this type. This will make your code look like this:
private
type
TEntryStage = (esCardNumber, esPin);
var
FCardNumber: string;
FPin: string;
FEntryStage: TEntryStage;
In Delphi, class fields (class member variables) are always initialized, so FEntryStage will be esCardNumber (=TEntryStage(0)) when the form is newly created.
Add a TLabeledEdit (I see you use those) and a TButton; name them eInput and btnNext, respectively. Let the labeled edit's caption be Card number: and the caption of the button be Next.
Now add the following OnClick handler to the button:
procedure TForm1.btnNextClick(Sender: TObject);
begin
case FEntryStage of
esCardNumber:
begin
// Save card number
FCardNumber := eInput.Text;
// Prepare for the next stage
eInput.Clear;
eInput.EditLabel.Caption := 'Pin:';
FEntryStage := esPin;
end;
esPin:
begin
// Save pin
FPin := eInput.Text;
// Just do something with the data
ShowMessageFmt('Card number: %s'#13#10'Pin: %s', [FCardNumber, FPin]);
end;
end;
end;
You might notice that you cannot trigger the Next button using Enter, which is very annoying. To fix this, do
procedure TForm1.eInputEnter(Sender: TObject);
begin
btnNext.Default := True;
end;
procedure TForm1.eInputExit(Sender: TObject);
begin
btnNext.Default := False;
end;
Much better!
In the following tFDMemTable I try to sum value of records whose ID field starting letter A. A1, A2 and the result should be 4.
type
TForm1 = class(TForm)
FDMemTable1: TFDMemTable;
DBGrid1: TDBGrid;
DataSource1: TDataSource;
Button1: TButton;
Button2: TButton;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
end;
procedure TForm1.FormCreate(Sender: TObject);
var
_FieldDef: TFieldDef;
begin
_FieldDef := FDMemTable1.FieldDefs.AddFieldDef;
_FieldDef.Name := 'ID';
_FieldDef.DataType := ftString;
_FieldDef.Size := 5;
_FieldDef := FDMemTable1.FieldDefs.AddFieldDef;
_FieldDef.Name :='value';
_FieldDef.DataType := ftInteger;
FDMemTable1.CreateDataSet;
FDMemTable1.Append;
FDMemTable1.FieldValues['ID'] := 'A1';
FDMemTable1.FieldValues['value'] := 1;
FDMemTable1.Append;
FDMemTable1.FieldValues['ID'] := 'B1';
FDMemTable1.FieldValues['value'] := 2;
FDMemTable1.Append;
FDMemTable1.FieldValues['ID'] := 'A2';
FDMemTable1.FieldValues['value'] := 3;
FDMemTable1.Append;
FDMemTable1.FieldValues['ID'] := 'B2';
FDMemTable1.FieldValues['value'] := 4;
end;
I wrote the following code but it changes tDBGrid as filtered. What I want is just an internal process that tDBGrid should stay without any change.
procedure TForm1.Button1Click(Sender: TObject);
var
_ValueSum: Integer;
i: Integer;
begin
FDMemTable1.Filter := 'ID like ' + QuotedStr('A%');
FDMemTable1.Filtered := True;
_ValueSum := 0;
FDMemTable1.FindFirst;
for i := 0 to FDMemTable1.RecordCount - 1 do
begin
_ValueSum := _ValueSum + FDMemTable1.FieldValues['value'];
FDMemTable1.FindNext;
end;
Button1.Caption := IntToStr(_ValueSum);
end;
I know tDataSet.Locate doesn't allow NEXT SEARCH that I tried a primitive way like this. It works fine but seems a little stupid.
procedure TForm1.Button2Click(Sender: TObject);
var
_ValueSum: Integer;
i: Integer;
begin
_ValueSum := 0;
FDMemTable1.First;
for i := 0 to FDMemTable1.RecordCount do
begin
if Copy(FDMemTable1.FieldValues['ID'], 1, 1) = 'A' then
begin
_ValueSum := _ValueSum + FDMemTable1.FieldValues['value'];
end;
FDMemTable1.FindNext;
end;
Button2.Caption := IntToStr(_ValueSum);
end;
When I disconnect tFDMemTable and tDBGrid or set inactive before filtering to hold the last grid status, the grid changes to blank one. Is the last code the best solution or is there any better way which shows not filtered result while the filtering is working?
There are several things which, if not "wrong", are not quite right with your code.
You should be using Next, not FindNext to move to the next row in the dataset. Next moves to the next row in the dataset, whereas FindNext moves to the next row which matches search criteria you have already set up e.g. using DataSet.SetKey; ... - read the online help for FindKey usage.
You should NOT be trying to traverse the dataset using a For loop; use a While not FDMemData.Eof do loop. Eof stands for 'End of file' and returns true once the dataset is on its last row.
You should be calling FDMemTable1.DisableControls before the loop and FDMemTable1.EnableControls after it. This prevents db-aware controls like your DBGrid from updating inside the loop, which would otherwise slow the loop down as the grid is updating.
Unless you have a very good reason not to, ALWAYS clear a dataset filter in the same method as you set it, otherwise you can get some very confusing errors if you forget the filter is active.
Try to avoid using RecordCount when you don't absolutely need to. Depending on the RDMS you are using, it can cause a lot of avoidable processing overhead on the server and maybe the network (because with some server types it will cause the entire dataset to be retrieved to the client).
Change your first loop to
procedure TForm1.Button1Click(Sender: TObject);
var
_ValueSum : Integer;
begin
_ValueSum := 0;
FDMemTable1.Filter := 'ID like ' + QuotedStr('A%');
try
FDMemTable1.DisableControls;
FDMemTable1.First;
while not FDMemTable1.Eof do begin
_ValueSum:= _ValueSum + FDMemTable1.FieldByName('Value').AsInteger;
FDMemTable1.Next;
end
finally
FDMemTable1.Filter := '';
FDMemTable1.Filtered := False;
FDMemTable1.EnableControls;
end;
Button1.Caption := IntToStr(_ValueSum);
end;
If you do that, you don't need your Button2Click method at all.
As noted in a comment, you can use a TBookMark to record your position in the dataset before the loop and return to it afterwards, as in
var
_ValueSum : Integer;
BM : TBookMark;
begin
_ValueSum := 0;
BM := FDMemTable.GetBookMark;
FDMemTable1.Filter := 'ID like ' + QuotedStr('A%');
try
[etc]
finally
FDMemTable1.Filter := '';
FDMemTable1.Filtered := False;
FDMemTable1.GotoBookMark(BM);
FDMemTable1.FeeBookMark(BM);
FDMemTable1.EnableControls;
end;
By the way, you can save yourself some typing and get more concise code by using the InsertRecord method as in
FDMemTable1.InsertRecord(['A1', 1]);
FDMemTable1.InsertRecord(['B1', 2]);
FDMemTable1.InsertRecord(['A2', 3]);
FDMemTable1.InsertRecord(['B2', 4]);
Btw#2: The time to use FindKey is after you've set up a key to find, using by calling SetKey than then setting the key value(s).
For ordinary navigation of a dataset, use the standard navigation methods, e.g. Next, Prior, First, Last, MoveBy etc.
FireDAC has another interesting option - Aggregates:
procedure TForm1.Button1Click(Sender: TObject);
begin
FDMemTable1.Aggregates.Clear;
with FDMemTable1.Aggregates.Add do
begin
Name := 'SUM';
Expression := 'sum(iif(ID like ''A%'', value, 0))';
Active := True;
end;
FDMemTable1.AggregatesActive := True;
FDMemTable1.Refresh;
Button1.Caption := VarToStr(FDMemTable1.Aggregates[0].Value));
end;
I have a Firemonkey multi device project in Delphi 10 Seattle where the user can get a screen at the start of the app. Here the user needs to fill in 2 fields. But when I click on the edit fields the Virtual Keyboard isn't shown. If I skip this screen at start and call it later then the Virtual Keyboard is shown. This is done in the same way too.
I found sort of a solution:
When i click on the edit fields i call show VirtualKeyboard myself. The only problem is that the cursor isn't shown in the edit field.
Is there a way to place the cursor myself? Or does anyone know how to solve the Virtual Keyboard not showing problem in an other way?
Problem is on both Android and iOS
In the code below you can see the initial form create. The problem is when in ConnectFromProfile method the actCreateNewProfileExecute is called. There it will call a new form. In that form(TfrmProfile) the virtual keyboard isn't shown. I also call this form with another action and then it works fine.
procedure TfrmNocoreDKS.FormCreate(Sender: TObject);
begin
Inherited;
System.SysUtils.FormatSettings.ShortDateFormat := 'dd/mm/yyyy';
CheckPhone;
ConnectfromProfile;
if not Assigned(fProfileAction) then
ConnectDatabase
Else
lstDocuments.Enabled := False;
{$IFDEF ANDROID}
ChangeComboBoxStyle;
{$ENDIF}
end;
procedure TfrmNocoreDKS.ConnectfromProfile;
begin
fdmProfileConnection := TdmConnection.Create(nil);
fdmProfileConnection.OpenProfileDb;
fdmProfileConnection.LoadProfiles;
if fdmProfileConnection.Profiles.Count = 0 then
begin // Createdefault Profile
fProfileAction := actCreateNewProfileExecute;
end
else if fdmProfileConnection.Profiles.Count = 1 then
begin // one profile load connection;
fProfileAction := nil;
fCurrentProfile := fdmProfileConnection.Profiles.Items[0];
end
else
begin // multiple profiles choose connection;
fProfileAction := SelectProfileOnStartUp;
end;
end;
procedure TfrmNocoreDKS.FormShow(Sender: TObject);
begin
if Assigned(fProfileAction) then
fProfileAction(Self);
end;
procedure TfrmNocoreDKS.actCreateNewProfileExecute(Sender: TObject);
var
profilename, databasename, pathname: string;
prf: TfrmProfile;
begin
prf := TfrmProfile.Create(nil);
prf.Data := fdmProfileConnection.Profiles;
prf.ShowModal(
procedure(ModalResult: TModalResult)
begin
if ModalResult = mrOk then
begin
profilename := prf.edtProfilename.Text;
databasename := prf.edtDatabaseName.Text;
{$IFDEF IOS}
pathname := System.IOUtils.TPath.GetDocumentsPath;
{$ENDIF}
{$IFDEF ANDROID}
pathname := System.IOUtils.TPath.GetDocumentsPath;
{$ENDIF}
{$IFDEF WIN32}
pathname := ExtractFilePath(ParamStr(0)) + '\Data';
{$ENDIF}
FDSQLiteBackup1.Database := System.IOUtils.TPath.Combine(pathname,
'default.sqlite3'); // Default Database
FDSQLiteBackup1.DestDatabase := System.IOUtils.TPath.Combine(pathname,
databasename + '.sqlite3');
FDSQLiteBackup1.Backup;
fdmProfileConnection.AddProfile(databasename + '.sqlite3', profilename);
fdmProfileConnection.LoadProfiles;
fCurrentProfile := fdmProfileConnection.Profiles.Items[0];
connectDatabase;
end else
Application.Terminate;
end);
end;
Do not show any additional forms in MainForm.OnCreate/OnShow. Trying this on iOS 9.2 freeze app at "launch screen".
Instead of this, show new form asynchronously, like this:
procedure TForm4.FormShow(Sender: TObject);
begin
TTask.Run(procedure
begin
TThread.Synchronize(nil, procedure // work with visual controls - only throught Synchronize or Queue
begin
Form5:=TForm5.Create(Application);
Form5.ShowModal;
end)
end);
end;
of cource, you can separate this code to external procedures:
procedure ShowMyForm;
begin
Form5:=TForm5.Create(Application);
Form5.ShowModal;
end;
procedure TaskProc;
begin
TThread.Synchronize(nil, ShowMyForm);
end;
procedure TForm4.FormShow(Sender: TObject);
begin
TTask.Run(TaskProc);
end;
========
Another way - do not use any additional forms. Create frame and put it (at runtime) on MainForm with Align = Contents. After all needed actions - hide or release (due to ARC dont forget to set nil to frame variable) this frame.
I have Inherited form and a Ehlib dbgrid on it for selecting-listing records... The form is ready made for a lot of buttons and im using this form with different queries.
Like this...
If Dm.QrTmp.Active then Dm.QrTmp.Active:=False;
Dm.QrTmp.SQL.Clear;
Dm.QrTmp.SQL.Add(' SELECT ');
Dm.QrTmp.SQL.Add(' ch.cari_RECno AS KayitNo ');
Dm.QrTmp.SQL.Add(' FROM CARI_HESAPLAR ch ');
if FrmTmp=nil then FrmTmp:=TFrmTmp.Create(Self);
FrmTmp.StatusBar.Hide;
Dm.QrTmp.Open;
FrmTmp.DbGrid.DataSource:=Dm.DsQrTmp;
This query is cutted down but i have of course use a lot of fields. And Queries changes alot of time in the application.
The problem is column width. Manager wants to set column widths and restore them again. Actually my grid component supports save - restore column properties but as you can see my usage i m not using static columns. also i dont want to use xgrid.columns[0].width percent by percent.
Im using a ini in may app.
I want to add new section on it and named "Gridwidth"...
[Gridname]
Colwidths=x,y,z (where they are width values)
I'm now coding this line by line.
My write procedure is like this.
With dbgridx do
begin
For i:=0 to columns.count-1
begin
widthstr:=widthstr+Column[i].width+',';
end;
end;
Widthstr will be "15,23,45,67" etc...
But i want to know if this is good solution and if somebody know a better way and has some good code.
This should do it:
uses
IniFiles;
const
SETTINGS_FILE = 'Edijus\Settings.ini';
procedure TForm1.LoadDBGridColumnsWidth(const ADBGrid: TDBGrid);
var
_MemIniU: TMemIniFile;
_SettingsPath: string;
i, j: integer;
_ParentClass: TWinControl;
begin
_SettingsPath := GetHomePath + PathDelim + SETTINGS_FILE;
if (not Assigned(ADBGrid)) or (not Assigned(ADBGrid.DataSource)) or
(not Assigned(ADBGrid.DataSource.DataSet)) then
Exit;
_MemIniU := TMemIniFile.Create(_SettingsPath, TEncoding.UTF8);
try
_ParentClass := ADBGrid.Parent;
while not(_ParentClass is TForm) do
_ParentClass := _ParentClass.Parent;
for i := 0 to Pred(ADBGrid.DataSource.DataSet.Fields.Count) do
for j := 0 to Pred(ADBGrid.Columns.Count) do
begin
if (ADBGrid.DataSource.DataSet.Fields[i].FieldName = ADBGrid.Columns[j]
.FieldName) then
ADBGrid.Columns[j].Width :=
_MemIniU.ReadInteger(_ParentClass.Name + '_' + ADBGrid.Name,
ADBGrid.Columns[j].FieldName, 64);
end;
finally
FreeAndNil(_MemIniU);
end;
end;
procedure TForm1.SaveDBGridColumnsWidth(const ADBGrid: TDBGrid);
var
_MemIniU: TMemIniFile;
_SettingsPath: string;
i: integer;
_ParentClass: TWinControl;
begin
_SettingsPath := GetHomePath + PathDelim + SETTINGS_FILE;
if (not Assigned(ADBGrid)) or
(not ForceDirectories(ExtractFilePath(_SettingsPath))) then
Exit;
_MemIniU := TMemIniFile.Create(_SettingsPath, TEncoding.UTF8);
try
_ParentClass := ADBGrid.Parent;
while not(_ParentClass is TForm) do
_ParentClass := _ParentClass.Parent;
for i := 0 to Pred(ADBGrid.Columns.Count) do
if (ADBGrid.Columns[i].FieldName <> '') then
_MemIniU.WriteInteger(_ParentClass.Name + '_' + ADBGrid.Name,
ADBGrid.Columns[i].FieldName, ADBGrid.Columns[i].Width);
_MemIniU.UpdateFile;
finally
FreeAndNil(_MemIniU);
end;
end;
The program I'm working on uses an if statement to add a line to the SQL for the contents of another combo box
procedure TFmNewGarage.ComboBoxCountryEnter(Sender: TObject);
begin
ADOQueryCountry.SQL.Clear;
ADOQueryCountry.SQL.Add('SELECT DISTINCT Country');
ADOQueryCountry.SQL.Add(' FROM TblBaseCar');
ADOQueryCountry.Open;
while not ADOQueryCountry.Eof do
begin
ComboBoxCountry.Items.Add(ADOQueryCountry['Country']);
ADOQueryCountry.Next;
end;
end;
procedure TFmNewGarage.ComboBoxCountryChange(Sender: TObject);
begin
SelA:=True;
ComboBoxManufacturer.Show;
ComboBoxCountry.Hide;
end;
procedure TFmNewGarage.ComboBoxManufacturerEnter(Sender: TObject);
begin
ADOQueryManufacturer.SQL.Clear;
ADOQueryManufacturer.SQL.Add('SELECT DISTINCT Manufacturer');
ADOQueryManufacturer.SQL.Add(' FROM TblBaseCar');
if SelA=true then
ADOQueryManufacturer.SQL.Add(' WHERE Country=(ComboBoxCountry.seltext)');
ADOQueryManufacturer.Open;
while not ADOQueryManufacturer.Eof do
begin
ComboBoxManufacturer.Items.Add(ADOQueryManufacturer['Manufacturer']);
ADOQueryManufacturer.Next;
end;
end;
At runtime this results in the error ComboBoxCountry.seltext has no default value, can anyone help me to rectify this?
SelText is not the property you should be using. You need the combobox Items value for the chosen ItemIndex:
var
Country: string;
begin
...
if ComboBoxCountry.ItemIndex <> -1 then
begin
Country := ComboBoxCountryItems[ComboBoxCountry.ItemIndex];
ADOQueryManufacturer.SQL.Add('WHERE Country = ' + QuotedStr(Country));
end;
end;