How to find what form is calling a public procedure - delphi

I have too many Forms and I have a procedure that should be running on all form when created
procedure TDM.SetupForm(Max, DisableResize,
DisableMove: Boolean; FormWidth: Integer = 0; FormHeight: Integer = 0);
var
Form: TForm;
begin
Form := ??? // How to find the what form is running this procedure?
Form.AutoScroll := True;
if Max then
begin
Form.Width := Screen.WorkAreaWidth;
Form.Height := Screen.WorkAreaHeight;
Form.Top := 0;
Form.Left := 0;
end
else
begin
if FormWidth > 0 then
Form.Width := FormWidth;
if FormHeight > 0 then
Form.Height := FormHeight;
Form.Position := poScreenCenter;
Form.Align := alCustom;
end;
if DisableResize then
DeleteMenu(GetSystemMenu(Form.Handle, False), SC_SIZE, MF_BYCOMMAND);
if DisableMove then
DeleteMenu(GetSystemMenu(Form.Handle, False), SC_MOVE, MF_BYCOMMAND);
Form.BorderIcons := [biSystemMenu];
if Form.Height > Screen.WorkAreaHeight then
Form.Height := Screen.WorkAreaHeight;
if Form.Width > Screen.WorkAreaWidth then
Form.Width := Screen.WorkAreaWidth;
Form.ShowHint := True;
Form.OnClose := CloseFormAction;
end;
I call this Procedure on FormCreate event
How can I find what form is calling this procedure and use it inside same procedure without passing it as parameter?

