reading 2 lines from IniFile - delphi

Trying again. On advice, adding the piece of code that I do understand. I am fine with the fact that I have to save 4 bits of information in two lines like so:
IniFile.WriteString('TestSection','Name','Country');
IniFile.WriteString('TestSection','City','Street');
My question is more about loading this information back into the form. If in my IniFile I have saved for example the following code
[TestSection]
John=Uk
London=barlystreet
Mike=Spain
Madrid=eduardostrata
Emma=USA
New York=1st Avenue
Made up information in the IniFile. Added through the code above.
Now my question is: How could I load for example, when I type in an edit box Mike, the rest of the belonging information.(Spain, Madrid,eduardostrata).

That's not how an INI file works. You save name=value pairs, and have to have a way to associate them.
Maybe this can help you get started:
Ini := TIniFile.Create(YourIniFileName);
try
Ini.WriteString('Mike', 'Country', 'Spain');
Ini.WriteString('Mike', 'City', 'Madrid');
Ini.WriteString('Mike', 'Street', 'EduardoStrata');
finally
Ini.Free;
end;
Results in your INI file containing:
[Mike]
Country=Spain
City=Madrid
Street=EduardoStrata
To load back:
var
Country, City, Street: string;
Ini: TIniFile;
begin
Ini := TIniFile.Create(YourIniFilename);
try
Country := Ini.ReadString('Mike', 'Country', '<None>');
City := Ini.ReadString('Mike', 'City', '<None>');
Street := Ini.ReadString('Mike', 'Street', '<None>');
finally
Ini.Free;
end;
// Country, City, and Street now equal the values for 'Mike',
// or they contain '<None>' if the section 'Mike' doesn't
// exist or has no values for the variable.
end;
So you can probably figure out how this works. The section (the part in []) is the person's name, and the name/value pairs are the location and it's corresponding value (for instance, 'Country=Spain').

