I'm trying to write a simple SQLite application using Lazarus and the SQLdb components.
I connect to the database and populate a TDBGrid. Problem is that all columns that are text fields display the value "(MEMO)" rather then the string in database.
I have found a simple solution:
The property dgDisplayMemoText from the DBGrid must be enabled.
I forgot the source of this but this is what I am doing with memo fields in tdbgrid.
bluish is right about the gettext event, this is how to implement it in the code:
Create a class called MemoDifier:
MemoDifier = class
public
procedure DBGridOnGetText(Sender: TField; var aText: string;
DisplayText: boolean);
end;
At the implementation section of your code, put this:
procedure MemoDifier.DBGridOnGetText(Sender: TField; var aText: string;
DisplayText: boolean);
begin
if (DisplayText) then
aText := Sender.AsString;
end;
Then click the tdbgrid control in your form and at the Object Inspector(Lazarus IDE), click the Events tab, scroll below to find the OnPrepareCanvas event. Double click it to generate the code. Then modify the code to suit to your needs such as the name of your tdbgrid control:
procedure Tmainui.TDBGrid1PrepareCanvas(sender: TObject;
DataCol: Integer; Column: TColumn; AState: TGridDrawState);
var
MemoFieldReveal: MemoDifier;
begin
if (DataCol = 1) then
begin
try
TDBGrid1.Columns.Items[0].Field.OnGetText := #MemoFieldReveal.DBGridOnGetText;
TDBGrid1.Columns.Items[1].Field.OnGetText := #MemoFieldReveal.DBGridOnGetText;
TDBGrid1.Columns.Items[2].Field.OnGetText := #MemoFieldReveal.DBGridOnGetText;
except
On E: Exception do
begin
ShowMessage('Exception caught : ' + E.Message);
end;
end;
end;
end;
The variable MemoFieldReveal points to the class MemoDifier. Don't forget to modify the index (Items[x]) to point to your index number of the tdbgrid items/fields which shows the (MEMO) text.
Another option
If you are using TZConection add this line to you code when you Connect you database
TZConnection).Properties.Add('Undefined_Varchar_AsString_Length=100');
As said on IRC, you probably need to add the fields of your query to the form, (so that "field" components are generated for them) and then implement the TMemoField.GetText event.
See if entering the "fields" field in the object inspector brings up an editor to generate the components (it does so in Zeos iirc).
Memo fields cannot be shown in the TDBGrid. Add TDBMemo to the form and connect it to the same TDataSource. You will see the text in your memo in this case.
I have the same in MySQL and Tgrid so I'm hoping the answer is the same as it is quite simple :-)
Say if s is the field causing the problem then Instead of writing
SELECT s
write
SELECT LEFT(s,200) AS s
An apparently simple solution is to limit the length of the TEXT in the field using something like VARCHAR(n) in the column type where n is the maximum number of allowed characters.
Old question but I came across it while looking for a similar issue, but on the data retrieved by code rather than with a DBGrid; When I retrieved a string fields with the methods DisplayText or Text, I got "(memo)" instead of the correct value.
The answer is actually simpleā¦ The TSQLQuery class owns a set of methods called AsXxx to get the data according to a type of data. Here use AsString to assign a variable inline.
SQLQuery1.Open;
//some check to make sure there are fields
name:=SQLQuery1.Fields[1].AsString; //gives "English" for example
code:=SQLQuery1.Fields[2].DisplayText; //gives "(Memo) instead of "en"
More, if you don't know at design time of which type the variable is (for example, you want to design a generical function for any kind of table) with its FieldDefs property of your TSqlQuery object can help you.
It owns a DataType property that allows to choose which AsXxx function to use. Here for example.
SQLQuery1.Open;
//some check to make sure there are fields
with SQLQuery1.FieldDefs[1].DataType of
ftMemo: SQLQuery1.Fields[1].DisplayText; //gives "(Memo)"
ftString: name:=SQLQuery1.Fields[1].AsString; //gives "English" for example
...
end;
And if you look at the datatype while debugging, you will notice that with SQLite, any text is seen as a ftMemo, not a ftString for there is only one type of text there.
after some tests, I could see that although the fields are treated as "MEMO", for "string" fields, just keep the "VARCHAR" option instead of "TEXT" when creating the table.
This article gives a solution: Displaying and editing MEMO fiels in Delphi's TDBGrid.
Here I summarize what you have to do:
in the .dfm add OnGetText = MyDataSetMyFieldGetText to the TMemoField (here named MyField) belonging to your data set (for example a TTable, here named MyDataSet)
in the .pas > interface > type > inside your form definition, add
procedure MyDataSetMyFieldGetText(Sender: TField; var Text: string; DisplayText: Boolean);
in the .pas > implementation > add this method
procedure TDM.WorkVisiteNoteGetText(Sender: TField; var Text: string; DisplayText: Boolean);
begin
Text := Copy(WorkVisiteNote.AsString, 1, 100);
end;
Related
I feel like an idiot because my question seams so simple but I don't get it done :D
My Settings is that:
One Dataset (Memtable), One Stringgrid. The Grid is bind via live Bindungs.
I would like to sort my Columns by clicking on the GridHeader. In the OnHeaderClick Event I get an tColumn Object. I only can read the Column.Header String, but I changed the Text from the Header to a more speakable Text. When I put Column.header into Memtable.Indexfieldsname Memtable says that field does not exist, what is right, but I don't know how to get the right Fieldname from the column.
What you want is quite straightforward to do. In the example below, which uses the demo data from
the Biolife demo, I've linked the StringgRid to the FDMemTable entirely by binding objects
created in code so that there is no doubt about any of the binding steps or binding properties,
nor the method used to establish the bindings.
procedure TForm2.FormCreate(Sender: TObject);
var
BindSourceDB1 : TBindSourceDB;
LinkGridToDataSourceBindSourceDB1 : TLinkGridToDataSource;
begin
// Note : You need to load FDMemTable1 at design time from the sample Biolife.Fds datafile
// The following code creates a TBindSourceDB which Live-Binds FDMemTable1
// to StringGrid1
//
// As a result, the column header texts will be the fieldnames of FDMemTable1's fields
// However, the code that determines the column on which to sort the StringGrid does not depend
// on this
BindSourceDB1 := TBindSourceDB.Create(Self);
BindSourceDB1.DataSet := FDMemTable1;
LinkGridToDataSourceBindSourceDB1 := TLinkGridToDataSource.Create(Self);
LinkGridToDataSourceBindSourceDB1.DataSource := BindSourceDB1;
LinkGridToDataSourceBindSourceDB1.GridControl := StringGrid1;
end;
procedure TForm2.StringGrid1HeaderClick(Column: TColumn);
// Sorts the STringGrid on the column whose header has been clicked
var
ColIndex,
FieldIndex : Integer;
AFieldName : String;
begin
ColIndex := Column.Index;
FieldIndex := ColIndex;
AFieldName := FDMemTable1.Fields[FieldIndex].FieldName;
Caption := AFieldName;
// Should check here that the the field is a sortable one and not a blob like a graphic field
FDMemTable1.IndexFieldNames := AFieldName;
end;
Note that this answer assumes that there is a one-for-one correspondence between grid columns and fields of the bound dataset, which will usually be the case for bindings created using the default methods in the IDE. However, Live Binding is sophisticated enough to support situations where this correspondence does not exist, and in those circumstances it should not be assumed that the method in this answer will work.
I'm trying to find a universal** solution to extend the built-in Treeview/TreeNode by some features such as ToolTips per Node. So first I derived a TExtendedTreeNode = class(TTreeNode) and added a corresponding property which seems to work fine - I can add TExtendedTreeNodes with different ToolTips for each node.
For the next step, I want to use the TTreeView.OnMouseMove event to show the corresponding ToolTip, but what is the best solution to extend this functionality in a universal** way?
My idea was to use a class helper for TTreeView:
type
TTreeViewExtension = class helper for TTreeView
private
procedure ShowNodeToolTips(Sender: TObject; Shift: TShiftState; X, Y: Integer);
public
constructor Create(AnOwner: TComponent);
end;
...
constructor TTreeViewExtension.Create(AnOwner: TComponent);
begin
inherited Create(AnOwner);
ShowMessage('TTreeViewExtension.Create');
self.OnMouseMove := #self.ShowNodeToolTips;
end;
The code is compiled without warnings or errors, but this constructor is NOT executed on creation of a treeview in my form.
And yes, I'm using advancedrecords in objfpc mode in both, my form unit and my extension unit - in order to use the class helper:
{$mode objfpc}{$H+}
{$modeswitch advancedrecords+}
** "universal" means, I want to use the integrated controls from my Lazarus IDE at least for the TreeView control, but use the extended functionality without writing code twice.
Why don't you use the already available OnHint event to show these tooltips. The TTreeView.OnHint event already returns you reference to the tree node that is beneath the mouse cursor so you should not have any problem reading your custom hints (tooltips) from the node.
If the tips can be shown in a single line of text you can simply change the value of Hint variable that is exposed in this event method.
You can easily read such value from your Extended TreeNode by typecasting the Node constant returned by the event method to your TExtendedTreeNode class.
Don't forget to check if the node in question is indeed of the right class.
procedure TForm1.TreeView1Hint(Sender: TObject; const Node: TTreeNode;
var Hint: string);
begin
//Check to see if the node beneath the cursor is the extended node
if Node is TExtendedTreeNode then
//if it is change the hint text to the custom hint stored in the
//node itself
Hint := TExtendedTreeNode(Node).CustomHint
//Else change the hint to empty string so no hintbox will be shown
else Hint := '';
end;
And if you don't want any hint text to be shown and show your information in a different way you simply set the Hint value to an empty string.
procedure TForm1.TreeView1Hint(Sender: TObject; const Node: TTreeNode;
var Hint: string);
begin
//Set Hint to empty string in order to not show any hint box
Hint := '';
//Do some other code instead if you like
MessageBeep(0);
end;
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.
I would like some advice and or tips on how to go about setting up a new Object Class for a TreeView using the TreeView's Node.Data property.
I know how to create a basic Object type that can write/read from the .Data property, I do something like this:
type
TMyData = class(TComponent)
private
FString1: String;
FString2: String;
FInteger1: Integer;
published
property String1: String read FString1 write FString1;
property String2: String read FString2 write FString2;
property Integer1: Integer read FInteger1 write FInteger1;
end;
To write a value to one of the properties Node.Data property:
TMyData(TreeView1.Selected.Data).String1:= 'save this String';
To read a value:
ShowMessage(TMyData(TreeView1.Selected.Data).String1);
Thats a pretty basic example of assigning Data to a Tree Node, Now this is where I need the help and advice, tips etc...
From my example above, you can easily read/write standard properties such as String, Integer, Boolean etc. What about including and reading/writing to and from say a TListBox or TListView?
Instead of writing standard data to a Tree Node, how about adding items to that TListView and writing the contents of that TListView to the TreeView Node.
What if when clicking on one of the Tree Nodes, instead of returning a string, it could populate that TListView with Items and Images that were saved to earlier.
(1) This is the main form where you can add the Nodes to the TreeView. I actually use a more enhanced function to add to my TreeView, like so:
function AddMyDataNode(ATreeView: TTreeView; NodeName: String; NodeImage: Integer;
String1, String2: String; Integer1: Integer;
SelectNode: Boolean): TTreeNode;
var
Obj: TMyData;
Node: TTreeNode;
begin
Result:= nil;
Obj:= TMyData.Create(nil);
try
Obj.String1:= String1;
Obj.String2:= String2;
Obj.Integer1:= Integer1;
Node:= ATreeView.Items.AddObject(ATreeView.Selected, NodeName, Obj);
Node.ImageIndex:= NodeImage;
Node.SelectedIndex:= NodeImage;
Result:= Node;
if SelectNode then
Node.Selected:= True;
ATreeView.SetFocus;
except on E: Exception do
ShowMessage('Could not add MyData Node: ' + E.Message);
end;
end;
Editing a Node will show another form called the Data Form...:
(2) This Data Form is where the data from the Node on the Main Form is read/written. With an addition of a TListView. The ListView should also be read/saved with the standard properties too.
Adding/Editing a ListView item may include other standard properties, such as:
(3) The values on this form are saved into the ListView data.
So in a nutshell:
The TreeView Node.Data stores
standard properties and also a
TListView.
This Data is show on another form
called Data Form.
The ListView on the Data Form also
has data.
Editing the ListView on the Data Form
will show an additional Form with
standard properties.
I would like to know how I could best approach this, what tips could you provide, what is the easiest way to achieve something like this etc?
Writing/Reading to and from the ListView is easily implemented I imagine in that it can be done by accessing the ListView Items.Item[x].Data property. Getting the ListView Data to store with the TreeView is the problem, I don't know how I could implement this type of behaviour.
I would like to know advice on things like how I could define the Types etc.
I look forward to hearing your ideas on this.
I think your confusion is thinking of storing Visual Forms and components including their data. I believe you can do this with the the streaming system - although I never have! I like to keep the data and the visuals as separate as possible.
So you need a custom list to put in MyData that contains all the data and settings that need to go in the listview something like MyDataList: (I'm typing this straight in!)
type
TMyDataViewData = class(TPersistent)
Caption: string;
ImageIndex: Integer;
StateIndex: Integer;
Group: Integer;
MemoData: TStrings;
end;
TMyDataViewDataList = class(TPersistent)
TheList: TObjectList;
property Items[Index:Integer]:MyDataViewData read getItems write SetItems;
property Count: Integer read GetCount;
.
.
.
end;
TMyData = class(TComponent)
private
FString1: String;
FString2: String;
FInteger1: Integer;
private
MyForm: TMyEditform;
published
procedure Edit(Owner: TComponent);
property String1: String read FString1 write FString1;
property String2: String read FString2 write FString2;
property Integer1: Integer read FInteger1 write FInteger1;
property TheDataList: TMyDataViewDataList read fTheDataList write fTheDataList;
end;
procedure TMyData.Edit;
var
F: TMyEditForm;
begin
F:= TMyEditForm.create(Owner);
F.DataList := TheDatalist; // data for listview and memos
F.ShowModal;
.
.
.
end;
Then in your Treeview you only need to do:
If TreeNode.Data is TMyData then
TMyData(TreeNode.Data).Edit(Self);
After the edit all the data for changing the treeview node info should be in MyData
so you can do
Node.Text := TMyData(Node.Data).TheDataList.Items[0].Caption;
or
for i := 0 to TMyData(Node.data).TheDataList.Count do
Node.AddChild(TMyData(Node.Data).TheDataList.Items[i].Caption);
You can also easily expose properties of the Datalist to TMyData directly :)
and such like. Probably a few syntax error in there!
I don't see any problems with your approach. You can let your data object contain a list of objects (I don't know why you inherit from TComponent in a class that has nothing to with visual components):
TMyData = class
private
FString : String;
FInteger : Integer;
FItems : TList <TItem> // where TItem is your custom object class
public
// ... properties ...
end;
This list is then displayed in the list box:
for Item in MyData.Items do
ListBox.Items.AddObject (Item.ToString, Item);
If you edit one of the items, you do the same thing: get the object out of the list box's data and pass it on to the next form.
Hope that helps.
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)