Profile Manager with Combobox and TIniFile - delphi

I'm currently trying to create a "Profile Manager" using a TIniFile to store the data, and displaying the data in various components on a form (editboxes and such).
On the form, i've got a Combobox. This serves as a way of displaying the "Profile Name" as set by the user.
The data is being stored in the format of 1 profile per inifile section. Each section contains the configuration data for 1 profile including the Profile Name. The profile name key is the same across each section. This is the sort of layout i've currently got in the inifile (as an example);
[0]
PROFILE_NAME=Profile 1A
PROFILE_DATA=Profile Data 1A
PROFILE_PASS=Profile Password 1
PROFILE_USER=Profile Username 1
[1]
PROFILE_NAME=Profile 1B
PROFILE_DATA=Profile Data 1B
PROFILE_PASS=Profile Password 1B
PROFILE_USER=Profile Username 1B
What i want to do is load a list of all values with the key "PROFILE_NAME" into a combobox regardless of what section they're located in. The section names themselves are references to the itemindex in the combobox when that data was added.
From there, i can handle loading the other data into it's relevant fields, but i'm having a problem figuring out how to load the "PROFILE_NAME" values into the combobox. Any ideas?
For those familiar with the Voice Communication program "Ventrilo", it features something similar to what i'm trying to achieve with it's "Server and User Manager". It's inifile layout is very similar, and the only difference i can find is that it has a "USER_COUNT" value referencing how many users have been added. Each user has servers assigned to them, rather than the servers being accessible by every user.
Is it possible for me to achieve this?