You have clearly not understood how INI files work. What if both John and Dave live in New York? You cannot have two keys with the same name in an INI file. (In addition, you shouldn't rely on the ordering of the lines within each section.)
You thus need to rethink how you save your data. A very simple solution is to use a plain text file in which each line is an item in your database, and the fields are seperated by, for instance, a vertical line (|):
John|Uk|London|barlystreet
Mike|Spain|Madrid|eduardostrata
Emma|USA|New York|1st Avenue.
How to read this file? Well, that is trivial, and you should know how to do that. If you have a very specific question, then feel free to ask.
What INI files are for
But what are INI files for, then? Well, a typical application of a INI file is to save program settings. For instance, when you quit a text editor, it might save the settings to settings.ini:
[Font]
Name=Consolas
Size=10
[Behaviour]
AutoIndent=1
AutoReplace=1
AutoBrackets=1
BracketHighlight=1
SyntaxHighlight=1
[Window]
Width=800
Height=600
Maximized=0
etc. This is done by
WriteString('Font', 'Name', Editor.Font.Name);
WriteInteger('Font', 'Size', Editor.Font.Size);
etc. And when you start the application the next time, it will read the file to restore the settings:
Editor.Font.Name := ReadString('Font', 'Name', 'Consolas');
Editor.Font.Size := ReadInteger('Font', 'Size', 10);
etc., where the last parameters are the default values (in case the field is missing in the INI file). Notice that in each section, the keys are unique (and need to be), and that we don't care about the relative order of the keys inside each section.

The easiest way to create the relationship you want is to have all of a user's details on 1 line of text. This is not a job for INI files. Your issue is how to parse strings.
First of all, why do you need to save the "repeat password"? that doesn't make sense to me. Usually a UI will ask the user to repeat the password as a form of validation, but that's all it's good for. There's no benefit in storing it for later retrieval is there.
I think you need to save the user's first_name, last_name, and password (3 strings). Have a look at the following piece of code.
procedure SaveUserDetails(sFileName: string);
var
sFirstName, sLastName, sPassword: string;
slUsers: TStringList;
begin
sFirstName := txtFirstName.Text; // these could be from TEdit controls for example
sLastName := txtLastName.Text;
sPassword := txtPassword.Text;
slUsers := TStringList.Create;
slUsers.Add(sFirstName + ',' + sLastName + ',' + sPassword);
slUsers.SaveToFile(sFileName); // that has saved your stringlist to a file
slUsers.Free;
end;
The file will look this
Shane,Warne,cricket
Now, how to load it...
procedure LoadUserDetails(sFileName: string);
var
sFirstName, sLastName, sPassword: string;
sTemp: string;
slUsers: TStringList;
iPos: integer; // string position marker we'll use to split the string in 3
begin
slUsers := TStringList.Create;
slUsers.LoadFromFile(sFileName); // this loads the file's contents into stringlist
sTemp := slUsers[0];
if (Length(sTemp) > 0) then // just to check that there is data there
begin
iPos := pos(',', sTemp); // get position of first comma (our "delimiter")
sFirstName := Copy(sTemp, 0, iPos-1); // firstName everything upto 1st comma
sTemp := Copy(sTemp, iPos + 1, Length(sTemp)); // chop off bit we just read
iPos := pos(',', sTemp); // get position of second comma
sLastName := Copy(sTemp, 0, iPos-1); // LastName everything upto 2nd comma
sTemp := Copy(sTemp, iPos + 1, Length(sTemp)); // chop off bit we just read
sPassword := sTemp; // that's it
end;
slUsers.Free;
end;
Now... this far from "good code" but now you know at least 1 way to do your thing. Hope that helps.

Related

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;

Need a ComboBox with filtering

I need some type of ComboBox which can load it's items from DB. While I type some text in to it, it should filter it's list, leaving only those items, that have my text somewhere (at the beginning, middle...). Not all my DataSet's have filtering capabilities, so it is not possible to use them. Is there any ready to use components with such abilities? I have tried to search in JVCL, but without luck.
You could try customizing the autocomplete functionality of a regular ComboBox. Loading its items from a DB is easy:
ComboBox1.Items.Clear;
while not Table1.Eof do begin
ComboBox1.Items.AddObject( Table1.FieldByName('Company').AsString,
TObject(Table1.FieldByName('CustNo').AsInteger) );
Table1.Next;
end;
As far as the auto-complete for middle-of-word matching, you might try adapting this code. The functionality that matches at the beginning of the text in the Items is enabled by setting AutoComplete to true, and needs to be turned off before you try writing your own OnChange event handler that does auto-complete. I suggest that you could more safely do the match and selection on the enter key, because attempting to do it on the fly makes things quite hairy, as the code below will show you:
Here's my basic version: Use a regular combobox with onKeyDown, and onChange events, and AutoComplete set to false, use above code to populate it, and these two events
procedure TForm2.ComboBox1Change(Sender: TObject);
var
SearchStr,FullStr: string;
i,retVal,FoundIndex: integer;
ctrl:TComboBox;
begin
if fLastKey=VK_BACK then
exit;
// copy search pattern
ctrl := (Sender as TCombobox);
SearchStr := UpperCase(ctrl.Text);
FoundIndex := -1;
if SearchStr<>'' then
for i := 0 to ctrl.Items.Count-1 do begin
if Pos(SearchStr, UpperCase(ctrl.Items[i]))>0 then
begin
FoundIndex := i;
fsearchkeys := ctrl.Text;
break;
end;
end;
if (FoundIndex>=0) then
begin
retVal := ctrl.Perform(CB_SELECTSTRING, 0, LongInt(PChar(ctrl.Items[FoundIndex]))) ;
if retVal > CB_Err then
begin
ctrl.ItemIndex := retVal;
ctrl.SelStart := Pos(SearchStr,UpperCase(ctrl.Text))+Length(SearchStr)-1;
ctrl.SelLength := (Length(ctrl.Text) - Length(SearchStr));
end; // retVal > CB_Err
end; // lastKey <> VK_BACK
end;
procedure TForm2.ComboBox1KeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
fLastKey := Key;
end;
Suppose the contents of the list are "David Smith", and "Mark Smithers". You type S and it matches the first letter of the last name, in David Smith. Now it shows David Smith with the "David S" part not selected, and the "mith" part selected (so that the next characters you type will replace the auto completed portion, a standard auto-complete technique). Note that the above code has had to prefix the S you typed with the "David " part you didn't type. If you are a lot more clever than me, you can find a way to remember that the user typed "s" and then, maybe an "m", followed by some more letters, and eventually having typed "Smithe", match Smithers, instead of always David smith. Also note that you can only set the SelStart and SelLength to select a continuous length of a string.
The code I have provided will only work when the list of items never contains any repeated substrings. There are good reasons why the Windows Common Control combobox "autocomplete" functionality only works with prefix matching, and not mid-string matching.
Since anything that would implement mid-string matching should probably draw the part you typed in not-selected, and since that not-selected part would be in mid-string, you would probably need to write your own control from scratch and not rely on the TComboBox base code, and its underlying MS Common Controls combobox functionality.
DevExpress' "TcxExtLookupCombobox" has this capability - and more. Might be overkill though.

Ini Files - Read Multi Lines?

I am aware that Ini Files are meant for single lines of information, needless to say I am trying to read/write multi lines to and from the Ini - without much success (I always seem to do things the hard way!)
Lets say my Ini File when saved, looks like this:
[richardmarx]
Filenames=hazard
children of the night
right here waiting
Suppose the Ini File is built dynamically (ie, the richardmarx and the Filenames are not know, but unique - they could literally be anything).
How would I be able to read the Ini File?
In this example then, how could I put richardmarx into a TEdit, and the Filenames associated with richardmarx section into a memo?
Many thanks in advance.
Do not store multi-line strings into an INI file to begin with. Escape the line breaks, like #RobertFrank suggested. I would not use an asterik for that, though, as that is a valid text character. I would use something like this instead:
[richardmarx]
Filenames=hazard%nchildren of the night%nright here waiting
You can then read the string and replace the %n sequences with the value of the sLineBreak global variable. If you needed to store an actual % character, escape it as %%, eg:
[sales]
value=Sale! 50%% off%nat Macy's
You're not using a valid .ini format, so it's not going to be easy. It's much easier if you use a properly formed .ini file.
A valid ini file is of the format
[section]
akey=value
bkey=value
ckey=value
Here's a sample of reading multiple lines from an ini file. While it uses a TListBox instead of a TEdit, it should be enough to get you started.
The code below will work with an improperly formatted file as well, but you'll probably have to change the code in the ListBox1Click event to use ReadSectionValues instead and do some manual parsing for each item before displaying them; in that case, create another TStringList in the event handler and pass it instead of Memo1.Lines.
With a properly formatted ini file, you can use TIniFile.ReadSection or TMemIniFile.ReadSections to load all of the sections into a TStrings descendant, and then use ReadSection(SectionName) to get each section's values.
Here's an example - save this ini file somewhere (I've used d:\temp\sample.ini:
[A Section]
Item1=Item A1
Item2=Item A2
Item3=Item A3
Item4=Item A4
[B Section]
Item1=Item B1
Item2=Item B2
Item3=Item B3
Item4=Item B4
[C Section]
Item1=Item C1
Item2=Item C2
Item3=Item C3
Item4=Item C4
Here's a sample of the form's code:
unit Unit2;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, IniFiles;
type
TForm2 = class(TForm)
ListBox1: TListBox;
Memo1: TMemo;
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure ListBox1Click(Sender: TObject);
private
{ Private declarations }
FIni: TMemIniFile;
public
{ Public declarations }
end;
var
Form2: TForm2;
implementation
{$R *.dfm}
const
IniName = 'd:\Temp\Sample.ini';
procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction);
begin
FIni.Free;
end;
procedure TForm2.FormCreate(Sender: TObject);
begin
FIni := TMemIniFile.Create(IniName);
Memo1.Lines.Clear;
FIni.ReadSections(ListBox1.Items);
end;
procedure TForm2.ListBox1Click(Sender: TObject);
var
Section: string;
begin
if ListBox1.ItemIndex > -1 then
begin
Section := ListBox1.Items[ListBox1.ItemIndex];
FIni.ReadSection(Section, Memo1.Lines);
end;
end;
end.
Clicking on each section name in the ListBox displays the items that are in that section, as seen below:
EDIT: OK. I got curious to see how it would work with the ini file content you posted in your question.
So I made the following change:
Copied and pasted your sample ini content verbatim as a new section to the end of the Sample.ini I created above.
Ran the code, and clicked the new richardmarx item. Here's what I got:
Obviously, that wouldn't work. So I made the following additional changes:
Changed the ListBox1Click event to use FIni.ReadSectionValues instead of ReadSection.
Ran the modified application, and clicked on the C Section item to see how it displayed, and then the new richardmarx item to see how it displayed. The results are as follows:
Pre-process the .ini file! Change all line-breaks between ] and [ to some character that will never appear in a filename (like asterisk). Then use TInifile to access the file you just preprocessed, changing the asterisks back to line breaks after you retrieve the strings. (Use StringReplace)
It's a little more complicated than that if you have more than one identifier in a section. In that case, you could use the equals sign as a flag that the preceding line break should not be removed. Maybe you read the file from the end towards the beginning.
You could even create a descendant of TIniFile that did the astrerisk-to-linebreak change for you.
No, this is certainly not an elegant solution. But, sometimes brute force like this works if you're stuck! The other solutions here are probably better, but thought I'd share this anyway in case it gives you a direction to think about heading...
Using any TStrings descendent object you can just use the CommaText property to read and write all the lines as a single string.
MyStrings.CommaText := IniFile.ReadString('Section', 'Ident');
and
IniFile.WriteString('Section', 'Ident', MyStrings.CommaText);
CommaText is smart enough to handle lines containing commas by automatically embracing them in quotes.
this code show you how to write and read multi lines with INI file
procedure TForm1.SaveButtonClick(Sender: TObject);
begin
IniFile.WriteString('Name', 'FirtName', FirstName.Text);
IniFile.WriteString('Name', 'LastName', LastName.Text);
IniFile.WriteInteger('Alpha Blend', 'Alpha Blend Value', Form1.AlphaBlendValue);
//Here start save Memo Lines
LinesCount := AboutMemo.Lines.Count;
IniFile.WriteInteger('About', 'Lines Count', LinesCount);
for I := 0 to LinesCount-1 do
IniFile.WriteString('About', 'About'+IntToStr(I), AboutMemo.Lines[i]);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin;
GetCurrentDir;
IniFile := TIniFile.Create(ChangeFileExt(Application.ExeName, '.ini'));
FirstName.Text := IniFile.ReadString('Name', 'FirstName', '');
LastName.Text := IniFile.ReadString('Name', 'LastName', '');
Form1.AlphaBlendValue := IniFile.ReadInteger('Alpha Blend', 'Alpha Blend Value', 255);
//Here Start Read Memo Lins From INI File
LinesCount := IniFile.ReadInteger('About', 'Lines Count', 0);
for I := 0 to LinesCount-1 do
AboutMemo.Lines.Insert(I, IniFile.ReadString('About', 'About'+IntToStr(I), ''));
end;
end.
I wanted something similar, to include the body of an automated email in the ini file.
But I also wanted blank lines in the body. Here is part of the ini file
[EmailBody]
This is the weekly Survey Status notification email.
.
It lists Survey Reports that may need some attention.
.
The purpose of this information is to improve conduct-of-operations in our tracking,
recordkeeping, and maintenance of Survey Reports. This report will also enable
us to identify any changes made to electronic Surveys following the normal QA review.
.
Please review your information weekly and determine if you need to take corrective action.
Here is what I ended up doing
var
Config: TMemIniFile;
sl: TStringList;
...
sl := TStringList.Create;
Config.ReadSectionValues('EmailBody', sl);
for i := 0 to sl.Count - 1 do
if sl[i] = '.' then
sl[i] := '';
#Ken White
FIni.ReadSectionValues get full string after same key, like "key=value".
In .ini like
[B Section]
Item1=Item B1
Item B2
Item B3
Item4=Item B4
and after
FIni.ReadSectionValues('B Section',Memo1.Lines);
Memo1.Lines will be {'Item1=Item B1', 'Item4=Item B4'}
Unfortunately, we can't read values directly, without any key.
But in fact, it's not too difficult:
procedure TForm2.FormCreate(Sender: TObject);
var i:longint;
strtemp: TStringList;
begin
FIni := TMemIniFile.Create(IniName);
Memo1.Lines.Clear;
Fini.ReadSection('B Section',strtemp);
for i:=0 to strtemp.Count-1 do
Memo1.Items.Add(Fini.ReadString('B Section',strtemp[i],''));
end;
Memo1.Lines would be {'Item B1', 'Item B4'}
Upper method will read all values in section, without keys.
How to read not-keyed values - I don't know (if at all it possible in .ini).
As a solution for huge non-format .ini, just numerate all strings - read file like text file, find needed section and add same count index like a key, schematically(!):
FileString[i]:=inttostr(i)+'='+FileString[i];
If need mixed format, will require more complicated parser.
P.S. Sorry for my english, I'm not strong in it.
You can use ReadSections method to get all the section names in ini file.
And perhaps you can use TMemIniFile's ReadSectionValues method to read "multiline values" (ie to read whole section into stringlist). If that doesn't work then perhaps you could use GetStrings to get the content of the ini file and parse it "semy manually" - IIRC it returns you a stringlist with section names where each item's object holds another stringlist with section data.

Restoring dataset from delimiter separated values file

In short, i'm new to delphi and I want to achieve the following:
I have table definition as .cds file {index, data, date}, and some data in .csv format.
I want to load the .csv file to the table and show log it's changes and errors (ex: invalid date format).
Question
How to solve this task elegantly?
I would use JvCsvDataSet (JEDI JVCL component) because it parses CSV files properly, and then use a data-pump component, to move the data into the client dataset, along with some validation.
But if all you really need to do is provide a CSV file, to a data-aware control, I would leave out the ClientDataSet completely, and just use a component built for the purpose you are trying to do. Don't use a screw as a nail, or a nail as a screw. They are both made of metal, but they do different jobs.
CSV file table definitions are quite different in purpose, to a CDS table definition, and the JvCsvDataSet provides a simple string property which you can set up to give the metadata (field datatypes like integer or string or date-time, and associated field names, for CSV files that lack a header row) more easily, than you could hope to do it in ClientDatSet.
You can read line by line from the .csv, set each line to 'DelimitedText' of a StringList, append a record to the dataset, loop the string list to set each field's value and then post to the dataset. You can put the 'field value assinging'/'posting' in a try-except block and log any error message of raised exceptions together with information you like (e.g. malformed field value/name, line number, and/or entire line etc.) to a file f.i.
(I don't understand what you mean by 'changes', from what I understood, lines from the .csv will be inserted to a dataset, hence all changes will be inserts.)
edit: To be able to discuss on something concrete (I'm having a hard time grasping the task :))
Sample data (part of CodeGear sample 'Clients.cds'):
Davis;Jennifer;1023495,0000;100
Cranberry
St.;Wellesley;MA;02181;516-292-3945;01.01.93
Jones;Arthur;2094056,0000;10 Hunnewell
St;Los
Altos;CA;94024;415-941-4321;07.02.81
Parker;Debra;1209395,0000;74 South
St;Atherton;CA;98765;916-213-2234;23.10.90
Sawyer;Dave;3094095,0000;101 Oakland
St;Los
Altos;CA;94022;415-948-9998;21.12.89
White;Cindy;1024034,0000;1 Wentworth
Dr;Los
Altos;CA;94022;415-948-6547;01.10.92
procedure TForm1.FormCreate(Sender: TObject);
begin
CDS.FieldDefs.Add('LAST_NAME', ftString, 20);
CDS.FieldDefs.Add('FIRST_NAME', ftString, 20);
CDS.FieldDefs.Add('ACCT_NBR', ftInteger);
CDS.FieldDefs.Add('ADDRESS_1', ftString, 30);
CDS.FieldDefs.Add('CITY', ftString, 15);
CDS.FieldDefs.Add('STATE', ftString, 2);
CDS.FieldDefs.Add('ZIP', ftString, 5);
CDS.FieldDefs.Add('TELEPHONE', ftString, 12);
CDS.FieldDefs.Add('DATE_OPEN', ftDate);
CDS.CreateDataSet;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
csv: TextFile;
Rec: string;
Fields: TStringList;
LineNo: Integer;
i: Integer;
begin
Fields := TStringList.Create;
try
Fields.StrictDelimiter := True;
Fields.Delimiter := ';';
AssignFile(csv, ExtractFilePath(Application.ExeName) + 'clients.csv');
try
Reset(csv);
LineNo := 0;
while not Eof(csv) do begin
Inc(LineNo);
Readln(csv, Rec);
Fields.DelimitedText := Rec;
CDS.Append;
for i := 0 to Fields.Count - 1 do
try
CDS.Fields[i].Value := Fields[i]; // Variant conversion will raise
// exception where conversion from string fails
except
on E:EDatabaseError do begin
CDS.Cancel; // Failed, discard the record
// log the error instead of showing a message
ShowMessage(Format('Cannot set field "%s" at line %d' + sLineBreak +
'Error: %s', [CDS.Fields[i].FieldName, LineNo, E.Message]));
Break; // Continue with next record
end;
end;
if CDS.State = dsInsert then // It's not dsInsert if we Cancelled the Insert
try
CDS.Post;
except
on E:EDatabaseError do begin
// log error instead of showing
ShowMessage(Format('Cannot post line %d' + sLineBreak + 'Error: %s',
[LineNo, E.Message]));
CDS.Cancel;
end;
end;
end;
finally
CloseFile(csv);
end;
finally
Fields.Free;
end;
end;
procedure TForm1.CDSBeforePost(DataSet: TDataSet);
begin
// Superficial posting error
if CDS.FieldByName('LAST_NAME').AsString = '' then
raise EDatabaseError.Create('LAST_NAME cannot be empty');
end;
AFAIK, there's no direct way to load .csv data into a TClientDataset.
The easiest way I can think of would be to use the TTextDataSet (found in Demos\Delphi\Database\TextData, available from Start->All Programs->Embarcadero RAD Studio XE->Samples). You can use it just like any other TDataSet, meaning you can read from it's Fields or use FieldByName, and it supports Bof, Eof, Next, and Prior.
You can simply iterate through and try to assign to your CDS columns, and it will generate errors you can then handle or log.
You can install TTextDataset like any other component, or just add the unit to the uses clause and create it at runtime. There's a readme.htm file in the folder that doesn't explain much; the key properties are FileName and Active. :)
It includes both a pre-designed package (TextPkg.dproj) and a test app (TextTest.dproj). There's also a project group (TextDataGroup.groupproj) - you can simply open this in the IDE, build and install the TextPkg package, and then compile and run the test app. The source for the test app shows usage pretty well.
In the off-chance that your database is DBISAM, you can simply use the IMPORT SQL statement.
import table "tablename" from "myinputfile.csv" Delimiter ',';
Other databases may have a similar feature.

Delphi - Read File To StringList, then delete and write back to file

I'm currently working on a program to generate the hashes of files, in Delphi 2010. As part of this I have a option to create User Presets, e.g. pre-defined choice of hashing algo's which the user can create/save/delete. I have the create and load code working fine. It uses a ComboBox and loads from a file "fhpre.ini", inside this file is the users presets stored in format of:-
PresetName
PresetCode (a 12 digit string using 0 for don't hash and 1 for do)
On application loading it loads the data from this file into the ComboBox and an Array with the ItemIndex of ComboBox matching the corrisponding correct string of 0's and 1's in the Array.
Now I need to implement a feature to have the user delete a preset from the list. So far my code is as follows,
procedure TForm1.Panel23Click(Sender : TObject);
var
fil : textfile;
contents : TStringList;
x,i : integer;
filline : ansistring;
filestream : TFileStream;
begin //Start Procedure
//Load data into StringList
contents := TStringList.Create;
fileStream := TFileStream.Create((GetAppData+'\RFA\fhpre.ini'), fmShareDenyNone);
Contents.LoadFromStream(fileStream);
fileStream.Destroy();
//Search for relevant Preset
i := 0;
if ComboBox4.Text <> Contents[i] then
begin
Repeat
i := i + 1;
Until ComboBox4.Text = Contents[i];
end;
contents.Delete(i); //Delete Relevant Preset Name
contents.Delete(i); //Delete Preset Digit String
//Write StringList back to file.
AssignFile(fil,(GetAppData+'\RFA\fhpre.ini'));
ReWrite(fil);
for i := 0 to Contents.Count -1 do
WriteLn(Contents[i]);
CloseFile(fil);
Contents.Free;
end;
However if this is run, I get a 105 error when it gets to the WriteLn section. I'm aware that the code isn't great, for example doesn't have checks for presets with same name, but that will come, I want to get the base code working first then can tweak and add extra checks etc.
Any help would be appreciated.
You are aware, I hope, that TStringList has LoadFromFile and SaveToFile methods?
And if you can't use those methods for some reason, why use a stream for reading but WriteLn for writing?
To write to a file using WriteLn, you must specify the file as the first argument:
WriteLn(fil, Contents[i]);
without the argument it tries to write to the console (which is presumably not available in your Windows application). Error 105 is "File not open for output".
Since you are dealing with an .ini file, you should be using the TIniFile class to manipulate its contents as needed. That will make your configuration and code much easier to maintain.
Here is what the final code looks like after implementing TStringlist.LoadFromFile and TStringList.SaveToFile. It could probably still benifit from some optimization but that will come in time.
Procedure TForm1.Panel23Click(Sender : TObject);
var
contents : TStringList;
i : integer;
begin //Start Procedure
//Load data into StringList
Contents := TStringList.Create;
Contents.LoadFromFile((GetAppData+'\RFA\fhpre.ini'));
//Search for relevant Preset
i := 0;
if ComboBox4.Text <> Contents[i] then
begin
Repeat
i := i + 1;
Until ComboBox4.Text = Contents[i];
end;
contents.Delete(i); //Delete Relevant Preset Name
contents.Delete(i); //Delete Preset Digit String
Contents.SaveToFile((GetAppData+'\RFA\fhpre.ini'));
AddPresetCombo(GetAppData+'\RFA\fhpre.ini'); //Populate Comobo With Presets From File
Form1.ComboBox4.ItemIndex := 0;
Contents.Free;
end;

Resources