Delphi Bookmark Error: E2003 Undeclared identifier 'TBookmark' - delphi

Hey I wanted to use a TBookmark as a varialbe in my Form. I got it running in another Form and it is working there.
But in the new Form I get the Error.. I guess I have to include something in the uses statement but I cant remember what it was. Here is the code TBookmark is underlined in red so thats where the error sits.
procedure TForm4.FormCreate(Sender: TObject);
var test : string;
var selectedRow, rows : TBookmark;
begin
rows := Form1.DBGrid1.DataSource.DataSet.GetBookmark;
Form1.DBGrid1.SelectedRows.CurrentRowSelected := True;
Form1.DBGrid1.DataSource.DataSet.GotoBookmark(rows);
test := Form1.DBGrid1.DataSource.DataSet.FieldByName('name').AsString;
ShowMessage(test);
end;
end.

Your Form4 needs to Use the DB unit, because that's where TBookMark is declared.
Btw, what is in Form1's unit is irrelevant to this. The only relevant thing is that Form4's unit has to Use DB. What happens is that when the compiler tries to compile your Form4 unit, it needs to be able to find the definition of TBookMark, and that is in the standard DB.Pas unit along with lots of other dataset-related stuff. The same is true of any other identifier (or its class) that the compiler encounters in your project's source code.
99% of problems like this can be solved by doing a "Search | Find in Files" through Dephi's source code folders (and your project's folder if it's one of yours) to identify where the "undeclared" or missing item is declared.
Update So, you've got this code, which I'll assume is in your uForm4.Pas unit.
procedure TForm4.FormCreate(Sender: TObject);
var
test : string;
var
selectedRow, rows : TBookmark;
begin
rows := Form1.DBGrid1.DataSource.DataSet.GetBookmark;
Form1.DBGrid1.SelectedRows.CurrentRowSelected := True;
Form1.DBGrid1.DataSource.DataSet.GotoBookmark(rows);
test := Form1.DBGrid1.DataSource.DataSet.FieldByName('name').AsString;
ShowMessage(test);
end;
You want to be able to do something with the Name value that's shown in the current row of
DBGrid1 on Form1. There's nothing particularly wrong with the way you've done it, just that
it's long-winded, error-prone and invites problems like the one you've having with
TBookMark.
The point is that somewhere in your project, maybe in your uForm1.Pas unit, you know,
I don't, there must be a TDataSet-descendant (like TFDQuery, TAdoQuery or TTable) that is
specified in the DataSet property of Form1's DataSource1. For the sake of argument, lets'
say that the dataset component is FDQuery1 on Form1 and you want to get the Name field value
from the current row in DBGrid1.
To get that Name value, you don't actually need the bookmarks your code is using. The way
a TDBGrid works, the currently-selected row in the grid is always the current row in the
dataset component. So you could simply write
procedure TForm4.FormCreate(Sender: TObject);
var
test : string;
begin
test := Form1.FDQuery1.FieldByName('name').AsString;
ShowMessage(test);
end;
because you don't need to go through the rigmarole of Form1.DBGrid1.DataSource.DataSet to get to it.
Now, to explain another little mystery, how come your code would work fine if it was in uForm1.Pas
but you get the Undeclared Identifier: TBookMark error why you try the same code in uForm4.Pas
unit? Well, if you've ever watched the top of a source code file as it's being saved, you'll notice that
Delphi automatically adds, to the Uses list at the top, the units which contain any of the
components you've added to the form since its last save. So adding a TDataSource to the form would add
the DB unit to the Uses list, because that's where TDataSource is declared and so is TBookMark. Which
is why Delphi could compile Form1's code without the error, whereas when you try to mention a TBookMark
to uForm4, you need to add it to the unit's Uses list unless you add a component (like TDataSource)
to Form4 which will cause it to automatically add DB to the Uses list if it isn't already there. Mystery
solved.

Related

Function to recreate a TForm in Delphi

