saving delphi routines and memory - delphi

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).

Related

Delphi Bookmark Error: E2003 Undeclared identifier 'TBookmark'

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.

Changing background image in TeeChart Delphi XE8 at run-time

I would like to use the gallery pictures (Metal, Wood, Stone, Clouds, etc.) which are available at design time under Chart/Panel/Image/Gallery.
If I set it at design time, I can easily disable it at run time with:
g.backImage := nil;
But if I want to set it to a particular value, e.g. with
g.backImage := 'metal';
I get an 'Incompatible types' error, because the compiler requires a TBackImage value. I do not have the source codes, and I cannot find the appropriate values on several Google searches.
Thinking that it could be just an enum, I tried typecasting it to one:
g.backImage := TBackImage(1);
But it generates an exception. I also tried to "guess" the names, like tbiMetal, tbMetal, tMetal, and so on, to no avail...
What are those values?! Thank you
TBackImage is a class whose methods you must call.
Chart.BackImage.LoadFromFile('full/path/to/imagefile');
Those are real texture images embedded in TBrushDialog, they can be used/accessed like this:
uses TeeBrushDlg;
procedure TForm1.FormCreate(Sender: TObject);
var BrushDialog: TBrushDialog;
begin
BrushDialog:=TBrushDialog.Create(Self);
Chart1.BackImage.Graphic:=BrushDialog.ImageCarbon.Picture.Graphic;
end;

Pressing Buttons on a web page via Delphi [duplicate]

This question already has answers here:
How to find a button with "value" in html page (Webbrowser - Delphi)
(2 answers)
Closed 8 years ago.
Ex1: WebBrowser.OleObject.Document.GetElementByID('ID HERE').Click;
Ex2: < input type="submit" VALUE="Login" >
The above two examples are for pressing buttons on web pages via Delphi. Ex2 works well on various web sites but not all. Is this because Ex2 only works on HTML buttons? I tried Ex1 but some code is missing, when I try it, I get a message saying 'Object or class type required'. Also Ex1 has no example code, can anyone fill me in on why I get this message and put some code up for Ex1 please.
I got this code from: MrBaseball34 at delphipages
It didn't work initially because I wrote 'WebBrowser' instead of 'WebBrowser1'. But it works perfectly.
Here is the code:
procedure TForm1.Button1Click(Sender: TObject);
var
x: integer;
thelink: OleVariant;
begin
thelink:= WebBrowser1.OleObject.Document.all.tags('A');
if thelink.Length > 0 then
begin
for x := 0 to thelink.Length-1 do
begin
if Pos('put id string here', thelink.Item(x).id) > 0 then
begin
thelink.Item(x).click;
Break;
end;
end;
end;
end;
When working with TWebBrowser, and COM/ActiveX objects in general, it's really handy to know the difference between late binding and early binding. If you use OleVariant variables, have them refer to 'live' object, and use the dot operator (.) to invoke methods and properties, they get resolved at run-time. They are late bound as opposed to early binding where you would use specific interfaces.
Include unit MSHTML in the uses clause, and then use IHTMLDocument3(WebBrowser1.Document), and the different interfaces defined by MSHTML, such as IHTMLElementand IHTMLAnchorElement. You will find that you also get code completion up to some point, but also that you might need an extra cast between things like IHTMLElement and IHTMLElement2 with the asoperator.
There may be any kind of error. Like misspelled ID or wrong datatype missing interface you hope to use, or lacking some item and returning nil instead.
Problem with long lines like WebBrowser.OleObject.Document.GetElementByID('ID HERE').Click; is that you can hardly tell in which place the error occured. And sometimes it is not easy to check intermediate values and its properties. There are a lot of innate expectations coded in such long lines, and you can hardly detect which one was failed.
When you meet errors in such long lines, you'd better split them at tiny action items - the ol' good divide and conquer principle. Declare few variables and split this long complex line into multiple simplistic ones.
var0 := WebBrowser;
var1 := var0.OleObject;
var2 := var1.Document;
var3 := var2.GetElementByID('ID HERE');
var3.Click;
Tracing this, executing one line a time, you can check which values and data types would be issued at each traversing step.

Can NIL be used for a Sender object?

