Delete a listbox item in OnDraw? - delphi

I have a listbox and add items to it, items are address of files, items are added after some processes and they are inserted like this:
Listbox_Browser.Items := myItems;
so as im not adding them one by one i cant check them during inserting ti listbox, i tried to check them in OnDraw and used a code like this:
Try
FileOpenandP(Listbox_Browser.Items[Index]);
Except
ListBox_Browser.Items.Delete(Index);
End;
but i got the error "List index out of bounds", what is the solution?

The OnDrawItem event is for drawing only. You are not supposed to be managing your list inside that event, only drawing its current items as needed.
Instead of assiging the whole list at one time, you should be checking the files first, then assign the remaining list to the ListBox, eg:
I := 0;
while I < myItems.Count do
begin
try
FileOpenandP(myItems[I]);
Inc(I);
except
myItems.Delete(I);
end;
end;
ListBox_Browser.Items := myItems;
If you don't want myItems altered, use a separate list instead:
tmpItems := TStringList.Create;
try
tmpItems.Assign(myItems);
I := 0;
while I < tmpItems.Count do
begin
try
FileOpenandP(tmpItems[I]);
Inc(I);
except
tmpItems.Delete(I);
end;
end;
ListBox_Browser.Items := tmpItems;
finally
tmpItems.Free;
end;
Or:
ListBox_Browser.Items := myItems;
I := 0;
while I < ListBox_Browser.Items.Count do
begin
try
FileOpenandP(ListBox_Browser.Items[I]);
Inc(I);
except
ListBox_Browser.Items.Delete(I);
end;
end;
Or:
ListBox_Browser.Items.BeginUpdate;
try
ListBox_Browser.Items.Clear;
I := 0;
for I := 0 to myItems.Count-1 do
begin
try
FileOpenandP(myItems[I]);
except
Continue;
end;
ListBox_Browser.Items.Add(myItems[I]);
end;
finally
ListBox_Browser.Items.EndUpdate;
end;

Related

Load database field of all records into ListView Item Detail Object

Using Delphi XE8 I'm currently testing functionality with Firemonkey TListViews.
One thing I'm trying to do is to load a field of all records from a TFDMemtable component into a Listview Item, specifically into the DetailObject of the ListView Item.
For example, I have 3 records in a table (db field is called 'Name'):
Record 1 = Name 1
Record 2 = Name 2
Record 3 = Name 3
There is only 1 DetailObject property per ListView Item so my question is, would I be able to add all of the fields (Name 1, Name 2, Name 3) into that one DetailObject?
Below is what I've attempted so far but no luck. Not 100% sure what I need to do.
procedure MainForm.BuildList;
var LItem : TListViewItem;
begin
ListView1.BeginUpdate;
try
ListView1.CLearItems;
LItem := ListView1.Items.Add;
LItem.Objects.DetailObject.Visible := True;
with memtable do
begin
while not eof do
begin
LItem.Detail := FieldByName('Name').AsString;
end;
end;
finally
ListView1.EndUpdate;
end;
end;
I'm sorry if this isn't clear enough, please let me know.
Any help would be great.
I think I should warn you that before seeing your q, I'd never done anything with FMX ListViews and Master/Detail datasets. The Following is a little rough around the edges, and the layout isn't ideal, but it shows one way to populate a ListView from Master + Detail datasets. I have no idea whether there are better ways. Personally, I would see if I could use Live Bindings to do the job.
procedure TMasterDetailForm.BuildList;
var
LItem : TListViewItem;
DetailItem : TListViewItem;
ListItemText : TListItemText;
DetailIndex : Integer;
begin
ListView1.BeginUpdate;
ListView1.ItemAppearanceObjects.ItemEditObjects.Text.TextVertAlign := TTextAlign.Leading; // The default
// seems to be `Center`, whereas we want the Master field name to be at the top of the item
try
ListView1.Items.Clear; //Items;
Master.First;
while not Master.eof do begin
LItem := ListView1.Items.Add;
LItem.Text := Master.FieldByName('Name').AsString;
LItem.Height := 25;
Detail.First;
DetailIndex := 0;
while not Detail.Eof do begin
Inc(DetailIndex);
ListItemText := TListItemText.Create(LItem);
ListItemText.PlaceOffset.X := 100;
ListItemText.PlaceOffset.Y := 25 * (DetailIndex - 1);
ListItemText.TextAlign := TTextAlign.Leading;
ListItemText.Name := 'Name' + IntToStr(DetailIndex); //Detail.FieldByName('Name').AsString;
LItem.Data['Name' + IntToStr(DetailIndex)] := Detail.FieldByName('Name').AsString;
Detail.Next;
end;
LItem.Height := LItem.Height * (1 + DetailIndex);
Master.Next;
end;
finally
ListView1.EndUpdate;
end;
end;
TListItemText is one of a number of "drawable" FMX objects that can be added to do the TListViewItem. They seem to need unique names so that they can be accessed via the Names property.
FWIW, I used 2 TClientDataSets as the Master and Detail in my code.
Also FWIW, for FMX newbies like me, populating an FMX TreeView is a lot more like what you'd do in a VCL project:
procedure TMasterDetailForm.BuildTree;
var
PNode,
ChildNode : TTreeViewItem;
begin
TreeView1.BeginUpdate;
try
TreeView1.Clear;
Master.First;
while not Master.eof do begin
PNode := TTreeViewItem.Create(TreeView1);
TreeView1.AddObject(PNode);
PNode.Text := Master.FieldByName('Name').AsString;
Detail.First;
while not Detail.Eof do begin
ChildNode := TTreeViewItem.Create(TreeView1);
ChildNode.Text := Detail.FieldByName('Name').AsString;
PNode.AddObject(ChildNode);
Detail.Next;
end;
Master.Next;
end;
finally
TreeView1.EndUpdate;
end;
end;
Btw, in your code you should have been calling
memtable.Next;
in your while not eof loop, and memtable.First immediately before the loop.