I need a procedure that recreates a form.
The reason being is that I have forms with many different components. These component values (edit box text, checkbox checked or not, etc) are saved inside onhide and loaded again isnide onshow. This makes sure all user settings are retained between runs of the program.
The problem comes when they make a change (intentionally or otherwise) that leads to problems. I want to be able to "reset" the form back to the default settings when the application is first installed.
I can create a reset button that runs this code
FormName.free;
DeleteFile('FormNameSettings.ini');
FormName:=TFormName.Create(Application);
FormName.show;
That does what is required. The form is closed, clears the settings file (so states are not restored when it shows again) and then recreates the form. The form now has the original default settings.
My problem is trying to get that code into a function that I can call easily from multiple forms.
procedure ResetForm(form:tform;filename:string);
begin
form.free;
if fileexists(filename)=true then deletefile(filename);
<what goes here to recretae the form by the passed tform?>
end;
Can anyone help get that ResetForm procedure working? Latest Delphi 11.
To return the newly created form we actually need a var parameter for the form, but that alone is not very elegant, because one cannot pass a derived form class to a var parameter of type TForm and has to do a hard cast to please the compiler. Even using a function that returns a TForm is not much better as the result is most likely assigned to a variable of a derived form class and that would also be rejected by the compiler.
Thanks to generics we can write some code that overcomes these restrictions. As standalone generic procedures or functions are not supported in Delphi, we wrap it inside a record declaration:
type
TFormUtils = record
public
class procedure ResetForm<T: TForm>(var form: T; const filename: string); static;
end;
We also need to save some information about the form for later use:
the owner of the form
is the form currently showing
This allows to recreate the form.
class procedure TFormUtils.ResetForm<T>(var form: T; const filename: string);
begin
var formOwner := form.Owner;
var formShowing := form.Showing;
form.free;
if fileexists(filename) then
deletefile(filename);
form := T.Create(formOwner);
if formShowing then
form.Show;
end;

How to look into generic tList during Delphi debugging

I use Delphi 10.3.1 COMMUNITY version and can't look into generic tList while I debug the project.
I know the latest version of Delphi doesn't support the old-typed debug feature which allows to look into generic tList. So I used tList.List in the following code to evaluate the tList.
In tList<tRecord>.List I can look into it but can't do it in tList<Integer>.List.
type
tRecord = record
Field: Integer;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
_Record: tRecord;
_List1: TList<tRecord>;
_List2: TList<Integer>;
i: Integer;
begin
_List1 := TList<tRecord>.Create;
_List2 := TList<Integer>.Create;
for i := 0 to 4 do
begin
_Record.Field := i;
_List1.Add(_Record);
_List2.Add(i);
end;
Caption := IntToStr(_List1.List[0].Field) + IntToStr(_List2.List[0]);
_List1.Free;
_List2.Free;
end;
How can I look into tList<Integer> during the debugging?
Usually it should be possible to see the lists contained array over the List property. Internally there is only a field of type Pointer unlike before 10.3 when it was of type TArray<T>.
This is what I see when I put a breakpoint into the line where it assigns to Caption and put those two entries into my watches:
Update: It looks like the Linker is responsible for the issue you are experiencing here. When you uncheck the option to "allow side effects and function calls" in the watch
the watch window will show this:
I have seen this behavior before when using generics that are only specified in the implementation part of the unit (FWIW when I tried to repro this the first time I did not put the code you posted into a VCL project but into a console dpr and that one does not have an implementation part so I did not see this behavior).
To force the linker to not to remove the symbol or the debugger to actually see it (because even if I disable inlining to force the GetList method to stay the watch window will tell me that it got removed) you can simply put some dummy type into the interface part of this or any other unit.
type TDummy = TList<Integer>;
This will cause the debugger to see the symbol and see the values in the watches window.

Access Violation when handling forms

