How to do incremental search in delphi FM2? - delphi

Hi i am having a problem with incremental search in delphi.
I Have looked at this http://delphi.about.com/od/vclusing/a/lb_incremental.htm
But this doesn't work in firemonkey so i came up with this :
for I := 0 to lstbxMapList.Items.Count-1 do
begin
if lstbxMapList.Items[i] = edtSearch.Text then
begin
lstbxMapList.ItemByIndex(i).Visible := True;
end;
if lstbxMapList.Items[I] <> edtSearch.Text then
begin
lstbxMapList.ItemByIndex(i).Visible := False;
end;
end;
When i use this the listbox is just blank.

You're hiding every item that doesn't exactly match edtSearch.Text. Try this instead (tested in XE3):
// Add StrUtils to your uses clause for `StartsText`
uses
StrUtils;
procedure TForm1.edtSearchChange(Sender: TObject);
var
i: Integer;
NewIndex: Integer;
begin
NewIndex := -1;
for i := 0 to lstBxMapList.Items.Count - 1 do
if StartsText(Edit1.Text, lstBxMapList.Items[i]) then
begin
NewIndex := i;
Break;
end;
// Set to matching index if found, or -1 if not
lstBxMapList.ItemIndex := NewIndex;
end;

Following from Kens answer, if you want to hide items as per your question, just set the Visible property but note that since the expression of an if statement returns a boolean and Visible is a boolean property it's possible to greatly simplify things. Note also that I've also used ContainsText which will match the string anywhere in the item text:
procedure TForm1.edtSearchChange(Sender: TObject);
var
Item: TListBoxItem;
begin
for Item in lstbxMapList.ListItems do
Item.Visible := ContainsText(Item.Text.ToLower, Edit1.Text.ToLower);
end;

Related

Hiding items in TListBox while filtering by String