You have to use TIniFile.ReadSections to get a list of all the section names, and then you can loop through them and read the individual PROFILE_NAME from each of those sections. (I prefer TMemIniFile, as TIniFile is based on the WinAPI functions directly and has issues sometimes on network drives when trying to update with new values. TMemIniFile also works cross-platform when you get to XE2.)
I'm creating the TMemIniFile and TStringList and freeing them, but if you're using them repeatedly you'll probably want to create them in your form's OnCreate and free them in FormClose instead; that way you'll have a list of the section names to match back to the items in the ComboBox when you want to access the rest of the items in the OnClick event to populate the rest of the form.
var
Sections: TStringList;
Ini: TMemIniFile;
s: string;
begin
Sections := TStringList.Create;
try
Ini := TMemIniFile.Create('YourIniFile.ini');
try
Ini.ReadSections(Sections);
for s in Sections do
ComboBox1.Items.Add(Ini.ReadString(s, `PROFILE_NAME`, `Empty`);
finally
Ini.Free;
end;
finally
Sections.Free;
end;
end;
To make it easier to tie back to the items in the ComboBox, declare a new integer variable (i in my snippet below), and change the for loop to this (make sure you don't sort the Sections - let the ComboBox handle the sorting!):
for i := 0 to Sections.Count - 1 do
begin
s := Ini.ReadString(Sections[i], 'PROFILE_NAME', 'Empty');
ComboBox1.Items.AddObject(s, TObject(i));
end;
To get the section name again when the user clicks a combobox item:
procedure TForm1.ComboBox1Click(Sender: TObject);
var
i: Integer;
SectionName: string;
begin
// Get the Sections item index we stored above
i := Integer(ComboBox1.Items.Objects[ComboBox1.ItemIndex]));
// Get the associated Sections section name
SectionName := Sections[i];
// Use the retrieved section name to get the rest of the values
ProfileNameEdit.Text := Ini.ReadString(SectionName, 'PROFILE_NAME', '');
ProfileDataEdit.Text := Ini.ReadString(SectionName, 'PROFILE_DATA', ''); // etc
end;

Related

Storing Component Name as a String for later use

On my form I have a number of TMyQuery Components. Their names identify which MySQL Tables they work with. For example, COMPONENTSTABLE works with the COMPONENTS TABLE, etc.
There are about 30 tables, but that might change in the future.
I also use a basic String List to read field names from a Table called TIMESTAMPS. This table is updated via triggers when an UPDATE, INSERT, or DELETE occurs. Each field within the TIMESTAMPS Table refers to which Table was modified. There's only one record in the table! Based on the field values I can see which table changed so I can refresh it rather than refreshing all of them.
I don't want to do this;
If fieldbyname['COMPONENTSTABLE'] <> CurrentTimeStamp
then ComponentsTable.Refresh;
If fieldbyname['ORDERSTABLE'] <> CurrentTimeStamp
then OrdersTable.Refresh;
{ and so on forever }
What I want to do is;
Right now I have a String List with "Names / Values". Each "Name" is the Fieldname within the Table and "Value" is the TIMESTAMP provided by MySQL Triggers.
I've got the following;
For Idx := 0 to MyStringList.Count -1 do
Begin
If MyStringlist.ValueFromIndex[Idx] <> SomethingElse then
Begin
with (MyStringList.Names[Idx] as tMyQuery).Refresh;
End;
End;
I've got the String List functioning, the Names, the Values etc are all correct.
My question is this;
Is there a way I can use a String ("Names" column in the list) to refer to an Object if that Object exists?
I already have a function I use to refresh individual tables by passing an Object to it, but that's an Object and easy to work with. I'd like to pass the "Object" based on it's name retrieved from a String.
I hope this makes sense and you can follow what I'm after.
I am not sure what your question actually is. In the first part of the answer I assume that you don't really care about names of the objects but rather want some automated way of getting all the tables available refer to a field in another table. Below that, I answer your question about referring to an object if you know its name.
Automated way of handling all tables
It depends on what class your objects are.
From your description, I assume your TMyQuery are TComponent descendants owned by the form. Then the solution is very simple, as each TComponent has both a public Name and a list of owned components Components. You can then use something like this:
var
i: integer;
MyQuery: TMyQuery;
begin
for i := 0 to Pred(MyForm.ComponentCount) do
if MyForm.Components[i] <> TimeStampsTable then
if MyForm.Components[i] is TMyQuery then
begin
MyQuery := TMyQuery(MyForm.Components[i]);
if TimeStampsTable.FieldByName(MyQuery.Name).AsDateTime >= LastAccess then ...
end;
end;
Note that you may want to add extra checks, e.g. to make sure that MyQuery.Name is not empty or that it exists as a field in TimeStampsTable.
If your objects are only TObjects, then there is no "standard" name property and no standard registration of these objects. Name can be handled, apparently your component already has one so it's just a question of a proper type coercion, but object registration is a different matter. You may have to create some kind of a global list for all your created TMyQuery instances.
Getting an object instance based on that object's name
function TMyForm.GetQueryByName(const Name: string): TMyQuery;
var
Obj: TObject;
begin
Result := nil;
Obj := Self.FindComponent(Name);
if Obj <> nil then
if Obj is TMyQuery then
Result := TMyQuery(Obj);
end;
Or you could simply loop over all Components and use your own Name matching.
While the first part of the accepted Answer from #pepak isn't what I was looking for ( I've used similar code in the app previously and found it slow ), the second part of the Answer pointed my in the right direction.
My (thanks to Pepak) eventual solution was;
Function RefreshQueryByName(Const Name: String): Boolean;
Var
Obj: TComponent;
Begin
Result := False;
Obj := Self.FindComponent(Name);
If Obj <> nil Then
If Obj Is TMyQuery Then
With Obj As TMyQuery Do
If Active Then
Begin
Refresh;
Result := True;
End;
End;
Which I use by by passing a String I get from a Field Value that identifies which table I want to refresh.
Now, my Database App automatically refreshes a table changed by other users. It will now refresh any of the 30 tables of they are modified by another user without refreshing all tables.
Thanks for your help Pepak, I've accepted your answer and hope it is useful to others.

Save and Load Checklistbox checked items to .ini file

I want to save to .ini and after fill checklistbox items from the saved ini file. I have 2 listboxes like...
First listbox contains tables:
Cars
Customers
Users
Suppliers
The second listbox links to the first, if I click on Cars table and checked it the following datas will be visible on the second checklistbox...
Second listbox contains table fields:
CARS
- Car_ID
- Car_Name
- Car_LicNum
- Car_Color etc..
USERS
User_ID
User_Name
User_Password etc...
Anyone can help me how can I save all the checked items (from checklistbox 1 and checklistbox2) to .ini file? And after how can I load and fill the checklistboxes with them?
I did for the first checklistbox but...
procedure TForm1.btn_SaveClick(Sender: TObject);
begin
ini := TIniFile.Create('C:\checklistbox.ini');
try
for i := 0 to Checklistbox1.Items.Count - 1 do
ini.WriteBool('items', Checklistbox1.Items[i], Checklistbox1.Checked[i]);
finally
ini.Free;
end;
end;
Loading items to checklistbox1
procedure TForm1.btn_LoadClick(Sender: TObject);
begin
ini := TIniFile.Create('c:\checklistbox.ini');
try
ini.ReadSection('items', Checklistbox1.Items);
for i := 0 to Checklistbox1.Items.Count - 1 do
CheckListbox1.Checked[i] := ini.ReadBool('items', Checklistbox1.Items[i], False);
finally
ini.Free;
end;
end;
I dont know how can I save items from checklistbox2 which items links to checklistbox1 items. I want to load all the checked items after. I am using Delphi XE7 at the moment. Thanks for the answers!
I guess your problem is getting your head around the fact that the contents of the second list box change, and so the risk of error is quite high. I agree with that and so the answer is to ignore the list boxes themselves and focus on what they represent, and so store the data that the user wants to see - and in this case I would use field names for that - so
ini.WriteString('File To View', 'Name', 'Cars');
and for the fields
ini.WriteInteger('Cars', 'Count', 2);
ini.WriteString('Cars', 'Field 1', 'Cars_ID');
ini.WriteString('Cars', 'Field 2', 'Car_LICNUM');
I guess that you only allow one box in the first checkbox to be checked. If that were not true, or later became not true, you would add count and 'Name x' parameters like this
ini.WriteInteger('File To View', 'Count', 2);
ini.WriteString('File To View', 'Name 1', 'Cars');
ini.WriteString('File To View', 'Name 2', 'Users');
So changing your GUI later becomes easy, as does making your new program backwards compatible. This is the point LU RD makes about basing your INI file on your business model and not your GUI.
Note also the fact that you may store multiple sections - one for each file in fact, but that doesn't really matter and has the hidden benefit that the INI file 'remembers' the users last choice of fields for each file.

Tlistview - There is any component like Tlistview but with DB access?

I've been trying to make a creative thing to avoid the dbgrids, and i've found the Tlistview (using the one from alphaskins, tslistview), and seems to be a nice way!
The problem is, I don't want to code the event onclick on every tlistview to position a record/dataset according to the item I selected on the tlistview .. and I'm doing it with the tlistview item's caption.. and there could be records with the same names
Here is one of the codes I want to avoid:
with q_find_process do
begin
close;
sql.Clear;
sql.Add('Select * from t_process where process_name like '+quotedstr(streeview1.Selected.Text)+');
open;
end;
And no, I don't want to put the ID of the Record on the item caption..!
Any ideas?
Does anyone know other way of showing a lot of records without being only text text and more text? I don't know all components on the tool palette, maybe someone could suggest me other one..
I have sometimes used listviews which have been loaded from database tables - only for small amounts of data. I don't understand what you mean by I don't want to code the event onclick on every tlistview to position a record/dataset according to the item I selected on the tlistview, so I'm going to show you how I solved this problem.
Basically, I create a sub-item which holds the primary key of each record. All the user interface code uses two list views, and at the end, the database is updated. There is no interaction with the database between loading and storing (which might be where I avoid your 'onclick' problem). The widths of each fields are set in the Object Inspector; the final subitem's width is 0 (ie not displayed).
Loading the list view:
srclist.items.clear;
with qSrcList do
begin
close;
params[0].asdate:= dt; // use date of deposit
open;
while not eof do
begin
ListItem:= srclist.Items.Add;
ListItem.Caption:= fieldbyname ('kabnum').asstring;
ListItem.SubItems.Add (fieldbyname ('price').asstring);
ListItem.SubItems.Add (fieldbyname ('duedate').asstring);
ListItem.SubItems.Add (fieldbyname ('docket').asstring);
ListItem.SubItems.Add (fieldbyname ('id').asstring);
next
end;
close
end;
Saving data:
with dstlist do
for index:= 1 to items.count do
with qInsert do
begin
dstlist.itemindex:= index - 1;
lvitem:= dstlist.selected;
parambyname ('p1').asinteger:= deposit;
parambyname ('p2').asinteger:= strtoint (lvitem.SubItems[3]);
parambyname ('p3').asfloat:= strtofloat (lvitem.SubItems[0]);
execsql;
end;
I hope that this helps you. The context of this code (not that it matters too much) is in a financial application where the user wishes to populate a bank deposit form with cheques. SrcList holds the cheques which have yet to be deposited (there will only be a few per given date) and DstList holds the cheques which have already been connected to a given deposit form.

Inputting data from a dbgrid into a word mail merge

I'm wanting to create a mail marge for a letter inputting different names and address on each. I've used Microsoft example as a base point http://support.microsoft.com/kb/229310 and i've customized it to how i like. But my problem arises when trying to get the data for either selected rows of the dbgrid or just the whole thing. I have no idea how to do it. My first thought was do 1 to the amount of rows, then put some tedit boxes down and put them equal to mailmerged data but that still only does it one at a time. The dbgrid is linked up to a ms outlook.
This is how they fill the data..
// Open the file to insert data
wrdDataDoc := wrdApp.Documents.Open('E:\Temp.doc');
for iCount := 1 to (DBGrid1.DataSource.DataSet.RecordCount) do
wrdDataDoc.Tables.Item(1).Rows.Add;
FillRow(wrdDataDoc, 2, 'Steve', 'DeBroux',
'4567 Main Street', 'Buffalo, NY 98052');
// Fill in the data
FillRow(wrdDataDoc, 3, 'Jan', 'Miksovsky',
'1234 5th Street', 'Charlotte, NC 98765');
FillRow(wrdDataDoc, 4, 'Brian', 'Valentine',
'12348 78th Street Apt. 214', 'Lubbock, TX 25874');
So how would I grab the data from the dbgrid and fill the file with that information?
var
i: Integer;
bm: TBookmark;
begin
DBGrid1.DataSource.DataSet.DisableControls;
try
bm := DBGrid1.DataSource.DataSet.GetBookmark;
try
i := 0;
DBGrid1.DataSource.DataSet.First;
while not DBGrid1.DataSource.DataSet.Eof do begin
Inc(i);
FillRow(wrdDataDoc, i,
DBGrid1.DataSource.DataSet.FieldByName('Name').AsString,
DBGrid1.DataSource.DataSet.FieldByName('Address1').AsString,
..
);
DBGrid1.DataSource.DataSet.Next;
end;
if Assigned(bm) then
DBGrid1.DataSource.DataSet.GotoBookmark(bm);
finally
DBGrid1.DataSource.DataSet.FreeBookmark(bm);
end;
finally
DBGrid1.DataSource.DataSet.EnableControls;
end;
end;
Hmm, this gives me a bit of a clue on how to use Bookmarks to manage selectedrows in DBGrid.
My problem is being able to read through certain fields of selectedRows, such as extract the email addresses (or record ID No) of selected contacts(records) to,perhaps, send email to.
Any further info on using TBookmarkList and TBookmark would be helpful :)
Too Easy... it seems the only way to loop through a TBookmarkLIst is using its Count property,
and using its Item[index] as a TBookmark. Which is then used to dataset.gotBookMark and then access the required fieldByName('Fieldname').
I would have liked...
For bmBookmark in bmlBookmarkList do
but this works...
var bmlGridSelectedRows: TBookmarkList;
bmRecord: TBookmark;
I: Integer;
begin
JvdbUltimGridContacts.DataSource.DataSet.DisableControls;
bmlGridSelectedRows := JvdbUltimGridContacts.SelectedRows;
for I := 0 to bmlGridSelectedRows.Count - 1 do
begin
bmRecord := bmlGridSelectedRows.Items[I];
ABSTableContacts.GotoBookmark(bmRecord);
MessageDlg(ABSTableContacts.FieldByName('DisplayName').AsString,mtInformation,[mbOK],0); //this is just to show that you are accessing the correct record you expect, replace with your own code of course
end;
JvdbUltimGridContacts.DataSource.DataSet.EnableControls;
Nice tip about the Dataset.DisableControls / EnableControls properties,:)
Of course you do not need to declare the variables for TbookmarkList and TBookmark as they can be accessed directly, I am just in the habit of doing that as I think it is cleaner code.
i.e.
DBGrid.DataSource.DataSet.DisableControls;
for I := 0 to DBGrid.SelectedRows.Count - 1 do
begin
ABSTableContacts.GotoBookmark(DBGrid.SelectedRows.Items[I]);
MessageDlg(ABSTableContacts.FieldByName('DisplayName').AsString,mtInformation,[mbOK],0);
end;
DBGrid.DataSource.DataSet.EnableControls;