I have procedure to show/hide one element on TForm like that:
procedure ShowHideControl(const ParentForm: TForm; const ControlName: String; ShowControl: Boolean);
var
i: Integer;
begin
for i := 0 to pred(ParentForm.ComponentCount) do
begin
if (ParentForm.Components[i].Name = ControlName) then
begin
if ShowControl then
TControl(ParentForm.Components[i]).Show
else
TControl(ParentForm.Components[i]).Hide;
Break;
end;
end;
end;
then I try to use it like:
procedure TForm1.Button6Click(Sender: TObject);
begin
ShowHideEveryControl(TForm(TForm1), 'Button4', True);
end;
Why do I get Access Violation on Button6 click?
For me everything is OK... Button4 exists as a child :)
This cast is wrong:
TForm(TForm1)
You are telling the compiler to ignore the fact that TForm1 is not a TForm instance, and asking it to pretend that it is. That is fine until you actually try to use it as an instance, and then the error occurs.
You need to pass a real instance to a TForm descendent. You can write it like this:
ShowHideEveryThing(Self, 'Button4', True);
Just in case you are not clear on this, the parameter of your procedure is of type TForm. That means you need to supply an instance of a class that either is, or derives from TForm. I repeat, you must supply an instance. But you supply TForm1 which is a class.
And then the next problem comes here:
if (ParentForm.Components[i].Name = FormName) then
begin
if ShowForm then
TForm(ParentForm.Components[i]).Show
else
TForm(ParentForm.Components[i]).Hide;
Break;
end;
Again you have used an erroneous cast. When the compiler tells you that it a particular object does not have a method, you must listen to it. It's no good telling the compiler to shut up and pretend that an object of one type is really an object of a different type. Your button is categorically not a form, so don't try to cast it to TForm.
It is very hard to know what you are actually trying to do here. When you write:
ShowHideEveryThing(Self, 'Button4', True);
It would seem to me to me more sensible to write:
Button4.Show;
It is not a good idea to refer to controls using their names represented as text. It is much safer and cleaner to refer to them using reference variables. That way you let the compiler do its job and check the type safety of your program.
The names used in your function are suspect:
procedure ShowHideEveryThing(const ParentForm: TForm; const FormName: String;
ShowForm: Boolean);
Let's look at them:
ShowHideEveryThing: but you claim that the function should show/hide one element. That does not tally with the use of everything.
FormName: you actually pass a component name, and then look for components owned by ParentForm that have that name. It seems that FormName is wrong.
ShowForm: again, do you want to control visibility of the form, or a single element?
Clearly you need to step back and be clear on the intent of this function.
As an aside, you should generally never need to write:
if b then
Control.Show
else
Control.Hide;
Instead you can write:
Control.Visible := b;
My number one piece of advice to you though is to stop casting until you understand it properly. Once you understand it properly, design your code if at all possible so that you don't need to cast. If you ever really do need to cast, make sure that your cast is valid, ideally by using a checked cast with the as operator. Or at least testing first with the is operator.
Your code shows all the hallmarks of a classic mistake. The compiler objects to the code that you write. You have learnt from somewhere that casting can be used to suppress these compiler errors and now you apply this technique widely as a means to make your program compile. The problem is that the compiler invariably knows what it is talking about. When it objects, listen to it. If ever you find yourself suppressing a compiler error with a cast, take a step back and think carefully about what you are doing. The compiler is your friend.

How to set THTTPRio.Converter.Options to soLiteralParams in OnBeforeExecuteEvent

This refer to the post Delphi SOAP Envelope and WCF.
Could someone please post a sample code which can show me how to set soLiteralParams in THTTPRio.Converter.Options in Delphi 7. I have the following code currently.
I have drag-dropped a HTTPRIO component into the document which has created a line HTTPRIO1: THTTPRIO at the beginning of the code. I basically want to understand how I set soLiteralParams in the above component. Following is the code I am trying to execute which is giving me error.
procedure TForm1.CleanUpSOAP(const MethodName: String; var SOAPRequest: WideString);
var RIO: THTTPRIO;
begin
//The following line is giving error
// RIO.Converter.options := [soLiteralParams];
end;
In the above code I have declared a variable RIO of the type THTTPRIO, which I am not sure is correct.
Just guessing, as you provide very little information in your question.
Use the variable assigned to the component you dropped on your form. Don't declare a new local one (which you never created anyway). To set the Converter.Options in code, you'll need to add OPToSOAPDomConv to your uses clause.
implementation
uses
OPToSOAPDomConv;
// BTW, this name might not be a good one if it's the
// OnBeforeExecute event handler as that isn't
// clear from the name.
procedure TForm1.CleanUpSOAP(const MethodName: String; var SOAPRequest: WideString);
begin
// Note this clears any previous options!
HTTPRIO1.Converter.Options := [soLiteralParams];
// If you want to keep the previous options instead of replacing them
// HTTPRIO1.Converter1.Options := HTTPRIO1.Converter1.Options + [soLiteralParams];
end;
If you've dropped the component on the form, I'm not sure why you're not handling this in the Object Inspector instead, however.
If this doesn't solve the problem, edit your question and provide the exact error message you're receiving, including any memory addresses in the case of an exception being raised.
I have cracked this. The issue was that I didn't refer to OPconvert.pas file, which contained the TSOAPConvertOption enumeration. I don't know whether copying this file into the same folder as my project files and referring to this in the "uses" section is the right way, but it worked fine.

saving delphi routines and memory