Improve performance of Search Replace in Word document using OLE and Delphi

After some experiments I ended up with the following code to perform Search and Replace in MSWord. This code works perfectly also in header and footer, including the cases in which header and/or footer are different for the first page or odd/even pages.
The problem is that I need to call MSWordSearchAndReplaceInAllDocumentParts for every string I replace, and I get an unacceptable performance (2 minutes for about 50 strings in a 4 pages doc word). Ideally it should be "instantaneous" of course.
Before handling headers and footers I was just doing search and replace in the main document (using wdSeekMainDocument). In that case the perofmrance was acceptable (even if quite slow). I just wonder why is it so slow: does switching view takes time? Typically headers or footers contain few words, so I expected that all the Search And Replace in headers and footers was not making the overall performance so worse. But this is not what I observed.
This is the code, at the bottom i put profiler results:
// global variable (just for convenience of posting to Stack Overflow)
var
aWordApp: OLEVariant; // global
// This is the function that is executed once per every string I replace
function MSWordSearchAndReplaceInAllDocumentParts;
begin
try
iseekValue := aWordApp.ActiveWindow.ActivePane.View.SeekView;
iViewType := aWordApp.ActiveWindow.ActivePane.View.Type;
if iViewType <> wdPrintView then
aWordApp.ActiveWindow.ActivePane.View.Type := wdPrintView;
if aWordApp.ActiveDocument.PageSetup.OddAndEvenPagesHeaderFooter then
begin
Try
aWordApp.ActiveWindow.ActivePane.View.SeekView := wdSeekEvenPagesFooter;
SearchAndReplaceInADocumentPart;
Except
// do nothing ..it was not able to set above view
end;
Try
aWordApp.ActiveWindow.ActivePane.View.SeekView := wdSeekEvenPagesHeader;
SearchAndReplaceInADocumentPart;
Except
// do nothing ..it was not able to set above view
end;
end;
if aWordApp.ActiveDocument.PageSetup.DifferentFirstPageHeaderFooter then
begin
Try
aWordApp.ActiveWindow.ActivePane.View.SeekView := wdSeekFirstPageFooter;
SearchAndReplaceInADocumentPart;
Except
// do nothing ..it was not able to set above view
end;
Try
aWordApp.ActiveWindow.ActivePane.View.SeekView := wdSeekFirstPageHeader;
SearchAndReplaceInADocumentPart;
Except
// do nothing ..it was not able to set above view
end;
end;
//Replace in Main Docpart
Try
aWordApp.ActiveWindow.ActivePane.View.SeekView := wdSeekMainDocument;
SearchAndReplaceInADocumentPart;
Except
// do nothing ..it was not able to set above view
end;
//Replace in Header
Try
aWordApp.ActiveWindow.ActivePane.View.SeekView := wdSeekCurrentPageHeader;
SearchAndReplaceInADocumentPart;
Except
// do nothing ..it was not able to set above view
end;
//Replace in Footer
Try
aWordApp.ActiveWindow.ActivePane.View.SeekView := wdSeekCurrentPageFooter;
SearchAndReplaceInADocumentPart;
Except
// do nothing ..it was not able to set above view
end;
//Replace in Header
Try
aWordApp.ActiveWindow.ActivePane.View.SeekView := wdSeekPrimaryHeader;
SearchAndReplaceInADocumentPart;
Except
// do nothing ..it was not able to set above view
end;
//Replace in Footer
Try
aWordApp.ActiveWindow.ActivePane.View.SeekView := wdSeekPrimaryFooter;
SearchAndReplaceInADocumentPart;
Except
// do nothing ..it was not able to set above view
end;
finally
aWordApp.ActiveWindow.ActivePane.View.SeekView := iseekValue;
if iViewType <> wdPrintView then
aWordApp.ActiveWindow.ActivePane.View.Type := iViewType;
end;
end;
// This is the function that performs Search And Replace in the selected View
// it is called once per view
function SearchAndReplaceInADocumentPart;
begin
aWordApp.Selection.Find.ClearFormatting;
aWordApp.Selection.Find.Text := aSearchString;
aWordApp.Selection.Find.Replacement.Text := aReplaceString;
aWordApp.Selection.Find.Forward := True;
aWordApp.Selection.Find.MatchAllWordForms := False;
aWordApp.Selection.Find.MatchCase := True;
aWordApp.Selection.Find.MatchWildcards := False;
aWordApp.Selection.Find.MatchSoundsLike := False;
aWordApp.Selection.Find.MatchWholeWord := False;
aWordApp.Selection.Find.MatchFuzzy := False;
aWordApp.Selection.Find.Wrap := wdFindContinue;
aWordApp.Selection.Find.Format := False;
{ Perform the search}
aWordApp.Selection.Find.Execute(Replace := wdReplaceAll);
end;
Here i paste profiling results (i have aqtime pro):
Can you please help me in pinpointing the problem?
I didn't see such terrible performance when testing on my machine, but still, there are ways to improve performance.
Biggest improvement is setting the aWordApp.ActiveWindow.Visible to False before calling MSWordSearchAndReplaceInAllDocumentParts.
Second improvement is setting aWordApp.ScreenUpdating to False.
When you are calling MSWordSearchAndReplaceInAllDocumentParts multiple times in a row, apply above settings once. Also, set ActiveWindow.ActivePane.View.Type to wdPrintView before calling MSWordSearchAndReplaceInAllDocumentParts multiple times.
Edit:
I got another improvement by changing the way you de find/replace: Instead of changing the SeekView, iterate through all the sections and get the range of the document, headers and footers yourself and do a Find/Replace over those ranges.
procedure TForm1.MSWordSearchAndReplaceInAllDocumentParts(const aDoc: OleVariant);
var
i: Integer;
lSection: OleVariant;
lHeaders: OleVariant;
lFooters: OleVariant;
lSections: OleVariant;
begin
lSections := aDoc.Sections;
for i := 1 to lSections.Count do
begin
lSection := lSections.Item(i);
lHeaders := lSection.Headers;
lFooters := lSection.Footers;
if lSection.PageSetup.OddAndEvenPagesHeaderFooter then
begin
SearchAndReplaceInADocumentPart(lHeaders.Item(wdHeaderFooterEvenPages).Range);
SearchAndReplaceInADocumentPart(lFooters.Item(wdHeaderFooterEvenPages).Range);
end;
if lSection.PageSetup.DifferentFirstPageHeaderFooter then
begin
SearchAndReplaceInADocumentPart(lHeaders.Item(wdHeaderFooterFirstPage).Range);
SearchAndReplaceInADocumentPart(lFooters.Item(wdHeaderFooterFirstPage).Range);
end;
SearchAndReplaceInADocumentPart(lHeaders.Item(wdHeaderFooterPrimary).Range);
SearchAndReplaceInADocumentPart(lFooters.Item(wdHeaderFooterPrimary).Range);
SearchAndReplaceInADocumentPart(lSection.Range);
end;
end;
procedure TForm1.SearchAndReplaceInADocumentPart(const aRange: OleVariant);
begin
aRange.Find.ClearFormatting;
aRange.Find.Text := aSearchString;
aRange.Find.Replacement.Text := aReplaceString;
aRange.Find.Forward := True;
aRange.Find.MatchAllWordForms := False;
aRange.Find.MatchCase := True;
aRange.Find.MatchWildcards := False;
aRange.Find.MatchSoundsLike := False;
aRange.Find.MatchWholeWord := False;
aRange.Find.MatchFuzzy := False;
aRange.Find.Wrap := wdFindContinue;
aRange.Find.Format := False;
{ Perform the search}
aRange.Find.Execute(Replace := wdReplaceAll);
end;
You will see even a bigger improvement if you open the document you want to modify while the application is invisible, or if you open the document with Visible := False; (setting the application visible again will also set the document visible).