I call this procedure on FormCreate event
It seems to me you don't actually need to know the "Last Created Form", but rather which form is currently being created, which you want to call this code for. If that is the case, simply add a TForm parameter to this procedure instead of declaring a variable and trying to obtain it from elsewhere...
procedure TDM.SetupForm(Form: TForm; Max, DisableResize,
DisableMove: Boolean; FormWidth: Integer = 0; FormHeight: Integer = 0);
begin
...Use the `Form` parameter...
Then you would pass Self into this whenever you call it from FormCreate...
DM.SetupForm(Self, ....
Ultimately, this sort of thing is best accomplished by creating a base form first, and then inheriting all the rest of your forms from this base. Such code would be implemented in the base form's constructor, and then you wouldn't have to explicitly call it from each and every form you wish to apply it to. However, it seems you already have many forms written and this would require modifying all of your existing code to consider the base form. Such design should be done from the beginning of development.
I must also note that putting UI code of such nature into a data module is not the right practice. A data module's purpose is to be disconnected from the UI. That's why it's not actually a visible form, but a non-visual-component-only solution. It's best to put such code in independent units for that purpose, such as MyApp.UICommon.pas.

Related

Dynamically creating a new component without predeclared variable limitation?

I am working on a school project. I need the user to click a button and each time the button is pressed a new component with a new name is created.
The idea I had looked something like this
procedure TForm1.btnClick (Sender: TObject);
Var
pnlTest1, pnlTest2 : TPanel;
Begin
If iCount = 1
then
Begin
pnlTest1 := TPanel.Create(Self);
pnlTest1.Parent := Self;
pnlTest.Top := 0;
Etc...
End
Else if iCount = 2
Then
Begin
PnlTest2 := TPanel.Create(Self);
PnlTest2.Parent := Self;
PnlTest2.Top := 0;
Etc...
End;
The problem is i can only create as many components as I have created varaibles and I need the user to craete basically an infinite amount of new components. I tried other methods as well but the same problem occurs, I also tried creating dynamic variables with pointers and so on but the data types of those varaibles worked only (in my efforts) with the basic data types (Integer, String etc) and so i could not create a variable in run time to create a component (in the example pnlTest : TPanel was declared, this did not work with the dynamic variables)
Please advise on how i can create a new component everytime the button is clicked without bieng limited by the preexisting variables or please advise on how i can have a ''ínfinite'' amount of variables to use for creating a new component each time.
You only need to declare 1 pointer variable to receive the new component instance, eg:
private
iCount: Integer;
...
procedure TForm1.btnClick (Sender: TObject);
Var
pnlTest : TPanel;
Begin
pnlTest := TPanel.Create(Self);
pnlTest.Parent := Self;
pnlTest.Name := 'pnlTest' + IntToStr(iCount);
pnlTest.Top := 0;
//...
Inc(iCount);
End;
The component instance is stored in the Form's Components and Controls properties, since you are assigning the Form as the component's Owner and Parent, respectively.

FindComponent Not Finding Components Created at Runtime

I use Delphi 7 with a number of third party components. My main stub application loads a number of DLLs, which are various modules like creditors, debtors, purchase orders, and so on.
I have an issue with FindComponent(). 99% of the time, it works how it should. But not for the code below.
I was trying to create a form reports, where I keep all the details of the reports selection criteria in a table, and then create the criteria on the fly. In theory, it should work perfectly, but for some reason after creating the components, FindComponent() cannot find them.
try
for i := gbSelectionCriteria.ComponentCount - 1 downto 0 do begin
ShowMessage(gbSelectionCriteria.Components[i].Name);
gbSelectionCriteria.Components[i].Free;
end;
// The above loop to remove the components from the groupbox works fine
// Creating the components works
fSysData.tbSelectionCriteria.First;
while not fSysData.tbSelectionCriteria.EOF do begin
case fSysData.tbSelectionCriteriaComponentType.AsInteger of
1 : begin // TMyAdvEdit
with TMyAdvEdit.Create(gbSelectionCriteria) do begin
Visible := False;
Parent := gbSelectionCriteria;
Name := fSysData.tbSelectionCriteriaName.AsString;
Left := fSysData.tbSelectionCriteriaLeft.AsInteger;
Top := fSysData.tbSelectionCriteriaTop.AsInteger;
Width := fSysData.tbSelectionCriteriaWidth.AsInteger;
LabelCaption := fSysData.tbSelectionCriteriaCaption.AsString;
LabelPosition := AdvEdit.lpLeftCenter;
LabelAlwaysEnabled := True;
LabelTransparent := True;
EditType := MyEditType[fSysData.tbSelectionCriteriaDataType.AsInteger];
Text := '';
OnClick := GetClickEvent(fSysData.tbSelectionCriteriaOnClickEvent.AsString);
OnDblClick := GetClickEvent(fSysData.tbSelectionCriteriaOnDblClickEvent.AsString);
OnKeyPress := GetKeyPressEvent(fSysData.tbSelectionCriteriaOnKeyPressEvent.AsString);
Visible := True;
// at this point findComponent finds nothing
if FindComponent(Name) <> nil then
ShowMessage(Name+' Created');
end;
edEdit.OnClick := GetClickEvent(fSysData.tbSelectionCriteriaOnClickEvent.AsString);
edEdit.OnDblClick := GetClickEvent(fSysData.tbSelectionCriteriaOnDblClickEvent.AsString);
edEdit.OnKeyPress := GetKeyPressEvent(fSysData.tbSelectionCriteriaOnKeyPressEvent.AsString);
edEdit.Visible := True;
if FindComponent(edEdit.Name) <> nil then
ShowMessage(edEdit.Name+' Created');
end;
2 : begin
end;
3 : begin
end;
4 : begin
end;
5 : begin
end;
6 : begin
end;
7 : begin
end;
8 : begin
end;
end;
fSysData.tbSelectionCriteria.Next;
end;
if fSysData.tbSysReports.Locate('ReportID', TAdvOfficeRadioButton(Sender).Tag, []) then begin
ReportData.ReportID := TAdvOfficeRadioButton(Sender).Tag;
ReportData.RepName := fSysData.tbSysReportsReportName.AsString;
ReportData.RepTitle := fSysData.tbSysReportsReportTitle.AsString;
ReportData.RepModule := fSysData.tbSysReportsModule.AsString;
ReportData.RepOrientation := fSysData.tbSysReportsReportOrientaton.AsString;
ReportData.RepPageIndex := fSysData.tbSysReportsCriteriaPageIndex.AsInteger;
end;
finally
end;
The Process of the reports is:
User clicks a button
Radio buttons are created from the button click
User clicks a radio button
Report criteria is created from the radio button click
User enters data or DblClicks to select data from a list.
User Clicks Preview button to view Report - this is where FindComponent fails and returns nil..
All the code worked before when I had created all the criteria at design time, then added the code above.
The code below is part of what needs to be added to the query to retrieve the data for the report:
if Length(TMyAdvEdit(FindComponent('edQuoteReference')).Text) > 0 then
qryTempTable.SQL.Add(' and q.UserReference = "' + TMyAdvEdit(FindComponent('edQuoteReference')).Text + '"');
This is the first time FindComponent() fails and goes no further.
I have tried various ways to create the components, but each of them results in an Access Violation because the component is nil.
I have looked everywhere, and tried everything I can think of, for a solution to this problem.
FindComponent searches for components owned by the subject of the method call. You call FindComponent on the form, and so look for the component amongst those components owned by the form. But the control you search for is not owned by the form, it is owned by gbSelectionCriteria, which is what you passed to the control's constructor as the Owner argument.
If you wish to use FindComponent in the way you do you therefore need to make the form be the owner of the controls that you create. Then when you call FindComponent on the form, it can find the control because it is the owner. Pass Self to the control's constructor to make this come to pass:
TMyAdvEdit.Create(Self)
I'm having to make some reasonably large guesses here. Perhaps this code actually resides in a data module rather than a form. But the essential principle will be as I say.
Firstly I do apologize if this is in the wrong spot..
Thanks for the response and the answer, I have been doing this for a lot of years and I can't believe I missed something so small.
this,
if FindComponent(Name) <> nil then
should have been this,
if gbSelectionCriteria.FindComponent(Name) <> nil then
I don't normally use with, it was just one way to test create the component.
I set the components visibility to false before and then to true after it is created to stop flicker as it creates.
Thanks again..

Get current field in TQuery to a TField

This is related to another question but doesn't really fit enough to include it with the original. When a Post is called, how can I get the field (or fields) that was modified to a TField?
For logging, I use the OnBeforePost event, which is called (as it says) just before the data is posted. The drawback to this, of course, is that your log table has to have fields wide enough to hold all possible content.
procedure TMyData.SomeTableBeforePost(DataSet: TDataSet);
var
i: Integer;
begin
for i := 0 to DataSet.FieldCount - 1 do
begin
// Skip calculated and lookup fields
if DataSet.Fields[i].FieldType = ftData then
begin
if DataSet.Fields[i].OldValue <> DataSet.Fields[i].NewValue then
begin
LogTable.Insert;
LogTableColumnName.AsString := DataSet.Fields[i].FieldName;
LogTableOldValue.Value := DataSet.Fields[i].OldValue;
LogTableNewValue.Value := DataSet.Fields[i].NewValue;
LogTable.Post;
end;
end;
end;
end;

Check If TEdit's Text Property Is Null On A Frame

So here is my situation. I have a Form (MainMenu) and a Frame (TestFrame). TestFrame is displayed on a TPanel located on MainMenu. Using this code:
frTestFrame := TfrTestFrame.Create(nil);
frTestFrame.Parent := plMain;
frTestFrame.Align := alClient;
frTestFrame.Visible := true;
TestFrame displays fine with no error. TestFrame has a few TEdit boxes on it. A TButton on MainMenu calls a procedure located in TestFrame to check if the TEdit boxes text property is null.
procedure TfmMainMenu.tbCheckClick(Sender: TObject);
begin
frTestFrame.Check;
end;
This function on TestFrame is supposed to go through all the "TEdit" components and use the function GetErrorData that returns a string if the TEdit's text property is null. That string is added to a TStringList and displayed if any TEdit boxes are null.
function TfrTestFrame.Check: Boolean;
var
ErrorList: TStringList;
ErrorString: string;
I: Integer;
begin
ErrorList := TStringList.Create;
for I := 0 to (frTestFrame.ComponentCount - 1) do
begin
if (frTestFrame.Components[I] is TEdit) then
begin
ErrorString := GetErrorData(frTestFrame.Components[I]);
if (ErrorString <> '') then
begin
ErrorList.Add(ErrorString);
end;
end;
end;
if (ErrorList.Count > 0) then
begin
ShowMessage('Please Add The Following Information: ' + #13#10 + ErrorList.Text);
result := false;
end;
result := true;
end;
function TfrTestFrame.GetErrorData(Sender: TObject): string;
var
Editbox: TEdit;
ErrorString: string;
begin
if (Sender is TEdit) then
begin
Editbox := TEdit(Sender);
if (Editbox.Text <> '') then
begin
Editbox.Color := clWindow;
result := '';
end
else
begin
Editbox.Color := clRed;
ErrorString := Editbox.Hint;
result := ErrorString;
end;
end;
end;
The problem is that when it hits the line "for I := 0 to (frTestFrame.ComponentCount - 1) do
" It blows up and I get the error "Access violation at 0x00458... Read of address 0x000..."
I do not know why this error is happening. I can only assume that maybe the Frame is not getting creating. Any help would be great. Thanks in advance.
According to your question, the line
for I := 0 to (frTestFrame.ComponentCount - 1) do
leads to an access violation at address 0x000..... Now, for a start, why won't you tell us the precise error message with the full details? Hiding the address makes it harder!
Anyway, it looks like the address is going to be a value very close to zero. In any case the only explanation for an access violation there is that frTestFrame is invalid. Most likely it is nil.
I note that the code in question is inside a TfrTestFrame method. So why do you use frTestFrame to refer to the object? You are already inside an instance of the object. Do you have multiple global variables named frTestFrame? Perhaps one in the main form unit and one in the frame unit?
You should stop using global variables for your GUI objects. I know that the IDE leads you that way. Resist the temptation to program that way. Abuse of global variables leads to pain and suffering.
Since the code is inside a TfrTestFrame method you can use Self. In all your TfrTestFrame methods remove all references to frTestFrame. Your loop should be like this:
for I := 0 to ComponentCount - 1 do
and the rest of the methods in that class need similar treatment. Note that you don't need to explicitly write Self and it is idiomatic not to.
Finally, I urge you to learn how to use the debugger. It's a wonderful tool and if you would use it, it would have told you what the problem was. Don't be helpless, let the tools help you.

Delphi & shared datasources

In my app I have different forms that use the same datasource (so the queries are the same too), defined in a common datamodule. Question is, is there a way to know how many times did I open a specific query? By being able to do this, I could avoid close that query without closing it "every where else".
Edit: It's important to mention that I'm using Delphi3 and it is not a single query but several.
The idea is to use the DataLinks property of the TDataSource.
But, as it is protected, you have to gain access to it. One common trick is to create a fake descendant just for the purpose of casting:
type
TDataSourceHack = class(TDataSource);
Then you use it like:
IsUsed := TDataSourceHack(DataSource1).DataLinks.Count > 0;
You can get creative using a addref/release like approach. Just create a few functions and an integer variable in your shared datamodule to do the magic, and be sure to call them..partial code follows:
TDMShared = class(tDataModule)
private
fQueryCount : integer; // set to 0 in constructor
public
function GetQuery : tDataset;
procedure CloseQuery;
end;
function TDMShared.GetQuery : tDataset;
begin
inc(fQueryCount);
if fQueryCount = 1 then
SharedDatsetQry.open;
Result := shareddatasetqry; // your shared dataset here
end;
procedure TDMShared.CloseQuery;
begin
dec(fQueryCount);
if fQueryCount <= 0 then
shareddatasetqry.close; // close only when no refs left.
end;
EDIT: To do this with multiple queries, you need a container to hold the query references, and a way to manipulate them. a tList works well for this. You will need to make appropriate changes for your TDataset descendant, as well as create a FreeAndNil function if you are using an older version of Delphi. The concept I used for this was to maintain a list of all queries you request and manipulate them by the handle which is in effect the index of the query in the list. The method FreeUnusedQueries is there to free any objects which no longer have a reference...this can also be done as part of the close query method, but I separated it to handle the cases where a specific query would need to be reopened by another module.
Procedure TDMShared.DataModuleCreate(Sender:tObject);
begin
dsList := tList.create;
end;
Function TDMShared.CreateQuery(aSql:String):integer;
var
ds : tAdoDataset;
begin
// create your dataset here, for this example using TADODataset
ds := tAdoDataset.create(nil); // self managed
ds.connection := database;
ds.commandtext := aSql;
ds.tag := 0;
Result := dsList.add(ds);
end;
function TDMShared.GetQuery( handle : integer ) : tDataset;
begin
result := nil;
if handle > dsList.count-1 then exit;
if dsList.Items[ handle ] = nil then exit; // handle already closed
result := tAdoDataset( dsList.items[ handle ]);
Inc(Result.tag);
if Result.Tag = 1 then
Result.Open;
end;
procedure TDMShared.CloseQuery( handle : integer );
var
ds : tAdoDataset;
begin
if handle > dsLIst.count-1 then exit;
ds := tAdoDataset( dsList.items[ handle ]);
dec(ds.Tag);
if ds.Tag <= 0 then
ds.close;
end;
procedure TDMShared.FreeUnusedQueries;
var
ds : tAdoDataset;
ix : integer;
begin
for ix := 0 to dsList.Count - 1 do
begin
ds := tAdoDataset(dsLIst.Items[ ix ]);
if ds.tag <= 0 then
FreeAndNil(dsList.Items[ix]);
end;
end;
procedure TDMShared.DataModuleDestroy(Sender: TObject);
var
ix : integer;
begin
for ix := 0 to dsList.count-1 do
begin
if dsLIst.Items[ix] <> nil then
FreeAndNil(dsLIst.Items[ix]);
end;
dsList.free;
end;
Ok, a completely different solution...one that should work for Delphi 3.
Create a new "Descendant Object" from your existing dataset into a new unit, and add some behavior in the new object. Unfortunately I do not have Delphi 3 available for testing, but it should work if you can find the proper access points. For example:
TMySharedDataset = class(tOriginalDataset)
private
fOpenCount : integer;
protected
procedure Internal_Open; override;
procedure Internal_Close; override;
end;
TMySharedDataset.Internal_Open;
begin
inherited Internal_Open;
inc(fOpenCount);
end;
TMySharedDataset.Internal_Close;
begin
dec(fOpenCount);
if fOpenCount <= 0 then
Inherited Internal_Close;
end;
Then just include the unit in your data module, and change the reference to your shared dataset (you will also have to register this one and add it to the palette if your using components). Once this is done, you won't have to make changes to the other units as the dataset is still a descendant of your original one. What makes this all work is the creation of YOUR overridden object.
You could have a generic TDataSet on the shared datamodule and set it on the OnDataChange, using the DataSet property of the Field parameter
dstDataSet := Field.DataSet;
This way, when you want to close the dataset, close the dataset on the datamodule, which is a pointer to the correct DataSet on some form you don't even have to know

Resources