I'm trying to iterate the DOM using TChromium and because i use Delphi 2007 i can't use anonymous methods, so i created a class inherited of TCEFDomVisitorOwn. My code is as below, but for some reason the 'visit' procedure is never called, so nothings happens.
unit udomprinc;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ceflib, cefvcl;
type
TForm1 = class(TForm)
Chromium1: TChromium;
procedure FormCreate(Sender: TObject);
procedure Chromium1LoadEnd(Sender: TObject; const browser: ICefBrowser; const frame: ICefFrame;
httpStatusCode: Integer);
private
{ Private declarations }
public
{ Public declarations }
end;
type
TElementVisitor = class(TCefDomVisitorOwn)
private
FTagName, FHtml: string;
protected
procedure visit(const document: ICefDomDocument); override;
public
constructor Create(const par1, par2: string); reintroduce;
end;
var
Form1: TForm1;
implementation
constructor TElementVisitor.Create(const par1, par2: string);
begin
inherited create;
FTagName := par1;
FHtml := par2;
end;
procedure TElementVisitor.visit(const document: ICefDomDocument);
procedure ProcessNode(ANode: ICefDomNode);
var
Node: ICefDomNode;
tagname, name, html, value : string;
begin
if Assigned(ANode) then
begin
Node := ANode.FirstChild;
while Assigned(Node) do
begin
name := Node.GetElementAttribute('name');
tagname := Node.GetElementAttribute('tagname');
html := Node.GetElementAttribute('outerhtml');
value := Node.GetElementAttribute('value');
ProcessNode(Node);
Node := Node.NextSibling;
end;
end;
end;
begin
// this never happens
ProcessNode(document.Body);
end;
{$R *.dfm}
procedure TForm1.Chromium1LoadEnd(Sender: TObject; const browser: ICefBrowser; const frame: ICefFrame;
httpStatusCode: Integer);
var visitor : TElementVisitor;
begin
visitor := TElementVisitor.Create('input','test');
chromium1.Browser.MainFrame.VisitDom(visitor);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
chromium1.load('www.google.com');
end;
end.
It's all about sending messages back and forth. Your code is missing a RenderProcessHandler, this allows the Renderer to receive messages.
In your DPR you should have code like this
if not CefLoadLibDefault then
Exit;
in your pas file
type
TNotifyVisitor = procedure(aNode: ICefDomNode; var aLevel: integer);// of object;
TAttributeType = (atNodeName, atName, atId, atClass, atLevel);
TElementNameVisitor = class(TCefDomVisitorOwn)
private
FName: string;
FAttributeName: string;
FOnFound: TNotifyVisitor;
FOnVisited: TNotifyVisitor;
function getAttributeName: string;
protected
procedure visit(const document: ICefDomDocument); override;
public
constructor Create(const AName: string); reintroduce;
property OnFound: TNotifyVisitor read FOnFound write FOnFound;
property OnVisited: TNotifyVisitor read FOnVisited write FOnVisited;
property AttributeName: string read getAttributeName write FAttributeName;
end;
TCustomRenderProcessHandler = class(TCefRenderProcessHandlerOwn)
protected
function OnProcessMessageReceived(const browser: ICefBrowser;
sourceProcess: TCefProcessId; const message: ICefProcessMessage): Boolean; override;
end;
implementation
var
_Browser: ICefBrowser;
{ TElementNameVisitor }
constructor TElementNameVisitor.Create(const AName: string);
begin
inherited Create;
FName := AName;
end;
function TElementNameVisitor.getAttributeName: string;
begin
if FAttributeName = '' then
Result := 'name'
else
Result := FAttributeName;
end;
procedure TElementNameVisitor.visit(const document: ICefDomDocument);
var
a_Level: integer;
a_message: iCefProcessMessage;
procedure ProcessNode(aNode: ICefDomNode; var aLevel: integer);
var
a_Node: ICefDomNode;
a_Name: string;
begin
if Assigned(aNode) then
begin
inc(aLevel);
a_Node := aNode.FirstChild;
while Assigned(a_Node) do
begin
if Assigned(FOnVisited) then
FOnVisited(a_Node, aLevel);
if Assigned(FOnFound) then
begin
a_Name := a_Node.GetElementAttribute(AttributeName);
if SameText(a_Name, FName) then
begin
// do what you need with the Node here
if Assigned(FOnFound) then
FOnFound(a_Node, aLevel);
end;
end;
ProcessNode(a_Node, aLevel);
a_Node := a_Node.NextSibling;
end;
end;
end;
begin
a_Level := 0;
ProcessNode(document.Body, a_Level);
a_message := TCefProcessMessageRef.New(cdomdataFin);
_Browser.SendProcessMessage(PID_BROWSER, a_message);
end;
You'll need to create a RenderProcessHandler:
initialization
CefRenderProcessHandler := TCustomRenderProcessHandler.Create;
To use it...You send a message to Renderer like this
function TformBrowser.HasBrowser: boolean;
begin
Result := Assigned(Chromium1.browser);
end;
procedure TformBrowser.Button1Click(Sender: TObject);
var
a_message: ICefProcessMessage;
a_list: ICefListValue;
a_How: string;
begin
if HasBrowser and FLoaded then
begin
FLoaded := False;
Case rgFindDomNodeBy.ItemIndex of
0: a_How := 'ByName';
1: a_How := 'ById';
2: a_How := 'ByClass';
3: a_How := 'ByAll';
end;
lbFrames.Items.Clear;
a_message := TCefProcessMessageRef.New(a_How);
a_list := a_message.ArgumentList;
a_list.SetString(0, edtAttribute.Text);
Chromium1.browser.SendProcessMessage(PID_RENDERER,a_message);
end;
end;
The RenderProcessHandler will get the message:
{ TCustomRenderProcessHandler }
procedure _ElementCB(aNode: ICefDomNode; var aLevel: integer);
var
a_message: ICefProcessMessage;
begin
a_message := TCefProcessMessageRef.New('domdata');
a_message.ArgumentList.SetString(Ord(atNodeName), aNode.Name);
a_message.ArgumentList.SetString(Ord(atName), aNode.GetElementAttribute('name'));
a_message.ArgumentList.SetString(Ord(atId), aNode.GetElementAttribute('id'));
a_message.ArgumentList.SetString(Ord(atClass), aNode.GetElementAttribute('class'));
a_message.ArgumentList.SetInt(Ord(atLevel), aLevel);
_Browser.SendProcessMessage(PID_BROWSER, a_message);
end;
function TCustomRenderProcessHandler.OnProcessMessageReceived(
const browser: ICefBrowser; sourceProcess: TCefProcessId;
const message: ICefProcessMessage): Boolean;
var
a_list: ICefListValue;
begin
_Browser := browser;
Result := False;
if SameText(message.Name, 'ByAll') then
begin
_ProcessElements(browser.MainFrame, _ElementCB);
Result := True;
end else
if SameText(message.Name, 'ByName') then
begin
a_list := message.ArgumentList;
_ProcessElementsByAttribute(browser.MainFrame, a_list.GetString(0),'name', _ElementCB);
Result := True;
end else
if SameText(message.Name, 'ById') then
begin
a_list := message.ArgumentList;
_ProcessElementsByAttribute(browser.MainFrame, a_list.GetString(0), 'id', _ElementCB);
Result := True;
end else
if SameText(message.Name, 'ByClass') then
begin
a_list := message.ArgumentList;
_ProcessElementsByAttribute(browser.MainFrame, a_list.GetString(0), 'class', _ElementCB);
Result := True;
end;
end;
The RenderProcessHandler creates the Visitor(TElementNameVisitor)
procedure _ProcessElementsByAttribute(const aFrame: ICefFrame; aName, aAttributeName: string; aVisitor: TNotifyVisitor);
var
a_Visitor: TElementNameVisitor;
begin
if Assigned(aFrame) then
begin
a_Visitor := TElementNameVisitor.Create(aName);
a_Visitor.AttributeName := aAttributeName;
a_Visitor.OnFound := aVisitor;
aFrame.VisitDom(a_Visitor);
end;
end;
procedure _ProcessElements(const aFrame: ICefFrame; aVisitor: TNotifyVisitor);
var
a_Visitor: TElementNameVisitor;
begin
if Assigned(aFrame) then
begin
a_Visitor := TElementNameVisitor.Create('');
a_Visitor.OnVisited := aVisitor;
aFrame.VisitDom(a_Visitor);
end;
end;
The Visitor (TElementNameVisitor)then sends a message back to TChromium and you can tie into it like:
procedure TformBrowser.Chromium1ProcessMessageReceived(Sender: TObject;
const browser: ICefBrowser; sourceProcess: TCefProcessId;
const message: ICefProcessMessage; out Result: Boolean);
var
a_List: ICefListValue;
begin
if SameText(message.Name, 'domdata') then
begin
a_List := message.ArgumentList;
lbFrames.Items.Add(a_List.GetString(Ord(atNodeName)));
lbFrames.Items.Add('Name: ' + a_List.GetString(Ord(atName)));
lbFrames.Items.Add('Id: ' + a_List.GetString(Ord(atId)));
lbFrames.Items.Add('Class: ' + a_List.GetString(Ord(atClass)));
lbFrames.Items.Add('Level: ' + IntToStr(a_List.GetInt(Ord(atLevel))));
lbFrames.Items.Add('------------------');
Result := True;
end else
if SameText(message.Name, cdomdataFin) then
begin
FLoaded := True;
end else
begin
lbFrames.Items.Add('Unhandled message: ' + message.Name);
inherited;
end;
end;
-----------edit-------------
After looking at this code...it can be improved...to be more thread friendly
Delete this
var
_Browser: ICefBrowser;
change this
TNotifyVisitor = procedure(aBrowser: ICefBrowser; aNode: ICefDomNode; var aLevel: integer);// of object;
add this to TElementNameVisitor
property Browser: ICefBrowser read getBrowser write FBrowser;
Change references in TElementNameVisitor to Browser also add this
function TElementNameVisitor.getBrowser: ICefBrowser;
begin
if not Assigned(FBrowser) then
Raise Exception.Create('Need to set the Browser property when creating TElementNameVisitor.');
Result := FBrowser;
end;
Change these
procedure _ProcessElementsByAttribute(const aBrowser: ICefBrowser; aName, aAttributeName: string; aVisitor: TNotifyVisitor);
var
a_Visitor: TElementNameVisitor;
begin
if Assigned(aBrowser) and Assigned(aBrowser.MainFrame) then
begin
a_Visitor := TElementNameVisitor.Create(aName);
a_Visitor.Browser := aBrowser;
a_Visitor.AttributeName := aAttributeName;
a_Visitor.OnFound := aVisitor;
aBrowser.MainFrame.VisitDom(a_Visitor);
end;
end;
procedure _ProcessElements(const aBrowser: ICefBrowser; aVisitor: TNotifyVisitor);
var
a_Visitor: TElementNameVisitor;
begin
if Assigned(aBrowser) and Assigned(aBrowser.MainFrame) then
begin
a_Visitor := TElementNameVisitor.Create('');
a_Visitor.Browser := aBrowser;
a_Visitor.OnVisited := aVisitor;
aBrowser.MainFrame.VisitDom(a_Visitor);
end;
end;
Also change these
procedure _ElementCB(aBrowser: ICefBrowser; aNode: ICefDomNode; var aLevel: integer);
var
a_message: ICefProcessMessage;
begin
a_message := TCefProcessMessageRef.New(cdomdata);
a_message.ArgumentList.SetString(Ord(atNodeName), aNode.Name);
a_message.ArgumentList.SetString(Ord(atName), aNode.GetElementAttribute('name'));
a_message.ArgumentList.SetString(Ord(atId), aNode.GetElementAttribute('id'));
a_message.ArgumentList.SetString(Ord(atClass), aNode.GetElementAttribute('class'));
a_message.ArgumentList.SetInt(Ord(atLevel), aLevel);
aBrowser.SendProcessMessage(PID_BROWSER, a_message);
end;
function TCustomRenderProcessHandler.OnProcessMessageReceived(
const browser: ICefBrowser; sourceProcess: TCefProcessId;
const message: ICefProcessMessage): Boolean;
var
a_list: ICefListValue;
begin
Result := False;
if SameText(message.Name, 'ByAll') then
begin
_ProcessElements(browser, _ElementCB);
Result := True;
end else
if SameText(message.Name, 'ByName') then
begin
a_list := message.ArgumentList;
_ProcessElementsByAttribute(browser, a_list.GetString(0),'name', _ElementCB);
Result := True;
end else
if SameText(message.Name, 'ById') then
begin
a_list := message.ArgumentList;
_ProcessElementsByAttribute(browser, a_list.GetString(0), 'id', _ElementCB);
Result := True;
end else
if SameText(message.Name, 'ByClass') then
begin
a_list := message.ArgumentList;
_ProcessElementsByAttribute(browser, a_list.GetString(0), 'class', _ElementCB);
Result := True;
end;
end;
Related
In Delphi, when the IDE inserts a new component at design time, it gives its name a prefix which is the ClassName minus the leading "T" and then adds a number that makes its name unique.
This recent question asks how to change the prefix part of the component's name, so that
Edit1
could be changed to
ed1
The accepted answer refers to the "rename components" utility in GExperts, which is fine so far as it goes.
However, is there a way I can always get the same, non-standard, prefix for a component without having to create and install a descendant component in the Componxent Palette? I know how to do this but it is an unwelcome chore if I want to do it for more than one component.
I noticed that Q&A, too.
It seemed to me to be a special case of something I wrote an answer for, how to specify
defaults for the properties of components added to a form. It is rather long-winded
and doesn't work for the component's Name property. However, it can be made to work
for the Name, too, with some quite minor modifications, so that it will now support a specfication
of defaults like this:
[TMemo]
Lines.Strings=
[TEdit]
Font.Name=Courier New
Font.Size=11
[TButton]
Name=btn
Caption=
[TCheckBox]
Name=cb
Caption=
Note that the TButton and TCheckBox use non-standard Name prefixes and a blank Caption.
The blank Caption is to save having to rub out the default the IDE provides.
The full code of the unit which needs to be added to a design-time package and installed in the IDE is below. The main change,
compared to my previous answer, is that the component's Name property is handled separately from
any other properties by this
procedure TDesignNotifierForm.SetComponentName(AComponent : TComponent; AComponentName : String);
var
AOwner : TComponent;
begin
// First, try to find the component's Form. We need this so that we can ask the Form's
// Designer to generate a unique name for the component instance (Comp1, Comp2, etc).
AOwner := AComponent.Owner;
while (AOwner <> Nil) and not(AOwner is TForm) do
AOwner := AOwner.Owner;
if AOwner is TForm then begin
AComponentName := TForm(AOwner).Designer.UniqueName(AComponentName);
AComponent.Name := AComponentName;
TForm(AOwner).Designer.Modified; // Notify the Form's Designer
end;
end;
procedure TDesignNotifierForm.SetComponentProperties(AComponent : TComponent; ComponentClassName : String);
var
i : Integer;
AString : String;
Index : Integer;
begin
// Note: The defaults which can be set include the Component's Name. For simplicity, it can be included
// amongst the other defaults but requires special treatment (see SetComponentName).
//
if Ini.SectionExists(ComponentClassName) then begin
Ini.ReadSectionValues(ComponentClassName, SL);
Index := SL.IndexOfName('Name');
if Index >= 0 then begin
AString := SL.Values['Name'];
SetComponentName(AComponent, AString);
end;
for i := 0 to SL.Count - 1 do begin
if i <> Index then begin
AString := ComponentClassName + '.' + SL[i];
SetComponentProperty(AComponent, AString);
end;
end;
end;
end;
Unit code:
unit DesignNotifierFormu;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls, StdCtrls, TypInfo, ToolsApi, DesignIntf, IniFiles;
const
WM_CompInserted = WM_User + 1;
type
TDesignNotifierForm = class(TForm)
Memo1: TMemo;
Panel1: TPanel;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
procedure SetComponentProperties(AComponent : TComponent; ComponentClassName: String);
procedure SetComponentName(AComponent: TComponent; AComponentName: String);
public
AComp : TComponent;
Ini : TMemIniFile;
SL : TStringList;
procedure Log(const Title, Msg : String);
procedure WMCompInserted(var Msg : TMsg); message WM_CompInserted;
end;
TDesignNotification = class(TInterfacedObject, IDesignNotification)
F : TDesignNotifierForm;
procedure ItemDeleted(const ADesigner: IDesigner; AItem: TPersistent);
procedure ItemInserted(const ADesigner: IDesigner; AItem: TPersistent);
procedure ItemsModified(const ADesigner: IDesigner);
procedure SelectionChanged(const ADesigner: IDesigner;
const ASelection: IDesignerSelections);
procedure DesignerOpened(const ADesigner: IDesigner; AResurrecting: Boolean);
procedure DesignerClosed(const ADesigner: IDesigner; AGoingDormant: Boolean);
constructor Create;
destructor Destroy; override;
end;
var
DesignNotification : TDesignNotification;
implementation
{$R *.dfm}
constructor TDesignNotification.Create;
begin
inherited Create;
F := TDesignNotifierForm.Create(Nil);
F.Show;
F.Log('Event', 'Notifier created');
end;
procedure TDesignNotification.DesignerClosed(const ADesigner: IDesigner;
AGoingDormant: Boolean);
begin
end;
procedure TDesignNotification.DesignerOpened(const ADesigner: IDesigner;
AResurrecting: Boolean);
var
C : TComponent;
Msg : String;
begin
EXIT; // following for experimenting only
C := ADesigner.Root;
if C <> Nil then begin
Msg := C.ClassName;
// At this point, you can call ShowMessage or whatever you like
ShowMessage(Msg);
end
else
Msg := 'no root';
F.Log('Designer Opened', Msg);
end;
destructor TDesignNotification.Destroy;
begin
F.Close;
F.Free;
inherited;
end;
procedure TDesignNotification.ItemDeleted(const ADesigner: IDesigner;
AItem: TPersistent);
begin
end;
procedure TDesignNotification.ItemInserted(const ADesigner: IDesigner;
AItem: TPersistent);
var
S : String;
begin
if AItem is TComponent then begin
S := 'Component name: ' + TComponent(AItem).Name;
F.AComp := TComponent(AItem);
PostMessage(F.Handle, WM_CompInserted, 0, 0);
end
else
S := 'Item';
F.Log('Designer', ADesigner.GetComponentName(TComponent(AItem)));
F.Log('ItemInserted', S);
end;
procedure TDesignNotification.ItemsModified(const ADesigner: IDesigner);
begin
end;
procedure TDesignNotification.SelectionChanged(const ADesigner: IDesigner;
const ASelection: IDesignerSelections);
begin
end;
procedure SetUp;
begin
DesignNotification := TDesignNotification.Create;
RegisterDesignNotification(DesignNotification);
end;
procedure TDesignNotifierForm.FormCreate(Sender: TObject);
begin
Ini := TMemIniFile.Create('d:\aaad7\ota\componentdefaults\defaults.ini');
SL := TStringList.Create;
end;
procedure TDesignNotifierForm.FormDestroy(Sender: TObject);
begin
SL.Free;
Ini.Free;
end;
procedure SplitStr(const Input, Delim : String; var Head, Tail : String);
var
P : Integer;
begin
P := Pos(Delim, Input);
if P = 0 then begin
Head := Input;
Tail := '';
end
else begin
Head := Copy(Input, 1, P - 1);
Tail := Copy(Input, P + Length(Delim), MaxInt);
end;
end;
procedure SetComponentProperty(AComponent : TComponent; AString : String);
var
Value,
Head,
Tail,
ObjName,
PropName : String;
Obj : TObject;
AType : TTypeKind;
begin
// needs to Use TypInfo
SplitStr(AString, '=', PropName, Value);
if PropName = '' then else;
SplitStr(PropName, '.', Head, Tail);
if Pos('.', Tail) = 0 then begin
SetStrProp(AComponent, Tail, Value);
end
else begin
SplitStr(Tail, '.', ObjName, PropName);
Obj := GetObjectProp(AComponent, ObjName);
if Obj is TStrings then begin
// Work around problem setting e.g. TMemo.Lines.Text
TStrings(Obj).Text := Value;
end
else begin
AType := PropType(Obj, PropName);
case AType of
// WARNING - incomplete list
tkString,
tkLString : SetStrProp(Obj, PropName, Value);
tkInteger : SetOrdProp(Obj, PropName, StrToInt(Value));
tkFloat : SetFloatProp(Obj, PropName, StrToFloat(Value));
end; { case }
end;
end;
end;
procedure TDesignNotifierForm.SetComponentName(AComponent : TComponent; AComponentName : String);
var
AOwner : TComponent;
begin
// First, try to find the component's Form. We need this so that we can ask the Form's
// Designer to generate a unique name for the component instance (Comp1, Comp2, etc).
AOwner := AComponent.Owner;
while (AOwner <> Nil) and not(AOwner is TForm) do
AOwner := AOwner.Owner;
if AOwner is TForm then begin
AComponentName := TForm(AOwner).Designer.UniqueName(AComponentName);
AComponent.Name := AComponentName;
TForm(AOwner).Designer.Modified; // Notify the Form's Designer
end;
end;
procedure TDesignNotifierForm.SetComponentProperties(AComponent : TComponent; ComponentClassName : String);
var
i : Integer;
AString : String;
Index : Integer;
begin
// Note: The defaults which can be set include the Component's Name. For simplicity, it can be included
// amongst the other defaults but requires special treatment (see SetComponentName).
//
if Ini.SectionExists(ComponentClassName) then begin
Ini.ReadSectionValues(ComponentClassName, SL);
Index := SL.IndexOfName('Name');
if Index >= 0 then begin
AString := SL.Values['Name'];
SetComponentName(AComponent, AString);
end;
for i := 0 to SL.Count - 1 do begin
if i <> Index then begin
AString := ComponentClassName + '.' + SL[i];
SetComponentProperty(AComponent, AString);
end;
end;
end;
end;
procedure TDesignNotifierForm.WMCompInserted(var Msg: TMsg);
var
S : String;
begin
if AComp <> Nil then
S := AComp.Name
else
S := 'Name not known';
Log('WMCompInserted', S);
SetComponentProperties(AComp, AComp.ClassName);
AComp := Nil; // We're done with AComp
end;
procedure TDesignNotifierForm.Log(const Title, Msg: String);
begin
if csDestroying in ComponentState then
exit;
Memo1.Lines.Add(Title + ': ' + Msg);
end;
initialization
SetUp;
finalization
if DesignNotification <> Nil then begin
UnRegisterDesignNotification(DesignNotification);
end;
end.
I have made a code, that inserts CFE_LINK into RichEdit text, but it works only for last inserted text. All previous insertions of Links are Undone.
I want to insert multiple Link-texts, but I cant figure out how to do that.
Here is a working code (with no errors):
unit uRichEditExtended;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, RichEdit, WinApi.ShellApi, Vcl.Controls, Vcl.ComCtrls,
Generics.Collections;
type
TZ_RichEditClickEvent = reference to procedure(const ALinkText: string);
TZ_RichEditLink = class
IsDefaultEvent: boolean;
Text: string;
OnLinkClickEvent: TZ_RichEditClickEvent;
end;
TZ_RichEditLinks = TList<TZ_RichEditLink>;
TRichEditExtended = class
protected
class var FInstance: TRichEditExtended;
private
FPrevRichEditWndProc: TWndMethod;
FRichEdit: TRichEdit;
FRichEditLinks: TZ_RichEditLinks;
procedure InsertLinkText(const LinkText: string; SelStart: integer = -1);
procedure SetRichEditMasks;
procedure RichEditWndProc(var Message: TMessage);
procedure AfterConstruction; override;
procedure BeforeDestruction; override;
public
class function This: TRichEditExtended;
class procedure ApplyRichEdit(ARichEdit: TRichEdit); // -1 - inserts to the end of text, otherwise into a position indicated by SelStart
class function AddLinkText(AText: string; AOnLinkClickEvent: TZ_RichEditClickEvent; SelStart: integer = -1): integer;
class function AddLinkTextWithDefaultEvent(AText: string; SelStart: integer = -1): integer;
class procedure AddDefaultLinkTextEvent(AOnLinkClickEvent: TZ_RichEditClickEvent);
end;
implementation
{ TRichEditExtended }
uses StrUtils;
class procedure TRichEditExtended.AddDefaultLinkTextEvent(AOnLinkClickEvent: TZ_RichEditClickEvent);
var
REL: TZ_RichEditLink;
begin
if (This.FRichEditLinks.Count > 0) and This.FRichEditLinks[0].IsDefaultEvent then
This.FRichEditLinks[0].OnLinkClickEvent := AOnLinkClickEvent
else
begin
REL := TZ_RichEditLink.Create;
REL.IsDefaultEvent := true;
REL.Text := '';
REL.OnLinkClickEvent := AOnLinkClickEvent;
This.FRichEditLinks.Insert(0, REL);
REL := nil;
end;
end;
class function TRichEditExtended.AddLinkText(AText: string; AOnLinkClickEvent: TZ_RichEditClickEvent; SelStart: integer = -1): integer;
var REL: TZ_RichEditLink;
begin
REL := TZ_RichEditLink.Create;
REL.IsDefaultEvent := false;
REL.Text := AText;
REL.OnLinkClickEvent := AOnLinkClickEvent;
Result := This.FRichEditLinks.Add(REL);
This.InsertLinkText(AText, SelStart);
REL := nil;
end;
class function TRichEditExtended.AddLinkTextWithDefaultEvent(AText: string; SelStart: integer): integer;
begin
This.AddLinkText(AText, nil, SelStart);
end;
procedure TRichEditExtended.AfterConstruction;
begin
inherited;
FRichEdit := nil;
FRichEditLinks := TZ_RichEditLinks.Create;
end;
class procedure TRichEditExtended.ApplyRichEdit(ARichEdit: TRichEdit);
begin
This.FRichEdit := ARichEdit;
This.FPrevRichEditWndProc := This.FRichEdit.WindowProc;
This.FRichEdit.WindowProc := This.RichEditWndProc;
This.FRichEditLinks.Clear;
This.SetRichEditMasks;
end;
procedure TRichEditExtended.BeforeDestruction;
begin
if Assigned(FRichEdit) then
FRichEdit.WindowProc := FPrevRichEditWndProc;
FRichEdit := nil;
FRichEditLinks.Clear;
FRichEditLinks.Free;
inherited;
end;
procedure TRichEditExtended.InsertLinkText(const LinkText: string; SelStart: integer = -1);
var
Fmt: CHARFORMAT2;
begin
if SelStart = -1 then
begin
SelStart := FRichEdit.Lines.Text.Length - 1;
FRichEdit.Text := FRichEdit.Text + LinkText;
dec(SelStart,2 * (FRichEdit.Lines.Text.CountChar(#$D) - 1));
end
else
begin
FRichEdit.SelStart := SelStart;
FRichEdit.SelText := LinkText;
end;
FRichEdit.SelStart := SelStart;
FRichEdit.SelLength := Length(LinkText);
FillChar(Fmt, SizeOf(Fmt), 0);
Fmt.cbSize := SizeOf(Fmt);
Fmt.dwMask := CFM_LINK;
Fmt.dwEffects := CFE_LINK;
SendMessage(FRichEdit.Handle, EM_SETCHARFORMAT, SCF_SELECTION, LPARAM(#Fmt));
FRichEdit.SelStart := SelStart + Length(LinkText);
FRichEdit.SelLength := 0;
end;
procedure TRichEditExtended.RichEditWndProc(var Message: TMessage);
type
PENLINK = ^ENLINK;
var
tr: TEXTRANGE;
str: string;
p: PENLINK;
i: integer;
begin
FPrevRichEditWndProc(Message);
case Message.Msg of
CN_NOTIFY: begin
if TWMNotify(Message).NMHdr.code = EN_LINK then
begin
P := PENLINK(Message.LParam);
if p.msg = WM_LBUTTONDOWN then
begin
SetLength(str, p.chrg.cpMax - p.chrg.cpMin);
tr.chrg := p.chrg;
tr.lpstrText := PChar(str);
SendMessage(FRichEdit.Handle, EM_GETTEXTRANGE, 0, LPARAM(#tr));
for I := 0 to FRichEditLinks.Count - 1 do
if str.ToUpper.Equals(FRichEditLinks[I].Text.ToUpper) then
begin
if not Assigned(FRichEditLinks[I].OnLinkClickEvent) then
begin
if not FRichEditLinks[0].IsDefaultEvent then
raise Exception.Create('No default event is set.')
else
FRichEditLinks[0].OnLinkClickEvent(str)
end
else
FRichEditLinks[I].OnLinkClickEvent(str);
exit;
end;
end;
end;
end;
CM_RECREATEWND: begin
SetRichEditMasks;
end;
end;
end;
procedure TRichEditExtended.SetRichEditMasks;
var
Mask: DWORD;
begin
Mask := SendMessage(FRichEdit.Handle, EM_GETEVENTMASK, 0, 0);
SendMessage(FRichEdit.Handle, EM_SETEVENTMASK, 0, Mask or ENM_LINK);
SendMessage(FRichEdit.Handle, EM_AUTOURLDETECT, 1, 0);
end;
class function TRichEditExtended.This: TRichEditExtended;
begin
if not Assigned(TRichEditExtended.FInstance) then
TRichEditExtended.FInstance := TRichEditExtended.Create;
Result := TRichEditExtended.FInstance;
end;
{ TRichEditExList }
initialization
finalization
if Assigned(TRichEditExtended.FInstance) then
TRichEditExtended.FInstance.Free;
end.
To run this code you should create a new Application, add TRichEdit on the Form and type the following in a FormCreate method:
TRichEditExtended.ApplyRichEdit(ed1);
TRichEditExtended.AddDefaultLinkTextEvent(procedure (const T: String)begin showmessage(T); end);
TRichEditExtended.AddLinkTextWithDefaultEvent('Link');
ed1.Text := ed1.Text + '1231232 ';
TRichEditExtended.AddLinkTextWithDefaultEvent('Link2');
InsertLinkText() is replacing FRichEdit.Text with a completely new string when inserting a link with SelStart=-1, thus losing all previous text and formatting.
Use FRichEdit.GetTextLen() instead of FRichEdit.Lines.Text.Length to get the length of the existing text. And regardless of the input SelStart, always use the FRichEdit.SelStart|SelLength|SelText properties to add the new link into FRichEdit, preserving all existing text and formatting.
procedure TRichEditExtended.InsertLinkText(const LinkText: string; SelStart: integer = -1);
var
Fmt: CHARFORMAT2;
//Range: CHARRANGE;
begin
if SelStart = -1 then SelStart := FRichEdit.GetTextLen;
FRichEdit.SelStart := SelStart;
FRichEdit.SelLength := 0;
{ or:
Range.cpMin := SelStart;
Range.cpMax := SelStart;
SendMessage(FRichEdit.Handle, EM_EXSETSEL, 0, LPARAM(#Range));
}
FRichEdit.SelText := LinkText;
FRichEdit.SelStart := SelStart;
FRichEdit.SelLength := Length(LinkText);
{ or:
Range.cpMin := SelStart;
Range.cpMax := SelStart + Length(LinkText);
SendMessage(FRichEdit.Handle, EM_EXSETSEL, 0, LPARAM(#Range));
}
FillChar(Fmt, SizeOf(Fmt), 0);
Fmt.cbSize := SizeOf(Fmt);
Fmt.dwMask := CFM_LINK;
Fmt.dwEffects := CFE_LINK;
SendMessage(FRichEdit.Handle, EM_SETCHARFORMAT, SCF_SELECTION, LPARAM(#Fmt));
FRichEdit.SelStart := SelStart + Length(LinkText);
FRichEdit.SelLength := 0;
{ or:
Range.cpMin := SelStart + Length(LinkText);
Range.cpMax := Range.cpMax;
SendMessage(FRichEdit.Handle, EM_EXSETSEL, 0, LPARAM(#Range));
}
end;
As suggested by #KenWhite I will post my reseach upon the topic with edits about correct text highlighting due to issue of CRLF symbols in the text. As I found out line breaks are counted as one character for SelStart and GetTextLen, so you need to find the line where you are going to place a text and count all breaks before it and substract it from the desired SelStart position. For that purpose a function GetReilableSelStart is.
unit uRichEditExtended;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, RichEdit, WinApi.ShellApi, Vcl.Controls, Vcl.ComCtrls,
Generics.Collections, Vcl.Graphics;
type
TZ_RichEditClickEvent = reference to procedure(const ALinkText: string; LinkClickAccepted: boolean; var OutData: string);
TZ_RichEditLink = class
IsDefaultEvent: boolean;
Text: string;
OnLinkClickEvent: TZ_RichEditClickEvent;
end;
TZ_RichEditLinks = TList<TZ_RichEditLink>;
TZ_RichEditInsertOptions = set of (rioAppendBeforeCRLF);
TRichEditExtended = class
protected
class var FInstance: TRichEditExtended;
procedure SetRichEditMasks;
procedure RichEditWndProc(var Message: TMessage);
function GetReilableSelStart(SelStart: integer; InsertOptions: TZ_RichEditInsertOptions = [rioAppendBeforeCRLF]): integer;
private
FLastPressedLinkText: string;
FLinkClickAccepted: boolean;
FPrevRichEditWndProc: TWndMethod;
FRichEdit: TRichEdit;
FRichEditLinks: TZ_RichEditLinks;
function InsertText(const AText: string; const SelStart: integer = -1; InsertOptions: TZ_RichEditInsertOptions = [rioAppendBeforeCRLF]; Font: TFont = nil;
IsLink: boolean = false): integer;
procedure InsertLinkText(const LinkText: string; SelStart: integer = -1; InsertOptions: TZ_RichEditInsertOptions = [rioAppendBeforeCRLF]);
procedure AddText(const AText: string; AddCRLF: boolean; Font: TFont = nil);
procedure AddFmtText(const AText: string; FontSizeDelta: integer; FontStyle: TFontStyles; FontColor: TColor; AddCRLF: boolean);
procedure AfterConstruction; override;
procedure BeforeDestruction; override;
public
class function This: TRichEditExtended;
class procedure ApplyRichEdit(ARichEdit: TRichEdit);
class procedure AppendText(AText: string);
class procedure AppendTextLine(AText: string);
class procedure AppendFmtText(AText: string; FontSizeDelta: integer; FontStyle: TFontStyles; FontColor: TColor);
class procedure AppendFmtTextLine(AText: string; FontSizeDelta: integer; FontStyle: TFontStyles; FontColor: TColor);
class function AppendLinkText(AText: string; AOnLinkClickEvent: TZ_RichEditClickEvent; SelStart: integer = -1; InsertOptions: TZ_RichEditInsertOptions = [rioAppendBeforeCRLF]): integer;
class function AppendLinkTextWithDefaultEvent(AText: string; SelStart: integer = -1; InsertOptions: TZ_RichEditInsertOptions = [rioAppendBeforeCRLF]): integer;
class procedure AddDefaultLinkClickEvent(AOnLinkClickEvent: TZ_RichEditClickEvent);
class function LastLinkText: string;
class procedure PerformDefaultLinkClickEvent(const LinkText: string; CanOpen: boolean; var FullFilePath: string);
class procedure SetDefaultLinkClickReaction(ALinkClickAccepted: boolean);
end;
implementation
{ TRichEditExtended }
uses StrUtils, Math;
class procedure TRichEditExtended.AddDefaultLinkClickEvent(AOnLinkClickEvent: TZ_RichEditClickEvent);
var
REL: TZ_RichEditLink;
begin
if (This.FRichEditLinks.Count > 0) and This.FRichEditLinks[0].IsDefaultEvent then
This.FRichEditLinks[0].OnLinkClickEvent := AOnLinkClickEvent
else
begin
REL := TZ_RichEditLink.Create;
REL.IsDefaultEvent := true;
REL.Text := '';
REL.OnLinkClickEvent := AOnLinkClickEvent;
This.FRichEditLinks.Insert(0, REL);
REL := nil;
end;
end;
procedure TRichEditExtended.AddFmtText(const AText: string; FontSizeDelta: integer; FontStyle: TFontStyles; FontColor: TColor; AddCRLF: boolean);
var Font: TFont;
begin
try
Font := TFont.Create;
Font.Size := This.FRichEdit.Font.Size + FontSizeDelta;
Font.Style := FontStyle;
Font.Color := FontColor;
Font.Name := This.FRichEdit.Font.Name;
This.AddText(AText, AddCRLF, Font);
finally
FreeAndNil(Font);
end;
end;
class function TRichEditExtended.AppendLinkText(AText: string; AOnLinkClickEvent: TZ_RichEditClickEvent; SelStart: integer = -1;
InsertOptions: TZ_RichEditInsertOptions = [rioAppendBeforeCRLF]): integer;
var REL: TZ_RichEditLink;
begin
REL := TZ_RichEditLink.Create;
REL.IsDefaultEvent := false;
REL.Text := AText;
REL.OnLinkClickEvent := AOnLinkClickEvent;
Result := This.FRichEditLinks.Add(REL);
This.InsertLinkText(AText, SelStart, InsertOptions);
REL := nil;
end;
class function TRichEditExtended.AppendLinkTextWithDefaultEvent(AText: string; SelStart: integer = -1; InsertOptions: TZ_RichEditInsertOptions = [rioAppendBeforeCRLF]): integer;
begin
This.AppendLinkText(AText, nil, SelStart, InsertOptions);
end;
procedure TRichEditExtended.AfterConstruction;
begin
inherited;
FRichEdit := nil;
FRichEditLinks := TZ_RichEditLinks.Create;
FLinkClickAccepted := false;
end;
procedure TRichEditExtended.AddText(const AText: string; AddCRLF: boolean; Font: TFont);
begin
if AddCRLF then
InsertText(Format('%s'#13#10,[AText]), -1, [rioAppendBeforeCRLF], Font)
else
InsertText(AText, -1, [rioAppendBeforeCRLF], Font);
end;
class procedure TRichEditExtended.AppendFmtText(AText: string; FontSizeDelta: integer; FontStyle: TFontStyles; FontColor: TColor);
begin
This.AddFmtText(AText, FontSizeDelta, FontStyle, FontColor, false);
end;
class procedure TRichEditExtended.AppendFmtTextLine(AText: string; FontSizeDelta: integer; FontStyle: TFontStyles; FontColor: TColor);
begin
This.AddFmtText(AText, FontSizeDelta, FontStyle, FontColor, true);
end;
class procedure TRichEditExtended.AppendText(AText: string);
begin
This.AddText(AText, false);
end;
class procedure TRichEditExtended.AppendTextLine(AText: string);
begin
This.AddText(AText, true);
end;
class procedure TRichEditExtended.ApplyRichEdit(ARichEdit: TRichEdit);
begin
This.FRichEdit := ARichEdit;
This.FPrevRichEditWndProc := This.FRichEdit.WindowProc;
This.FRichEdit.WindowProc := This.RichEditWndProc;
This.FRichEditLinks.Clear;
This.SetRichEditMasks;
end;
procedure TRichEditExtended.BeforeDestruction;
begin
if Assigned(FRichEdit) then
FRichEdit.WindowProc := FPrevRichEditWndProc;
FRichEdit := nil;
FRichEditLinks.Clear;
FRichEditLinks.Free;
inherited;
end;
function TRichEditExtended.GetReilableSelStart(SelStart: integer; InsertOptions: TZ_RichEditInsertOptions = [rioAppendBeforeCRLF]): integer;
var
LineNo, LinesCount: integer;
begin
LinesCount := FRichEdit.Lines.Count;
if SelStart = -1 then
begin
Result := Max(FRichEdit.GetTextLen - Max((LinesCount - ord(not String(FRichEdit.Text).EndsWith(#$D#$A))),0), 0);
end
else
begin
LineNo := FRichEdit.Perform(EM_LINEFROMCHAR, SelStart, 0);
Result := Max(SelStart - (Max(LineNo - ord(rioAppendBeforeCRLF in InsertOptions) * ord(FRichEdit.Lines[LineNo].EndsWith(#$D#$A)),0)), 0);
end;
end;
procedure TRichEditExtended.InsertLinkText(const LinkText: string; SelStart: integer = -1; InsertOptions: TZ_RichEditInsertOptions = [rioAppendBeforeCRLF]);
var
Fmt: CHARFORMAT2;
begin
SelStart := InsertText(LinkText, SelStart, InsertOptions, nil, true);
FillChar(Fmt, SizeOf(Fmt), 0);
Fmt.cbSize := SizeOf(Fmt);
Fmt.dwMask := CFM_LINK;
Fmt.dwEffects := CFE_LINK;
SendMessage(FRichEdit.Handle, EM_SETCHARFORMAT, SCF_SELECTION, LPARAM(#Fmt));
FRichEdit.SelStart := FRichEdit.GetTextLen;
FRichEdit.SelLength := 0;
end;
function TRichEditExtended.InsertText(const AText: string; const SelStart: integer = -1; InsertOptions: TZ_RichEditInsertOptions = [rioAppendBeforeCRLF];
Font: TFont = nil; IsLink: boolean = false): integer;
var
Fmt: CHARFORMAT2;
begin
Result := GetReilableSelStart(SelStart, InsertOptions);
FRichEdit.SelStart := Result;
FRichEdit.SelText := Format('%s%s',[AText, DupeString(#32,ord(IsLink))]);
FRichEdit.SelStart := Result;
FRichEdit.SelLength := Length(AText);
FRichEdit.SelAttributes.Color := FRichEdit.DefAttributes.Color;
FRichEdit.SelAttributes.Name := FRichEdit.DefAttributes.Name;
FRichEdit.SelAttributes.Size := FRichEdit.DefAttributes.Size;
FRichEdit.SelAttributes.Style := FRichEdit.DefAttributes.Style;
if Assigned(Font) then
begin
FRichEdit.SelAttributes.Color := Font.Color;
FRichEdit.SelAttributes.Name := Font.Name;
FRichEdit.SelAttributes.Size := Font.Size;
FRichEdit.SelAttributes.Style := Font.Style;
FRichEdit.SelStart := FRichEdit.GetTextLen;
FRichEdit.SelLength := 0;
end;
end;
class function TRichEditExtended.LastLinkText: string;
begin
Result := This.FLastPressedLinkText;
end;
class procedure TRichEditExtended.PerformDefaultLinkClickEvent(const LinkText: string; CanOpen: boolean; var FullFilePath: string);
begin
if (This.FRichEditLinks.Count = 0) or not This.FRichEditLinks[0].IsDefaultEvent then
raise Exception.Create('A default action should be set.');
This.FRichEditLinks[0].OnLinkClickEvent(LinkText, CanOpen, FullFilePath);
end;
procedure TRichEditExtended.RichEditWndProc(var Message: TMessage);
type
PENLINK = ^ENLINK;
var
tr: TEXTRANGE;
p: PENLINK;
i: integer;
OutDat: string;
function GetLinkText: string;
begin
SetLength(Result, p.chrg.cpMax - p.chrg.cpMin);
tr.chrg := p.chrg;
tr.lpstrText := PChar(Result);
SendMessage(FRichEdit.Handle, EM_GETTEXTRANGE, 0, LPARAM(#tr));
end;
begin
FPrevRichEditWndProc(Message);
case Message.Msg of
CN_NOTIFY: begin
if TWMNotify(Message).NMHdr.code = EN_LINK then
begin
P := PENLINK(Message.LParam);
case p.msg of
WM_LBUTTONDOWN:
begin
FLastPressedLinkText := GetLinkText;
for I := 0 to FRichEditLinks.Count - 1 do
if FLastPressedLinkText.ToUpper.Equals(FRichEditLinks[I].Text.ToUpper) then
begin
if not Assigned(FRichEditLinks[I].OnLinkClickEvent) then
begin
if not FRichEditLinks[0].IsDefaultEvent then
raise Exception.Create('A default action should be set.')
else
FRichEditLinks[0].OnLinkClickEvent(FLastPressedLinkText, FLinkClickAccepted, OutData)
end
else
FRichEditLinks[I].OnLinkClickEvent(FLastPressedLinkText, FLinkClickAccepted, OutData);
exit;
end;
end;
WM_RBUTTONDOWN:
begin
FLastPressedLinkText := GetLinkText;
if Assigned(FRichEdit.PopupMenu) then
begin
FRichEdit.PopupMenu.Popup(Mouse.CursorPos.X, Mouse.CursorPos.Y);
end;
end;
end;
end;
end;
CM_RECREATEWND: begin
SetRichEditMasks;
end;
end;
end;
class procedure TRichEditExtended.SetDefaultLinkClickReaction(ALinkClickAccepted: boolean);
begin
This.FLinkClickAccepted := ALinkClickAccepted;
end;
procedure TRichEditExtended.SetRichEditMasks;
var
Mask: DWORD;
begin
Mask := SendMessage(FRichEdit.Handle, EM_GETEVENTMASK, 0, 0);
SendMessage(FRichEdit.Handle, EM_SETEVENTMASK, 0, Mask or ENM_LINK);
SendMessage(FRichEdit.Handle, EM_AUTOURLDETECT, 1, 0);
end;
class function TRichEditExtended.This: TRichEditExtended;
begin
if not Assigned(TRichEditExtended.FInstance) then
TRichEditExtended.FInstance := TRichEditExtended.Create;
Result := TRichEditExtended.FInstance;
end;
{ TRichEditExList }
initialization
finalization
if Assigned(TRichEditExtended.FInstance) then
TRichEditExtended.FInstance.Free;
end.
Any suggestions are appreciated.
How to use:
TRichEditExtended.ApplyRichEdit(edMessages);
TRichEditExtended.SetDefaultLinkClickReaction(true); //Link click accepted by default or not
TRichEditExtended.AddDefaultLinkClickEvent(
procedure (const LinkText: string; LinkClickAccepted: boolean; var OutData: string)
begin
if LinkClickAccepted then
DoSomething;
DoAnythingTo(OutData); // if you call somewhere after TRichEditExtended.PerformDefaultLinkClickEvent then you get the OutData there
end
);
I have a problem to sync the GUI of server. I'm using Delphi 2007 and Indy 10.1.5.
This is my case:
Server send to all connected client a hearbit (this is the message send from server --> "REQ|HeartBit")
Client response to server with "I'm alive" (this is the message send from client --> "ANS|USERNAME|I'm alive"
In onExecute procedure of the TIdTCPServer I want to see the answer of the client in a TlistView of server, so I have done like in this Link
When I start my application with two process client connected (that are runs in my PC) and send a hearbit message to clients, I see in the listview of server this situation:
REQ|HeartBit (send to Client1)
REQ|HeartBit (send to Client2)
ANS|Client2|I'm Alive
ANS|Client2|I'm Alive
two response message from Client2 (!?!?)
Where is my mistake?
Sorry for my poor English.
Thanks
The code of server side is this:
type
TLog = class(TIdSync)
private
FMsg : string;
protected
procedure DoSynchronize; override;
public
constructor Create(const AMsg: String);
//class procedure AddMsg(const AMsg: String);
end;
// procedure that add items in listview of server
procedure WriteListLog(aTimeStamp : TDateTime;strMessaggio: String);
implementation
procedure TLog.DoSynchronize;
begin
WriteListLog(Now,FMsg);
end
procedure TForm1.tsExecute(AContext: TIdContext);
var
Ctx: TMyContext;
tmp : String;
sync : Tlog;
begin
Ctx := TMyContext(AContext);
tmp := Ctx.Connection.IOHandler.ReadLn;
sync := Tlog.Create(tmp);
try
sync.FMsg := tmp;
sync.Synchronize;
finally
Sync.Free;
end;
end;
If I add lockList in OnExecute I have this correct sequence of message
REQ|HeartBit (send to Client1)
REQ|HeartBit (send to Client2)
ANS|Client1|I'm Alive
ANS|Client2|I'm Alive
Is it Correct?
procedure TForm1.tsExecute(AContext: TIdContext);
var
Ctx: TMyContext;
tmp : String;
sync : Tlog;
begin
Ctx := TMyContext(AContext);
tmp := Ctx.Connection.IOHandler.ReadLn;
Ctx.FContextList.LockList;
try
sync := Tlog.Create(tmp);
try
sync.FMsg := tmp;
sync.Synchronize;
finally
Sync.Free;
end;
finally
Ctx.FContextList.UnlockList;
end;
end;
Update
In my project, the listView and WriteListLog() is in the unit FLogMsg, not in the same unit of the IdTCSPServer.
This is how is defined the tlistview in dfm
object ListLog: TListView
Left = 0
Top = 0
Width = 737
Height = 189
Align = alClient
Columns = <
item
Caption = 'Data'
Width = 140
end
item
Caption = 'Da'
end
item
Caption = 'A'
end
item
Caption = 'Tipo'
end
item
Caption = 'Messaggio'
Width = 900
end>
ColumnClick = False
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
FlatScrollBars = True
OwnerData = True
ReadOnly = True
ParentFont = False
TabOrder = 0
ViewStyle = vsReport
OnData = ListLogData
end
Code of unit FlogMsg:
type
TTipoMessaggio = (tmSend,tmReceived,tmSystem);
TDataItem = class
private
FDITimeStamp: TDateTime;
FDIRecipient: String;
FDISender: String;
FDITipo: TTipoMessaggio;
FDIMessaggio: String;
public
property DITimeStamp: TDateTime read FDITimeStamp;
property DISender : String read FDISender;
property DIRecipient : String read FDIRecipient;
property DITipo : TTipoMessaggio read FDITipo;
property DIMessaggio: String read FDIMessaggio;
end;
TfrmLog = class(TForm)
ListLog: TListView;
Panel1: TPanel;
procedure FormCreate(Sender: TObject);
procedure ListLogData(Sender: TObject; Item: TListItem);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
FItems: TObjectList;
FActiveItems: TList;
FFilterLogStation: String;
procedure SetFilterLogStation(const Value: String);
public
{ Public declarations }
property FilterLogStation : String read FFilterLogStation write SetFilterLogStation;
end;
procedure WriteListLog(aTimeStamp : TDateTime;
aSender,aRecipient: String;aTipo:TTipoMessaggio;strMessaggio: String);
var
frmLog: TfrmLog;
implementation
{$R *.dfm}
procedure WriteListLog(aTimeStamp : TDateTime;
aSender,aRecipient: String;aTipo:TTipoMessaggio;strMessaggio: String);
var
DataItem: TDataItem;
begin
DataItem := TDataItem.Create;
try
DataItem.FDITimeStamp := aTimeStamp;
DataItem.FDISender := aSender;
DataItem.FDIRecipient := aRecipient;
DataItem.FDITipo := aTipo;
DataItem.FDIMessaggio := strMessaggio;
frmLog.FItems.Add(DataItem);
if (frmLog.FilterLogStation = '') or (frmLog.FilterLogStation = aRecipient) or
(frmLog.FilterLogStation = aSender) then
begin
frmLog.FActiveItems.Add(DataItem);
frmLog.ListLog.AddItem('',DataItem);
end;
except
DataItem.Free;
raise;
end;
frmLog.ListLog.Repaint;
end;
procedure TfrmLog.FormCreate(Sender: TObject);
begin
FFilterLogStation := '';
FItems := TObjectList.Create;
FActiveItems := TList.Create;
end;
procedure TfrmLog.FormDestroy(Sender: TObject);
begin
FActiveItems.clear;
FreeAndNil(FActiveItems);
FreeAndNil(FItems);
end;
procedure TfrmLog.ListLogData(Sender: TObject; Item: TListItem);
var
DataItem: TDataItem;
begin
DataItem := FActiveItems[Item.Index];
Item.Caption := FormatDateTime('dd/mm/yyy hh.nn.ss', DataItem.DITimeStamp);
Item.SubItems.Add(DataItem.DISender);
Item.SubItems.Add(DataItem.DIRecipient);
// Tipo Messaggio
case DataItem.DITipo of
tmSend: Item.SubItems.Add('Inviato');
tmReceived: Item.SubItems.Add('Ricevuto');
tmSystem: Item.SubItems.Add('Sistema');
end;
Item.SubItems.Add(DataItem.DIMessaggio);
Item.MakeVisible(true);
end;
procedure TfrmLog.SetFilterLogStation(const Value: String);
var
I: Integer;
begin
FFilterLogStation := Value;
ListLog.Items.BeginUpdate;
try
ListLog.Clear;
FActiveItems.Clear;
for I := 0 to FItems.Count - 1 do
if (CompareText(UpperCase(FFilterLogStation),UpperCase(TDataItem(FItems[I]).DISender)) = 0) or
(CompareText(UpperCase(FFilterLogStation),UpperCase(TDataItem(FItems[I]).DIRecipient)) = 0)
or (FFilterLogStation = '') then
begin
FActiveItems.Add(FItems[I]);
end;
ListLog.Items.Count := FActiveItems.Count;
finally
ListLog.Items.EndUpdate;
ListLog.Repaint;
end;
end;
procedure TfrmLog.FormDestroy(Sender: TObject);
begin
FActiveItems.clear;
FreeAndNil(FActiveItems);
FreeAndNil(FItems);
end;
UPDATE 2 - Try with TMemo
this is the result:
(First SendBroadCast HeartBit)
ANS|CARICO1|I'm Alive
ANS|CARICO2|I'm Alive
(Second SendBroadCast HeartBit)
ANS|CARICO1|I'm Alive
ANS|CARICO2|I'm Alive
(Third SendBroadCast HeartBit)
ANS|CARICO1|I'm Alive
ANS|CARICO1|I'm Alive
I add a TStringList variable in my TMyContext class.
In debug session, for each Context, if I inspect the queue of message that is saved on my TStringList variable the messages are correct!
So, I think that the problem is in Synchronize...
type
TTipoStazione = (tsNone,tsCarico,tsScarico);
TLog = class(TIdSync)
private
FMsg : string;
FFrom : String;
protected
procedure DoSynchronize; override;
public
end;
TMyContext = class(TIdContext)
public
IP: String;
UserName: String;
Stazione : Integer;
tipStaz : TTipoStazione;
Con: TDateTime;
isValid : Boolean;
ls : TStringList;
// compname:string;
procedure ProcessMsg;
end;
TForm1 = class(TForm)
ts: TIdTCPServer;
Memo1: TMemo;
btconnect: TButton;
edport: TEdit;
Button2: TButton;
procedure btconnectClick(Sender: TObject);
procedure tsConnect(AContext: TIdContext);
procedure tsExecute(AContext: TIdContext);
procedure tsDisconnect(AContext: TIdContext);
constructor Create(AOwner: TComponent);override;
procedure FormDestroy(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
procedure SendMsgBroadcast(aMsg : String);
public
{ Public declarations }
procedure MyWriteListLog(strMessaggio : String);
end;
implementation
constructor TLog.Create(const aFrom: String; const AMsg: String);
begin
inherited Create;
FMsg := AMsg;
FFrom := aFrom;
end;
procedure TLog.DoSynchronize;
begin
Form1.MyWriteListLog(FMsg);
end;
procedure TMyContext.ProcessMsg;
var
str,TypeMsg:string;
myTLog: TLog;
begin
if Connection.IOHandler.InputBufferIsEmpty then
exit;
str:=self.Connection.IOHandler.ReadLn;
ls.Add('1='+str);
myTLog := Tlog.Create;
try
myTLog.FMsg := str;
myTLog.FFrom := UserName;
myTLog.Synchronize;
ls.Add('2='+str);
finally
myTLog.Free;
end;
end;
constructor TForm1.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
ts.ContextClass := TMyContext;
DMVern := TDMVern.Create(nil);
end;
procedure TForm1.btconnectClick(Sender: TObject);
begin
ts.DefaultPort:=strtoint(edport.Text);
ts.Active:=true;
MyWriteListLog('Listening');
end;
procedure TForm1.tsConnect(AContext: TIdContext);
var
strErr : String;
I: Integer;
tmpNrStaz: String;
tmpMsg : String;
begin
strErr := '';
ts.Contexts.LockList;
try
with TMyContext(AContext) do
begin
ls := TStringList.Create;
isValid := false;
Con := Now;
if (Connection.Socket <> nil) then
IP :=Connection.Socket.Binding.PeerIP;
tmpMsg := Connection.IOHandler.ReadLn;
try
if not (Pos('START|',tmpMsg) > 0) then
begin
strErr := 'Comando non valido';
exit;
end;
UserName := Copy(tmpMsg,Length('START|')+1,Length(tmpMsg));
if Trim(UserName) = '' then
begin
strErr := 'How Are You?';
exit;
end;
tipStaz := tsNone;
if UpperCase(Copy(UserName,1,6)) = 'CARICO' then
tipStaz := tsCarico
else if UpperCase(Copy(UserName,1,7)) = 'SCARICO' then
tipStaz := tsCarico;
if tipStaz = tsNone then
begin
strErr := 'Tipo Stazione non valida.';
exit;
end;
tmpNrStaz := '';
for I := Length(UserName) downto 1 do
begin
if (UserName[i] in ['0'..'9']) then
tmpNrStaz:= UserName[i] + tmpNrStaz
else if tmpNrStaz <> '' then
break;
end;
if tmpNrStaz = '' then
begin
strErr := 'Numero Stazione non specificato.';
exit;
end;
Stazione := StrToInt(tmpNrStaz);
isValid := true;
tmpMsg := 'HELLO|' + UserName;
Connection.IOHandler.WriteLn(tmpMsg);
finally
if strErr <> '' then
begin
Connection.IOHandler.WriteLn(strErr);
Connection.Disconnect;
end;
end;
end;
finally
ts.Contexts.UnlockList;
end;
end;
procedure TForm1.tsExecute(AContext: TIdContext);
var
Ctx: TMyContext;
tmp : String;
begin
Ctx := TMyContext(AContext);
Ctx.ProcessMsg;
end;
procedure TForm1.tsDisconnect(AContext: TIdContext);
begin
TMyContext(AContext).ProcessMsg;
end;
procedure TForm1.MyWriteListLog(strMessaggio: String);
begin
Memo1.Lines.Add(strMessaggio);
end;
procedure TForm1.Button2Click(Sender: TObject);
var
aMsg: String;
begin
aMsg := 'REQ|HeartBit';
SendMsgBroadcast(aMsg);
end;
procedure TForm1.SendMsgBroadcast(aMsg: String);
var
List: TList;
I: Integer;
Context: TMyContext;
begin
List := ts.Contexts.LockList;
try
for I := 0 to List.Count-1 do
begin
Context := TMyContext(List[I]);
if Context.isValid then
begin
try
Context.Connection.IOHandler.WriteLn(aMsg);
except
end;
end;
end;
finally
ts.Contexts.UnlockList;
end;
end;
You are using a virtual ListView, but I see two mistakes you are making with it:
You are calling AddItem() and Clear() on it. Do not do that. The whole point of a virtual ListView is to not put any real data in it at all. After you add or remove objects in your FActiveItems list, all you have to do is update the TListView.Items.Count property to reflect the new item count. It will invalidate itself by default to trigger a repaint (but if you want to trigger a repaint manually, use Invalidate() instead of Repaint(), and call it only when you have done something to modify FActiveItems).
Your OnData handler is calling TListItem.MakeVisible(). That call does not belong in that event, it belongs in WriteListLog() instead. OnData triggered whenever the ListView needs data for an item for any reason, including during drawing. Don't perform any UI management operations in a data management event.
Try this instead:
procedure WriteListLog(aTimeStamp : TDateTime;
aSender,aRecipient: String;aTipo:TTipoMessaggio;strMessaggio: String);
var
DataItem: TDataItem;
Index, ActiveIndex: Integer;
begin
DataItem := TDataItem.Create;
try
DataItem.FDITimeStamp := aTimeStamp;
DataItem.FDISender := aSender;
DataItem.FDIRecipient := aRecipient;
DataItem.FDITipo := aTipo;
DataItem.FDIMessaggio := strMessaggio;
Index := frmLog.FItems.Add(DataItem);
try
if (frmLog.FilterLogStation = '') or
AnsiSameText(frmLog.FilterLogStation, aRecipient) or
AnsiSameText(frmLog.FilterLogStation, aSender) then
begin
ActiveIndex := frmLog.FActiveItems.Add(DataItem);
frmLog.ListLog.Items.Count := frmLog.FActiveItems.Count;
frmLog.Items[ActiveIndex].MakeVisible(true);
end;
except
frmLog.FItems.Delete(Index);
DataItem := nil;
raise;
end;
except
DataItem.Free;
raise;
end;
end;
procedure TfrmLog.FormCreate(Sender: TObject);
begin
FFilterLogStation := '';
FItems := TObjectList.Create(True);
FActiveItems := TList.Create;
end;
procedure TfrmLog.FormDestroy(Sender: TObject);
begin
FItems.Free;
FActiveItems.Free;
end;
procedure TfrmLog.ListLogData(Sender: TObject; Item: TListItem);
var
DataItem: TDataItem;
begin
DataItem := TDataItem(FActiveItems[Item.Index]);
Item.Caption := FormatDateTime('dd/mm/yyy hh.nn.ss', DataItem.DITimeStamp);
Item.SubItems.Add(DataItem.DISender);
Item.SubItems.Add(DataItem.DIRecipient);
// Tipo Messaggio
case DataItem.DITipo of
tmSend: Item.SubItems.Add('Inviato');
tmReceived: Item.SubItems.Add('Ricevuto');
tmSystem: Item.SubItems.Add('Sistema');
else
Item.SubItems.add('');
end;
Item.SubItems.Add(DataItem.DIMessaggio);
end;
procedure TfrmLog.SetFilterLogStation(const Value: String);
var
I: Integer;
DataItem: TDataItem;
begin
if FFilterLogStation = Value then Exit;
ListLog.Items.Count := 0;
FActiveItems.Clear;
FFilterLogStation := Value;
try
for I := 0 to FItems.Count - 1 do
begin
DataItem := TDataItem(FItems[I]);
if (FFilterLogStation = '') or
AnsiSameText(FFilterLogStation, DataItem.DISender) or
AnsiSameText(FFilterLogStation), DataItem.DIRecipient) then
begin
FActiveItems.Add(DataItem);
end;
end;
finally
ListLog.Items.Count := FActiveItems.Count;
end;
end;
I have a program created in Delphi 7 that uses ftp downloading.
How can i insert into that program to check for a server status?
For example if server is online to produce a green image, if server is offline o produce a red image. Here is the code.
unit Download;
interface
uses
Classes, Wininet, Windows, SysUtils, Dialogs, Forms;
type
GFilesThread = class(TThread)
private
LTemp : Longword;
STemp : string;
FilesToGet : TStringList;
FilesSize : Longword;
CBackup : integer;
CRevision : integer;
CForceCheck : boolean;
CSwitch : integer;
UUrl : string;
USelfParam : string;
Dir: string;
FSource: TStream;
protected
procedure Execute; override;
procedure UpdateFileProgress;
procedure SetFileProgressMax;
procedure UpdateStatusLabel;
procedure UpdateFileDecompStat;
procedure UpdateFilesProgress;
procedure CheckFiles(FList : TStringList);
procedure BZProgress(Sender: TObject);
procedure LockFMain;
procedure UNLockFMain;
procedure GetFiles;
procedure SelfUpdate(SelfVal : string);
procedure UpdateRevision;
procedure ModHosts(Lines : TStringList);
procedure DoUncompressStream(ASource, ADest: TStream);
procedure DoUncompress(const ASource, ADest: TFileName);
function HTTPGetFile(const fileURL, FileName: string; sh_progress: boolean): boolean;
public
property CreateBackup : integer write CBackup;
property UpdatesUrl : string write UUrl;
property LocalRevision : integer write CRevision;
property ForceCheck : boolean write CForceCheck;
end;
implementation
uses Launcher, CheckFiles, BZip2, md5, FileList;
// -------- by 667
procedure GFilesThread.UpdateStatusLabel;
begin
FMain.Label3.Caption:=STemp;
end;
procedure GFilesThread.SetFileProgressMax;
begin
if(CSwitch=0) then
FMain.Gauge1.MaxValue:=LTemp;
if(CSwitch=1) then
FMain.Gauge2.MaxValue:=LTemp;
end;
procedure GFilesThread.UpdateFileProgress;
begin
FMain.Gauge1.Progress:=LTemp;
end;
procedure GFilesThread.UpdateFilesProgress;
begin
FMain.Gauge2.Progress:=LTemp;
end;
procedure GFilesThread.UpdateRevision;
begin
FMain.UpdateRevision(IntToStr(CRevision));
end;
procedure GFilesThread.UpdateFileDecompStat;
begin
FMain.Gauge1.Progress:=LTemp;
end;
procedure GFilesThread.BZProgress(Sender: TObject);
begin
LTemp:=FSource.Position;
Synchronize(UpdateFileDecompStat);
end;
procedure GFilesThread.LockFMain;
begin
Fmain.ImgBtn1.Visible:=False;
Fmain.ImgBtn2.Visible:=False;
Fmain.ImgBtn5.Enabled:=False;
end;
procedure GFilesThread.UNLockFMain;
begin
Fmain.ImgBtn1.Visible:=True;
Fmain.ImgBtn2.Visible:=True;
Fmain.ImgBtn5.Enabled:=True;
end;
// --------- by 667
function GFilesThread.HTTPGetFile(const fileURL, FileName: string; sh_progress: boolean): boolean;
const
BufferSize = 1024;
var
hSession, hURL: HInternet;
Buffer: array[1..BufferSize] of Byte;
BufferLen: Longword;
f: file;
sAppName: string;
begin
Result := False;
sAppName := 'L2ClientUpdater';
LTemp:=0;
hSession := InternetOpen(PChar(sAppName),
INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);
try
hURL := InternetOpenURL(hSession, PChar(fileURL), nil, 0, 0, 0);
if (hURL <> nil) then begin
try
DeleteUrlCacheEntry(PChar(fileURL));
AssignFile(f, FileName);
Rewrite(f,1);
repeat
InternetReadFile(hURL, #Buffer, SizeOf(Buffer), BufferLen);
BlockWrite(f, Buffer, BufferLen);
if (sh_progress) then
begin
LTemp:=LTemp+BufferLen;
Synchronize(UpdateFileProgress);
end;
until
BufferLen = 0;
CloseFile(f);
Result := True;
finally
InternetCloseHandle(hURL);
end;
end;
finally
InternetCloseHandle(hSession);
end;
LTemp:=0;
Synchronize(UpdateFileProgress);
end;
procedure GFilesThread.DoUncompress(const ASource, ADest: TFileName);
var
Source, Dest: TStream;
begin
Source := TFileStream.Create(ASource, fmOpenRead + fmShareDenyWrite);
try
Dest := TFileStream.Create(ADest, fmCreate);
try
DoUncompressStream(Source, Dest);
finally
Dest.Free;
end;
finally
Source.Free;
DeleteFile(ASource);
end;
end;
procedure GFilesThread.DoUncompressStream(ASource, ADest: TStream);
const
BufferSize = 65536;
var
Count: Integer;
Decomp: TBZDecompressionStream;
Buffer: array[0..BufferSize - 1] of Byte;
begin
FSource := ASource;
LTemp:=FSource.Size;
CSwitch:=0;
Synchronize(SetFileProgressMax);
Decomp := TBZDecompressionStream.Create(ASource);
try
Decomp.OnProgress := BZProgress;
while True do
begin
Count := Decomp.Read(Buffer, BufferSize);
if Count <> 0 then ADest.WriteBuffer(Buffer, Count) else Break;
end;
finally
Decomp.Free;
FSource := nil;
LTemp:=0;
Synchronize(UpdateFileDecompStat);
end;
end;
procedure GFilesThread.CheckFiles(FList : TStringList);
var
i: integer;
FParam: TStringList;
FNameLocal: string;
begin
if(FList.Count>0) and (FList[0]<>'FAIL') and (not terminated) then
begin
STemp:='Checking files';
Synchronize(UpdateStatusLabel);
CSwitch:=1;
LTemp:=FList.Count-1;
Synchronize(SetFileProgressMax);
FParam:=TStringList.Create;
for i:=0 to FList.Count-1 do
begin
LTemp:=i;
Synchronize(UpdateFilesProgress);
FParam:=Tokenize(FList[i],'|');
FNameLocal:=Dir+FParam[2];
STemp:='Checking '+FParam[2];
Synchronize(UpdateStatusLabel);
if (not FileExists(FNameLocal)) then
begin
FilesToGet.Add(FList[i]);
FilesSize:=FilesSize+StrToInt(FParam[0]);
end
else
begin
if (MD5Print(MD5File(FNameLocal))<>FParam[1]) then
begin
FilesToGet.Add(FList[i]);
FilesSize:=FilesSize+StrToInt(FParam[0]);
end;
end;
end;
FParam.Free;
LTemp:=0;
Synchronize(UpdateFilesProgress);
STemp:='';
Synchronize(UpdateStatusLabel);
end;
end;
procedure GFilesThread.SelfUpdate(SelfVal : string);
var
FParam: TStringList;
FNameLocal: string;
F:boolean;
begin
if(SelfVal<>'') then
begin
FParam:=TStringList.Create;
FParam:=Tokenize(SelfVal,'|');
FNameLocal:=Dir+FParam[2];
if (MD5Print(MD5File(FNameLocal))<>FParam[1]) then
begin
FilesSize:=FilesSize+StrToInt(FParam[0]);
F:=HTTPGetFile(UUrl+FParam[2]+'.bz2',FNameLocal+'.bz2',True);
if(F) then begin
try
DoUncompress(FNameLocal+'.bz2',Dir+FParam[2]+'.New');
GenKillerBat(FParam[2]);
RunApp(Dir+'Update.bat');
except
STemp:='Update Failed';
DeleteFile(FNameLocal);
end;
end;
end;
FParam.Free;
end;
end;
procedure GFilesThread.ModHosts(Lines : TStringList);
var
Hosts : textfile;
H, HostsStrings, HostLineParam : TStringList;
HostsPath, temp : string;
i, z, funnyFlag : integer;
WindirP : PChar;
Res : cardinal;
begin
WinDirP := StrAlloc(MAX_PATH);
Res := GetWindowsDirectory(WinDirP, MAX_PATH);
if Res > 0 then
begin
if(FileExists(StrPas(WinDirP)+'\system32\drivers\etc\hosts.msn')) then
HostsPath := StrPas(WinDirP)+'\system32\drivers\etc\hosts.msn'
else
HostsPath := StrPas(WinDirP)+'\system32\drivers\etc\hosts';
AssignFile(Hosts,HostsPath);
Reset(Hosts);
HostsStrings:= TStringList.Create;
H:= TStringList.Create;
H.Add('#-------- Added by L2Updater --------');
while (not Eof(Hosts)) do
begin
ReadLn(Hosts, temp);
HostsStrings.Add(Trim(temp));
end ;
Reset(Hosts);
for i:=0 to Lines.Count-1 do
begin
funnyFlag:=0;
HostLineParam:=Tokenize(Lines[i],'|');
for z:=0 to HostsStrings.Count-1 do
begin
if (StrSearch(1,HostsStrings[z],HostLineParam[0])>0) and (HostsStrings[z][1]<>'#') then
begin
if (StrSearch(1,HostsStrings[z],HostLineParam[1]+#9)= 0) and (StrSearch(1,HostsStrings[z],HostLineParam[1]+' ')= 0 ) then
begin
HostsStrings[z]:= '#'+HostsStrings[z];
funnyFlag:=1;
end
else funnyFlag:=2;
end;
end;
if (funnyFlag=1) or (funnyFlag=0) then
H.Add(HostLineParam[1]+#9+HostLineParam[0]);
end;
H.Add('#-----------------');
if H.Count>2 then
begin
Rewrite(Hosts);
STemp:='Applying changes to Hosts';
Synchronize(UpdateStatusLabel);
for i:=0 to HostsStrings.Count-1 do
begin
WriteLn(Hosts,HostsStrings[i]);
end;
for i:=0 to H.Count-1 do
begin
WriteLn(Hosts,H[i]);
end;
STemp:='Hosts file chamged';
Synchronize(UpdateStatusLabel);
end;
H.Free; HostsStrings.Free; HostLineParam.Free;
CloseFile(Hosts);
end;
end;
procedure GFilesThread.GetFiles;
var
FParam : TStringList;
i : integer;
F, error : boolean;
LocalFile, BakFile: string;
begin
error := False;
if (FilesToGet.Count>0) then
begin
FParam:=TStringList.Create;
LTemp:=FilesToGet.Count-1;
CSwitch:=1;
Synchronize(SetFileProgressMax);
i:=0;
while (i < FilesToGet.Count) and (not terminated) do
begin
FParam:=Tokenize(FilesToGet[i],'|');
LocalFile:= Dir+FParam[2];
STemp:='Downloading '+ FParam[2];
Synchronize(UpdateStatusLabel);
CSwitch:=0;
LTemp:= StrToInt(FParam[0]);
Synchronize(SetFileProgressMax);
if (not DirectoryExists(ExtractFilePath(LocalFile))) then
ForceDirectories(ExtractFilePath(LocalFile));
F:=HTTPGetFile(UUrl+ReplaceStr(FParam[2],'\','/')+'.bz2',LocalFile+'.bz2',True);
if (F) then
begin
try
if (CBackup=1) then
begin
BakFile:=Dir+'backup\'+FParam[2];
if (not DirectoryExists(ExtractFilePath(BakFile))) then
ForceDirectories(ExtractFilePath(BakFile));
CopyFile(PChar(LocalFile),PChar(BakFile),false);
end;
STemp:='Extracting '+ FParam[2];
Synchronize(UpdateStatusLabel);
DoUncompress(LocalFile+'.bz2',Dir+FParam[2]);
except
STemp:='Update Failed';
error := True;
end;
end
else
begin
STemp:='Update Failed';
error := True;
Break;
end;
inc(i);
LTemp:=i;
CSwitch:=1;
Synchronize(UpdateFilesProgress);
end;
LTemp:=0;
Synchronize(UpdateFilesProgress);
FParam.Free;
if (not error) then
STemp:='All files have been updated.';
end
else STemp:='';
end;
procedure GFilesThread.Execute;
var
List: TListFile;
CFiles, NFiles, HostsLines : TStringList;
TRev, IsModHosts : integer;
F : boolean;
begin
Dir:=GetCurrentDir+'\';
FilesSize:=0;
Synchronize(LockFMain);
STemp:='Downloading updates list';
Synchronize(UpdateStatusLabel);
if(UUrl[length(UUrl)]<>'/') then UUrl:=UUrl+'/';
F:=HTTPGetFile(UUrl+'files.lst.bz2',Dir+'files.lst.bz2', True);
if (F) then
begin
STemp:='';
Synchronize(UpdateStatusLabel);
try
DoUncompress(Dir+'files.lst.bz2',Dir+'files.lst');
except
STemp:='Update Failed';
Synchronize(UpdateStatusLabel);
DeleteFile(Dir+'files.lst');
end;
if(FileExists(Dir+'files.lst')) then
begin
FilesToGet := TStringList.Create;
List := TListFile.Create(Dir+'files.lst');
CFiles:=TStringList.Create;
TRev:=StrToInt(List.GetKeyValue('settings','Rev'));
IsModHosts:=StrToInt(List.GetKeyValue('settings','ModHosts'));
if (IsModHosts = 1) then
begin
HostsLines:= TStringList.Create;
HostsLines:= List.GetFSection('hosts');
try
ModHosts(HostsLines);
finally
HostsLines.Free;
end;
end;
USelfParam:= List.GetFSection('self')[0];
if(USelfParam<>'FAIL') then SelfUpdate(USelfParam);
CFiles:=List.GetFSection('files_critical');
CheckFiles(CFiles);
CFiles.Free;
if (CForceCheck) or (TRev>CRevision) then
begin
if (CBackup=1) then
begin
DelDir(Dir+'backup');
MkDir(Dir+'backup');
end;
NFiles:=TStringList.Create;
NFiles:=List.GetFSection('files_normal');
CheckFiles(NFiles);
NFiles.Free;
end;
GetFiles;
List.Destroy;
FilesToGet.Free;
DeleteFile(Dir+'files.lst');
if TRev>CRevision then
begin
CRevision:=TRev;
Synchronize(UpdateRevision);
end;
end;
end
else
begin
STemp:='Update Failed';
DeleteFile(Dir+'files.lst');
end;
Synchronize(UpdateStatusLabel);
Synchronize(UNLockFMain);
end;
end.
function CanConnect(const aUserName, aPassword, aHost: String; out aErrm: string): boolean;
var
LocalIDFTP: TIdFTP;
begin
aErrm := '';
LocalIDFTP := TIdFTP.Create(nil);
try
LocalIDFTP.UserName := aUserName;
LocalIDFTP.Password := aPassword;
LocalIDFTP.Host := aHost;
LocalIDFTP.Passive := True;
try
LocalIDFTP.Connect;
LocalIDFTP.Quit;
result := true;
except
on E: Exception do
begin
aErrm := 'Unable to connect to FTP site: ' + E.Message;
Result := FALSE;
end;
end;
finally
if Assigned(LocalIDFTP) then
LocalIDFTP.Free
else
Result := FALSE;
end;
end; {CanConnect}
I need a program to overwrite the file, but sometimes some process is lock it. How to check which process locks a file, and how to unlock it? What functions should I use?
I found on the Internet such a code, but it doesn't work me.
unit proc;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls, StdCtrls, ComCtrls, Grids, ValEdit, MTSUtilsUnit, TLHelp32,
Menus, PsAPI;
type
TApp = class
fPID: Integer;
fPArentPID: Integer;
fPIDName: string;
fThread: Integer;
fDLLName: TStringList;
fDLLPath: TStringList;
fDescription: string;
end;
TForm2 = class(TForm)
StatusBar1: TStatusBar;
Panel1: TPanel;
Panel2: TPanel;
Panel3: TPanel;
Panel4: TPanel;
Splitter2: TSplitter;
Edit1: TEdit;
Button1: TButton;
Label1: TLabel;
RichEdit1: TRichEdit;
PopupMenu1: TPopupMenu;
kill1: TMenuItem;
StringGrid1: TStringGrid;
function GetApps(AppName: string): TStringList;
function GetInfo(PID: Integer): string;
function Kill(PID: Integer): Boolean;
procedure kill1Click(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure StringGrid1SelectCell(Sender: TObject; ACol, ARow: Integer;
var CanSelect: Boolean);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form2: TForm2;
ApplicationList: TStringList;
row: Integer;
implementation
{$R *.dfm}
function TForm2.Kill(PID: Integer): Boolean;
var fHandle: THandle;
begin
fHandle := OpenProcess(PROCESS_TERMINATE, BOOL(0), PID);
if TerminateProcess(fHandle, 0) then
Result := True
else
Result := False;
CloseHandle(fHandle);
end;
procedure TForm2.kill1Click(Sender: TObject);
var i: Integer;
fApp: TApp;
begin
if Kill(StrToInt(StringGrid1.Cells[1, row])) then
begin
ApplicationList.Delete(row);
StringGrid1.RowCount := ApplicationList.Count;
for i := 1 to ApplicationList.Count - 1 do
begin
fApp := TApp(ApplicationList.Objects[i]);
Form2.StringGrid1.Cells[0,i] := fApp.fPIDName;
Form2.StringGrid1.Cells[1,i] := IntToStr(fApp.fPID);
end;
MessageBox(0, 'Terminate successfully', 'Kill', MB_ICONINFORMATION or MB_OK);
end
else
MessageBox(0, 'Could not terminate process', 'Kill', MB_ICONINFORMATION or MB_OK);
end;
procedure TForm2.StringGrid1SelectCell(Sender: TObject; ACol, ARow: Integer;
var CanSelect: Boolean);
var fApp: TApp;
begin
row := ARow;
RichEdit1.Lines.Clear();
if ApplicationList.Count >= row then
begin
fApp := TApp(ApplicationList.Objects[row]);
RichEdit1.Lines.Add(fApp.fDescription);
end;
end;
procedure TForm2.Button1Click(Sender: TObject);
var i: Integer;
fApp: TApp;
sItem: string;
CanSelect: Boolean;
begin
for i := 0 to ApplicationList.Count - 1 do
begin
fApp := TApp(ApplicationList.Objects[i]);
FreeAndNil(fApp.fDLLName);
FreeAndNil(fApp.fDLLPath);
FreeAndNil(fApp);
end;
FreeAndNil(ApplicationList);
ApplicationList := GetApps(Edit1.Text);
StringGrid1.RowCount := ApplicationList.Count;
for i := 0 to ApplicationList.Count - 1 do
begin
fApp := TApp(ApplicationList.Objects[i]);
StringGrid1.Cells[0,i] := fApp.fPIDName;
StringGrid1.Cells[1,i] := IntToStr(fApp.fPID);
end;
StringGrid1.OnSelectCell(Self, 0, 1, CanSelect);
end;
procedure TForm2.FormCreate(Sender: TObject);
begin
StringGrid1.Cells[0,0] := 'Name';
StringGrid1.Cells[1,0] := 'PID';
end;
function TForm2.GetInfo(PID: Integer): string;
var fHandle: THandle;
fModule: TModuleEntry32;
sInfo: string;
begin
Result := '';
sInfo := 'DLL Name: %s'#13#10 +
'DLL Path: %s'#13#10 +
'ModuleId: %d'#13#10;
fHandle := CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, PID);
if fHandle <> INVALID_HANDLE_VALUE then
if Module32First(fHandle, fModule) then
repeat
if SameText(ExtractFileExt(fModule.szModule), '.dll') then
begin
sInfo := Format(sInfo, [fModule.szModule, fModule.szExePath, fModule.th32ModuleID]);
Result := Result + sInfo;
end;
until not Module32Next(fHandle, fModule);
end;
function TForm2.GetApps(AppName: string): TStringList;
var fHandle: THandle;
fModHandle: THandle;
fProcess: TProcessEntry32;
fModule: TMODULEENTRY32;
App: TApp;
i: Integer;
IsDLL: Boolean;
IsProcess: Boolean;
fDesc: string;
sPath: string;
begin
IsDLL := False;
IsProcess := False;
Result := TStringList.Create();
Result.Clear();
fDesc := 'DLL Name: %s'#13#10 +
'DLL Path: %s'#13#10 +
'ModuleId: %d'#13#10;
fHandle := CreateToolHelp32SnapShot(TH32CS_SNAPPROCESS, 0);
fProcess.dwSize := SizeOf(fProcess);
IsProcess := Process32First(fHandle, fProcess);
while IsProcess do
begin
App := TApp.Create();
App.fDLLName := TStringList.Create();
App.fDLLPath := TStringList.Create();
fModHandle := CreateToolHelp32SnapShot(TH32CS_SNAPMODULE, fProcess.th32ProcessID);
IsDLL := Module32First(fModHandle, fModule);
while IsDLL do
begin
if Edit1.Text <> '' then
sPath := fModule.szModule
else
sPath := ExtractFileExt(fModule.szModule);
if SameText(sPath, Edit1.Text + '.dll') then
begin
App.fPID := fProcess.th32ProcessID;
App.fPIDName := fProcess.szExeFile;
App.fDLLName.Add(fModule.szModule);
App.fDLLPath.Add(fModule.szExePath);
App.fDescription := App.fDescription +
Format(fDesc, [fModule.szModule, fModule.szExePath, fModule.th32ModuleID]);
end;
IsDLL := Module32Next(fModHandle, fModule)
end;
if App.fDLLName.Count > 0 then
Result.AddObject(IntToStr(App.fPID), App);
IsProcess := Process32Next(fHandle, fProcess);
end;
CloseHandle(fHandle);
Result.Count;
end;
end.
You should not unlock the file yourself this will lead to lost data! Leave it to the user and instead show an error and explaining which process holds open the file.
This solution here will help you to do so:
http://www.remkoweijnen.nl/blog/2011/01/03/cannot-access-files-but-need-the-origin
Check out Process Explorer. It will show you which processes have which files opened, and will allow you to close individual files.