Short Version: Is there any way to control or modify LisBox items individually? for example set their Visible property to False separately.
I found a TListBoxItem class in Fire Monkey when I was searching, but I don't want to use Fire Monkey and want it in VCL.
Detailed Version:
I tried to filter my ListBox using two TStringList and an Edit, one StringList is global to keep the original list (list_files_global) and another StringList to help filtering procedure (list_files_filter) and my primary list of files is my ListBox (list_files).
I created my global StringList on onCreate event while program is starting to store my original list:
procedure Tfrm_main.FormCreate(Sender: TObject);
Begin
list_files_global := TStringList.Create;
list_files_global.Assign(list_files.Items);
End;
and used Edit's onChange event for filtering:
procedure Tfrm_main.edit_files_filterChange(Sender: TObject);
Var
list_files_filter: TStringList;
i: Integer;
Begin
list_files_filter := TStringList.Create;
list_files_filter.Assign(list_files.Items);
list_files.Clear;
for i := 0 to list_files_filter.Count - 1 do
if pos(edit_files_filter.text, list_files_filter[i]) > 0 then
list_files.Items.Add(list_files_filter[i]);
End;
and for switching off the filter, just recover the list from my global list that I created at first:
list_files.Items := list_files_global;
here so far, everything works just fine, but problem is when I'm trying to edit/rename/delete items from filtered list, for example I change an item:
list_files.Items[i] := '-- Changed Item --';
list will be edited, but when I switch off the filter, the original list will be back and all changes are lost.
so I want to know is there any proper way to solve this problem? Something like hiding items individually or change items visibility, etc... so I can change the filtering algorithm and get rid of all this making extra lists.
I searched the internet and looked into Delphi's help file for a whole day and nothing useful came up.
The items of a VCL listbox, List Box in the API, does not have any visibility property. The only option for not showing an item is to delete it.
You can use the control in virtual mode however, where there are no items at all. You decide what data to keep, what to display. That's LBS_NODATA window style in the API. In VCL, set the style property to lbVirtual.
Extremely simplified example follows.
Let's keep an array of records, one record per virtual item.
type
TListItem = record
FileName: string;
Visible: Boolean;
end;
TListItems = array of TListItem;
You can extend the fields as per your requirements. Visibility is one of the main concerns in the question, I added that. You'd probably add something that represents the original name so that you know what name have been changed, etc..
Have one array per listbox. This example contains one listbox.
var
ListItems: TListItems;
Better make it a field though, this is for demonstration only.
Required units.
uses
ioutils, types;
Some initialization at form creation. Empty the filter edit. Set listbox style accordingly. Fill up some file names. All items will be visible at startup.
procedure TForm1.FormCreate(Sender: TObject);
var
ListFiles: TStringDynArray;
i: Integer;
begin
ListFiles := ioutils.TDirectory.GetFiles(TDirectory.GetCurrentDirectory);
SetLength(ListItems, Length(ListFiles));
for i := 0 to High(ListItems) do begin
ListItems[i].FileName := ListFiles[i];
ListItems[i].Visible := True;
end;
ListBox1.Style := lbVirtual;
ListBox1.Count := Length(ListFiles);
Edit1.Text := '';
end;
In virtual mode the listbox is only interested in the Count property. That will arrange how many items will show, accordingly the scrollable area.
Here's the filter part, this is case sensitive.
procedure TForm1.Edit1Change(Sender: TObject);
var
Text: string;
Cnt: Integer;
i: Integer;
begin
Text := Edit1.Text;
if Text = '' then begin
for i := 0 to High(ListItems) do
ListItems[i].Visible := True;
Cnt := Length(ListItems);
end else begin
Cnt := 0;
for i := 0 to High(ListItems) do begin
ListItems[i].Visible := Pos(Text, ListItems[i].FileName) > 0;
if ListItems[i].Visible then
Inc(Cnt);
end;
end;
ListBox1.Count := Cnt;
end;
The special case in the edit's OnChange is that when the text is empty. Then all items will show. Otherwise code is from the question. Here we also keep the total number of visible items, so that we can update the listbox accordingly.
Now the only interesting part, listbox demands data.
procedure TForm1.ListBox1Data(Control: TWinControl; Index: Integer;
var Data: string);
var
VisibleIndex: Integer;
i: Integer;
begin
VisibleIndex := -1;
for i := 0 to High(ListItems) do begin
if ListItems[i].Visible then
Inc(VisibleIndex);
if VisibleIndex = Index then begin
Data := ListItems[i].FileName;
Break;
end;
end;
end;
What happens here is that the listbox requires an item to show providing its index. We loop through the master list counting visible items to find out which one matches that index, and supply its text.
This is something I often do, but with list views instead of list boxes. The basic principles are the same, though.
I tend to store the individual items as objects, which are reference types in Delphi. And I keep them all in one main unfiltered list, which owns the objects, while I maintain a filtered list (which does not own the objects) for display purposes. Like #Sertac, I combine this with a virtual list view.
To see how this works in practice, create a new VCL application and drop a list view (lvDisplay) and an edit control (eFilter) on the main form:
Notice I have added three columns to the list view control: "Name", "Age", and "Colour". I also make it virtual (OwnerData = True).
Now define the class for the individual data items:
type
TDogInfo = class
Name: string;
Age: Integer;
Color: string;
constructor Create(const AName: string; AAge: Integer; const AColor: string);
function Matches(const AText: string): Boolean;
end;
where
{ TDogInfo }
constructor TDogInfo.Create(const AName: string; AAge: Integer;
const AColor: string);
begin
Name := AName;
Age := AAge;
Color := AColor;
end;
function TDogInfo.Matches(const AText: string): Boolean;
begin
Result := ContainsText(Name, AText) or ContainsText(Age.ToString, AText) or
ContainsText(Color, AText);
end;
And let us create the unfiltered list of dogs:
TForm1 = class(TForm)
eFilter: TEdit;
lvDisplay: TListView;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
FList, FFilteredList: TObjectList<TDogInfo>;
public
end;
where
function GetRandomDogName: string;
const
DogNames: array[0..5] of string = ('Buster', 'Fido', 'Pluto', 'Spot', 'Bill', 'Rover');
begin
Result := DogNames[Random(Length(DogNames))];
end;
function GetRandomDogColor: string;
const
DogColors: array[0..2] of string = ('Brown', 'Grey', 'Black');
begin
Result := DogColors[Random(Length(DogColors))];
end;
procedure TForm1.FormCreate(Sender: TObject);
var
i: Integer;
begin
FList := TObjectList<TDogInfo>.Create(True); // Owns the objects
// Populate with sample data
for i := 1 to 1000 do
FList.Add(
TDogInfo.Create(GetRandomDogName, Random(15), GetRandomDogColor)
);
FFilteredList := FList;
lvDisplay.Items.Count := FFilteredList.Count;
lvDisplay.Invalidate;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
if FFilteredList <> FList then
FreeAndNil(FFilteredList);
FreeAndNil(FList);
end;
The idea is that the list view control always displays the FFilteredList, which either points to the same object instance as FList, or points to a filtered (or sorted) version of it:
// The list view's OnData event handler
procedure TForm1.lvDisplayData(Sender: TObject; Item: TListItem);
begin
if FFilteredList = nil then
Exit;
if not InRange(Item.Index, 0, FFilteredList.Count - 1) then
Exit;
Item.Caption := FFilteredList[Item.Index].Name;
Item.SubItems.Add(FFilteredList[Item.Index].Age.ToString);
Item.SubItems.Add(FFilteredList[Item.Index].Color);
end;
// The edit control's OnChange handler
procedure TForm1.eFilterChange(Sender: TObject);
var
i: Integer;
begin
if string(eFilter.Text).IsEmpty then // no filter, display all items
begin
if FFilteredList <> FList then
begin
FreeAndNil(FFilteredList);
FFilteredList := FList;
end;
end
else
begin
if (FFilteredList = nil) or (FFilteredList = FList) then
FFilteredList := TObjectList<TDogInfo>.Create(False); // doesn't own the objects
FFilteredList.Clear;
for i := 0 to FList.Count - 1 do
if FList[i].Matches(eFilter.Text) then
FFilteredList.Add(FList[i]);
end;
lvDisplay.Items.Count := FFilteredList.Count;
lvDisplay.Invalidate;
end;
The result:
Notice that there always is only one in-memory object for each dog, so if you rename a dog, the changes will reflect in the list view, filtered or not. (But don't forget to invalidate it!)

StringGrid Objects - access violation

I am trying to make use of the Objects property of the Stringgrid inside my descendant and I think I am doing something wrong.
So, I created a simple class for use in my StringGrid cells, something like:
type
TKind = (tkNone,tkStart, tkEnd, tkMiddle);
TMyCell = class(TObject)
private
FKind:string; // TKind: start, middle, end, none
FOfType:string; // new, registered, paid, over, none
FActualDate:TDate;
FTheText:string; // if you want to show a text in it
FIsWeekend:Boolean;
function IsItWeekend(dt:Tdate):boolean;
procedure setKind(s:string);
{ Private declarations }
protected
{ Protected declarations }
public
constructor Create;
property Kind:string read FKind write setKind;
property OfType:string read FOfType write FOfType;
property ActualDate:TDate read FActualDate write FActualDate;
property TheText:string read FTheText write FTheText;
property IsWeekend:Boolean read FIsWeekend write FIsWeekend default false;
{ Public declarations }
published
{ Published declarations }
end;
implementation
procedure TMyCell.setKind(s:string);
begin
FKind:=s;
end;
function TMyCell.IsItWeekend(dt:Tdate):boolean;
begin
if (DayOfTheWeek(dt)=6) or (DayOfTheWeek(dt)=7) then IsItWeekend:=true else IsItWeekend:=false;
end;
constructor TMyCell.Create;
var
i:integer;
a,l,z:word;
dt:TDate;
begin
FKind:='none';
FOfType:='none';
FActualDate:=now;
FTheText:='';
FIsWeekend:=IsItWeekend(FActualDate);
end;
then, in my StringGrid descendant (TMyGrid), I do the following:
TMyGrid = class(TStringGrid)
private
FStartSelection:integer;
FFixedColor:TColor;
FRowCount:integer;
...
published
property ColCount;
property RowCount;
...
constructor TMyGrid.Create(AOwner: TComponent);
var
i:integer;
a,l,z:word;
dt:TDate;
j: Integer;
myCell:TMyCell;
begin
inherited;
...// different settings
RowCount:=5;
for i := 0 to colCount-1 do
for j := 0 to RowCount-1 do
begin
Objects[i, j] := TMyCell.Create;
end;
end;
destructor TMyGrid.Destroy;
var
i,j:integer;
begin
for i := 0 to colCount-1 do
for j := 0 to RowCount-1 do
begin
TMyCell(Objects[i, j]).Free;
end;
inherited;
end;
... // other stuff
procedure Register;
begin
RegisterComponents('mygrid', [TMyGrid]);
end;
The problem is I don't know how do I tell my control that there are more rows when the developer changes the RowCount in the objectInspector before running the app.
So I drop my StrinGrid descendant on a form, and set the rowCount to 10. But my StringGrid does not have Objects created for the new rows, So the cells on ARow=5->9 do not have objects created... because in OnCreate I only set the initial value of the RowCount to 5 and create objects for i:=0 to RowCount-1.
Is there an event or method where I can tell the StringGrid to create the Objects after the developer changes the rowCount in ObjectInspector?
I am sure that this is my problem because, using the above code, when I drop my stringGrid on a form and set it's rowCount (design time or runtime) to 10 then I want to assign a value to Kind property of a cell that is on a Row>4 I get an AccessViolation, but if I do that for a row that is <= 4 the assignment works just fine.
I found something that should help here: http://embarcadero.newsgroups.archived.at/public.delphi.ide/200904/0904193279.html
but I do not know how and where to place this code in my StringGrid descendant class so it would get called when RowCount is changed at designtime/runtime
EDIT
After reading your comments I tried your idea (that seemed to work) to override the SizeChanged (I did not know that method existed, must have skipped it when I serached before).
Anyway, I added this code to my class:
TMyGrid = class(TStringGrid)
private
...
procedure SizeChanged(OldColCount, OldRowCount: Longint); override;
procedure UpdateGridDimensions(NewColCount, NewRowCount: Integer);
...
procedure TMyGrid.SizeChanged(OldColCount, OldRowCount: Longint);
begin
inherited;
if (OldRowCount<>FRowCount)or(OldColCount<>ColCount) then
UpdateGridDimensions(ColCount, FRowCount);
end;
procedure TMyGrid.UpdateGridDimensions(NewColCount, NewRowCount: Integer);
var
C, R: Integer;
Old: Integer;
begin
if NewColCount <> ColCount then
begin
if NewColCount < ColCount then
begin
for R := 0 to RowCount-1 do
begin
for C := ColCount-1 downto NewColCount do
Objects[C, R].Free;
end;
end;
Old := ColCount;
ColCount := NewColCount;
if NewColCount > Old then
begin
for R := 0 to RowCount-1 do
begin
for C := Old to NewColCount-1 do
Objects[C, R] := TMyCell.Create;
end;
end;
end;
if NewRowCount <> RowCount then
begin
if NewRowCount < RowCount then
begin
for C := 0 to ColCount-1 do
begin
for R := RowCount-1 downto NewRowCount do
Objects[C, R].Free;
end;
end;
Old := RowCount;
RowCount := NewColCount;
if NewRowCount > Old then
begin
for C := 0 to ColCount-1 do
begin
for R := Old to NewRowCount-1 do
Objects[C, R] := TMyCell.Create;
end;
end;
end;
end;
but now whenever I drop my control on a form, the rowcount is 93... where do I set that rowCount? Because I DONT.
And still, if I increase the RowCount from 93 to something else like 100, then my Objects exist for the first 93 rows but they do not get created for the 93-100 rows...
So the idea sounded great, but it does not work as I expect it...
Any thoughts?
Am I doing it wrong?
// SizeChanged - Called when the size of the grid has changed.
protected
procedure SizeChanged(OldColCount, OldRowCount: Longint); dynamic;
You can override dynamic method SizeChanged and initialize grid according to new size. You can check is it designtime or not (LU RD suggested link). And as David mentioned, it is better to keep Objects property for consumers of component. Create and use your own TList/TObjectList instead.

Updating field in cxGrid acting strange

I have a function to update a cxGrid made with help from answers to Loop through records on a cxgrid and update a field/column
But it is sometimes acting a bit strange. If I open the form with the cxGrid and click the columnheader without doing anything else, the records are updateted OK. But if the 'selectorbar' is moved away from the top, the record marked is not updated.
I am sure it is a property that needs to be changed, but which one.
The variable fSelected is set to False at FormShow and is ther so that the user can unselect records as well.
procedure TfrmContactsSelect.colContactSelectedHeaderClick(Sender: TObject);
var
i: Integer;
Index: Integer;
BookMark : TBookMark;
Contact: variant;
begin
if fMulti = True then
begin
Screen.Cursor := crHourGlass;
fSelected := not fSelected;
BookMark := qryContacts.GetBookmark;
qryContacts.DisableControls;
try
for i := 0 to grdContactsView1.DataController.FilteredRecordCount - 1 do
begin
Index := grdContactsView1.DataController.FilteredRecordIndex[i];
Contact := grdContactsView1.DataController.Values[Index, 4];
if grdContactsView1.DataController.LocateByKey(Contact) then
begin
qryContacts.Edit;
qryContacts.FieldByName('fldcontact_selected').AsBoolean := fSelected;
qryContacts.Post;
end;
end;
finally
qryContacts.EnableControls;
qryContacts.GotoBookmark(BookMark);
qryContacts.FreeBookmark(BookMark);
end;
Screen.Cursor := crDefault;
end;
end;
Delphi XE7, DevExpress 14.2.2, UniDAC 5.5.12 for DB access
Comment:
I have ended up with the following solution based on the answer and input from MartynA
procedure TfrmContactsSelect.colContactSelectedHeaderClick(Sender: TObject);
var
i: Integer;
Index: Integer;
MarkedRecord: variant;
CurrentRecord: variant;
begin
if fMulti = True then
begin
Screen.Cursor := crHourGlass;
fSelected := not fSelected;
Index := grdContactsView1.DataController.FocusedRecordIndex;
MarkedRecord := grdContactsView1.DataController.Values[Index, colContactGuid.ID];
try
for i := 0 to grdContactsView1.DataController.FilteredRecordCount - 1 do
begin
Index := grdContactsView1.DataController.FilteredRecordIndex[i];
CurrentRecord := grdContactsView1.DataController.Values[Index, colContactGuid.ID];
if grdContactsView1.DataController.LocateByKey(CurrentRecord) then
begin
grdContactsView1.DataController.Edit;
grdContactsView1.DataController.SetEditValue(colContactSelected.ID, fSelected, evsText);
grdContactsView1.DataController.Post;
end;
end;
finally
grdContactsView1.DataController.LocateByKey(MarkedRecord);
end;
Screen.Cursor := crDefault;
end;
end;
I can reproduce your problem using the sample project I posted in my answer to your other q.
Try this: Add a TMemo to your form, and inside the 'if grdContactsView1.DataController.LocateByKey(Contact) then' block, write the value of a row-unique datafield and the Selected datafield value to the memo.
Then, what I get when some row other than the top row is selected is that one row is listed twice in the memo, with Selected both false and true, and one of the rows in the filter isn't listed at all, which I think accounts for the behaviour you're seeing. If I then comment out the .Edit .. .Post lines, it correctly lists all the rows in the filter.
So evidently doing the Selected field changes inside a block which iterated the FilteredRecordIndex property of the DBTableView is what's causing the problem.
Personally, I find that it goes a bit against the grain to modify dataset rows in code via a DB-aware control (because you usually end up fighting the DB-awareness of the control), but in this case, it's straightforward to do the processing via the DBTableView of the cxGrid.
procedure TForm1.ProcessFilteredRecords;
var
PrevV,
V : Variant;
i,
Index: Integer;
S : String;
begin
// First, pick up a reference to the current record
// so that we can return to it afterwards
Index := cxGrid1DBTableView1.DataController.FocusedRecordIndex;
PrevV := cxGrid1DBTableView1.DataController.Values[Index, 0];
try
for i := 0 to cxGrid1DBTableView1.DataController.FilteredRecordCount - 1 do begin
Index := cxGrid1DBTableView1.DataController.FilteredRecordIndex[i];
V := cxGrid1DBTableView1.DataController.Values[Index, 0];
if cxGrid1DBTableView1.DataController.LocateByKey(V) then begin
cxGrid1DBTableView1.DataController.Edit;
// 2 is the index of my Selected column in the grid
if cxGrid1DBTableView1.DataController.SetEditValue(2, True, evsText) then
Caption := 'OK'
else
Caption := 'Failed';
cxGrid1DBTableView1.DataController.Post;
end;
end;
finally
if cxGrid1DBTableView1.DataController.LocateByKey(PrevV) then
Caption := 'OK'
else
Caption := 'Failed';
end;
end;
Another way to avoid the problem is to change the Selected states in two steps:
Iterate the FilteredRecordIndex to build a list of rows to change - in your case this would be a list of guids
Then, iterate the list of rows and update their Selected states.
Code:
procedure TForm1.ProcessFilteredRecords;
var
V : Variant;
i,
Index: Integer;
BM : TBookMark;
S : String;
TL : TStringList;
begin
Memo1.Lines.Clear;
TL := TStringList.Create;
try
for i := 0 to cxGrid1DBTableView1.DataController.FilteredRecordCount - 1 do begin
Index := cxGrid1DBTableView1.DataController.FilteredRecordIndex[i];
V := cxGrid1DBTableView1.DataController.Values[Index, 0];
if cxGrid1DBTableView1.DataController.LocateByKey(V) then begin
if CDS1.FieldByName('Selected').AsBoolean then
S := 'True'
else
S := 'False';
S := CDS1.FieldByName('Name').AsString + ' ' + S;
Memo1.Lines.Add(S);
TL.Add(CDS1.FieldByName('Guid').AsString);
end;
end;
try
BM := CDS1.GetBookMark;
CDS1.DisableControls;
for i := 0 to TL.Count - 1 do begin
if CDS1.Locate('guid', TL[i], []) then begin
CDS1.Edit;
CDS1.FieldByName('Selected').AsBoolean := True;
CDS1.Post;
end
end;
finally
CDS1.EnableControls;
CDS1.GotoBookmark(BM);
CDS1.FreeBookmark(BM);
end;
finally
TL.Free;
end;
end;
Like you, I was expecting that changing a property or two of the cxGrid might avoid the problem without any code, but I haven't been able to find anything which does.

Delphi: Shift-Up and Shift-Down in the Listview

Is there a feature in the Listview control to shift items up and down?
Not having worked with TListView very much (I mostly use database grids), I took your question as a chance to learn something. The following code is the result, it is more visually oriented that David's answer. It has some limitations: it will only move the first selected item, and while it moves the item, the display for vsIcon and vsSmallIcon is strange after the move.
procedure TForm1.btnDownClick(Sender: TObject);
var
Index: integer;
temp : TListItem;
begin
// use a button that cannot get focus, such as TSpeedButton
if ListView1.Focused then
if ListView1.SelCount>0 then
begin
Index := ListView1.Selected.Index;
if Index<ListView1.Items.Count then
begin
temp := ListView1.Items.Insert(Index+2);
temp.Assign(ListView1.Items.Item[Index]);
ListView1.Items.Delete(Index);
// fix display so moved item is selected/focused
ListView1.Selected := temp;
ListView1.ItemFocused := temp;
end;
end;
end;
procedure TForm1.btnUpClick(Sender: TObject);
var
Index: integer;
temp : TListItem;
begin
// use a button that cannot get focus, such as TSpeedButton
if ListView1.Focused then
if ListView1.SelCount>0 then
begin
Index := ListView1.Selected.Index;
if Index>0 then
begin
temp := ListView1.Items.Insert(Index-1);
temp.Assign(ListView1.Items.Item[Index+1]);
ListView1.Items.Delete(Index+1);
// fix display so moved item is selected/focused
ListView1.Selected := temp;
ListView1.ItemFocused := temp;
end;
end;
end;
You have two options:
Delete them and then re-insert them at the new location.
Use a virtual list view and move them in your data structure.
My routine for doing the first of these options is like this:
procedure TBatchTaskList.MoveTasks(const Source: array of TListItem; Target: TListItem);
var
i, InsertIndex: Integer;
begin
Assert(IsMainThread);
BeginUpdate;
Try
//work out where to move them
if Assigned(Target) then begin
InsertIndex := FListItems.IndexOf(Target);
end else begin
InsertIndex := FListItems.Count;
end;
//create new items for each moved task
for i := 0 to high(Source) do begin
SetListItemValues(
FListItems.Insert(InsertIndex+i),
TBatchTask(Source[i].Data)
);
Source[i].Data := nil;//handover ownership to the new item
end;
//set selection and focus item to give feedback about the move
for i := 0 to high(Source) do begin
FListItems[InsertIndex+i].Selected := Source[i].Selected;
end;
FBatchList.ItemFocused := FListItems[InsertIndex];
//delete the duplicate source tasks
for i := 0 to high(Source) do begin
Source[i].Delete;
end;
Finally
EndUpdate;
End;
end;
The method SetListItemValues is used to populate the columns of the list view.
This is a perfect example of why virtual controls are so great.

to retrieve stringlist items in memo from table POS_CatBreakDownValue into tchecklistbox

i have a problem on how to retrieve the items in memo from table into tchecklistbox...before this i do the procedure to insert the items that have been checked into memo in tbl POS_CatBreakDownValue..but now i want to know how to retrieve items from table and automatic check in tchecklistbox...thanks..here my procedure to insert the checklist into memo in table..i hope anyone can help me..thanks
procedure TfrmSysConfig.saveCatBreakDownValue;
var
lstCat:TStringList;
i :integer;
begin
lstcat := TStringList.Create;
try
for i:=0 to clCat.Items.Count-1 do begin
if clCat.Checked[i] then begin
lstcat.Add(clcat.Items.Strings[i]);
end;
end;
tblMainPOS_CatBreakDownValue.Value := lstCat.Text;
finally
lstcat.Free;
end;
end;
procedure TfrmSysConfig.saveCatBreakDownValue;
var
lstCat: TStringList;
i:integer;
begin
lstcat := TStringList.Create;
try
for i:=0 to clCat.Items.Count-1 do begin
if clCat.Checked[i] then begin
lstcat.Add(clcat.Items.Strings[i]);
end;
end;
tblMainPOS_CatBreakDownValue.Value := lstCat.Text;
finally
lstcat.Free;
end;
end;
Reading your code I'm guessing you've got a MemoField in a database that you are reading and writing the checked values from/to. You also have a predefined list of checkable items.
So you'll need to create a new string list and read the field back into it (Revesing the writing code). for each item in the list get the Index and check it.
Something like..
procedure TfrmSysConfig.saveCatBreakDownValue;
var
lstCat: TStringList;
i, Index:integer;
begin
lstcat := TStringList.Create;
try
lstcat.Text = tblMainPOS_CatBreakDownValue.Value;
for i:=0 to lstcat.Count-1 do
begin
Index := clCat.Items.IndexOf(lstcat.Items[i])
if Index > -1 then
begin
clCat.Checked[Index] := True;
end;
end;
finally
lstcat.Free;
end;
end;
Not sure I understand your question 100%
TCheckListBox can check or uncheck items using the Checked Property.
CheckListBox.Checked[Index] := True/False;
Since you sound like your comparing strings you may need to determine the index in the TCheckListBox based on the string this can be done like this:
CheckListBox1.Items.IndexOf('StringToFind')
If the string is not found then the result is -1;
If you want to check lines in a TMemo Control and see if they exists as rows in a table you can do the following.
While not Table.EOF do
begin
if Memo1.lines.IndexOf(Table.FieldByName('MyField').AsString) = -1 then
begin
// What you want to do if not found
end
else
begin
// what you want to do if it is found.
end;
Table.Next;
end;

Resources