Recently I was asked to automate a little sub-routine that presented a series of data records and any two of four potential buttons for the user to select after seeing an analysis of the record. The boss said having the users see the analysis was wasting time since the users invariably selected the number one choice in the button list and he was prepared to live with my guesses for all but the best of his users. So, he wanted a NEW series of buttons added to offer Handle Automatically, Handle Manually and Handle Case by Case. The last button would just run the already existing code. The second button would essentially do nothing and just exit. The first button? Well, that was the rub.
What I decided to do was to use a couple of flags and then have the automatic path just simulate the click of whatever sub-button was best, based on the analysis. The issue was that calling Button1Click(Sender) wasn't possible because the procedure running the Analysis was called RunAnalysis and wasn't attached to a specific object to pass the TObject through. I eventually refactored the guts of the Button1Click method into Button1Pressed and then called THAT from Button1Click. Thus I was able to call Button1Pressed from within RunAnalysis.
The avoided path would have been to call Button1Click(Nil). I didn't try it since I had an easy solution (Thanks Modelmaker, by the way). But my question is, would the nil reference have worked or would it have caused a disaster. Could I have called a higher function (randomly?) that did have a sender, JUST to have a sender object in the procedure call? Just how important IS the Sender object, if I don't use anything that actually REFERENCES the Sender?
System details: Delphi 7 in Win 7 programming environment for use in Windows XP.
Thanks in advance for any wisdom, GM
I tend to put the event handler's code into another method when possible:
procedure TForm1.DoSomething(const Test: Boolean);
begin
// Do Something based on Test
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
DoSomething(False); // init here
end;
procedure TForm1.CheckBox1Click(Sender: TObject);
begin
DoSomething(TCheckBox(Sender).Checked);
end;
So when I have a need to call CheckBox1Click(nil) it's a good sign for me to pull off the code from the event handler into a separate method.
A callback or "event" is no different than any other function. You can pass a NIL reference anywhere you want, as long as you either (a) wrote that code and know it's nil safe, or (b) you have read that code and everything it calls without checking for nil, and know it's nil safe.
Some programmers use NIL freely and some consider using NIL parameter values to be bad style. My style tends to be "don't assume Sender is assigned and don't assume that it is of a particular type", which leads to lots of check-code in my event handlers, but other code than mine, it varies widely and so the First Rule of coding comes in; "Don't make assumptions. Read the code.".
You can use nil as a parameter to a TNotify event (or any other expecting a Sender), as long as the code doesn't reference Sender:
procedure TForm1.Button1Click(Sender: TObject);
begin
DoSomeStuff(nil);
end;
procedure TForm1.DoSomeStuff(Sender: TObject);
begin
// Safe
DoSomeOtherStuff;
// Safe
// Do stuff with Sender
if Sender is TButton then
TButton(Sender).Caption := 'In DoSomeStuff'
// NOT safe!
with TButton(Sender) do
begin
Caption := 'In DoSomeStuff';
end;
end;
Short Answer - yes.
I use it to distinguish whether (say) a menu Event was clicked by a user or called directly by code.

Is there some better way to copy all DataSet Fields and their properties to another DataSet?

I'm cloning a TClientDataSet and I want to copy all the fields to the clone (which is a new DataSet), I know I can loop through the Fields and copy the info, or make 2 instances of my class and just clone the cursor, but is there some better way? Something like create a new DataSet and assign the fields info?
EDIT:
The following class helper method works for me:
procedure TDataSetHelper.CopyFieldDefs(Source: TDataSet);
var
Field, NewField: TField;
FieldDef: TFieldDef;
begin
for Field in Source.Fields do
begin
FieldDef := FieldDefs.AddFieldDef;
FieldDef.DataType := Field.DataType;
FieldDef.Size := Field.Size;
FieldDef.Name := Field.FieldName;
NewField := FieldDef.CreateField(Self);
NewField.Visible := Field.Visible;
NewField.DisplayLabel := Field.DisplayLabel;
NewField.DisplayWidth := Field.DisplayWidth;
NewField.EditMask := Field.EditMask;
if IsPublishedProp(Field, 'currency') then
SetPropValue(NewField, 'currency', GetPropValue(Field, 'currency'));
end;
end;
Anyone has a better way for doing this?
If you just want to copy the field definitions you can do the following:
ds2.FieldDefs.Assign(ds1.FieldDefs);
ds2.CreateDataSet;
ds2.Open;
Of course this assumes you created FieldDefs for ds1.
Are you looking for a more aesthetic way of doing it or a faster way of doing it?
If the former, create your own classes that hide the loop.
If the latter, don't even worry about it. A very wise coder once said to me: disk access costs; network access costs; maybe screen access costs; everything else is free.
Don't confuse the size of the source code with execution time. Looping a thousand times through memory and copying bits is undetectable compared to the initial handshake of a database connection.
Cheers
If you're going to loop through the dataset to make a copy, remember to call DisableControls on it before, and EnableControl afterwards.
Without that, things can get really slow if you've got visual controls showing the data of the dataset on your form.
Would CloneCursor work for you?
NON-PROGRAMMABLE METHOD
first tclientdataset: open fields editor. add all fields if not already shown. select all fields. copy to clipboard.
second tclientdataset: open fields editor. paste clipboard in fields editor.
done
you should now see identical fieldDefs for both tclientdatasets now.

Resources