Ini Files - Read Multi Lines? - delphi

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.

Related

How to replace TListbox Items property with my own published object list based type in a TCustomListBox control?

Overview
This question is a second attempt based on this one I recently asked: How can I make a TList property from my custom control streamable?
Although I accepted the answer in that question and it worked, I soon realized that TCollection is not the solution or requirement I was looking for.
Requirements
To keep my requirements as simple and clear to understand as possible, this is what I am attempting to:
Derive a new custom control based on TCustomListBox
Replace the Items property with my own Items type, eg a TList.
The TList (Items property) will hold objects, each containing a caption and a image index property etc.
Ownerdraw my listbox and draw its icons and text etc.
Create a property editor to edit the Items at design-time.
With that in mind, I know how to create the custom control, I know how to work with TList or even TObjectList for example, I know how to ownerdraw the control and I also know how to create the property editor.
Problem
What I don't know is how to replace the standard listbox Items type with my own? well I kind of do (publishing my own property that shares the same name), only I need to make sure it is fully streamable with the dfm.
I have searched extensively on this subject and have tried studying code where TListView and TTreeView etc publishes its Items type but I have found myself more confused than ever.
In fact I came across this very old question asked by someone else on a different website which asks very much what I want to do: Streaming a TList property of a component to a dfm. I have quoted it below in the event the link is lost:
I recently wrote a component that publishes a TList property. I then created a property editor for the TList to enable design-time editing. The problem is that the TList doesn't stream to the dfm file, so all changes are lost when the project is closed. I assume this is because TList inherits from TObject and not from TPersistant. I was hoping there was an easy work around for this situation (or that I have misunderstood the problem to begin with). Right now all I can come up with is to switch to a TCollection or override the DefineProperties method. Is there any other way to get the information in the TList streamed to and from the dfm?
I came across that whilst searching keywords such as DefineProperties() given that this was an alternative option Remy Lebeau briefly touched upon in the previous question linked at the top, it also seemed to be the answer to that question.
Question
I need to know how to replace the Items (TStrings) property of a TCustomListBox derived control with my own Items (TList) or Items (TObjectList) etc type but make it fully streamable with the dfm. I know from previous comments TList is not streamable but I cannot use TStrings like the standard TListBox control does, I need to use my own object based list that is streamable.
I don't want to use TCollection, DefineProperties sounds promising but I don't know how exactly I would implement this?
I would greatly appreciate some help with this please.
Thank you.
Override DefineProperties procedure in your TCustomListBox (let's name it TMyListBox here). In there it's possible to "register" as many fields as you wish, they will be stored in dfm in the same way as other fields, but you won't see them in object inspector. To be honest, I've never encountered having more then one property defined this way, called 'data' or 'strings'.
You can define 'normal' property or binary one. 'Normal' properties are quite handy for strings, integers, enumerations and so on. Here is how items with caption and ImageIndex can be implemented:
TMyListBox = class(TCustomListBox)
private
//other stuff
procedure ReadData(reader: TReader);
procedure WriteData(writer: TWriter);
protected
procedure DefineProperties(filer: TFiler); override;
//other stuff
public
//other stuff
property Items: TList read fItems; //not used for streaming, not shown in object inspector. Strictly for use in code itself. We can make it read-only to avoid memory leak.
published
//some properties
end;
that's DefineProperties implementation:
procedure TMyListBox.DefineProperties(filer: TFiler);
begin
filer.DefineProperty('data', ReadData, WriteData, items.Count>0);
end;
fourth argument, hasData is Boolean. When your component is saved to dfm, DefineProperties is called and it's possible to decide at that moment is there any data worth saving. If not, 'data' property is omitted. In this example, we won't have this property if there is no items present.
If we expect to ever use visual inheritance of this control (for example, create a frame with this listBox with predefined values and then eventually change them when put to form), there is a possibility to check, is value of this property any different than on our ancestor. Filer.Ancestor property is used for it. You can watch how it's done in TStrings:
procedure TStrings.DefineProperties(Filer: TFiler);
function DoWrite: Boolean;
begin
if Filer.Ancestor <> nil then
begin
Result := True;
if Filer.Ancestor is TStrings then
Result := not Equals(TStrings(Filer.Ancestor))
end
else Result := Count > 0;
end;
begin
Filer.DefineProperty('Strings', ReadData, WriteData, DoWrite);
end;
This would save a little bit of space (or lots of space if image is stored within) and sure is elegant, but in first implementation it can well be omitted.
Now the code for WriteData and ReadData. Writing is much easier usually and we may begin with it:
procedure TMyListBox.WriteData(writer: TWriter);
var i: Integer;
begin
writer.WriteListBegin; //in text dfm it will be '(' and new line
for i:=0 to items.Count-1 do begin
writer.WriteString(TListBoxItem(items[I]).caption);
writer.WriteInteger(TListBoxItem(items[I]).ImageIndex);
end;
writer.WriteListEnd;
end;
In dfm it will look like this:
object MyListBox1: TMyListBox
data = (
'item1'
-1
'item2'
-1
'item3'
0
'item4'
1)
end
Output from TCollection seems more elegant to me (triangular brackets and then items, one after another), but what we have here would suffice.
Now reading it:
procedure TMyListBox.ReadData(reader: TReader);
var item: TListBoxItem;
begin
reader.ReadListBegin;
while not reader.EndOfList do begin
item:=TListBoxItem.Create;
item.Caption:=reader.ReadString;
item.ImageIndex:=reader.ReadInteger;
items.Add(item); //maybe some other registering needed
end;
reader.ReadListEnd;
end;
That's it. In such a way rather complex structures can be streamed with ease, for example, two-dimensional arrays, we WriteListBegin when writing new row and then when writing new element.
Beware of WriteStr / ReadStr - these are some archaic procedures which exist for backward compatibility, ALWAYS use WriteString / ReadString instead!
Other way to do is to define binary property. That's used mostly for saving images into dfm. Let's say, for example, that listBox has hundreds of items and we'd like to compress data in it to reduce size of executable. Then:
TMyListBox = class(TCustomListBox)
private
//other stuff
procedure LoadFromStream(stream: TStream);
procedure SaveToStream(stream: TStream);
protected
procedure DefineProperties(filer: TFiler); override;
//etc
end;
procedure TMyListBox.DefineProperties(filer: TFiler);
filer.DefineBinaryProperty('data',LoadFromStream,SaveToStream,items.Count>0);
end;
procedure TMyListBox.SaveToStream(stream: TStream);
var gz: TCompressionStream;
i: Integer;
value: Integer;
item: TListBoxItem;
begin
gz:=TCompressionStream.Create(stream);
try
value:=items.Count;
//write number of items at first
gz.Write(value, SizeOf(value));
//properties can't be passed here, only variables
for i:=0 to items.Count-1 do begin
item:=TListBoxItem(items[I]);
value:=Length(item.Caption);
//almost as in good ol' Pascal: length of string and then string itself
gz.Write(value,SizeOf(value));
gz.Write(item.Caption[1], SizeOf(Char)*value); //will work in old Delphi and new (Unicode) ones
value:=item.ImageIndex;
gz.Write(value,SizeOf(value));
end;
finally
gz.free;
end;
end;
procedure TMyListBox.LoadFromStream(stream: TStream);
var gz: TDecompressionStream;
i: Integer;
count: Integer;
value: Integer;
item: TListBoxItem;
begin
gz:=TDecompressionStream.Create(stream);
try
gz.Read(count,SizeOf(count)); //number of items
for i:=0 to count-1 do begin
item:=TListBoxItem.Create;
gz.Read(value, SizeOf(value)); //length of string
SetLength(item.caption,value);
gz.Read(item.caption[1],SizeOf(char)*value); //we got our string
gz.Read(value, SizeOf(value)); //imageIndex
item.ImageIndex:=value;
items.Add(item); //some other initialization may be needed
end;
finally
gz.free;
end;
end;
In dfm it would look like this:
object MyListBox1: TMyListBox1
data = {
789C636260606005E24C86128654865C064386FF40802C62C40002009C5607CA}
end
78 is sort of signature of ZLib, 9C means default compression, so it works (there are only 2 items actually, not hundreds). Of course, this is just one example, with BinaryProperties any possible format may be used, for example saving to JSON and putting it into stream, or XML or something custom. But I'd not recommend to use binary unless it's absolutely inevitable, because it's difficult to see from dfm, what happens in component.
It seems like good idea to me to actively use streaming when implementing component: we can have no designer at all and set all values by manually editing dfm and see if component behaves correctly. Reading/loading itself can be tested easily: if component is loaded, then saved and text is just the same, it's all right. It's so 'transparent' when streaming format is 'human-readable', self-explaining that it often overweighs drawbacks (like file size) if there are any.

Parsing a flat text file

I am developing an application and I have to upload data from CSV files into a DB tables. Problem is, I don’t have CSV files but I have flat text files to be converted into CSV.
An additional problem is, as the application is used by several customers who have different systems, I have different flat text files with different layouts.
What I want to achieve is to create an application that loads “rules” from a special file; these rules will be processed with the flat text file in order generate the CSV file. The application that converts from flat file to CSV would be the same, just the set of rules would be different.
How can I achieve this? What is the best practice you recommend?
It depends on the complexity of the rules. If the only varying input is the names of the columns and the separator used, then it's pretty easy, but if you want to be able to parse completely different formats (like XML or so) as well, then it's a different story.
I myself would choose to implement a base class for a 'record' reader that reads records from a file and outputs them to a dataset or CSV.
Then, you can implement child classes that implement reading different source formats.
If you like, you can then add specific rules for those format, so you can make a generic XMLReader that descends from BaseReader, but which allows for configurable column names. But I would start with a bunch of hard-coded readers for the formats you got, until it's more clear which dialects of those formats you may encounter.
Edit: On request, an example of how it could look like.
Note, this example is far from ideal! It reads a custom format, transfers it to one specific table structure and saves that as an CSV file. You may want to split it a little further, so you can reuse the code for different table structures. Especially the field defs, you may want to be able to set in a descendant class or maybe a factory class.
But for the sake of simplicity I have taken a more rigid approach and put a little too much intelligence in one single base class.
The base class has the logic needed to create an in-memory dataset (I used a TClientDataSet). It can 'Migrate' a file. In practice, this means it reads, validates and exports the file.
The reading is abstract and must be implemented in a child class. It should read the data to the in memory dataset. That allows you to do all necessary validation in the client dataset. This allows to you enforce field types and sized and do any additional checking if you need to, in a database/file format agnostic way.
The validating and writing is done using the data in the dataset. From the moment where the source file is parsed to a dataset, no knowledge about the source file format is required anymore.
Declaration:
Don't forget to use DB, DBClient.
type
TBaseMigrator = class
private
FData: TClientDataset;
protected
function CSVEscape(Str: string): string;
procedure ReadFile(AFileName: string); virtual; abstract;
procedure ValidateData;
procedure SaveData(AFileName: string);
public
constructor Create; virtual;
destructor Destroy; override;
procedure MigrateFile(ASourceFileName, ADestFileName: string); virtual;
end;
Implementation:
{ TBaseReader }
constructor TBaseMigrator.Create;
begin
inherited Create;
FData := TClientDataSet.Create(nil);
FData.FieldDefs.Add('ID', ftString, 20, True);
FData.FieldDefs.Add('Name', ftString, 60, True);
FData.FieldDefs.Add('Phone', ftString, 15, False);
// Etc
end;
function TBaseMigrator.CSVEscape(Str: string): string;
begin
// Escape the string to a CSV-safe format;
// Todo: Check if this is sufficient!
Result := '"' + StringReplace(Result, '"', '""', [rfReplaceAll]) + '"';
end;
destructor TBaseMigrator.Destroy;
begin
FData.Free;
inherited;
end;
procedure TBaseMigrator.MigrateFile(ASourceFileName, ADestFileName: string);
begin
// Read the file. Descendant classes need to override this method.
ReadFile(ASourceFileName);
// Validation. Implemented in base class.
ValidateData;
// Saving/exporting. For now implemented in base class.
SaveData(ADestFileName);
end;
procedure TBaseMigrator.SaveData(AFileName: string);
var
Output: TFileStream;
Writer: TStreamWriter;
FieldIndex: Integer;
begin
Output := TFileStream.Create(AFileName,fmCreate);
Writer := TStreamWriter.Create(Output);
try
// Write the CSV headers based on the fields in the dataset
for FieldIndex := 0 to FData.FieldCount - 1 do
begin
if FieldIndex > 0 then
Writer.Write(',');
// Column headers are escaped, but this may not be needed, since
// they likely don't contain quotes, commas or line breaks.
Writer.Write(CSVEscape(FData.Fields[FieldIndex].FieldName));
end;
Writer.WriteLine;
// Write each row
FData.First;
while not FData.Eof do
begin
for FieldIndex := 0 to FData.FieldCount - 1 do
begin
if FieldIndex > 0 then
Writer.Write(',');
// Escape each value
Writer.Write(CSVEscape(FData.Fields[FieldIndex].AsString));
end;
Writer.WriteLine;
FData.Next
end;
finally
Writer.Free;
Output.Free;
end;
end;
procedure TBaseMigrator.ValidateData;
begin
FData.First;
while not FData.Eof do
begin
// Validate the current row of FData
FData.Next
end;
end;
An example child class: TIniFileReader, which reads inifile sections as if they were database records. As you can see, you only need to implement the logic to read the file.
type
TIniFileReader = class(TBaseMigrator)
public
procedure ReadFile(AFileName: string); override;
end;
{ TIniFileReader }
procedure TIniFileReader.ReadFile(AFileName: string);
var
Source: TMemIniFile;
IDs: TStringList;
ID: string;
i: Integer;
begin
// Initialize an in-memory dataset.
FData.Close; // Be able to migrate multiple files with one instance.
FData.CreateDataSet;
// Parsing a weird custom format, where each section in an inifile is a
// row. Section name is the key, section contains the other fields.
Source := TMemIniFile.Create(AFileName);
IDs := TStringList.Create;
try
Source.ReadSections(IDs);
for i := 0 to IDs.Count - 1 do
begin
// The section name is the key/ID.
ID := IDs[i];
// Append a row.
FData.Append;
// Read the values.
FData['ID'] := ID;
FData['Name'] := Source.ReadString(ID, 'Name', '');
// Names don't need to match. The field 'telephone' in this propriety
// format maps to 'phone' in your CSV output.
// Later, you can make this customizable (configurable) if you need to,
// but it's unlikely that you encounter two different inifile-based
// formats, so it's a waste to implement that until you need it.
FData['Phone'] := Source.ReadString(ID, 'Telephone', '');
FData.Post;
end;
finally
IDs.Free;
Source.Free;
end;
end;
This is very similar to the problems faced by "screen scrapers". If end users are intended to be able to use this, I would avoid regular expressions (except as an internal implementation detail, if needed) and not expose raw regular expression editing to end users.
Instead, I would let them load up samples of their data files, and construct their rules visually, with a drag, and drop style.
Click a "Match text" button, click and drag to select a rectangular region on the screen. Have options so that it might be allowed to move a certain amount up or down or left or right, if the format isn't precise or repeatable. Establish limits on how far you can go outside the original box.
Click a "grab text" button, click and drag to a rectangular or non-rectangular (flow) area on the screen. Name the output with a field, and give it a type (integer, string[x], etc). Similar limits apply as step 1.
Click save and the template rules are written to disk. Load a different file and see if the rules still apply nicely.
Relevant wikipedia topic.

How to create your own non system Clipboard?

Is it possible, and if so how would you go about implementing your own clipboard?
By this I mean be able to Copy and Paste anything to and from it just like the Windows clipboard does, but without actually interfering with the system clipboard.
To give a better idea this is what I tried:
uses
ClipBrd;
...
procedure TMainForm.actCopyExecute(Sender: TObject);
var
MyClipboard: TClipboard;
begin
MyClipboard := TClipboard.Create;
try
MyClipboard.AsText := 'Copy this text';
finally
MyClipboard.Free;
end;
end;
That works in that it will copy the string "Copy this text" to the clipboard, but it overwrites whatever was on the Windows clipboard.
The above must just create an instance of the Windows clipboard, not actually creating your own.
Note that the custom clipboard could hold any data not just plain text. It should work just the same as the Windows clipboard, but without interfering with it (losing whatever was on it).
How could this be achieved?
Thanks.
Your question is confusing; you say you want to do it without affecting the system clipboard, but then (from your own comment to your question) you seem to be wanting to implement something like MS Office's Paste Special.
If it's the first, as others have said you can't do that using the TClipboard wrapper; you have to implement your own, and passing information between applications will be very difficult.
If it's the second, you do this by using the Windows API RegisterClipboardFormat to define your own format.
type
TForm1=class(TForm)
YourCustomFormat: Word;
procedure FormCreate(Sender: TObject);
end;
implementation
constructor TForm1.FormCreate(Sender: TObject);
begin
YourCustomFormat := RegisterClipboardFormat('Your Custom Format Name');
end;
To put info into the clipboard in a custom format, you have to use GlobalAlloc and GlobalLock to allocate and lock a global memory block, copy your data into that block, unlock the block using GlobalUnlock, use TClipboard.SetAsHandle to transfer the memory block into the clipboard. You then need to then call GlobalFree to free the memory block.
To retrieve things in your custom format, you do basically the same thing with a couple of steps reversed. You use GlobalAlloc/GlobalLock as before, use TClipboard.GetAsHandle to retrieve the clipboard's content, copy it into a local variable, and then call GlobalFree.
Here's an old example of putting a custom format (in this case, RTF text) into the clipboard - it's from a newsgroup post by Dr. Peter Below of TeamB. (The code and formatting are his from the original post; I've not tested it or even compiled it.) Reversing the process to get it back out should be clear from my instructions on what to change above, and I leave that to you to work out. :)
procedure TForm1.BtnSetRTFClick(Sender: TObject);
Const
testtext: PChar = '{\rtf1\ansi\pard\plain 12{\ul 44444}}';
testtext2: PChar = '{\rtf1\ansi'+
'\deff4\deflang1033{\fonttbl{\f4\froman\fcharset0\fprq2 Times New Roman;}}'
+'\pard\plain 12{\ul 44444}}';
flap: Boolean = False;
Var
MemHandle: THandle;
rtfstring: PChar;
begin
If flap Then
rtfstring := testtext2
Else
rtfstring := testtext;
flap := not flap;
MemHandle := GlobalAlloc( GHND or GMEM_SHARE, StrLen(rtfstring)+1 );
If MemHandle <> 0 Then Begin
try
StrCopy( GlobalLock( MemHandle ), rtfstring );
GlobalUnlock( MemHandle );
With Clipboard Do Begin
Open;
try
AsText := '1244444';
SetAsHandle( CF_RTF, MemHandle );
finally
Close;
end;
End;
Finally
GlobalFree( MemHandle );
End;
End
Else
MessageDlg('Global Alloc failed!',
mtError, [mbOK], 0 );
end;
You should define your own custom Clipboard. It may look something like this:
type
TMyCustomClipboard = class
private
FStream: TMemoryStream;
function GetAsText: string;
procedure SetAsText(const Value: string);
...
public
constructor Create;
destructor Destroy; override;
procedure Clear;
property AsText: string read GetAsText write SetAsText;
procedure AsAnyThing: AnyType read GetAsAnyThing write AsAnyThing;
...
end;
Then you can use FStream as custom clipboard container. You can store (Copy) any data inside that stream and use(Paste) it when you need it. You just need to write some Get/Set methods for your data types.
TClipboard is a class incapsulating system clipboard, so you can't use it to instantiate another copy of a clipboard. You should implement your own class, representing a universal buffer with setters and getters.
You cannot. You can have an internal memory buffer that you move data into and out of, you can call it "copy" and "paste" if you want, but don't put it in the user interface that way, or you'll just confuse your users. There is only one system clipboard, and you cannot put data in it without affecting other programs. If your next thought is to save the clipboard, overwrite with your stuff, then restore the original contents, don't bother.

Create an exact copy of TPanel on Delphi5

I have a TPanel pnlMain, where several dynamic TPanels are created (and pnlMain is their Parent) according to user actions, data validations, etc. Every panel contains one colored grid full of strings. Apart from panels, there are some open source arrows components and a picture. Whole bunch of stuff.
Now I want user to be able to print this panel (I asked how to do it on this question), but before printing, user must be presented with a new form, containing copy of pnlMain. On this form user has to do some changes, add few components and then print his customized copy of pnlMain. After printing user will close this form and return to original form with original pnlMain. And – as you can guess – original pnlMain must remain intact.
So is there any clever way to copy whole TPanel and it’s contents? I know I can make it manually iterating through pnlMain.Controls list.
Code based as iterating on child controls, but not bad in anyway ;-)
procedure TForm1.btn1Click(Sender: TObject);
function CloneComponent(AAncestor: TComponent): TComponent;
var
XMemoryStream: TMemoryStream;
XTempName: string;
begin
Result:=nil;
if not Assigned(AAncestor) then
exit;
XMemoryStream:=TMemoryStream.Create;
try
XTempName:=AAncestor.Name;
AAncestor.Name:='clone_' + XTempName;
XMemoryStream.WriteComponent(AAncestor);
AAncestor.Name:=XTempName;
XMemoryStream.Position:=0;
Result:=TComponentClass(AAncestor.ClassType).Create(AAncestor.Owner);
if AAncestor is TControl then TControl(Result).Parent:=TControl(AAncestor).Parent;
XMemoryStream.ReadComponent(Result);
finally
XMemoryStream.Free;
end;
end;
var
aPanel: TPanel;
Ctrl, Ctrl_: TComponent;
i: integer;
begin
//handle the Control (here Panel1) itself first
TComponent(aPanel) := CloneComponent(pnl1);
with aPanel do
begin
Left := 400;
Top := 80;
end;
//now handle the childcontrols
for i:= 0 to pnl1.ControlCount-1 do begin
Ctrl := TComponent(pnl1.Controls[i]);
Ctrl_ := CloneComponent(Ctrl);
TControl(Ctrl_).Parent := aPanel;
TControl(Ctrl_).Left := TControl(Ctrl).Left;
TControl(Ctrl_).top := TControl(Ctrl).top;
end;
end;
code from Delphi3000 article
Too much code... ObjectBinaryToText and ObjectTextToBinary do the job nicely using streaming.
Delphi 7 have a code example, don't know 2009 (or 2006, never bothered to look) still have it.
See D5 help file for those functions (don't have d5 available here).
I'd do it by using RTTI to copy all the properties. You'd still have to iterate over all the controls, but when you need to set up the property values, RTTI can help automate the process. You can get an example towards the bottom of this article, where you'll find a link to some helper code, including a CopyObject routine.

How to save a Tlistview layout

How do I save a Tlistviews layout in Delphi 2007?
I have been asked to write some code to allow users to re-order columns in a TListview (well all TListviews in our application), I have the code working (by manipulating the columns index and setting width to zero to hide columns not needed) but now I need a way to save the state of the view when to form exits.
What is the best way to do this? I thought about serialization, but I dont need the data or sort order so that seamed a bit overkill to me.
Some things to ponder
It needs to be on a per user basis
It needs to be flexible, in-case we add a new column in the middle of the listview
There is no garantee that the Column headding will be unique
The listview name may not be unique across the application
Any ideas?
If you only want to save and load a certain part of the data you can store it n an ini or xml file.
General data can be written to the file. Columns is another problem. You need to find an unique identification for each column. The ini could be something like:
[Settings]
[Col_1]
position=1
width=500
title=hello world
align=left
sort=ascending
.. etc for more fields and more columns.
If you uses a listview helper class, you only need to write the code once:
TListviewHelper = class helper for TListView;
public
procedure SaveToFile(const AFilename: string);
procedure LoadFromFile(const AFileName: string);
end;
procedure TListviewHelper.SaveToFile(const AFilename: string);
var
ini : TIniFile;
begin
ini := TIniFile.Create(AFileName);
try
// Save to ini file
finally
ini.Free;
end;
end;
procedure TListviewHelper.LoadFromFile(const AFileName: string);
var
ini : TIniFile;
begin
ini := TIniFile.Create(AFileName);
try
// Load from ini file
finally
ini.Free;
end;
end;
If TListviewHelper is within scope, you have access to the extra methods.
I suggest you inherit from Tlistview (or is there a TCustomListView) to create your own component, class helpers are nice but unofficial.
Perhaps the easiest way to store the order of the columns would be to define a ID for each as a meaningfull string, and store the list in the right order in the registry.
For instance, let's suppose your columns were ordered like:
Name | First name | Age | Job title
Then the stored string in the registry could be:
"Name,FName,Age,JTitle"
To be stored in the appropriate registry entry, under the appropriate key (typically HCKU\SOFTWARE\MyApplication, under the key ColumnOrder for instance)

Resources