Delphi: Correct way to store objects fetched from TObjectList

This example is of course simplified, but basically I have a main form that triggers another form (frmSettings) with
function Execute(var aSettings: TSettings):Boolean
TSettings is my own object created in main form for keeping track of the settings.
In this newly opened form (frmSettings) I fetch a TMyObjectList that is a descendant from TObjectList.
It's filled with TMyObj.
I then fill a TListBox with values from that TMyObjectList.
the code:
...
FMyObjectList : TMyObjectList;
property MyObjectList: TMyObjectList read getMyObjectList;
...
function TfrmSettings.getMyObjectList: TMyObjectList ;
begin
If not Assigned(FMyObjectList) then FMyObjectList := TMyObjectList.Create(True)
Result := FMyObjectList;
end;
function TfrmSettings.Execute(var aSettings: TSettings): Boolean;
begin
//Fill myObjectList
FetchObjs(myObjectList);
//Show list to user
FillList(ListBox1, myObjectList);
//Show form
ShowModal;
Result := self.ModalResult = mrOk;
if Result then
begin
// Save the selected object, but how??
// Store only pointer? Lost if list is destroyed.. no good
//Settings.selectedObj := myObjectList.Items[ListBox1.ItemIndex];
// Or store a new object? Have to check if exist already?
If not Assigned(Settings.selectedObj) then Settings.selectedObj := TMyObj.Create;
Settings.selectedObj.Assign(myObjectList.Items[ListBox1.ItemIndex];);
end;
end;
procedure TfrmSettings.FillList(listBox: TListBox; myObjectList: TMyObjectList);
var
i: Integer;
begin
listBox.Clear;
With myObjectList do
begin
for i := 0 to Count - 1 do
begin
//list names to user
listBox.Items.Add(Items[i].Name);
end;
end;
end;
procedure TfrmSettings.FormDestroy(Sender: TObject);
begin
FreeAndNil(FMyObjectList);
end;
Storing just the pointer doesn't seem as a good idea, as triggering the settings form again, recreates the list, and the original object would be lost even if user hits "cancel"
So storing a copy seems better, using assign to get all the properties correct. And first checking if I already have an object.
If not Assigned(Settings.selectedObj) then Settings.selectedObj := TMyObj.Create;
Settings.selectedObj.Assign(myObjectList.Items[ListBox1.ItemIndex];);
Should I move those two lines to a method instead like Settings.AssignSelectedObj(aMyObj:TMyObj)
Does this look correct or am I implementing this the wrong way?
Something more/less needed?
I need some guidelines so I feel more secure that I don't open up for memory leaks and other trouble.
Other than that reviewing the code a bit, the real question is: Is this the correct way to store my SelectedObject in the settings class?
Is this the correct way to store the selected object in the settings?
Probably not. Your settings class should not depend on the form in any way. What if you decide to create and destroy your form dynamically each time the user opens the settings? In this case your settings would hold an invalid object reference.
IMHO it is better to store the object list in the settings together with the index of the selected object. The form should just access the settings, fill the list box and modify the selected object index after the user confirmed with OK.
You are producing a memory leak in your code. You create a TObjectList as a local variable but you never free it. And if you free the local variable, the object references in the listbox will be invalid. You have two options:
Store the object list as a member variable of your form, create in the FromCreate event handler and destroy it in the FormDestroy event handler. You can then safely use object references in your list box.
Store the object list somewhere outside and pass it into the form as a parameter of the Execute method. In this scenario, you can also safely use object references.
I would rename myObjectList to GlobalObjectList, and move it out of the class. It can be declared in the form, but create/free in the initialization/finalization sections. During initialization, after you create the list, populate it from the ini file (or wherever you store it). Now you can access it from anywhere that has your unit in the Uses.
What about the serialization of TSettings? Put your settings in some published properties, then let the RTTI save its content:
type
TSettings = class(TPersistent)
public
function SaveAsText: UTF8String;
end;
function TSettings.SaveAsText: UTF8String;
begin
var
Stream1, Stream2: TMemoryStream;
begin
Stream1 := TMemoryStream.Create;
Stream2 := TMemoryStream.Create;
try
Stream1.WriteComponent(MyComponent);
ObjectBinaryToText(Stream1, Stream2);
SetString(result,PAnsiChar(Stream2.Memory),Stream2.Size);
finally
Stream1.Free;
Stream2.Free;
end;
end;
Then your settings can be stored in a text file or text string.
It's just one solution. But storing settings as text is very handy. We use such an approach in our framework, to store settings via a code-generated user interface. A settings tree is created, from a tree of TPersistent instances.

Resources