This question is about being able to save routines, and being able to select them from a list…. When you select one it knows what to link where etc. Just for understanding. not the actual program
Say I want to create a routine in a Delphi form. And I want to create several different ones. They wont be exactly the same but some might be similar. I want to know how you can save things in Delphi and when you close or terminate the application they will remain remembered when you reopen it. I have no idea where to start and how to work this. Any help would be great. Just a hint or a direction, maybe a website with more info or even examples. I’m going to try to give a simpler description below about how it would look on the form…. Just for the idea and I think if I understand this then it would be enough, or a good start at least.
The form will contain a list box a save button and 4 different edit boxes. Lets say I type in edit1;1 and edit2;2 and edit3;3 and edit4;4. Then click the save button and it remembers these 4 value to each edit box and lets say saves in under the value in the list box of ≔edit1.text + ‘to’ + edit4.text. Hope it makes sense so far and then I type in the edit boxes everything the wrong way around. edit1;4 and edit2;3 and edit3;2 and edit4;1. And click save button and it does that again (≔edit1.text + ‘to’ + edit4.text) into the list box. Then I want to close the application. Open it again and still have this in there and still be able to add more of these odd samples….
Can anyone help me?
Edit question, might make it more clear....
I'm going to place the following elements on the form: 2 listboxes(with each 3 lines, in the first listbox: wood, plastic and glass. in the second listbox: tree,cup,window.)
Now I want to link the correct ones, they are in order here, but what is they were not. In a table or in a memory of the application which is not visible on the form I want to link them.
Then if i were to put two edit boxes on the form as wel and I type in the first one wood or tree, it places the other one in the other edit box. So in a way I suppose you are creating a table which knows which one correcsponds with which but also looksup up when you type in edit box. hope that makes sense
From what you wrote I assume that you already know how to add the values to remember to your list box, so I won't deal with this part here. Starting with this answer, you should already have a clue to what to look at in the delphi help files. Please be aware that I didn't test the example, so there may be typos in it.
To store data in the computers registry, delphi provides you with the unit Registry, which contains a class TRegistry.
TRegistry has all the methods needed to retrieve and store values. Following is a short example without any practical use, just to give you the picture. Please be aware that, like mentioned in the comments, there's plenty of room left for you to optimise it. It would, for example, be better to create the TRegistry object only once and then repeatedly call the methods. And - while this plain old delphi unit I wrote is syntactically valid - you might be better off using a more object-oriented approach, like deriving a new class from TRegistry. Please do also check the documentation for the return values of the methods, as some (like OpenKey) simply return false when they are unable to fulfill your request while others (like ReadString) can throw an execption.
unit Unit1;
interface
uses TRegistry, Windows;
procedure saveStringToRegistry(name : String; value : String);
function readIntegerFromRegistry(name : String) : Integer;
implementation
procedure saveStringToRegistry(name : String; value : String);
var r : TRegistry;
begin
r := TRegistry.Create;
try
r.RootKey := HKEY_CURRENT_USER; // Defined in unit Windows, as well as HKEY_LOCAL_MACHINE and others.
r.OpenKey('Software\Manufacturer Name\Produkt Name\Other\Folders',true); // TRUE allows to create the key if it doesn't already exist
r.WriteString(name,value); //Store the contents of variable 'value' in the entry specified by 'name'
r.CloseKey;
finally
r.Free;
end;
end;
function readIntegerFromRegistry(name : String) : Integer;
var r : TRegistry;
begin
r := TRegistry.Create;
try
r.RootKey := HKEY_LOCAL_MACHINE; // Defined in unit Windows
r.OpenKey('Software\Manufacturer Name\Produkt Name\Other\Folders',false); // FALSE: do not create the key, fail if it doesn't already exist
result := r.ReadInteger(name);
r.CloseKey;
finally
r.Free;
end;
end;
end.
OK, now you could use a for loop in your application to process all the items of your list box and save it to the registry using the procedure above, like this:
for i := 0 to ListBox1.Count -1 do
saveStringToRegistry('Entry' + IntToStr(i),ListBox1.Items[i];
Then, you would probably save the number of items you have (of course you would have to define the procedure saveIntegerToRegistry):
saveIntegerToRegistry('NumberOfEntries',ListBox1.Count);
When you need to reload the data, you could do:
ListBox1.Clear;
for i := 0 to readIntegerFromRegistry('NumberOfEntries') -1 do
ListBox1.Items.Add(readStringFromRegistry('Entry' + IntToStr(i));
OK, it's a very basic example, but should give you pointer in the right direction. It could of course need some exception handling (imagine the user accidently deleted Entry42 from the registry, but NumberOfEntries still says there are 55 entries).

Resources