Creating TQReport elements at run time

Creating TQReport elements at run time.
Well, at least trying...
I don't know what headings or data shall appear on this report. I get a TList of TStrings representing the data rows and columns. I plant the 'Create' directives in the band print event for the group and the OnNeedData event for main data row bands.
But nothing appears. Must I make the labels at design time? Do not want.
To get you started, this works:
// uses QuickRpt, qrpBaseCtrls, QRCtrls, QRPrntr;
procedure TForm1.Button1Click(Sender: TObject);
var QR: TQuickRep;
QB: TQRBand;
QL: TQRLabel;
begin
QR := TQuickRep.Create(Self);
try
QR.PrintIfEmpty := True;
QB := TQRBand.Create(Self);
QB.Parent := QR;
QB.BandType := rbTitle;
QL := TQRLabel.Create(Self);
QL.Parent := QB;
QL.Left := 10;
QL.Top := 10;
QL.AutoSize := True;
QL.Caption := 'This works';
QR.Preview;
finally QR.Free;
end;
end;

Delphi7 TMS TDBAdvGrid Sort data when column header is clicked

I'm a newbie into Delphi and i need an advice.
I'm using a TMS TDBAdvGrid and i need to sort the data when the user is clicking the header of a column. I setup the sort settings of the grid and i write code for the onclicksort event, but it is not working.
The sort settings of the grid:
SortSettings.Show = True;
SortSettings.IgnoreBlanks = True;
SortSettings.BlankPos = blLast;
the onclicksort event:
try
try
if FSortISWorking then
Exit;
FSortISWorking := true;
if ACol < 0 then
begin
grid.BeginUpdate;
grid.SortSettings.Column := ACol;
Application.ProcessMessages;
grid.QSort;
grid.EndUpdate;
end;
except on e: Exception do
begin
// log the error
end;
end;
finally
FSortISWorking := false;
end;
The grid is not linked directly to the database. The data is loaded into memory (TClientDataSet) and i need to sort the data only in memory, without another query to the database.
Thank you
I tried your example and this solved the issue for me:
Grid.PageMode := False;
In order to resolve this problem you must order the dataset behind your grid. here you have how to do this in general:http://delphi.about.com/od/usedbvcl/l/aa042203a.htm.
bellow you have an example:
procedure TForm1.DBAdvGrid1CanSort(Sender:TObject; ACol: Integer; var DoSort: Boolean);
var fldname:string;
begin
DoSort := False; // disable internal sort
// toggle sort order if
dbadvgrid1.SortSettings.Direction = sdAscending then
dbadvgrid1.SortSettings.Direction := sdDescending else
dbadvgrid1.SortSettings.Direction := sdAscending;
// get field name of the column
clicked fldname :=query1.FieldList.Fields[ACol -1].FieldName;
if pos(' ',fldname) 0 then fldname:= 'biolife.db."'+fldname+'"';
// add ORDER BY clause to the query
query1.SQL.Text := 'select * from
biolife.db ORDER BY '+fldname;
if dbadvgrid1.SortSettings.Direction =
sdDescending then query1.SQL.Text :=
query1.SQL.Text + ' DESC';
query1.Active := true;
DBAdvGrid1.SortSettings.Column := ACol;
end;
if you want to order your clientdataset here you have how to do it:
http://edn.embarcadero.com/article/29056
best regards,
Radu

