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.
Related
From Delphi 6 on it was possible to put millions of lines in the TListBox component via .Style:= lbVirtual and using the OnData event. In Lazarus lbVirtual exists, too, but not the OnData event. I want to extend this component to be able to display millions of lines, but I get errors during compilation.
My problem is that I can't really port code from Delphi to Lazarus when it comes to using lbVirtual in Lazarus, as no OnData event exists.
Delphi 7:
ListBox.Style:= lbVirtual;
property OnData;
ListBox.Count:= // for reading
Lazarus:
ListBox.Style:= lbVirtual; // which behaves like lbStandard
ListBox.Count:= // ReadOnly
In Lazarus I used the property OnData in my new L_Listbox component and ListBox.Count:=. I still don't know if L_ListBox lines will show up like I know it from lbVirtual. Now I get compiler error messages such as
resourcestring
LongInt
I thought I would solve this by appending to uses Math. However, it did not help. All compilation errors pop up in the file: l_listbox.pas
LLB.pas
{ This file was automatically created by Lazarus. Do not edit!
This source is only used to compile and install the package.
}
unit LLB;
{$warn 5023 off : no warning about unused units}
interface
uses
L_ListBox, LazarusPackageIntf;
implementation
procedure Register;
begin
RegisterUnit('L_ListBox', #L_ListBox.Register);
end;
initialization
RegisterPackage('LLB', #Register);
end.
LLB.lpk
<?xml version="1.0" encoding="UTF-8"?>
<CONFIG>
<Package Version="4">
<PathDelim Value="\"/>
<Name Value="LLB"/>
<Type Value="RunAndDesignTime"/>
<CompilerOptions>
<Version Value="11"/>
<PathDelim Value="\"/>
<SearchPaths>
<UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)\"/>
</SearchPaths>
</CompilerOptions>
<Files Count="1">
<Item1>
<Filename Value="l_listbox.pas"/>
<HasRegisterProc Value="True"/>
<UnitName Value="L_ListBox"/>
</Item1>
</Files>
<RequiredPkgs Count="2">
<Item1>
<PackageName Value="LCL"/>
</Item1>
<Item2>
<PackageName Value="FCL"/>
</Item2>
</RequiredPkgs>
<UsageOptions>
<UnitPath Value="$(PkgOutDir)"/>
</UsageOptions>
<PublishOptions>
<Version Value="2"/>
<UseFileFilters Value="True"/>
</PublishOptions>
</Package>
</CONFIG>
l_listbox.pas (see comments where the compiler complains in function TListBoxStrings.GetObject(Index: Integer): TObject;)
unit L_ListBox;
{$mode objfpc}{$H+}
interface
uses Math, StdCtrls, Controls, Classes, Forms, Graphics, Messages, Windows, SysUtils, Commctrl, Types,
LResources, LCLType, LCLIntf, LMessages;
resourcestring
SErrorSettingCount = 'Error setting %s.Count';
SListBoxMustBeVirtual = 'Listbox (%s) style must be virtual in order to set Count';
SListIndexError = 'List %s is invalid';
type
TListBoxStyle = (lbStandard, lbOwnerDrawFixed, lbOwnerDrawVariable, lbVirtual, lbVirtualOwnerDraw);
TLBGetDataEvent = procedure(Control: TWinControl; Index: Integer; var Data: string) of object;
TLBFindDataEvent = function(Control : TWinControl; FindString: string): Integer of object;
TLBGetDataObjectEvent = procedure(Control: TWinControl; Index: Integer; var DataObject: TObject) of object;
TL_ListBox = class(Tlistbox)
private
FCount : Integer;
FStyle : TListBoxStyle;
FOnDataFind : TLBFindDataEvent;
FOnData : TLBGetDataEvent;
FOnDataObject : TLBGetDataObjectEvent;
function GetSelCount : Integer;
function GetCount : Integer;
procedure SetCount(const Value: Integer);
procedure SetStyle(Value: TListBoxStyle);
protected
function DoGetData(const Index: Integer): String;
function DoGetDataObject(const Index: Integer): TObject;
function DoFindData(const Data: String): Integer;
function InternalGetItemData(Index: Integer): Longint; dynamic;
procedure InternalSetItemData(Index: Integer; AData: Longint); dynamic;
function GetItemData(Index: Integer): LongInt; dynamic;
procedure SetItemData(Index: Integer; AData: LongInt); dynamic;
procedure CreateParams(var Params: TCreateParams); override;
public
property SelCount : Integer read GetSelCount;
property Count : Integer read GetCount write SetCount;
published
property OnData : TLBGetDataEvent read FOnData write FOnData;
property OnDataObject : TLBGetDataObjectEvent read FOnDataObject write FOnDataObject;
property OnDataFind : TLBFindDataEvent read FOnDataFind write FOnDataFind;
property Style: TListBoxStyle read FStyle write SetStyle default lbStandard;
end;
procedure Register;
implementation
uses RTLConsts;
procedure Register;
begin
RegisterComponents('ex',[TL_ListBox]);
end;
type
TListBoxStrings = class(TStrings)
private
ListBox: TL_ListBox;
protected
function GetCount: Integer; override;
function GetObject(Index: Integer): TObject; override;
procedure PutObject(Index: Integer; AObject: TObject); override;
public
end;
{ TL_ListBox }
procedure TL_ListBox.CreateParams(var Params: TCreateParams);
const
Styles: array[TListBoxStyle] of DWORD = (0, LBS_OWNERDRAWFIXED, LBS_OWNERDRAWVARIABLE, LBS_OWNERDRAWFIXED, LBS_OWNERDRAWFIXED);
Data: array[Boolean] of DWORD = (LBS_HASSTRINGS, LBS_NODATA);
begin
inherited CreateParams(Params);
CreateSubClass(Params, 'ListBox');
with Params do begin
Style := Style or ({WS_HSCROLL or }WS_VSCROLL or Data[Self.Style in [lbVirtual]] or LBS_NOTIFY) or Styles[FStyle];
end;
end;
function TL_ListBox.DoFindData(const Data: String): Integer;
begin
if Assigned(FOnDataFind) then Result := FOnDataFind(Self, Data) else Result := -1;
end;
function TL_ListBox.DoGetData(const Index: Integer): String;
begin
if Assigned(FOnData) then FOnData(Self, Index, Result);
end;
function TL_ListBox.DoGetDataObject(const Index: Integer): TObject;
begin
if Assigned(FOnDataObject) then FOnDataObject(Self, Index, Result);
end;
function TL_ListBox.GetCount: Integer;
begin
if Style in [lbVirtual] then Result := FCount else Result := Items.Count;
end;
function TL_ListBox.GetItemData(Index: Integer): LongInt;
begin
Result := SendMessage(Handle, LB_GETITEMDATA, Index, 0);
end;
function TL_ListBox.GetSelCount: Integer;
begin
Result := SendMessage(Handle, LB_GETSELCOUNT, 0, 0);
end;
function TL_ListBox.InternalGetItemData(Index: Integer): Longint;
begin
Result := GetItemData(Index);
end;
procedure TL_ListBox.InternalSetItemData(Index, AData: Integer);
begin
SetItemData(Index, AData);
end;
procedure TL_ListBox.SetCount(const Value: Integer);
var
Error: Integer;
begin
if Style in [lbVirtual] then
begin
// Limited to 32767 on Win95/98 as per Win32 SDK
Error := SendMessage(Handle, LB_SETCOUNT, Value, 0);
if (Error <> LB_ERR) and (Error <> LB_ERRSPACE) then FCount := Value else raise Exception.CreateFmt(LoadStr(SErrorSettingCount), [Name]);
end
else raise Exception.CreateFmt(LoadStr(SListBoxMustBeVirtual), [Name]);
end;
procedure TL_ListBox.SetItemData(Index, AData: Integer);
begin
SendMessage(Handle, LB_SETITEMDATA, Index, AData);
end;
procedure TL_ListBox.SetStyle(Value: TListBoxStyle);
begin
if FStyle <> Value then
begin
if Value in [lbVirtual] then
begin
Items.Clear;
Sorted := False;
end;
FStyle := Value;
end;
end;
{ TListBoxStrings }
function TListBoxStrings.GetCount: Integer;
begin
Result := SendMessage(ListBox.Handle, LB_GETCOUNT, 0, 0);
end;
function TListBoxStrings.GetObject(Index: Integer): TObject;
begin
if ListBox.Style in [lbVirtual] then
Result := ListBox.DoGetDataObject(Index)
else
begin
Result := TObject(ListBox.GetItemData(Index)); // Compiler complains here on TObject...
if Longint(Result) = LB_ERR then Error(SListIndexError, Index); // ...and here on Longint
end;
end;
procedure TListBoxStrings.PutObject(Index: Integer; AObject: TObject);
begin
if (Index <> -1) and not (ListBox.Style in [lbVirtual]) then
ListBox.SetItemData(Index, LongInt(AObject));
end;
end.
My Form:
unit Unit1;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls,
L_ListBox;
type
{ TForm1 }
TForm1 = class(TForm)
Button1: TButton;
L_ListBox1: TL_ListBox;
procedure Button1Click(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure L_ListBox1Data(Control: TWinControl; Index: Integer;
var Data: string);
private
public
end;
var
Form1: TForm1;
MyList : TStringlist;
implementation
{$R *.lfm}
{ TForm1 }
procedure TForm1.Button1Click(Sender: TObject);
begin
MyList := TStringlist.Create;
L_ListBox1.Style := lbVirtual;
MyList.LoadFromFile('ex.txt');
L_ListBox1.Count := MyList.Count;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
MyList.Free;
end;
procedure TForm1.L_ListBox1Data(Control: TWinControl; Index: Integer;
var Data: string);
begin
Data := MyList[Index];
end;
end.
I corrected the code in L_ListBox.pas
procedure Register;
implementation
uses RTLConsts;
resourcestring
SErrorSettingCount = 'Error setting% s.Count';
SListBoxMustBeVirtual = 'Listbox (% s) style must be virtual in order to set Count';
SListIndexError = 'List% s is invalid';
procedure Register;
begin
RegisterComponents ('ex', [TL_ListBox]);
end;
I am getting an error:
[Debugger Exception Notification]
Project project1 raised exception class 'Exception' with message:
Error setting L_ListBox1.Count
What is the construction in Lazarus ?
since the compiler stops I marked bold
TObject
Longint
Result := TObject(ListBox.GetItemData(Index));
if Longint(Result) = LB_ERR then Error(SListIndexError, Index);
I want to be notified when my computer power source changes.
So first I 've created a simple Delphi application and listening for
WM_POWERBROADCAST at the main form.
WM_POWERBROADCAST
type
TForm38 = class(TForm)
public
procedure WM_POWERBROADCAST(var Msg: TMessage); message WM_POWERBROADCAST;
end;
implementation
procedure TForm38.WM_POWERBROADCAST(var Msg: TMessage);
begin
Caption := Msg.LParam.ToString;
end;
Then I got my notifications, but Msg.LParam is allways 0 (zero)
Then I've tried to call RegisterPowerSettingNotification and found an example in this old SO Question, but I still have the same problem: Msg.LParam is allways 0 (zero)
RegisterPowerSettingNotification
type
TForm38 = class(TForm)
Button1: TButton;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
FHPOWERNOTIFY: HPOWERNOTIFY;
public
{ Public declarations }
procedure WM_POWERBROADCAST(var Msg: TMessage); message WM_POWERBROADCAST;
end;
implementation
const
GUID_ACDC_POWER_SOURCE: TGUID = '{5D3E9A59-E9D5-4B00-A6BD-FF34FF516548}';
procedure TForm38.FormCreate(Sender: TObject);
begin
FHPOWERNOTIFY := RegisterPowerSettingNotification(Handle, GUID_ACDC_POWER_SOURCE, DEVICE_NOTIFY_WINDOW_HANDLE);
end;
procedure TForm38.FormDestroy(Sender: TObject);
begin
UnregisterPowerSettingNotification(FHPOWERNOTIFY);
end;
procedure TForm38.WM_POWERBROADCAST(var Msg: TMessage);
begin
Caption := Msg.LParam.ToString;
end;
The application run on Windows 10.
What am I doing wrong?
THE RESULT
Using the code from the answer to this question, I've ended up writing this class:
unit PowerWatcherU;
interface
uses
Winapi.Windows, System.Classes, System.SyncObjs, Winapi.Messages;
{$M+}
type
TPowerSource = (PoAc = 0, PoDc = 1, PoHot = 2);
TPowerSourceChanged = procedure(const PowerSource: TPowerSource) of object;
TPowerWatcher = class(TComponent)
private
FMyHWND: HWND;
FHPOWERNOTIFY: HPOWERNOTIFY;
FOnPowerSourceChanged: TPowerSourceChanged;
procedure DoPowerSourceChanged(const Value: TPowerSource);
procedure WndHandler(var Msg: TMessage);
procedure SetOnPowerSourceChanged(const Value: TPowerSourceChanged);
published
property OnPowerSourceChanged: TPowerSourceChanged read FOnPowerSourceChanged write SetOnPowerSourceChanged;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
end;
const
GUID_ACDC_POWER_SOURCE: TGUID = '{5D3E9A59-E9D5-4B00-A6BD-FF34FF516548}';
implementation
uses
System.SysUtils;
{ TPowerWatcher }
constructor TPowerWatcher.Create;
begin
inherited;
FMyHWND := AllocateHWND(WndHandler);
FHPOWERNOTIFY := RegisterPowerSettingNotification(FMyHWND, GUID_ACDC_POWER_SOURCE, DEVICE_NOTIFY_WINDOW_HANDLE);
end;
destructor TPowerWatcher.Destroy;
begin
DeallocateHWND(FMyHWND);
UnregisterPowerSettingNotification(FHPOWERNOTIFY);
inherited;
end;
procedure TPowerWatcher.DoPowerSourceChanged(const Value: TPowerSource);
begin
if Assigned(FOnPowerSourceChanged) then
FOnPowerSourceChanged(Value);
end;
procedure TPowerWatcher.SetOnPowerSourceChanged(const Value: TPowerSourceChanged);
begin
FOnPowerSourceChanged := Value;
end;
procedure TPowerWatcher.WndHandler(var Msg: TMessage);
begin
if (Msg.Msg = WM_POWERBROADCAST) and (Msg.WParam = PBT_POWERSETTINGCHANGE) then
begin
if PPowerBroadcastSetting(Msg.LParam)^.PowerSetting = GUID_ACDC_POWER_SOURCE then
DoPowerSourceChanged(TPowerSource(PPowerBroadcastSetting(Msg.LParam)^.Data[0]));
end
else
Msg.Result := DefWindowProc(FMyHWND, Msg.Msg, Msg.WParam, Msg.LParam);
end;
end.
It is possible that you are suffering from window re-creation. Your code as posted works fine for me but this may not be the case in Win10. With that aside, the only other oddity is that you are duplicating an identifier by naming a method WM_POWERBROADCAST, although this should not cause the code to break. Working example using a dedicated HWND :
unit Unit1;
interface
uses
Windows, SysUtils, Classes, Forms, StdCtrls, Vcl.Controls, Vcl.ExtCtrls,
Messages;
type
TForm1 = class(TForm)
Button1: TButton;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
FMyHWND : HWND;
FHPowerNotify: HPOWERNOTIFY;
public
procedure WndHandler(var Msg: TMessage);
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
const
GUID_ACDC_POWER_SOURCE: TGUID = '{5D3E9A59-E9D5-4B00-A6BD-FF34FF516548}';
procedure TForm1.FormCreate(Sender: TObject);
begin
FMyHWND := AllocateHWND(WndHandler);
FHPowerNotify := RegisterPowerSettingNotification(FMyHWND,
GUID_ACDC_POWER_SOURCE,
DEVICE_NOTIFY_WINDOW_HANDLE);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
UnregisterPowerSettingNotification(FHPowerNotify);
DeallocateHWND(FMyHWND);
end;
procedure TForm1.WndHandler(var Msg: TMessage);
begin
if (Msg.Msg = WM_POWERBROADCAST) and
(Msg.WParam = PBT_POWERSETTINGCHANGE) then
begin
if PPowerBroadcastSetting(Msg.LParam)^.PowerSetting = GUID_ACDC_POWER_SOURCE then
case cardinal(PPowerBroadcastSetting(Msg.LParam)^.Data[0]) of
0: Caption := 'AC Power';
1: Caption := 'DC Power';
2: Caption := 'HOT - UPS, etc';
end;
end else
msg.Result := DefWindowProc(FMyHWND, Msg.Msg, Msg.WParam, Msg.LParam);
end;
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 have this code with which I can set the font size of the control hint, but I want to be able somehow to adjust it later at runtime. How can I do that ?
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TMyHintWindow = class(THintWindow)
constructor Create(AOwner: TComponent); override;
end;
TMyButton = class(TButton)
protected
procedure CMHintShow(var Message: TCMHintShow); message CM_HINTSHOW;
end;
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
MyButton: TMyButton;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
MyButton:=TMyButton.Create(Form1);
MyButton.Parent:=Form1;
MyButton.Caption:='Test';
MyButton.Left:=100;
MyButton.Top:=100;
MyButton.ShowHint:=true;
end;
procedure TMyButton.CMHintShow(var Message: TCMHintShow);
begin
inherited;
Message.HintInfo.HintWindowClass:=TMyHintWindow;
Message.HintInfo.HintStr:='My custom hint';
end;
constructor TMyHintWindow.Create(AOwner: TComponent);
begin
inherited;
Canvas.Font.Size:=25;
end;
end.
Since there is only one hint window instance at the time, and that instance will be created after call to CMHintShow, you can use class variables to do additional hint customization. Class variable is class member that is shared among all instances of the class and can be accessed directly through class type or class instance.
type
TMyHintWindow = class(THintWindow)
protected
class constructor ClassCreate;
public
class var FontSize: integer;
constructor Create(AOwner: TComponent); override;
end;
class constructor TMyHintWindow.ClassCreate;
begin
FontSize := 25;
end;
constructor TMyHintWindow.Create(AOwner: TComponent);
begin
inherited;
Canvas.Font.Size := FontSize;
end;
and then you can change FontSize in CMHintShow method
procedure TMyButton.CMHintShow(var Message: TCMHintShow);
begin
inherited;
TMyHintWindow.FontSize := 12;
Message.HintInfo.HintWindowClass := TMyHintWindow;
Message.HintInfo.HintStr := 'My custom hint';
end;
Starting from indications given by TLama I finally solved this problem. The key was to set Canvas.Font.Size in TMyHintWindow.CalcHintRect.
Here is the code:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TMyHintData = record
FontSize: Integer;
end;
TMyHintWindow = class(THintWindow)
public
function CalcHintRect(MaxWidth: Integer; const AHint: string; AData: TCustomData): TRect; override;
end;
TMyButton = class(TButton)
private
procedure CMHintShow(var AMessage: TCMHintShow); message CM_HINTSHOW;
public
FMyHintData: TMyHintData;
constructor Create(AOwner: TComponent); override;
end;
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
MyButton: TMyButton;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
TMyButton(Sender).FMyHintData.FontSize:=44;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
MyButton:=TMyButton.Create(Form1);
MyButton.Parent:=Form1;
MyButton.Caption:='Test';
MyButton.Left:=100;
MyButton.Top:=100;
MyButton.ShowHint:=true;
MyButton.OnClick:=Button1Click;
end;
function TMyHintWindow.CalcHintRect(MaxWidth: Integer; const AHint: string; AData: TCustomData): TRect;
begin
Canvas.Font.Size:=TMyHintData(AData^).FontSize;
Result:=inherited;
end;
constructor TMyButton.Create(AOwner: TComponent);
begin
inherited;
FMyHintData.FontSize:=25;
end;
procedure TMyButton.CMHintShow(var AMessage: TCMHintShow);
begin
inherited;
AMessage.HintInfo.HintData:=#FMyHintData;
AMessage.HintInfo.HintWindowClass:=TMyHintWindow;
AMessage.HintInfo.HintStr:='My custom hint';
end;
end.
procedure TForm1.FormCreate(Sender: TObject);
begin
Application.OnShowHint:=AppOnShowHint;
end;
procedure TForm1.AppOnShowHint(var HintStr: string; var CanShow: Boolean; var HintInfo: THintInfo);
begin
{Use HintInfo (type:THintInfo) to specify some property of hint-window}
{For example: set hint-window width to the width of longest word in the hint-text}
HintInfo.HintMaxWidth:=1;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
{Set HintFont at runtime}
Screen.HintFont.Size:=strtoint(Edit1.Text);
{It's necessary to recreate the Application.FHintWindow private variable, so:}
Application.ShowHint:=False;
Application.ShowHint:=True;
end;
I Creating a Hydra Host Application and a Hydra Plugin. I put a Procedure for Handling a Windows Message in Plugin; but in this case we can't handle this windows message. for solving this problem we can handle It in Host App and then we must talk with pluging via passing an Interface.
In this case I want to find a direct way for handle windows messages in Hydra Plugin. Please help me for solving this problem.
Update 1 for this Question:
this is a simple code for testing:
Plugin Side:
unit VisualPlugin;
interface
uses
{ vcl: } Windows, Messages, SysUtils, Variants, Classes, Graphics,
Controls, Forms, Dialogs, StdCtrls,
{ Hydra: } uHYVisualPlugin, uHYIntf;
type
TVisualPlugin1 = class(THYVisualPlugin)
private
procedure WMDEVICECHANGE(var Msg: TMessage); message WM_DEVICECHANGE;
end;
implementation
uses
{ Hydra: } uHYPluginFactories;
{$R *.dfm}
procedure Create_VisualPlugin1(out anInstance: IInterface);
begin
anInstance := TVisualPlugin1.Create(NIL);
end;
resourcestring
sDescription = '';
const
sRequiredPrivilege = '';
sUserData = '';
{ TVisualPlugin1 }
procedure TVisualPlugin1.WMDEVICECHANGE(var Msg: TMessage);
begin
// ===================================
// This Line Of Code Can't Be Run!!!!!!
ShowMessage('USB Changed');
// ===================================
end;
initialization
THYPluginFactory.Create(HInstance, 'VisualPlugin1', Create_VisualPlugin1,
TVisualPlugin1, 1, 0, sRequiredPrivilege, sDescription, sUserData);
end.
PluginController in Plugin Side:
unit hcPluginController;
interface
uses
{vcl:} SysUtils, Classes,
{Hydra:} uHYModuleController, uHYIntf, uHYCrossPlatformInterfaces;
type
TPluginController = class(THYModuleController)
private
public
end;
var
PluginController : TPluginController;
implementation
uses
{Hydra:} uHYRes;
{$R *.dfm}
procedure HYGetCrossPlatformModule(out result: IHYCrossPlatformModule); stdcall;
begin
result := PluginController as IHYCrossPlatformModule;
end;
function HYGetModuleController : THYModuleController;
begin
result := PluginController;
end;
exports
HYGetCrossPlatformModule,
HYGetModuleController name name_HYGetModuleController;
resourcestring
sDescription = '';
const
sRequiredPrivilege = '';
initialization
PluginController := TPluginController.Create('Plugin.Library', 1, 0, sRequiredPrivilege, sDescription);
finalization
FreeAndNil(PluginController);
end.
Host Application Side:
unit fMain;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, uHYModuleManager, uHYIntf, ExtCtrls, StdCtrls;
type
TMainForm = class(TForm)
HYModuleManager1: THYModuleManager;
Panel1: TPanel;
btnLoadPlugin: TButton;
procedure btnLoadPluginClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
{ Private declarations }
public
{ Public declarations }
end;
var
MainForm: TMainForm;
implementation
{$R *.dfm}
var
AppDir: string;
fPlugin: IHYVisualPlugin;
const
PluginDll = 'Plugin.dll';
PluginName = 'VisualPlugin1';
procedure TMainForm.btnLoadPluginClick(Sender: TObject);
begin
if HYModuleManager1.FindModule(AppDir + PluginDll) = nil then
HYModuleManager1.LoadModule(AppDir + PluginDll);
HYModuleManager1.CreateVisualPlugin(PluginName, fPlugin, Panel1);
end;
procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
HYModuleManager1.ReleaseInstance(fPlugin);
HYModuleManager1.UnloadModules;
end;
procedure TMainForm.FormCreate(Sender: TObject);
begin
AppDir := ExtractFilePath(Application.ExeName);
end;
end.
Not sure about the real cause of the problem, but you can use RegisterDeviceNotification function to achieve same result:
type
DEV_BROADCAST_DEVINTERFACE = record
dbcc_size: DWORD;
dbcc_devicetype: DWORD;
dbcc_reserved: DWORD;
dbcc_classguid: TGUID;
dbcc_name: short;
end;
const
DEVICE_NOTIFY_ALL_INTERFACE_CLASSES = $4;
DBT_DEVTYP_DEVICEINTERFACE = $5;
function RegisterNotification(Handle: THandle): HDEVNOTIFY;
var
Filter: DEV_BROADCAST_DEVINTERFACE;
begin
ZeroMemory(#Filter, SizeOf(DEV_BROADCAST_DEVINTERFACE));
Filter.dbcc_size := SizeOf(DEV_BROADCAST_DEVINTERFACE);
Filter.dbcc_devicetype := DBT_DEVTYP_DEVICEINTERFACE;
Filter.dbcc_reserved := 0;
Filter.dbcc_name := 0;
Result := RegisterDeviceNotification(Handle, #Filter, DEVICE_NOTIFY_WINDOW_HANDLE or DEVICE_NOTIFY_ALL_INTERFACE_CLASSES);
end;
Now inside plugin you need something like this:
TVisualPlugin = class(THYVisualPlugin)
protected
NofitifyHandle: HDEVNOTIFY;
procedure WMDEVICECHANGE(var Msg: TMessage); message WM_DEVICECHANGE;
procedure CreateWnd; override;
procedure DestroyWindowHandle; override;
end;
procedure TVisualPlugin.CreateWnd;
begin
inherited;
if HandleAllocated then
NofitifyHandle := RegisterNotification(Self.Handle);
end;
procedure TVisualPlugin.DestroyWindowHandle;
begin
if Assigned(NofitifyHandle) then begin
UnregisterDeviceNotification(NofitifyHandle);
NofitifyHandle := nil;
end;
inherited;
end;
procedure TVisualPlugin.WMDEVICECHANGE(var Msg: TMessage);
begin
ShowMessage('USB Changed');
end;