I don't know exactly how to explain my question. Here is my try to explain: the function FindNext(SearchRec) will get me the next file from a directory. In my application I am looking sometimes go to backward a few files from my current SearchRec index. So how can I do that?
So I am looking for the oppose of FindNext(SearchRec) a function like FindBackward(SearchRec)
There's no such function. You'll need to keep track of previous hits in a list, say, and do the back tracking using that list.
I suggest to place them in an array of TSearchRec
SearchRecArr:array of TSearchRec;
Then when you reach a certain file, get the SearchRec that you need from the Array.
for example, this is an example where I placed in some folder 3 text files of names (z, z1, & z2).
then If I reached 'z2.txt' I will read SearchRec 2 steps backwards:
procedure TForm2.Button1Click(Sender: TObject);
var
SearchRec:TSearchRec;
SearchRecArr:array of TSearchRec;
i:integer;
begin
i:=-1;
if FindFirst('C:\Users\zeina.shehab\Desktop\New folder\z*.txt',faAnyFile,SearchRec)=0 then
begin
repeat
SetLength(SearchRecArr,length(SearchRecArr)+1);
SearchRecArr[high(SearchRecArr)]:=SearchRec;
inc(i);
if SearchRec.Name='z2.txt' then
caption:=SearchRecArr[i-2].Name;
until (FindNext(SearchRec)<>0);
end;
end;
I wrote my own function. Here is the code which works very well for me and it is very efficient for thousands of files(because it doesn't slow my playback algorithm).
Procedure GetBackward(var SRInitial:TSearchRec; iForwardSpeed:integer);
var SR:TSearchRec;
iIndex:integer;
vLastFiles:Array of String;
begin
SetLength(vLastFiles,Trunc(iForwardSpeed));
FindFirst(sPath+'*.txt',faAnyFile,SR);
while (FindNext(SR) = 0)and(SR.Name <> SRInitial.Name) do
begin
for iIndex := 0 to high(vLastFiles)-1 do
vLastFiles[iIndex]:=vLast[iIndex+1];
vLastFiles[high(vLastFiles)]:=SR.Name;
end;
//Fewer than ForwardSpeed
if vLastFiles[0] = '' then
begin
Exit;
end;
FindClose(SR);
FindClose(SRInitial);
FindFirst(sPath+'*.'+cbType.Text,faAnyFile,SRInitial);
while (FindNext(SRInitial) = 0)and(SRInitial.Name <> vLastFiles[0]) do
;
end;
The function was modified.
Related
I have a StringList with 2 rows
aOldPriceTerms[0] = 'FuelAddition=336643160'
aOldPriceTerms[1] = 'Freight=336643155'
So it works fine to use
aOldPriceTerms.Values['Freight'] -> '336643155'
I want to have a list of ID's from the list.
So simply
'FuelAddition','Freight'
Currently I use this code where aOldPriceTerms is the actual StringList.
function GetList(aOldPriceTerms: TStringList): String;
var
vOldTerm : string;
vOldTermsList : TStringList;
begin
vOldTermsList := TStringList.Create;
try
for i := aOldPriceTerms.Count - 1 downto 0 do
begin
vOldTerm := Copy(aOldPriceTerms[i], 1, (Pos('=', aOldPriceTerms[i])-1));
vOldTermsList.Add(vOldTerm);
end;
Result := vOldTermsList.CommaText;
finally
FreeAndNil(vOldTermsList);
end;
end;
My question is there a cleaner way to get the ids ?
Example is from the Delphi Basics, but TStringList.Names is also described in the Delphi documentation
// Now display all name and age pair values
for i := 0 to names.Count-1 do
begin
ShowMessage(names.Names[i]+' is '+names.ValueFromIndex[i]);
end;
You can use the TALNVStringList (NV for nameValue) from alcinoe (https://github.com/Zeus64/alcinoe) to handle Name and Value without all the time splitting the string
TALNVStringList is exactly the same as TStringList (nothing to change in the code except replacing TstringList by TALNVStringList) except that it's more efficient in speed because it's store the name in one dedicated field and the value in another dedicated field (no need to do all the time pos('=') and copy() to retrieve the name and the value of the row)
for i := 0 to aOldPriceTerms.Count-1 do
begin
ShowMessage(aOldPriceTerms.Names[i]+' is '+aOldPriceTerms.ValueFromIndex[i]);
end;
Exe demo showing the speed penalty of classic TstringList: https://svn.code.sf.net/p/alcinoe/code/demos/ALSortedListBenchmark/win32/ALSortedListBenchmark.exe
To get all the id's
for name in names.Names do
begin
i := names.IndexOf[name];
end;
or
To get all the Values
for name in vOldTermsList.Names do
begin
Value := vOldTermsList.Value[name];
end;
I've been working with Word2010.pas for the past week and everything went well, until I found out that if you open a document manually, edit it (but don't save), press Alt+F4, a prompt will show up saying if you want to save your document or not, leave it like that. Go into code and try to access that document, all calls will result in EOleException: Call was rejected by callee. Once you cancel that Word save prompt, everything works fine.
I came across this while writing code that periodically checks if a document is open. Here is the function that checks if the document is open: (function runs in a timer every 2 seconds)
function IsWordDocumentOpen(FileName: string): Boolean;
var
WordApp: TWordApplication;
I: Integer;
begin
Result := False;
try
WordApp := TWordApplication.Create(nil);
try
WordApp.Connect;
for I := 1 to WordApp.Documents.Count do
begin
try
if WordApp.Documents.Item(I).FullName = FileName then
begin
Result := True;
System.Break;
end;
except
on E: EOleException do
// I always end up here while the document has the prompt
end;
end;
finally
FreeAndNil(WordApp);
end;
finally
//
end;
end;
Does anyone have any experience with this? Is there some sort of a lock that I'm not aware of?
UPDATE #1: So far the only solution I could find was to implement IOleMessageFilter, this way I do not receive any exceptions but the program stops and waits on the line WordApp.Documents.Item(I).FullName, but that is not what I want. Implementation of IOleMessageFilter goes like this:
type
IOleMessageFilter = class(TInterfacedObject, IMessageFilter)
public
function HandleInComingCall(dwCallType: Longint; htaskCaller: HTask;
dwTickCount: Longint; lpInterfaceInfo: PInterfaceInfo): Longint;stdcall;
function RetryRejectedCall(htaskCallee: HTask; dwTickCount: Longint;
dwRejectType: Longint): Longint;stdcall;
function MessagePending(htaskCallee: HTask; dwTickCount: Longint;
dwPendingType: Longint): Longint;stdcall;
procedure RegisterFilter();
procedure RevokeFilter();
end;
implementation
function IOleMessageFilter.HandleInComingCall(dwCallType: Integer; htaskCaller: HTask; dwTickCount: Integer; lpInterfaceInfo: PInterfaceInfo): Longint;
begin
Result := 0;
end;
function IOleMessageFilter.MessagePending(htaskCallee: HTask; dwTickCount, dwPendingType: Integer): Longint;
begin
Result := 2 //PENDINGMSG_WAITDEFPROCESS
end;
procedure IOleMessageFilter.RegisterFilter;
var
OldFilter: IMessageFilter;
NewFilter: IMessageFilter;
begin
OldFilter := nil;
NewFilter := IOleMessageFilter.Create;
CoRegisterMessageFilter(NewFilter,OldFilter);
end;
function IOleMessageFilter.RetryRejectedCall(htaskCallee: HTask; dwTickCount, dwRejectType: Integer): Longint;
begin
Result := -1;
if dwRejectType = 2 then
Result := 99;
end;
procedure IOleMessageFilter.RevokeFilter;
var
OldFilter: IMessageFilter;
NewFilter: IMessageFilter;
begin
OldFilter := nil;
NewFilter := nil;
CoRegisterMessageFilter(NewFilter,OldFilter);
end;
end;
BEST SOLUTION SO FAR: I used IOleMessageFilter implementation like this: (remember this will stop and wait on the line where I previously got an exception)
function IsWordDocumentOpen(FileName: string): Boolean;
var
OleMessageFilter: IOleMessageFilter;
WordApp: TWordApplication;
I: Integer;
begin
Result := False;
try
OleMessageFilter := IOleMessageFilter.Create;
OleMessageFilter.RegisterFilter;
WordApp := TWordApplication.Create(nil);
try
WordApp.Connect;
for I := 1 to WordApp.Documents.Count do
begin
if WordApp.Documents.Item(I).FullName = FileName then
begin
Result := True;
System.Break;
end;
end;
finally
OleMessageFilter.RevokeFilter;
FreeAndNil(WordApp);
FreeAndNil(OleMessageFilter);
end;
finally
//
end;
end;
Actually, I think that the problem is simply that Word is busy doing a modal dialog and so can't respond to external COM calls. This trivial code produces the same error:
procedure TForm1.Button1Click(Sender: TObject);
begin
Caption := MSWord.ActiveDocument.Name;
end;
Probably the simplest way to avoid this problem is to head it off before if happens. If you are using the TWordApplication server that comes with Delphi (on the Servers components tab), you can attach an event handler to its OnDocumentBeforeClose and use that to present your own "Save Y/N?" dialog and set the event's Cancel param to True to prevent Word's dialog from appearing.
Update: If you try experimenting with this code while the Save dialog is popped up
procedure TForm1.Button1Click(Sender: TObject);
var
vWin,
vDoc,
vApp : OleVariant;
begin
vWin := MSWord.ActiveWindow;
Caption := vWin.Caption;
vDoc := vWin.Document;
vApp := vDoc.Application; // Attempt to read Word Document property
Caption := vDoc.Path + '\';
Caption := Caption + vDoc.Name;
end;
I think you'll find that any attempt to read from the vDoc object will result in the "Call was rejected ..." message, so I am beginning to think that this behaviour is by design - it's telling you that the object is not in a state that it can be interacted with.
Interestingly, it is possible to read the Caption property of the vWin Window object, which will tell you the filename of the file but not the file's path.
Realistically, I still think your best option is to try and get the OnDocumentBeforeClose event working. I don't have Word 2010 installed on this machine by Word 2007 works fine with the Word server objects derived from Word2000.Pas so you might try those instead of Word2010.Pas, just to see.
Another possibility is simply to catch the "Call was rejected ..." exception, maybe return "Unavailable" as the document FullName, and try again later.
If you're not using TWordApplication and don't know how to catch the OnDocumentBeforeClose for the method your using to access Word, let me know how you are accessing it and I'll see if I can dig out some code to do it.
I vaguely recall there's a way of detecting that Word is busy with a modal dialog - I'll see if I can find where I saw that a bit later if you still need it. Your IOleMessageFilter looks more promising than anything I've found as yet, though.
Hey guy's so am making this program in delphi that loads a list of usernames i want to add them all to the Memo without using selectall i want to use the for loop to learn how it works as you can see i tried by failed it selects both but only adds the last one which is weird xD Any help would be great thanks guys
procedure TForm1.Button1Click(Sender: TObject);
begin
Memo1.Lines.Clear;
Listbox1.Items.LoadFromFile('names.txt');
end;
procedure TForm1.Button2Click(Sender: TObject);
var
I: Integer;
begin
for I:=Listbox1.Items.Count-1 downto 0 do
begin
ListBox1.ItemIndex:=I;
Memo1.Lines.Add(ListBox1.Items.Strings[1]);
end
end;
end;
end.
Well, you add the item with index 1 every time. You presumably mean:
for I:=Listbox1.Items.Count-1 downto 0 do
Memo1.Lines.Add(ListBox1.Items[I]);
This adds in reverse order. If you want the items in the same order it is simply:
Memo1.Lines.Assign(ListBox1.Items);
I clone a panel and its contents(A image and a checkbox) 20 times.
Sample of the panel being cloned:
This is the procedure used to clone a whole panel:
procedure TForm1.ClonePanel(pObjectName: Tpanel);
var apanel : Tpanel;
Ctrl, Ctrl_: TComponent;
i: integer;
begin
//handle the Control itself first
TComponent(apanel) := CloneComponent(pObjectName);
with apanel do
begin
Left := 24;
Top :=64;
end;
//now handle the childcontrols
for i:= 0 to pObjectName.ControlCount-1 do
begin
Ctrl := TComponent(pObjectName.Controls[i]);
Ctrl_ := CloneComponent(Ctrl);
TControl(Ctrl_).Parent := apanel;
TControl(Ctrl_).Left := TControl(Ctrl).Left;
TControl(Ctrl_).top := TControl(Ctrl).top;
end;
end;
The following is the the code that physically does the cloning(called above):
function TForm1.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 + inttostr(panels);
inc(panels);
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;
So now I want to use the cloned objects but how do I call them in my code?
For example how can I call the checked function of one of the cloned check boxes?
Thanks for your help :)
Others are right and it is better to use frame but if we want just use your code we must fix it first. there is a problem in your code and that is the Inc(panles); position. you must put this line after loop of for i:= 0 to pObjectName.ControlCount-1 do in the ClonePanle procedure, not in the CloneComponent function.
If you fix that, then you can use FindComponent function to access the components that you want as Marko Paunovic said.
For example the name of the component that you put on the first Panel that you defined as the first instance which other cloned panels are cloned from that is TestCheckBox. If you cloned 20 times the Panel that we talked about; you can access the TCheckBox of the 16th Cloned obejct like this and changing it's caption to whatever you want:
(I suppose that the panels variable was 0, when the program started.)
TCheckBox(FindComponent('clone_TestCheckBox15')).Caption:='aaaaa';
With the code below I am trying to reload a DirectionsResult back into a TGMDirections.
procedure Form2.Button2Click(Sender: TObject);
var
DR: TDirectionsResult;
i: Integer;
begin
DR:= TDirectionsResult.Create(Form1.FDirection, 0);
DR.XMLData.BeginUpdate;
for i:= 0 to Memo1.Lines.Count - 1 do
begin
DR.XMLData.Append(Memo1.Lines[i]);
end;
DR.XMLData.EndUpdate;
ShowMessage(Form1.FDirection.DirectionsResult[0].Routes[0].Leg[0].EndAddress);
end;
All seems well until the ShowMessage where I get a List out of bounds message.
I take it that the DR has not been created or the Memo has not loaded into the DirectionsResult.
Further adaption has confirmed the DirectionsResult[0] does not exist.
Help with the correction would be greatly appreciated.
You can't add a TDirectionsResult to DirectionsResult array programatically, you need to invoke Execute method from TGMDirections object.
However you can do something like this
procedure TForm1.Button1Click(Sender: TObject);
var
DR: TDirectionsResult;
begin
DR:= TDirectionsResult.Create(GMDirection1, 1);
DR.XMLData.Text := Memo1.Lines.Text;
ShowMessage(DR.Routes[0].Leg[0].EndAddress);
end;
That is, you can work without problems with your object and you can access to all properties and methods.
Note the assignation between XMLData and Memo.Lines, don't assign line to line because the control of XML is made on OnChange event of XMLData.
Regards.