Is it possible to select multiple columns in Virtual Treeview?

I need to add functionality to copy a rectangular selection of nodes and columns, but I can't find any way to actually select multiple columns in a Virtual Treeview (beside toFullRowSelect).
Am I just missing something? And if not, is there a descendant out there with grid-like multicolumn select support?
So after some testing I came up with the following, thanks DiGi for the extra push. DrawSelection won't work with this solution so it needs to be disabled. Since I don't think I'll need to do this again soon I didn't write a descendant.
Set toDisableDrawSelection, toExtendedFocus and toMultiSelect to True.
Declare the following variables/properties somewhere suitable:
StartSelectedColumn: integer;
FirstSelectedColumn: integer;
LastSelectedColumn: integer;
Selecting: boolean;
Update the following events:
OnKeyDown
if (not Selecting) and (Key = VK_SHIFT) then
begin
StartSelectedColumn := vtMain.FocusedColumn;
FirstSelectedColumn := StartSelectedColumn;
LastSelectedColumn := StartSelectedColumn;
Selecting := true;
end;
OnKeyUp
if Key = VK_SHIFT then
Selecting := false;
OnFocusChanged
if Selecting then
begin
if column < StartSelectedColumn then
begin
FirstSelectedColumn := column;
LastSelectedColumn := StartSelectedColumn;
end
else if column > StartSelectedColumn then
begin
FirstSelectedColumn := StartSelectedColumn;
LastSelectedColumn := column
end
else
begin
FirstSelectedColumn := column;
LastSelectedColumn := column;
end;
end
else
begin
StartSelectedColumn := column;
FirstSelectedColumn := column;
LastSelectedColumn := column;
end;
OnBeforeCellPaint
if vtMain.Selected[node] and InRange(column, FirstSelectedColumn, LastSelectedColumn) then
begin
if vtMain.Focused then
TargetCanvas.Brush.Color := vtMain.Colors.FocusedSelectionColor
else
TargetCanvas.Brush.Color := vtMain.Colors.UnfocusedSelectionColor;
TargetCanvas.Brush.Style := bsSolid;
TargetCanvas.FillRect(CellRect);
end;
OnPaintText
if vtMain.Selected[node] and InRange(column, FirstSelectedColumn, LastSelectedColumn) then
begin
if vtMain.Focused then
TargetCanvas.Font.Color := clHighlightText
else
TargetCanvas.Font.Color := vtMain.Font.Color;
end;
You can try enable/add toGridExtensions in TreeOptions.MiscOptions. It enables free moving in columns by cursor keys, but VT still deselect column on leaving. But I'm sure that is possible to "fix" it by custom draw and remembering starting node and column.
One more tip - look at OnStateChange event, maybe you can use
procedure TSomeForm.VTreeStateChange(Sender: TBaseVirtualTree; Enter,Leave: TVirtualTreeStates);
begin
if tsDrawSelecting in Enter then
begin
// Save position
end;
end;

Resources