I'm trying to download a file using indy10 http components TIdHttp while getting the progress , I have just setted the libraries in the application folder while using the code for http URL it works and progress but with https it simply does nothing and it doesn't raises any exception :/
with TIdHTTP.Create(nil) do
begin
IOHndl:=TIdSSLIOHandlerSocketOpenSSL.Create(nil);
Request.BasicAuthentication:=True;
HandleRedirects:=True;
IOHandler:=IOHndl;
OnWork:=FOnWork;
OnWorkBegin:=FOnWorkBegin;
OnWorkEnd:=FOnWorkEnd;
Get(FUrl,FStream);
end;
Best Regards
First you have to create a small class to wrap the HTTP component:
unit IdHTTPProgressU;
interface
uses
Classes, IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient, IdHTTP, IdSSLOpenSSL;
{$M+}
type
TIdHTTPProgress = class(TIdHTTP)
private
FProgress: Integer;
FBytesToTransfer: Int64;
FOnChange: TNotifyEvent;
IOHndl: TIdSSLIOHandlerSocketOpenSSL;
procedure HTTPWorkBegin(ASender: TObject; AWorkMode: TWorkMode; AWorkCountMax: Int64);
procedure HTTPWork(ASender: TObject; AWorkMode: TWorkMode; AWorkCount: Int64);
procedure HTTPWorkEnd(Sender: TObject; AWorkMode: TWorkMode);
procedure SetProgress(const Value: Integer);
procedure SetOnChange(const Value: TNotifyEvent);
public
Constructor Create(AOwner: TComponent);
procedure DownloadFile(const aFileUrl: string; const aDestinationFile: String);
published
property Progress: Integer read FProgress write SetProgress;
property BytesToTransfer: Int64 read FBytesToTransfer;
property OnChange: TNotifyEvent read FOnChange write SetOnChange;
end;
implementation
uses
Sysutils;
{ TIdHTTPProgress }
constructor TIdHTTPProgress.Create(AOwner: TComponent);
begin
inherited;
IOHndl := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
Request.BasicAuthentication := True;
HandleRedirects := True;
IOHandler := IOHndl;
ReadTimeout := 30000;
OnWork := HTTPWork;
OnWorkBegin := HTTPWorkBegin;
OnWorkEnd := HTTPWorkEnd;
end;
procedure TIdHTTPProgress.DownloadFile(const aFileUrl: string; const aDestinationFile: String);
var
LDestStream: TFileStream;
aPath: String;
begin
Progress := 0;
FBytesToTransfer := 0;
aPath := ExtractFilePath(aDestinationFile);
if aPath <> '' then
ForceDirectories(aPath);
LDestStream := TFileStream.Create(aDestinationFile, fmCreate);
try
Get(aFileUrl, LDestStream);
finally
FreeAndNil(LDestStream);
end;
end;
procedure TIdHTTPProgress.HTTPWork(ASender: TObject; AWorkMode: TWorkMode; AWorkCount: Int64);
begin
if BytesToTransfer = 0 then // No Update File
Exit;
Progress := Round((AWorkCount / BytesToTransfer) * 100);
end;
procedure TIdHTTPProgress.HTTPWorkBegin(ASender: TObject; AWorkMode: TWorkMode; AWorkCountMax: Int64);
begin
FBytesToTransfer := AWorkCountMax;
end;
procedure TIdHTTPProgress.HTTPWorkEnd(Sender: TObject; AWorkMode: TWorkMode);
begin
FBytesToTransfer := 0;
Progress := 100;
end;
procedure TIdHTTPProgress.SetOnChance(const Value: TNotifyEvent);
begin
FOnChance := Value;
end;
procedure TIdHTTPProgress.SetProgress(const Value: Integer);
begin
FProgress := Value;
if Assigned(FOnChance) then
FOnChance(Self);
end;
end.
I wont go in to details with the calss: Just say that it bacally wraps a TIdhttp component in and assign the 3 events: OnBegin, onWork and OnEnd
The Method DownloadFile does the actually download,
Then when you have to use it you could do like this:
Place a Button and a PrograssBar on an empty form. Add IdHTTPProgressU to the uses list.
Declare a vaiable of TIdHTTPProgress and a local onChangeEvent
Your form definition should lokke like this:
type
TForm1 = class(TForm)
Button1: TButton;
ProgressBar1: TProgressBar;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
procedure IdHTTPProgressOnChange(Sender : TObject);
public
IdHTTPProgress: TIdHTTPProgress;
end;
Then you just have to implement the methods:
{ TForm1 }
procedure TForm1.Button1Click(Sender: TObject);
begin
IdHTTPProgress.OnChange := IdHTTPProgressOnChance;
IdHTTPProgress.OnChance := IdHTTPProgressOnChance;
IdHTTPProgress.DownloadFile('https://wordpress.org/latest.zip', 'latest.zip');
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
IdHTTPProgress := TIdHTTPProgress.Create(Self);
end;
procedure TForm1.IdHTTPProgressOnChance(Sender: TObject);
begin
ProgressBar1.Position := TIdHTTPProgress(Sender).Progress;
Application.ProcessMessages;
end;
Thats about it. Give it at try.
Related
I have a problem with Delphi 6 and Indy's TIdIcmpClient component.
I get this message when compiling the following code, in the marked line (51):
FPing.OnReply := OnPingReply;
[Error] fire.pas(51): Incompatible types: 'TComponent' and 'TIdIcmpClient'
How should I fix it?
unit fire;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, IdBaseComponent, IdComponent, IdRawBase, IdRawClient,
IdIcmpClient;
type
TForm1 = class(TForm)
ListBox1: TListBox;
Button1: TButton;
Memo1: TMemo;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
type
TMyThread = class(TThread)
private
FIndex: Integer;
FPing: TIdIcmpClient;
FIP: string;
protected
procedure Execute; override;
procedure OnPingReply(ASender: TIdIcmpClient; AReplyStatus: TReplyStatus);
public
constructor Create(AIndex: Integer);
destructor Destroy; override;
end;
constructor TMyThread.Create(AIndex: Integer);
begin
inherited Create(False);
FIndex := AIndex;
FIP := '192.168.1.' + IntToStr(FIndex + 1);
FPing := TIdIcmpClient.Create(nil);
FPing.Host:=FIP;
FPing.ReceiveTimeout:=1500;
FPing.OnReply := OnPingReply;
end;
destructor TMyThread.Destroy;
begin
FPing.Free;
inherited;
end;
//var// icmp:array[0..10] of TIdIcmpClient;
// ip:string;
procedure TMyThread.Execute; // aici e ce face thread-ul
var
i: Integer;
begin
FPing.Ping;
// ICMP.Ping('a',1000);
// Sleep(1300);
// form1.memo1.lines.add(IntToStr(findex)+' '+ICMP.ReplyStatus.fromipaddress);
for i := 1 to 1 do
begin
// 'findex' este indexul thread-ului din matrice
form1.memo1.lines.add(inttostr(findex)+' Thread running...');
application.ProcessMessages;
Sleep(1000);
end;
end;
procedure TMyThread.OnPingReply(ASender: TIdIcmpClient; AReplyStatus: TReplyStatus);
begin
if AReplyStatus.BytesReceived > 0 then
form1.memo1.Lines.add(FIP+ ' is reachable')
else
form1.memo1.Lines.add(FIP+ ' is not reachable: ');
end;
procedure TForm1.Button1Click(Sender: TObject);
var
MyThreads: array[0..10] of TMyThread;
// icmp:array[0..10] of TIdIcmpClient;
i: Integer;
begin
{ for i := 0 to 10 do //10 fire
begin
icmp[i]:=tidicmpclient.create(nil);
icmp[i].ReceiveTimeout:=1200;
ip:=Format('%s.%d', ['192.168.1', i]);
ICMP[i].Host :=ip;
end; }
for i := 0 to 10 do //10 fire
begin
MyThreads[i] := TMyThread.Create(i);
MyThreads[i].Resume;
application.ProcessMessages;
end;
// Readln;
for i := 0 to 10 do
begin
MyThreads[i].Free;
// icmp[i].Free;
end;
end;
end.
I expected it to be compilable, but I don't see the reason why it is not.
Your event handler is declared wrong. The ASender parameter needs to be TComponent rather than TIdIcmpClient, and the AReplyStatus parameter needs to be const:
procedure OnPingReply(ASender: TComponent; const AReplyStatus: TReplyStatus);
That being said, you don't need to use the OnReply event at all in this situation. TIdIcmpClient operates synchronously, so you can simply use the TIdIcmpClient.ReplyStatus property after the TIdIcmpClient.Ping() method exits:
procedure TMyThread.Execute; // aici e ce face thread-ul
var
...
begin
FPing.Ping;
if FPing.ReplyStatus.BytesReceived > 0 then
...
else
...
...
end;
Also, you must synchronize with the main UI thread when accessing UI controls in a worker thread. You can use TThread.Synchronize() method for that.
And, you do not need to call Application.ProcessMessages() in a worker thread. Doing so will have no effect on the main UI thread.
With all of that said, try something more like this:
unit fire;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, IdBaseComponent, IdComponent, IdRawBase, IdRawClient,
IdIcmpClient;
type
TForm1 = class(TForm)
ListBox1: TListBox;
Button1: TButton;
Memo1: TMemo;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
procedure AddText(const AText: String);
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
type
TMyThread = class(TThread)
private
FIndex: Integer;
FPing: TIdIcmpClient;
FIP: string;
FText: String;
procedure AddTextToUI(const AText: String);
procedure DoSyncText;
protected
procedure Execute; override;
public
constructor Create(AIndex: Integer);
destructor Destroy; override;
end;
constructor TMyThread.Create(AIndex: Integer);
begin
inherited Create(False);
FIndex := AIndex;
FIP := '192.168.1.' + IntToStr(FIndex + 1);
FPing := TIdIcmpClient.Create(nil);
FPing.Host := FIP;
FPing.ReceiveTimeout := 1500;
end;
destructor TMyThread.Destroy;
begin
FPing.Free;
inherited;
end;
procedure TMyThread.AddTextToUI(const AText: String);
begin
FText := AText;
Synchronize(DoSyncText);
end;
procedure TMyThread.DoSyncText;
begin
Form1.AddText(FText);
end;
procedure TMyThread.Execute; // aici e ce face thread-ul
begin
AddTextToUI(IntToStr(FIndex) + ' Thread running...');
try
FPing.Ping;
except
AddTextToUI('Error pinging ' + FIP);
Exit;
end;
if FPing.ReplyStatus.BytesReceived > 0 then
AddTextToUI(FIP + ' is reachable')
else
AddTextToUI(FIP + ' is not reachable');
end;
procedure TForm1.Button1Click(Sender: TObject);
var
MyThreads: array[0..10] of TMyThread;
I: Integer;
begin
for I := Low(MyThreads) to High(MyThreads) do //10 fire
begin
MyThreads[I] := TMyThread.Create(I);
end;
for I := Low(MyThreads) to High(MyThreads) do
begin
MyThreads[i].WaitFor;
MyThreads[i].Free;
end;
end;
procedure TForm1.AddText(const AText: String);
begin
Memo1.Lines.Add(AText);
end;
end.
I implemented a TTask to perform multiple uploads. I still have to implement IdHttp's OnWorkBegin, OnWork, OnWorkEnd methods in the task I created but I don't know how.
var TASK: ITask;
begin
TASK := TTask.Create(
procedure
var
IdSSL: TIdSSLIOHandlerSocketOpenSSL;
lParam : TIdMultipartFormDataStream;
UrlAPI: string;
res: string;
lHTTP: TIdHTTP;
begin
UrlAPI := 'https://..........';
lHTTP := TIdHTTP.Create(nil);
//I want to handle the OnWork methods here but I don't know where to declare them with this code structure that I would like to keep.
//lhttp.OnWorkBegin:= IdHTTPOnWorkBegin;
//lhttp.OnWork:=IdHTTP1Work;
//lhttp.OnWorkEnd:=IdHTTPOnWorkEnd;
TThread.Synchronize(nil,
procedure
begin
Memo1.Lines.Add('Task Running...');
end
);
lHTTP.ReadTimeout := 30000;
lHTTP.HandleRedirects := false;
lParam := TIdMultipartFormDataStream.Create;
lParam.AddFormField('param1', code1);
lParam.AddFormField('param2', code2);
lParam.AddFile('source', TheFile);
lParam.Position := 0;
try
res := lHTTP.Post(UrlAPI, lparam);
memo1.Lines.Add(risposta);
Finally
lHTTP.Free;
end;
TThread.Synchronize(nil,
procedure
begin
Memo1.Lines.Add('SEND file '+TheFile);
end
);
end
);
TASK.Start();
Where do I write the declarations of the methods so that they can include the declaration of the IHTTP?
The same way you always do it in Delphi.
type
TForm1 = class(TForm)
procedure IdHTTP1Work(ASender: TObject; AWorkMode: TWorkMode;
AWorkCount: Int64);
procedure FormCreate(Sender: TObject);
private
{ private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
uses
System.Threading;
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
var
Task: ITask;
begin
Task := TTask.Create(
procedure
var
IdHttp1: TIdHttp;
begin
IdHttp1 := TidHttp.Create(Self);
IdHttp1.OnWork := Form1.IdHTTP1Work;
end
);
Task.Start;
end;
procedure TForm1.IdHTTP1Work(ASender: TObject; AWorkMode: TWorkMode;
AWorkCount: Int64);
var
IdHttp: TIdHttp;
begin
IdHttp := ASender as TIdHttp;
end;
I used this code but it doesn't work for SHCNE_FREESPACE, I don't receive any notification if I delete or copy files in the specified folder. Only if I use other flags I receive notifications.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ShlObj, ActiveX;
const
SHCNRF_INTERRUPTLEVEL = $0001;
SHCNRF_SHELLLEVEL = $0002;
SHCNRF_RECURSIVEINTERRUPT = $1000;
SHCNRF_NEWDELIVERY = $8000;
type
TSHChangeNotifyEntry = record
pidl: PItemIdList;
fRecursive: BOOL;
end;
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
procedure OnNotifyEvent(var AMessage:TMessage); message WM_USER;
end;
var
Form1: TForm1;
Hand: THandle;
function SHChangeNotifyRegister(OwnerHwnd:HWND; fSources:Integer; fEvents:DWord; wMsg:UINT;
cEntries:Integer; var pshcne:TSHChangeNotifyEntry):ULONG; stdcall; external 'shell32.dll';
function SHChangeNotifyDeregister(ulID:ULONG):BOOL; stdcall; external 'shell32.dll';
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
var Desktop:IShellFolder;
pidl:PItemIdList;
Path:String;
Eaten,attr,Events,Sources:DWord;
cnPIDL:TSHChangeNotifyEntry;
begin
if Succeeded(SHGetDesktopFolder(Desktop)) then begin
Path:='D:\Test';
if Succeeded(Desktop.ParseDisplayName(0, nil, PWideChar(Path), Eaten, pidl, attr)) then begin
Caption:=Path;
cnPIDL.pidl:=pidl;
cnPIDL.fRecursive:=true;
Sources:=SHCNRF_INTERRUPTLEVEL or SHCNRF_SHELLLEVEL or SHCNRF_NEWDELIVERY or SHCNRF_RECURSIVEINTERRUPT;
Events:=SHCNE_FREESPACE;
Hand:=SHChangeNotifyRegister(Handle, Sources, Events, WM_USER, 1, cnPIDL);;
CoTaskMemFree(pidl);
end;
end;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
SHChangeNotifyDeregister(Hand);
end;
procedure TForm1.OnNotifyEvent(var AMessage: TMessage);
begin
if AMessage.Msg = WM_USER then Caption:=Caption+' x';
end;
end.
Here's my attempt (written in Delphi 2009):
unit DiskSpace;
interface
uses
Windows, Messages, Classes, ShlObj;
type
PLONG = ^LONG;
LONG = LongInt;
TSpaceChangeEvent = procedure(Sender: TObject; const DiskFree, DiskTotal: Int64) of object;
TDiskSpace = class
strict private
FDiskRoot: string;
FDiskFree: Int64;
FDiskTotal: Int64;
FWndHandle: HWND;
FNotifierID: ULONG;
FOnSpaceChange: TSpaceChangeEvent;
protected
procedure WndProc(var Msg: TMessage); virtual;
procedure DoSpaceChange(const DiskFree, DiskTotal: Int64); virtual;
public
constructor Create(Drive: Char); virtual;
destructor Destroy; override;
property DiskRoot: string read FDiskRoot;
property DiskFree: Int64 read FDiskFree;
property DiskTotal: Int64 read FDiskTotal;
property OnSpaceChange: TSpaceChangeEvent read FOnSpaceChange write FOnSpaceChange;
end;
implementation
const
shell32 = 'shell32.dll';
SHCNRF_InterruptLevel = $0001;
SHCNRF_ShellLevel = $0002;
SHCNRF_RecursiveInterrupt = $1000;
SHCNRF_NewDelivery = $8000;
WM_SHELL_ITEM_NOTIFY = WM_USER + 666;
type
PSHChangeNotifyEntry = ^TSHChangeNotifyEntry;
TSHChangeNotifyEntry = record
pidl: PItemIDList;
fRecursive: BOOL;
end;
procedure ILFree(pidl: PItemIDList); stdcall;
external shell32 name 'ILFree';
function ILCreateFromPath(pszPath: PWideChar): PItemIDList; stdcall;
external shell32 name 'ILCreateFromPathW';
function SHChangeNotifyRegister(hwnd: HWND; fSources: Integer; fEvents: LONG; wMsg: UINT;
cEntries: Integer; pshcne: PSHChangeNotifyEntry): ULONG; stdcall;
external shell32 name 'SHChangeNotifyRegister';
function SHChangeNotifyDeregister(ulID: ULONG): BOOL; stdcall;
external shell32 name 'SHChangeNotifyDeregister';
{ TDiskSpace }
constructor TDiskSpace.Create(Drive: Char);
var
NotifyEntry: TSHChangeNotifyEntry;
begin
FDiskRoot := Drive + ':\';
FWndHandle := AllocateHWnd(WndProc);
NotifyEntry.pidl := ILCreateFromPath(PWideChar(FDiskRoot));
try
NotifyEntry.fRecursive := True;
FNotifierID := SHChangeNotifyRegister(
FWndHandle,
SHCNRF_ShellLevel or SHCNRF_InterruptLevel or SHCNRF_RecursiveInterrupt,
SHCNE_CREATE or SHCNE_DELETE or SHCNE_UPDATEITEM,
WM_SHELL_ITEM_NOTIFY,
1,
#NotifyEntry);
finally
ILFree(NotifyEntry.pidl);
end;
end;
destructor TDiskSpace.Destroy;
begin
if FNotifierID <> 0 then
SHChangeNotifyDeregister(FNotifierID);
if FWndHandle <> 0 then
DeallocateHWnd(FWndHandle);
inherited;
end;
procedure TDiskSpace.WndProc(var Msg: TMessage);
var
NewFree: Int64;
NewTotal: Int64;
begin
if (Msg.Msg = WM_SHELL_ITEM_NOTIFY) then
begin
if GetDiskFreeSpaceEx(PChar(FDiskRoot), NewFree, NewTotal, nil) then
begin
if (FDiskFree <> NewFree) or (FDiskTotal <> NewTotal) then
begin
FDiskFree := NewFree;
FDiskTotal := NewTotal;
DoSpaceChange(FDiskFree, FDiskTotal);
end;
end
else
begin
FDiskFree := -1;
FDiskTotal := -1;
end;
end
else
Msg.Result := DefWindowProc(FWndHandle, Msg.Msg, Msg.wParam, Msg.lParam);
end;
procedure TDiskSpace.DoSpaceChange(const DiskFree, DiskTotal: Int64);
begin
if Assigned(FOnSpaceChange) then
FOnSpaceChange(Self, DiskFree, DiskTotal);
end;
end.
And a possible usage:
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
FDiskSpace: TDiskSpace;
procedure DiskSpaceChange(Sender: TObject; const DiskFree, DiskTotal: Int64);
end;
implementation
procedure TForm1.FormCreate(Sender: TObject);
begin
FDiskSpace := TDiskSpace.Create('C');
FDiskSpace.OnSpaceChange := DiskSpaceChange;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FDiskSpace.Free;
end;
procedure TForm1.DiskSpaceChange(Sender: TObject; const DiskFree, DiskTotal: Int64);
begin
Caption := Format('%d/%d B', [DiskFree, DiskTotal]);
end;
I've used VBto as a starting point plus a lot of study of Delphi 6 User's Guide. I can make my new component compile, but I can't figure a way to get it to display so I can finish debugging it. And 50 years of programming experience isn't helping. Here are the guts of my component:
type
TChangeEvent = procedure(Sender: TObject; v: String) of object;
TTxtSpnr = class(TWinControl)
Lbl: TLabel;
Txt: TEdit;
Scrll: TScrollBar;
private
FonChange: TChangeEvent;
busy, tweaked: Boolean;
NewValue: String;
protected
procedure Changed(v: String); dynamic;
property onChange: TChangeEvent read FonChange write FOnChange;
procedure ScrllChange(Sender: TObject);
procedure ScrllScroll(Sender: TObject; ScrollCode: TScrollCode; var ScrollPos: Integer);
procedure TxtEnter(Sender: TObject);
procedure TxtKeyUp(Sender: TObject; var Key: WORD; Shift: TShiftState);
procedure TxtExit(Sender: TObject);
procedure Txt_Validate(var Cancel: Boolean);
public
function GetCaption(): String;
procedure SetCaption(New_Caption: String);
function GetMax(): Smallint;
procedure SetMax(New_Max: Smallint);
function MaxOf(a: Double; B: Longint): OleVariant;
function MinOf(a: OleVariant; B: Longint): OleVariant;
function GetMin(): Smallint;
procedure SetMin(New_Min: Smallint);
function GetText(): String;
procedure SetText(New_Text: String);
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property Caption: String read GetCaption write SetCaption;
property Enabled: Boolean read GetEnabled write SetEnabled;
property Max: Smallint read GetMax write SetMax;
property Min: Smallint read GetMin write SetMin;
property Text: String read GetText write SetText;
end;
var
TxtSpnr: TTxtSpnr;
implementation
uses Math;
{$R *.dfm}
procedure TTxtSpnr.Changed(V: String); begin
if assigned(FonChange) then FonChange(self,V);
end;
constructor TTxtSpnr.Create(AOwner: TComponent); begin
inherited Create(AOwner);
Lbl := TLabel.Create(Self);
with Lbl do begin
Parent := Self;
end;
Txt := TEdit.Create(Self);
with Txt do begin
Parent := Self;
end;
Scrll := TScrollBar(Self);
with Scrll do begin
Parent := Self;
end;
end;
and here's the test driver:
type
TForm1 = class(TForm)
FTxtSpnr: TTxtSpnr;
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject); begin
FTxtSpnr := TTxtSpnr.create(Self);
with FTxtSpnr do begin
Left:=10;
Top:=10;
Visible:=true;
Show;
end;
end;
But it doesn't compile and says, in the constructor, "An object can't be its own parent". Take out the Parent settings, it compiles but doesn't display the components. What am I missing?
First,
Scrll := TScrollBar(Self);
should of course read
Scrll := TScrollBar.Create(Self);
Second,
FTxtSpnr := TTxtSpnr.create(Self);
with FTxtSpnr do begin
Left:=10;
Top:=10;
Visible:=true;
Show;
end;
should be
FTxtSpnr := TTxtSpnr.create(Self);
with FTxtSpnr do
begin
Parent := Self;
Left := 10;
Top := 10;
end;
You forgot to set the parent.
Also, the global variable
var
TxtSpnr: TTxtSpnr;
looks dangerous. If you don't know exactly why you added those two lines, you should probably remove them.
I have an TImage on a TPanel, and an other (empty) TPanels. I want to drag
the image from the first to the second panel using the drag and drop.
I actually want to see the image while it's moving from one panel to the
other (semi-transparent).
I think I should use TDragObject.GetDragImages but I can't figure out how to construct the whole magic.
procedure TForm1.Image1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
Image1.ControlStyle := Image1.ControlStyle + [csDisplayDragImage]; // ???
TImage(Sender).BeginDrag(False);
end;
procedure TForm1.Image1StartDrag(Sender: TObject; var DragObject: TDragObject);
begin
// ???
end;
procedure TForm1.Panel1DragOver(Sender, Source: TObject; X, Y: Integer;
State: TDragState; var Accept: Boolean);
begin
if (Source is TImage) then
Accept := TImage(Source).Parent <> Sender;
end;
procedure TForm1.Panel1DragDrop(Sender, Source: TObject; X, Y: Integer);
begin
if (Source is TImage) then
begin
TImage(Source).Parent := TPanel(Sender);
TImage(Source).Align := alClient;
end;
end;
Update - I found a useful article: Implementing Professional Drag & Drop In VCL/CLX Applications
unit Unit3;
interface
// 2012 Thomas Wassermann - demo
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls;
type
TMyDragObject = class(TDragControlObject)
private
FImageList:TImageList;
FDragSource:TControl;
protected
function GetDragImages: TDragImageList; override;
public
Procedure StartDrag(G:TGraphic;p:TPoint;DragSource:TControl);
Constructor Create(AControl: TControl); override;
Destructor Destroy;override;
Property DragSource:TControl read FDragSource;
end;
TForm3 = class(TForm)
Panel1: TPanel;
Panel2: TPanel;
Image1: TImage;
procedure Image1StartDrag(Sender: TObject; var DragObject: TDragObject);
procedure FormCreate(Sender: TObject);
procedure Panel1DragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean);
procedure FormDestroy(Sender: TObject);
procedure Panel1DragDrop(Sender, Source: TObject; X, Y: Integer);
private
{ Private-Deklarationen }
FDragObject:TMyDragObject;
public
{ Public-Deklarationen }
end;
var
Form3: TForm3;
implementation
constructor TMyDragObject.Create(AControl: TControl);
begin
inherited;
FImageList:=TImageList.Create(nil);
end;
destructor TMyDragObject.Destroy;
begin
FImageList.Free;
inherited;
end;
function TMyDragObject.GetDragImages: TDragImageList;
begin
Result := FImageList;
end;
{$R *.dfm}
procedure TMyDragObject.StartDrag(G: TGraphic;p:TPoint;DragSource:TControl);
var
bmp:TBitMap;
begin
FDragSource := DragSource;
bmp:=TBitMap.Create;
try
FImageList.Width := g.Width;
FImageList.Height := g.Height;
bmp.Width := g.Width;
bmp.Height := g.Height;
bmp.Canvas.Draw(0,0,g);
FImageList.Add(bmp,nil);
finally
bmp.Free;
end;
FImageList.SetDragImage(0,p.x,p.y)
end;
procedure TForm3.FormCreate(Sender: TObject);
var
i:Integer;
begin
ControlStyle := ControlStyle + [csDisplayDragImage];
for I := 0 to ControlCount -1 do
if Controls[i] is TPanel then
TPanel(Controls[i]).ControlStyle := TPanel(Controls[i]).ControlStyle + [csDisplayDragImage];
ReportMemoryLeaksOnShutDown := True;
end;
procedure TForm3.FormDestroy(Sender: TObject);
begin
if Assigned(FDragObject) then FDragObject.Free;
end;
procedure TForm3.Image1StartDrag(Sender: TObject; var DragObject: TDragObject);
var
p:TPoint;
begin
p:=TImage(Sender).ScreenToClient(mouse.cursorpos);
if Assigned(FDragObject) then FDragObject.Free;
FDragObject := TMyDragObject.Create(TImage(Sender));
FDragObject.StartDrag(TImage(Sender).Picture.Graphic,p,TImage(Sender));
DragObject := FDragObject;
end;
procedure TForm3.Panel1DragDrop(Sender, Source: TObject; X, Y: Integer);
begin
if FDragObject.DragSource is TImage then
TImage(FDragObject.DragSource).Parent := TPanel(Sender);
end;
procedure TForm3.Panel1DragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean);
begin
Accept := Source is TMyDragObject;
end;
end.