How to use IAutoComplete together with TStringsAdapter? - delphi

In this SO post, it is suggested to use IAutoComplete together with TStringsAdapter to implement auto-complete. The following code tries to follow the suggestion but fails enabling the autocomplete feature without compilation & runtime exceptioncomplaining unmatched/inconsistent interface... Could you help to comment about the underlying reason and the work around ?
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, AxCtrls, StdVCL, ActiveX, ComObj;
const
IID_IAutoComplete = '{00bb2762-6a77-11d0-a535-00c04fd7d062}';
IID_IAutoComplete2 = '{EAC04BC0-3791-11d2-BB95-0060977B464C}';
CLSID_AutoComplete: TGUID = '{00BB2763-6A77-11D0-A535-00C04FD7D062}';
type
IAutoComplete = interface(IUnknown)
[IID_IAutoComplete]
function Init(hwndEdit: HWND; punkACL: IUnknown; pwszRegKeyPath: PWideChar;
pwszQuickComplete: PWideChar): HResult; stdcall;
function Enable(fEnable: Boolean): HResult; stdcall;
end;
IAutoComplete2 = interface(IAutoComplete)
[IID_IAutoComplete2]
function SetOptions(dwFlag: DWORD): HResult; stdcall;
function GetOptions(out dwFlag: DWORD): HResult; stdcall;
end;
TStringsAdapterCracker = class(TStringsAdapter);
TForm1 = class(TForm)
ComboBox1: TComboBox;
procedure FormCreate(Sender: TObject);
private
FAutoComplete: IAutoComplete2;
FStrings: IUnknown;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
var
hEditControl: THandle;
begin
With ComboBox1 do begin
with Items do begin
BeginUpdate;
Clear;
Add('Alpha');
Add('Beta');
Add('Gamma');
Add('Delta');
EndUpdate;
end;
AutoComplete := False;
ItemIndex := 0;
end;
FAutoComplete := CreateComObject(CLSID_AutoComplete) as IAutoComplete2;
hEditControl := GetWindow(ComboBox1.Handle, GW_CHILD);
FStrings := TStringsAdapterCracker(TStringsAdapter.Create(ComboBox1.Items))._NewEnum;
OleCheck(FAutoComplete.Init(hEditControl, FStrings, nil, nil));
end;
end.
Note that related SO posts (here and here) use TEnumString to implement IEnumString manually instead of TStringsAdapter to work with IAutoComplete

Could you help to comment about the underlying reason and the work around ?
The reason the code fails is because the TStringsAdapters constructor tries to load a StdVCL type library and fails, raising a "library not registered" error:
constructor TStringsAdapter.Create(Strings: TStrings);
var
StdVcl: ITypeLib;
begin
OleCheck(LoadRegTypeLib(LIBID_STDVCL, 4, 0, 0, StdVcl)); // <-- fails!
inherited Create(StdVcl, IStrings);
FStrings := Strings;
end;
The TStringsAdapter object is being constructed in the form's OnCreate event, which is triggered after the form's constructor has exited, so the exception does not abort construction or terminate the process, but it does reach a default exception handler that displays an error popup message. Also, the exception is bypassing the call to FAutoComplete.Init(), so no enumerator is created or registered for the ComboBox.
Even though you have added StdVCL to your uses clause, that is not enough to get the StdVCL type library registered on the machine that your app is running on. You would have to distribute and register that type library as part of your app's installation setup.
The workaround is to use a TEnumString implementation that simply enumerates the TStrings values directly, thus avoiding that requirement. As well as it has a little bit less runtime overhead then using TStringsAdapter (whose _NewEnum() method creates a separate TStringsEnumerator object to perform the actual enumeration, so you are actually creating 2 objects instead of 1), but at the expense of having to write a bit more code to implement it, eg:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ActiveX, ComObj;
const
IID_IAutoComplete = '{00bb2762-6a77-11d0-a535-00c04fd7d062}';
IID_IAutoComplete2 = '{EAC04BC0-3791-11d2-BB95-0060977B464C}';
CLSID_AutoComplete: TGUID = '{00BB2763-6A77-11D0-A535-00C04FD7D062}';
type
IAutoComplete = interface(IUnknown)
[IID_IAutoComplete]
function Init(hwndEdit: HWND; punkACL: IUnknown; pwszRegKeyPath: PWideChar;
pwszQuickComplete: PWideChar): HResult; stdcall;
function Enable(fEnable: Boolean): HResult; stdcall;
end;
IAutoComplete2 = interface(IAutoComplete)
[IID_IAutoComplete2]
function SetOptions(dwFlag: DWORD): HResult; stdcall;
function GetOptions(out dwFlag: DWORD): HResult; stdcall;
end;
TForm1 = class(TForm)
ComboBox1: TComboBox;
procedure FormCreate(Sender: TObject);
private
FAutoComplete: IAutoComplete;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ TEnumString }
type
TEnumString = class(TInterfacedObject, IEnumString)
private
FStrings: TStrings;
FCurrIndex: integer;
public
//IEnumString
function Next(celt: Longint; out elt;
pceltFetched: PLongint): HResult; stdcall;
function Skip(celt: Longint): HResult; stdcall;
function Reset: HResult; stdcall;
function Clone(out enm: IEnumString): HResult; stdcall;
//VCL
constructor Create(AStrings: TStrings; AIndex: Integer = 0);
end;
constructor TEnumString.Create(AStrings: TStrings; AIndex: Integer = 0);
begin
inherited Create;
FStrings := AStrings;
FCurrIndex := AIndex;
end;
function TEnumString.Clone(out enm: IEnumString): HResult;
begin
enm := TEnumString.Create(FStrings, FCurrIndex);
Result := S_OK;
end;
function TEnumString.Next(celt: Integer; out elt;
pceltFetched: PLongint): HResult;
type
TPointerList = array[0..0] of Pointer; //avoid bug of Classes.pas declaration TPointerList = array of Pointer;
var
I: Integer;
wStr: WideString;
begin
I := 0;
while (I < celt) and (FCurrIndex < FStrings.Count) do
begin
wStr := FStrings[FCurrIndex];
TPointerList(elt)[I] := CoTaskMemAlloc(2 * (Length(wStr) + 1));
StringToWideChar(wStr, TPointerList(elt)[I], 2 * (Length(wStr) + 1));
Inc(I);
Inc(FCurrIndex);
end;
if pceltFetched <> nil then
pceltFetched^ := I;
if I = celt then
Result := S_OK
else
Result := S_FALSE;
end;
function TEnumString.Reset: HResult;
begin
FCurrIndex := 0;
Result := S_OK;
end;
function TEnumString.Skip(celt: Integer): HResult;
begin
if (FCurrIndex + celt) <= FStrings.Count then
begin
Inc(FCurrIndex, celt);
Result := S_OK;
end
else
begin
FCurrIndex := FStrings.Count;
Result := S_FALSE;
end;
end;
{ TForm1 }
procedure TForm1.FormCreate(Sender: TObject);
var
hEditControl: THandle;
LStrings: IUnknown;
LAC2: IAutoComplete2;
begin
with ComboBox1 do
begin
with Items do
begin
BeginUpdate;
try
Clear;
Add('Alpha');
Add('Beta');
Add('Gamma');
Add('Delta');
finally
EndUpdate;
end;
end;
AutoComplete := False;
ItemIndex := 0;
end;
FAutoComplete := CreateComObject(CLSID_AutoComplete) as IAutoComplete;
hEditControl := GetWindow(ComboBox1.Handle, GW_CHILD); // alternatively, use GetComboBoxInfo() to get the Edit HWND
LStrings := TEnumString.Create(ComboBox1.Items);
OleCheck(FAutoComplete.Init(hEditControl, LStrings, nil, nil));
if Supports(FAutoComplete, IAutoComplete2, LAC2) then
begin
// use SetOption as needed...
OleCheck(LAC2.SetOptions(...));
end;
end;
end.
Also, keep in mind that if the TComboBox's HWND is ever recreated at runtime, you will have to create a new IAutoComplete object and call init() on it to provide the new HWND. So you should subclass the TComboBox to handle recreation messages, or better would be to use an interceptor class so you can override the TComboBox.CreateWnd() method directly, eg:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ActiveX, ComObj;
const
IID_IAutoComplete = '{00bb2762-6a77-11d0-a535-00c04fd7d062}';
IID_IAutoComplete2 = '{EAC04BC0-3791-11d2-BB95-0060977B464C}';
CLSID_AutoComplete: TGUID = '{00BB2763-6A77-11D0-A535-00C04FD7D062}';
type
IAutoComplete = interface(IUnknown)
[IID_IAutoComplete]
function Init(hwndEdit: HWND; punkACL: IUnknown; pwszRegKeyPath: PWideChar;
pwszQuickComplete: PWideChar): HResult; stdcall;
function Enable(fEnable: Boolean): HResult; stdcall;
end;
IAutoComplete2 = interface(IAutoComplete)
[IID_IAutoComplete2]
function SetOptions(dwFlag: DWORD): HResult; stdcall;
function GetOptions(out dwFlag: DWORD): HResult; stdcall;
end;
TComboBox = class(StdCtrls.TComboBox)
private
FAutoComplete: IAutoComplete;
protected
procedure CreateWnd; override;
procedure DestroyWnd; override;
end;
TForm1 = class(TForm)
ComboBox1: TComboBox;
procedure FormCreate(Sender: TObject);
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ TEnumString }
type
TEnumString = class(TInterfacedObject, IEnumString)
private
FStrings: TStrings;
FCurrIndex: integer;
public
//IEnumString
function Next(celt: Longint; out elt;
pceltFetched: PLongint): HResult; stdcall;
function Skip(celt: Longint): HResult; stdcall;
function Reset: HResult; stdcall;
function Clone(out enm: IEnumString): HResult; stdcall;
//VCL
constructor Create(AStrings: TStrings; AIndex: Integer = 0);
end;
constructor TEnumString.Create(AStrings: TStrings; AIndex: Integer = 0);
begin
inherited Create;
FStrings := AStrings;
FCurrIndex := AIndex;
end;
function TEnumString.Clone(out enm: IEnumString): HResult;
begin
enm := TEnumString.Create(FStrings, FCurrIndex);
Result := S_OK;
end;
function TEnumString.Next(celt: Integer; out elt;
pceltFetched: PLongint): HResult;
type
TPointerList = array[0..0] of Pointer; //avoid bug of Classes.pas declaration TPointerList = array of Pointer;
var
I: Integer;
wStr: WideString;
begin
I := 0;
while (I < celt) and (FCurrIndex < FStrings.Count) do
begin
wStr := FStrings[FCurrIndex];
TPointerList(elt)[I] := CoTaskMemAlloc(2 * (Length(wStr) + 1));
StringToWideChar(wStr, TPointerList(elt)[I], 2 * (Length(wStr) + 1));
Inc(I);
Inc(FCurrIndex);
end;
if pceltFetched <> nil then
pceltFetched^ := I;
if I = celt then
Result := S_OK
else
Result := S_FALSE;
end;
function TEnumString.Reset: HResult;
begin
FCurrIndex := 0;
Result := S_OK;
end;
function TEnumString.Skip(celt: Integer): HResult;
begin
if (FCurrIndex + celt) <= FStrings.Count then
begin
Inc(FCurrIndex, celt);
Result := S_OK;
end
else
begin
FCurrIndex := FStrings.Count;
Result := S_FALSE;
end;
end;
{ TComboBox }
procedure TComboBox.CreateWnd;
var
hEditControl: THandle;
LStrings: IUnknown;
LAC2: IAutoComplete2;
begin
inherited;
FAutoComplete := CreateComObject(CLSID_AutoComplete) as IAutoComplete;
hEditControl := GetWindow(Handle, GW_CHILD); // alternatively, use GetComboBoxInfo() to get the Edit HWND
LStrings := TEnumString.Create(Items);
OleCheck(FAutoComplete.Init(hEditControl, LStrings, nil, nil));
if Supports(FAutoComplete, IAutoComplete2, LAC2) then
begin
// use SetOption as needed...
OleCheck(LAC2.SetOptions(...));
end;
end;
procedure TComboBox.DestroyWnd;
begin
FAutoComplete := nil;
inherited;
end;
{ TForm1 }
procedure TForm1.FormCreate(Sender: TObject);
begin
with ComboBox1 do
begin
with Items do
begin
BeginUpdate;
try
Clear;
Add('Alpha');
Add('Beta');
Add('Gamma');
Add('Delta');
finally
EndUpdate;
end;
end;
AutoComplete := False;
ItemIndex := 0;
end;
end;
end.

Related

How to get notified when disk free space changes?

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;

Register for NLM events (INetworkListManager, Advise, Sink, etc.)

In my Delphi application I'd like to get informed about network changes using the Microsoft Windows Network List Manager API (NLM): http://msdn.microsoft.com/library/ee264321
I've looked at the linked "How to register for NLM events" example and translated it to Delphi. However, I have no idea how to continue with this.
var
pNLM: INetworkListManager;
pCpc: IConnectionPointContainer;
pConnectionPoint: IConnectionPoint;
pSink: IUnknown;
dwCookie: LongInt;
const
IID_IConnectionPointContainer: TGUID = '{B196B284-BAB4-101A-B69C-00AA00341D07}';
IID_IUnknown: TGUID = '{00000000-0000-0000-C000-000000000046}';
begin
if Succeeded(CoCreateInstance(CLASS_NetworkListManager, nil, CLSCTX_ALL, IID_INetworkListManager, pNLM)) then
begin
if Succeeded(pNLM.QueryInterface(IID_IConnectionPointContainer, pCpc)) then
begin
if Succeeded(pCpc.FindConnectionPoint(IID_INetworkEvents, pConnectionPoint)) then
begin
if Succeeded(pCpc.QueryInterface(IID_IUnknown, pSink)) then
begin
pConnectionPoint.Advise(pSink, dwCookie);
end;
end;
end;
end;
end;
The article sais:
"After you have created the INetworkListManager object above you will receive INetworkEvents notifications from that point forward. pSink implements the INetworkEvent interface including those event processing methods such as NetworkAdded, NetworkDeleted, NetworkConnectivityChanged, and NetworkPropertyChanged."
Unfortunately I have no idea how to do that. There's no further instructions and so I hope someone here could instruct me / provide an example what else to do to call custom procedures for those events. Thanks.
You are passing the wrong object to Advise(). You are passing the IConnectionPointContainer object. You need to instead write your own class that implements the INetworkEvents interface, then create an instance of the class and pass that object to Advise(). That is how the NLM (or any other COM object that uses Connection Points) is able to send events to your code.
Update: You need to change your NLMEvents unit to keep the NLM object alive after StartNLMEventListener() exits, then the events will work correctly. The way you have it coded, the NLM object is local to StartNLMEventListener() and thus is released when StartNLMEventListener() exits, which unregisters your event sink.
Try this instead:
unit NLMEvents;
interface
function StartNLMEventListener: HResult;
function StopNLMEventListener: HResult;
implementation
uses
Windows, ActiveX, NETWORKLIST_TLB, SysUtils, Unit1;
type
TMyNetworkEvents = class(TInterfacedObject, INetworkEvents, INetworkConnectionEvents, INetworkListManagerEvents)
public
function NetworkAdded(networkId: TGUID): HResult; stdcall;
function NetworkConnectivityChanged(networkId: TGUID; NewConnectivity: NLM_CONNECTIVITY): HResult; stdcall;
function NetworkDeleted(networkId: TGUID): HResult; stdcall;
function NetworkPropertyChanged(networkId: TGUID; fFlags: NLM_NETWORK_PROPERTY_CHANGE): HResult; stdcall;
function ConnectivityChanged(NewConnectivity: NLM_CONNECTIVITY): HResult; stdcall;
function NetworkConnectionConnectivityChanged(networkId: TGUID; NewConnectivity: NLM_CONNECTIVITY): HResult; stdcall;
function NetworkConnectionPropertyChanged(networkId: TGUID; fFlags: NLM_NETWORK_PROPERTY_CHANGE): HResult; stdcall;
end;
var
pNLM: INetworkListManager = nil;
dwCookie1: LongInt = -1;
dwCookie2: LongInt = -1;
dwCookie3: LongInt = -1;
const
IID_IConnectionPointContainer: TGUID = '{B196B284-BAB4-101A-B69C-00AA00341D07}';
//IID_IUnknown: TGUID = '{00000000-0000-0000-C000-000000000046}';
//CLSID_NetworkListManager: TGUID = '{DCB00C01-570F-4A9B-8D69-199FDBA5723B}';
function TMyNetworkEvents.ConnectivityChanged(NewConnectivity: NLM_CONNECTIVITY): HResult; stdcall;
begin
Form2.Memo1.Lines.Add('ConnectivityChanged');
Result := S_OK;
end;
function TMyNetworkEvents.NetworkConnectionConnectivityChanged(networkId: TGUID; NewConnectivity: NLM_CONNECTIVITY): HResult; stdcall;
begin
Form2.Memo1.Lines.Add('NetworkConnectionConnectivityChanged');
Result := S_OK;
end;
function TMyNetworkEvents.NetworkConnectionPropertyChanged(networkId: TGUID; fFlags: NLM_NETWORK_PROPERTY_CHANGE): HResult; stdcall;
begin
Form2.Memo1.Lines.Add('NetworkConnectionPropertyChange');
Result := S_OK;
end;
function TMyNetworkEvents.NetworkAdded(networkId: TGUID): HResult; stdcall;
begin
Form2.Memo1.Lines.Add('NetworkAdded');
Result := S_OK;
end;
function TMyNetworkEvents.NetworkConnectivityChanged(networkId: TGUID; NewConnectivity: NLM_CONNECTIVITY): HResult; stdcall;
begin
Form2.Memo1.Lines.Add('NetworkConnectivityChanged');
Result := S_OK;
end;
function TMyNetworkEvents.NetworkDeleted(networkId: TGUID): HResult; stdcall;
begin
Form2.Memo1.Lines.Add('NetworkDeleted');
Result := S_OK;
end;
function TMyNetworkEvents.NetworkPropertyChanged(networkId: TGUID; fFlags: NLM_NETWORK_PROPERTY_CHANGE): HResult; stdcall;
begin
Form2.Memo1.Lines.Add('NetworkPropertyChanged');
Result := S_OK;
end;
function StartNLMEventListener: HResult;
var
pCpc: IConnectionPointContainer;
pConnectionPoint: IConnectionPoint;
pSink: INetworkEvents;
begin
if pNLM = nil then
begin
Result := CoCreateInstance(CLASS_NetworkListManager, nil, CLSCTX_ALL, IID_INetworkListManager, pNLM);
if Failed(Result) then
Exit;
end else
begin
Result := S_OK;
end;
if Succeeded(pNLM.QueryInterface(IID_IConnectionPointContainer, pCpc)) then
begin
pSink := TMyNetworkEvents.Create as INetworkEvents;
if dwCookie1 = -1 then
begin
if Succeeded(pCpc.FindConnectionPoint(IID_INetworkEvents, pConnectionPoint)) then
begin
pConnectionPoint.Advise(pSink, dwCookie1);
pConnectionPoint := nil;
end;
end;
if dwCookie2 = -1 then
begin
if Succeeded(pCpc.FindConnectionPoint(IID_INetworkConnectionEvents, pConnectionPoint)) then
begin
pConnectionPoint.Advise(pSink, dwCookie2);
pConnectionPoint := nil;
end;
end;
if dwCookie3 = -1 then
begin
if Succeeded(pCpc.FindConnectionPoint(IID_INetworkListManagerEvents, pConnectionPoint)) then
begin
pConnectionPoint.Advise(pSink, dwCookie3);
pConnectionPoint := nil;
end;
end;
end;
end;
function StopNLMEventListener: HResult;
var
pCpc: IConnectionPointContainer;
pConnectionPoint: IConnectionPoint;
begin
if pNLM <> nil then
begin
if Succeeded(pNLM.QueryInterface(IID_IConnectionPointContainer, pCpc)) then
begin
if dwCookie1 <> -1 then
begin
if Succeeded(pCpc.FindConnectionPoint(IID_INetworkEvents, pConnectionPoint)) then
begin
pConnectionPoint.Unadvise(dwCookie1);
pConnectionPoint := nil;
end;
end;
if dwCookie2 <> -1 then
begin
if Succeeded(pCpc.FindConnectionPoint(IID_INetworkConnectionEvents, pConnectionPoint)) then
begin
pConnectionPoint.Unadvise(dwCookie2);
pConnectionPoint := nil;
end;
end;
if dwCookie3 <> -1 then
begin
if Succeeded(pCpc.FindConnectionPoint(IID_INetworkListManagerEvents, pConnectionPoint)) then
begin
pConnectionPoint.Unadvise(dwCookie3);
pConnectionPoint := nil;
end;
end;
end;
pNLM := nil;
end;
dwCookie1 := -1;
dwCookie2 := -1;
dwCookie3 := -1;
Result := S_OK;
end;
end.

How to draw something over WebBrowser component in Delphi

Is it possible to draw or put something over the WebBrowser component to draw on it ?
When I add an image on WebBrowser this image is always under the WebBrowser.
I need this to draw area over different map types always in the same way.
For example I need to draw the same area on Google Maps and open street maps...
You should use IHTMLPainter.Draw event method for doing this. The following code needs a TWebBrowser where you have to write the OnDocumentComplete event handler.
Note that this example has one big weakness, the user input events like mouse clicking are active because the only thing what this example do is the painting over the element. I've been playing with this a little bit, but without success. This might be a good topic for another question.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
StdCtrls, SHDocVw, MSHTML, OleCtrls;
type
TElementBehavior = class(TInterfacedObject, IElementBehavior, IHTMLPainter)
private
FPaintSite: IHTMLPaintSite;
public
{ IElementBehavior }
function Init(const pBehaviorSite: IElementBehaviorSite): HRESULT; stdcall;
function Notify(lEvent: Integer; var pVar: OleVariant): HRESULT; stdcall;
function Detach: HRESULT; stdcall;
{ IHTMLPainter }
function Draw(rcBounds: tagRECT; rcUpdate: tagRECT; lDrawFlags: Integer;
hdc: hdc; pvDrawObject: Pointer): HRESULT; stdcall;
function OnResize(size: tagSIZE): HRESULT; stdcall;
function GetPainterInfo(out pInfo: _HTML_PAINTER_INFO): HRESULT; stdcall;
function HitTestPoint(pt: tagPOINT; out pbHit: Integer; out plPartID: Integer): HRESULT; stdcall;
end;
TElementBehaviorFactory = class(TInterfacedObject, IElementBehaviorFactory)
public
function FindBehavior(const bstrBehavior: WideString;
const bstrBehaviorUrl: WideString; const pSite: IElementBehaviorSite;
out ppBehavior: IElementBehavior): HRESULT; stdcall;
end;
TForm1 = class(TForm)
WebBrowser1: TWebBrowser;
procedure FormDestroy(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure WebBrowser1DocumentComplete(ASender: TObject;
const pDisp: IDispatch; var URL: OleVariant);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
Image: TBitmap;
Behavior: TElementBehavior;
Factory: TElementBehaviorFactory;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
Image := TBitmap.Create;
Image.LoadFromFile('c:\yourpicture.bmp');
WebBrowser1.Navigate('maps.google.com');
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
Behavior := nil;
Factory := nil;
Image.Free;
end;
procedure TForm1.WebBrowser1DocumentComplete(ASender: TObject;
const pDisp: IDispatch; var URL: OleVariant);
var
HTMLElement: IHTMLElement2;
FactoryVariant: OleVariant;
begin
HTMLElement := (WebBrowser1.Document as IHTMLDocument3).getElementById('map') as IHTMLElement2;
if Assigned(HTMLElement) then
begin
Behavior := TElementBehavior.Create;
Factory := TElementBehaviorFactory.Create;
FactoryVariant := IElementBehaviorFactory(Factory);
HTMLElement.addBehavior('', FactoryVariant);
end;
end;
function TElementBehaviorFactory.FindBehavior(const bstrBehavior,
bstrBehaviorUrl: WideString; const pSite: IElementBehaviorSite;
out ppBehavior: IElementBehavior): HRESULT;
begin
ppBehavior := Behavior;
Result := S_OK;
end;
function TElementBehavior.Draw(rcBounds: tagRECT; rcUpdate: tagRECT; lDrawFlags: Integer;
hdc: hdc; pvDrawObject: Pointer): HRESULT;
begin
StretchBlt(
hdc,
rcBounds.Left,
rcBounds.Top,
rcBounds.Right - rcBounds.Left,
rcBounds.Bottom - rcBounds.Top,
Image.Canvas.Handle,
0,
0,
Image.Canvas.ClipRect.Right - Image.Canvas.ClipRect.Left,
Image.Canvas.ClipRect.Bottom - Image.Canvas.ClipRect.Top,
SRCCOPY);
Result := S_OK;
end;
function TElementBehavior.GetPainterInfo(out pInfo: _HTML_PAINTER_INFO): HRESULT;
begin
pInfo.lFlags := HTMLPAINTER_OPAQUE;
pInfo.lZOrder := HTMLPAINT_ZORDER_WINDOW_TOP;
FillChar(pInfo.rcExpand, SizeOf(TRect), 0);
Result := S_OK;
end;
function TElementBehavior.HitTestPoint(pt: tagPOINT; out pbHit,
plPartID: Integer): HRESULT;
begin
Result := E_NOTIMPL;
end;
function TElementBehavior.OnResize(size: tagSIZE): HRESULT;
begin
Result := S_OK;
end;
function TElementBehavior.Detach: HRESULT;
begin
if Assigned(FPaintSite) then
FPaintSite.InvalidateRect(nil);
Result := S_OK;
end;
function TElementBehavior.Init(
const pBehaviorSite: IElementBehaviorSite): HRESULT;
begin
Result := pBehaviorSite.QueryInterface(IHTMLPaintSite, FPaintSite);
if Assigned(FPaintSite) then
FPaintSite.InvalidateRect(nil);
end;
function TElementBehavior.Notify(lEvent: Integer;
var pVar: OleVariant): HRESULT;
begin
Result := E_NOTIMPL;
end;
end.

Is there a Preview Handler VCL for Windows 7?

This article
http://msdn.microsoft.com/en-gb/library/bb776867.aspx
describes preview handlers in Windows as
Preview handlers are called when an
item is selected to show a
lightweight, rich, read-only preview
of the file's contents in the view's
reading pane. This is done without
launching the file's associated
application.
and ...
A preview handler is a hosted
application. Hosts include the
Microsoft Windows Explorer in Windows
Vista or Microsoft Outlook 2007.
Is there some Delphi VCL code which can be used as a startingpoint for such a handler?
#Mjn, right know I'm writing an article for my blog to implement Preview Handlers from Delphi, but due to lack of time, I do not know when this is complete, as others users mention by the moment no exist a VCL component in Delphi to implement preview handlers, in the past I implemented a couple of preview handlers for a customer but using Delphi-Prism and C#.
As starting point here I leave some tips.
You must use the IPreviewHandler, InitializeWithFile, InitializeWithStream, IPreviewHandlerFrame, IPreviewHandlerVisuals interfaces.
This is the Delphi translation of the headers of these interfaces
uses
Windows, ActiveX, AxCtrls, ShlObj, ComObj;
type
IIPreviewHandler = interface(IUnknown)
['{8895b1c6-b41f-4c1c-a562-0d564250836f}']
function SetWindow(hwnd: HWND; var RectangleRef: TRect): HRESULT; stdcall;
function SetRect(var RectangleRef: TRect): HRESULT; stdcall;
function DoPreview(): HRESULT; stdcall;
function Unload(): HRESULT; stdcall;
function SetFocus(): HRESULT; stdcall;
function QueryFocus(phwnd: HWND): HRESULT; stdcall;
function TranslateAccelerator(PointerToWindowMessage: MSG): HRESULT; stdcall;
end;
IInitializeWithFile = interface(IUnknown)
['{b7d14566-0509-4cce-a71f-0a554233bd9b}']
function Initialize(pszFilePath: LPWSTR; grfMode: DWORD):HRESULT;stdcall;
end;
IInitializeWithStream = interface(IUnknown)
['{b824b49d-22ac-4161-ac8a-9916e8fa3f7f}']
function Initialize(pstream: IStream; grfMode: DWORD): HRESULT; stdcall;
end;
IIPreviewHandlerFrame = interface(IUnknown)
['{fec87aaf-35f9-447a-adb7-20234491401a}']
function GetWindowContext(pinfo: HWND): HRESULT; stdcall;
function TranslateAccelerator(PointerToWindowMessage: MSG): HRESULT; stdcall;
end;
IIPreviewHandlerVisuals = interface(IUnknown)
['{8327b13c-b63f-4b24-9b8a-d010dcc3f599}']
function SetBackgroundColor(color: COLORREF ): HRESULT; stdcall;
function SetFont(plf:LOGFONTW): HRESULT; stdcall;
function SetTextColor(color: COLORREF): HRESULT; stdcall;
end;
You must create a com dll with a class which descend from these interfaces IIPreviewHandler, IIPreviewHandlerVisuals, IOleWindow, IObjectWithSite to manage the visualization and a second class to load the files to show. this class must descend from IPreviewHandler, IInitializeWithStream.
something like this
TMyPreviewHandler = class(IIPreviewHandler, IIPreviewHandlerVisuals, IOleWindow, IObjectWithSite)
TMyStream = class(IIPreviewHandler, IInitializeWithStream, IStream)
Now you must create your own implementation of the methods for the parent interfaces.
this is the list of the methods which you need implement.
IPreviewHandler -> DoPreview, SetWindow, SetRect, Unload, SetFocus, TranslateAccelerator, QueryFocus.
IObjectWithSite -> GetSite, SetSite.
IOleWindow -> GetWindow
IPreviewHandlerVisuals - > SetBackgroundColor, SetFont, SetColor
InitializeWithStream -> Initialize
finally you must register your COM in the system as well as the file extensions which will use you PrevieHandler class.
Check this project as a starting point Windows Preview Handler Pack (is written in C#) and this article View Data Your Way With Our Managed Preview Handler Framework
I have made this unit to handle all the preview handler stuff:
unit PreviewHandler;
{$WARN SYMBOL_PLATFORM OFF}
{.$DEFINE USE_CODESITE}
interface
uses
Classes, Controls, ComObj;
type
TPreviewHandler = class abstract
public
{ Create all controls needed for the preview and connect them to the
parent given. The parent follows the size, color and font of the preview
pane. The parent will stay valid until this instance is destroyed, so if
you make the parent also the owner of the controls you don't need to free
them in Destroy. }
constructor Create(AParent: TWinControl); virtual;
class function GetComClass: TComClass; virtual; abstract;
class procedure Register(const AClassID: TGUID; const AName, ADescription, AFileExtension: string);
{$REGION 'Clear Content'}
/// <summary>Clear Content</summary>
/// <remarks>This method is called when the preview should be cleared because
/// either another item was selected or the PreviewHandler will be
/// closed.</remarks>
{$ENDREGION}
procedure Unload; virtual;
end;
TStreamPreviewHandler = class abstract(TPreviewHandler)
public
{$REGION 'Render the preview from the stream data'}
/// <summary>Render the preview from the stream data</summary>
/// <remarks>Here you should render the data from the stream in whatever
/// fashion you want.</remarks>
{$ENDREGION}
procedure DoPreview(Stream: TStream); virtual; abstract;
class function GetComClass: TComClass; override; final;
end;
TFilePreviewHandler = class abstract(TPreviewHandler)
public
{$REGION 'Render the preview from the file path'}
/// <summary>Render the preview from the file path</summary>
/// <remarks>Here you should render the data from the file path in whatever
/// fashion you want.</remarks>
{$ENDREGION}
procedure DoPreview(const FilePath: String); virtual; abstract;
class function GetComClass: TComClass; override; final;
end;
implementation
uses
{$IFDEF USE_CODESITE}
CodeSiteLogging,
{$ENDIF}
Windows, ActiveX, ComServ, ShlObj, PropSys, Types, SysUtils, Graphics, ExtCtrls;
type
TPreviewHandlerClass = class of TPreviewHandler;
TComPreviewHandler = class(TComObject, IPreviewHandler, IPreviewHandlerVisuals, IObjectWithSite, IOleWindow)
strict private
function IPreviewHandler.DoPreview = IPreviewHandler_DoPreview;
function ContextSensitiveHelp(fEnterMode: LongBool): HRESULT; stdcall;
function GetSite(const riid: TGUID; out site: IInterface): HRESULT; stdcall;
function GetWindow(out wnd: HWND): HRESULT; stdcall;
function IPreviewHandler_DoPreview: HRESULT; stdcall;
function QueryFocus(var phwnd: HWND): HRESULT; stdcall;
function SetBackgroundColor(color: Cardinal): HRESULT; stdcall;
function SetFocus: HRESULT; stdcall;
function SetFont(const plf: tagLOGFONTW): HRESULT; stdcall;
function SetRect(var prc: TRect): HRESULT; stdcall;
function SetSite(const pUnkSite: IInterface): HRESULT; stdcall;
function SetTextColor(color: Cardinal): HRESULT; stdcall;
function SetWindow(hwnd: HWND; var prc: TRect): HRESULT; stdcall;
function TranslateAccelerator(var pmsg: tagMSG): HRESULT; stdcall;
function Unload: HRESULT; stdcall;
private
FBackgroundColor: Cardinal;
FBounds: TRect;
FContainer: TWinControl;
FLogFont: tagLOGFONTW;
FParentWindow: HWND;
FPreviewHandler: TPreviewHandler;
FPreviewHandlerClass: TPreviewHandlerClass;
FPreviewHandlerFrame: IPreviewHandlerFrame;
FSite: IInterface;
FTextColor: Cardinal;
protected
procedure CheckContainer;
procedure CheckPreviewHandler;
procedure InternalUnload; virtual; abstract;
procedure InternalDoPreview; virtual; abstract;
property Container: TWinControl read FContainer;
property PreviewHandler: TPreviewHandler read FPreviewHandler;
public
destructor Destroy; override;
property PreviewHandlerClass: TPreviewHandlerClass read FPreviewHandlerClass write FPreviewHandlerClass;
end;
TComStreamPreviewHandler = class(TComPreviewHandler, IInitializeWithStream)
strict private
function IInitializeWithStream.Initialize = IInitializeWithStream_Initialize;
function IInitializeWithStream_Initialize(const pstream: IStream; grfMode: Cardinal): HRESULT; stdcall;
private
FIStream: IStream;
FMode: Cardinal;
function GetPreviewHandler: TStreamPreviewHandler;
protected
procedure InternalUnload; override;
procedure InternalDoPreview; override;
property PreviewHandler: TStreamPreviewHandler read GetPreviewHandler;
end;
TComFilePreviewHandler = class(TComPreviewHandler, IInitializeWithFile)
strict private
function IInitializeWithFile.Initialize = IInitializeWithFile_Initialize;
function IInitializeWithFile_Initialize(pszFilePath: LPCWSTR; grfMode: DWORD): HRESULT; stdcall;
private
FFilePath: string;
FMode: DWORD;
function GetPreviewHandler: TFilePreviewHandler;
protected
procedure InternalDoPreview; override;
procedure InternalUnload; override;
property PreviewHandler: TFilePreviewHandler read GetPreviewHandler;
end;
TComPreviewHandlerFactory = class(TComObjectFactory)
private
FFileExtension: string;
FPreviewHandlerClass: TPreviewHandlerClass;
class procedure DeleteRegValue(const Key, ValueName: string; RootKey: DWord);
class function IsRunningOnWOW64: Boolean;
protected
property FileExtension: string read FFileExtension;
public
constructor Create(APreviewHandlerClass: TPreviewHandlerClass; const AClassID: TGUID; const AName, ADescription, AFileExtension: string);
function CreateComObject(const Controller: IUnknown): TComObject; override;
procedure UpdateRegistry(Register: Boolean); override;
property PreviewHandlerClass: TPreviewHandlerClass read FPreviewHandlerClass;
end;
TWinControlHelper = class helper for TWinControl
public
procedure SetFocusTabFirst;
procedure SetFocusTabLast;
procedure SetBackgroundColor(AColor: Cardinal);
procedure SetBoundsRect(const ARect: TRect);
procedure SetTextColor(AColor: Cardinal);
procedure SetTextFont(const Source: tagLOGFONTW);
end;
TIStreamAdapter = class(TStream)
private
FTarget: IStream;
protected
function GetSize: Int64; override;
procedure SetSize(NewSize: Longint); override;
procedure SetSize(const NewSize: Int64); override;
public
constructor Create(ATarget: IStream);
function Read(var Buffer; Count: Longint): Longint; override;
function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; overload; override;
function Write(const Buffer; Count: Longint): Longint; override;
property Target: IStream read FTarget;
end;
procedure TWinControlHelper.SetFocusTabFirst;
begin
SelectNext(nil, true, true);
end;
procedure TWinControlHelper.SetFocusTabLast;
begin
SelectNext(nil, false, true);
end;
procedure TWinControlHelper.SetBackgroundColor(AColor: Cardinal);
begin
Color := AColor;
end;
procedure TWinControlHelper.SetBoundsRect(const ARect: TRect);
begin
SetBounds(ARect.Left, ARect.Top, ARect.Right - ARect.Left, ARect.Bottom - ARect.Top);
end;
procedure TWinControlHelper.SetTextColor(AColor: Cardinal);
begin
Font.Color := AColor;
end;
procedure TWinControlHelper.SetTextFont(const Source: tagLOGFONTW);
var
fontStyle: TFontStyles;
begin
Font.Height := Source.lfHeight;
fontStyle := Font.Style;
if Source.lfWeight >= FW_BOLD then
Include(fontStyle, fsBold);
if Source.lfItalic = 1 then
Include(fontStyle, fsItalic);
if Source.lfUnderline = 1 then
Include(fontStyle, fsUnderline);
if Source.lfStrikeOut = 1 then
Include(fontStyle, fsStrikeOut);
Font.Style := fontStyle;
Font.Charset := TFontCharset(Source.lfCharSet);
Font.Name := Source.lfFaceName;
case Source.lfPitchAndFamily and $F of
VARIABLE_PITCH: Font.Pitch := fpVariable;
FIXED_PITCH: Font.Pitch := fpFixed;
else
Font.Pitch := fpDefault;
end;
Font.Orientation := Source.lfOrientation;
end;
constructor TComPreviewHandlerFactory.Create(APreviewHandlerClass: TPreviewHandlerClass; const AClassID: TGUID; const
AName, ADescription, AFileExtension: string);
begin
inherited Create(ComServ.ComServer, APreviewHandlerClass.GetComClass, AClassID, AName, ADescription, ciMultiInstance, tmApartment);
FPreviewHandlerClass := APreviewHandlerClass;
FFileExtension := AFileExtension;
end;
function TComPreviewHandlerFactory.CreateComObject(const Controller: IUnknown): TComObject;
begin
result := inherited CreateComObject(Controller);
TComPreviewHandler(result).PreviewHandlerClass := PreviewHandlerClass;
end;
class procedure TComPreviewHandlerFactory.DeleteRegValue(const Key, ValueName: string; RootKey: DWord);
var
RegKey: HKEY;
begin
if RegOpenKeyEx(RootKey, PChar(Key), 0, KEY_ALL_ACCESS, regKey) = ERROR_SUCCESS then begin
try
RegDeleteValue(regKey, PChar(ValueName));
finally
RegCloseKey(regKey)
end;
end;
end;
class function TComPreviewHandlerFactory.IsRunningOnWOW64: Boolean;
{ code taken from www.delphidabbler.com "IsWow64" }
type
// Type of IsWow64Process API fn
TIsWow64Process = function(Handle: Windows.THandle; var Res: Windows.BOOL): Windows.BOOL; stdcall;
var
IsWow64Result: Windows.BOOL; // Result from IsWow64Process
IsWow64Process: TIsWow64Process; // IsWow64Process fn reference
begin
{$IF defined(CPUX64)}
// compiled for 64-bit: can't be running on Wow64
result := false;
{$ELSE}
// Try to load required function from kernel32
IsWow64Process := Windows.GetProcAddress(Windows.GetModuleHandle('kernel32'), 'IsWow64Process');
if Assigned(IsWow64Process) then begin
// Function is implemented: call it
if not IsWow64Process(Windows.GetCurrentProcess, IsWow64Result) then
raise SysUtils.Exception.Create('IsWindows64: bad process handle');
// Return result of function
Result := IsWow64Result;
end
else
// Function not implemented: can't be running on Wow64
Result := False;
{$IFEND}
end;
procedure TComPreviewHandlerFactory.UpdateRegistry(Register: Boolean);
var
plainFileName: string;
sAppID, sClassID, ProgID, ServerKeyName, RegPrefix: string;
RootKey: HKEY;
RootKey2: HKEY;
begin
if Instancing = ciInternal then
Exit;
ComServer.GetRegRootAndPrefix(RootKey, RegPrefix);
if ComServer.PerUserRegistration then
RootKey2 := HKEY_CURRENT_USER
else
RootKey2 := HKEY_LOCAL_MACHINE;
sClassID := GUIDToString(ClassID);
ProgID := GetProgID;
ServerKeyName := RegPrefix + 'CLSID\' + sClassID + '\' + ComServer.ServerKey;
if IsRunningOnWOW64 then
sAppID := '{534A1E02-D58F-44f0-B58B-36CBED287C7C}' // for Win32 shell extension running on Win64
else
sAppID := '{6d2b5079-2f0b-48dd-ab7f-97cec514d30b}';
if Register then begin
inherited;
plainFileName := ExtractFileName(ComServer.ServerFileName);
CreateRegKey(RegPrefix + 'CLSID\' + sClassID, 'AppID', sAppID, RootKey);
if ProgID <> '' then begin
CreateRegKey(ServerKeyName, 'ProgID', ProgID, RootKey);
CreateRegKey(ServerKeyName, 'VersionIndependentProgID', ProgID, RootKey);
CreateRegKey(RegPrefix + ProgID + '\shellex\' + SID_IPreviewHandler, '', sClassID, RootKey);
CreateRegKey(RegPrefix + FileExtension, '', ProgID, RootKey);
CreateRegKey('SOFTWARE\Microsoft\Windows\CurrentVersion\PreviewHandlers', sClassID, Description, RootKey2);
end;
end
else begin
if ProgID <> '' then begin
DeleteRegValue('SOFTWARE\Microsoft\Windows\CurrentVersion\PreviewHandlers', sClassID, RootKey2);
DeleteRegKey(RegPrefix + FileExtension, RootKey);
DeleteRegKey(RegPrefix + ProgID + '\shellex', RootKey);
end;
inherited;
end;
end;
destructor TComPreviewHandler.Destroy;
begin
FPreviewHandler.Free;
FContainer.Free;
inherited Destroy;
end;
procedure TComPreviewHandler.CheckContainer;
begin
if FContainer = nil then begin
{ I sprang for a TPanel here, because it makes things so much simpler. }
FContainer := TPanel.Create(nil);
TPanel(FContainer).BevelOuter := bvNone;
FContainer.SetBackgroundColor(FBackgroundColor);
FContainer.SetTextFont(FLogFont);
FContainer.SetTextColor(FTextColor);
FContainer.SetBoundsRect(FBounds);
FContainer.ParentWindow := FParentWindow;
end;
end;
procedure TComPreviewHandler.CheckPreviewHandler;
begin
if FPreviewHandler = nil then begin
CheckContainer;
FPreviewHandler := PreviewHandlerClass.Create(Container);
end;
end;
function TComPreviewHandler.ContextSensitiveHelp(fEnterMode: LongBool): HRESULT;
begin
result := E_NOTIMPL;
end;
function TComPreviewHandler.GetSite(const riid: TGUID; out site: IInterface): HRESULT;
begin
site := nil;
if FSite = nil then
result := E_FAIL
else if Supports(FSite, riid, site) then
result := S_OK
else
result := E_NOINTERFACE;
end;
function TComPreviewHandler.GetWindow(out wnd: HWND): HRESULT;
begin
if Container = nil then begin
result := E_FAIL;
end
else begin
wnd := Container.Handle;
result := S_OK;
end;
end;
function TComPreviewHandler.IPreviewHandler_DoPreview: HRESULT;
begin
try
CheckPreviewHandler;
InternalDoPreview;
except
on E: Exception do begin
{$IFDEF USE_CODESITE}
CodeSite.SendException(E);
{$ENDIF}
end;
end;
result := S_OK;
end;
function TComPreviewHandler.QueryFocus(var phwnd: HWND): HRESULT;
begin
phwnd := GetFocus;
result := S_OK;
end;
function TComPreviewHandler.SetBackgroundColor(color: Cardinal): HRESULT;
begin
FBackgroundColor := color;
if Container <> nil then
Container.SetBackgroundColor(FBackgroundColor);
result := S_OK;
end;
function TComPreviewHandler.SetFocus: HRESULT;
begin
if Container <> nil then begin
if GetKeyState(VK_SHIFT) < 0 then
Container.SetFocusTabLast
else
Container.SetFocusTabFirst;
end;
result := S_OK;
end;
function TComPreviewHandler.SetFont(const plf: tagLOGFONTW): HRESULT;
begin
FLogFont := plf;
if Container <> nil then
Container.SetTextFont(FLogFont);
result := S_OK;
end;
function TComPreviewHandler.SetRect(var prc: TRect): HRESULT;
begin
FBounds := prc;
if Container <> nil then
Container.SetBoundsRect(FBounds);
result := S_OK;
end;
function TComPreviewHandler.SetSite(const pUnkSite: IInterface): HRESULT;
begin
FSite := PUnkSite;
FPreviewHandlerFrame := FSite as IPreviewHandlerFrame;
result := S_OK;
end;
function TComPreviewHandler.SetTextColor(color: Cardinal): HRESULT;
begin
FTextColor := color;
if Container <> nil then
Container.SetTextColor(FTextColor);
result := S_OK;
end;
function TComPreviewHandler.SetWindow(hwnd: HWND; var prc: TRect): HRESULT;
begin
FParentWindow := hwnd;
FBounds := prc;
if Container <> nil then begin
Container.ParentWindow := FParentWindow;
Container.SetBoundsRect(FBounds);
end;
result := S_OK;
end;
function TComPreviewHandler.TranslateAccelerator(var pmsg: tagMSG): HRESULT;
begin
if FPreviewHandlerFrame = nil then
result := S_FALSE
else
result := FPreviewHandlerFrame.TranslateAccelerator(pmsg);
end;
function TComPreviewHandler.Unload: HRESULT;
begin
if PreviewHandler <> nil then
PreviewHandler.Unload;
InternalUnload;
result := S_OK;
end;
constructor TPreviewHandler.Create(AParent: TWinControl);
begin
inherited Create;
end;
class procedure TPreviewHandler.Register(const AClassID: TGUID; const AName, ADescription, AFileExtension: string);
begin
TComPreviewHandlerFactory.Create(Self, AClassID, AName, ADescription, AFileExtension);
end;
procedure TPreviewHandler.Unload;
begin
end;
constructor TIStreamAdapter.Create(ATarget: IStream);
begin
inherited Create;
FTarget := ATarget;
end;
function TIStreamAdapter.GetSize: Int64;
var
statStg: TStatStg;
begin
if Target.Stat(statStg, STATFLAG_NONAME) = S_OK then
result := statStg.cbSize
else
result := -1;
end;
function TIStreamAdapter.Read(var Buffer; Count: Longint): Longint;
begin
Target.Read(#Buffer, Count, #result);
end;
function TIStreamAdapter.Seek(const Offset: Int64; Origin: TSeekOrigin): Int64;
begin
Target.Seek(Offset, Ord(Origin), result);
end;
procedure TIStreamAdapter.SetSize(const NewSize: Int64);
begin
raise ENotImplemented.Create('SetSize not implemented');
// Target.SetSize(NewSize);
end;
procedure TIStreamAdapter.SetSize(NewSize: Longint);
begin
SetSize(Int64(NewSize));
end;
function TIStreamAdapter.Write(const Buffer; Count: Longint): Longint;
begin
raise ENotImplemented.Create('Write not implemented');
// Target.Write(#Buffer, Count, #result);
end;
function TComStreamPreviewHandler.GetPreviewHandler: TStreamPreviewHandler;
begin
Result := inherited PreviewHandler as TStreamPreviewHandler;
end;
function TComStreamPreviewHandler.IInitializeWithStream_Initialize(const pstream: IStream; grfMode: Cardinal): HRESULT;
begin
FIStream := pStream;
FMode := grfMode;
result := S_OK;
end;
procedure TComStreamPreviewHandler.InternalUnload;
begin
FIStream := nil;
end;
procedure TComStreamPreviewHandler.InternalDoPreview;
var
stream: TIStreamAdapter;
begin
stream := TIStreamAdapter.Create(FIStream);
try
PreviewHandler.DoPreview(stream);
finally
stream.Free;
end;
end;
function TComFilePreviewHandler.GetPreviewHandler: TFilePreviewHandler;
begin
Result := inherited PreviewHandler as TFilePreviewHandler;
end;
function TComFilePreviewHandler.IInitializeWithFile_Initialize(pszFilePath: LPCWSTR; grfMode: DWORD): HRESULT;
begin
FFilePath := pszFilePath;
FMode := grfMode;
result := S_OK;
end;
procedure TComFilePreviewHandler.InternalDoPreview;
begin
PreviewHandler.DoPreview(FFilePath);
end;
procedure TComFilePreviewHandler.InternalUnload;
begin
FFilePath := '';
end;
class function TFilePreviewHandler.GetComClass: TComClass;
begin
result := TComFilePreviewHandler;
end;
class function TStreamPreviewHandler.GetComClass: TComClass;
begin
result := TComStreamPreviewHandler;
end;
initialization
{$IFDEF USE_CODESITE}
CodeSiteManager.ConnectUsingTcp;
{$ENDIF}
end.
A sample preview handler based on this unit is shown here:
unit MyPreviewHandler;
interface
uses
PreviewHandler, Classes, Controls, StdCtrls;
const
{$REGION 'Unique ClassID of your PreviewHandler'}
/// <summary>Unique ClassID of your PreviewHandler</summary>
/// <remarks>Don't forget to create a new one. Best use Ctrl-G.</remarks>
{$ENDREGION}
CLASS_MyPreviewHandler: TGUID = '{64644512-C345-469F-B5FB-EB351E20129D}';
type
{$REGION 'Sample PreviewHandler'}
/// <summary>Sample PreviewHandler</summary>
/// <remarks>A sample PreviewHandler. You only have to derive from
/// TFilePreviewHandler or TStreamPreviewHandler and override some methods.</remarks>
{$ENDREGION}
TMyPreviewHandler = class(TFilePreviewHandler)
private
FTextLabel: TLabel;
protected
public
constructor Create(AParent: TWinControl); override;
procedure Unload; override;
procedure DoPreview(const FilePath: string); override;
property TextLabel: TLabel read FTextLabel;
end;
implementation
uses
SysUtils;
constructor TMyPreviewHandler.Create(AParent: TWinControl);
begin
inherited;
FTextLabel := TLabel.Create(AParent);
FTextLabel.Parent := AParent;
FTextLabel.AutoSize := false;
FTextLabel.Align := alClient;
FTextLabel.Alignment := taCenter;
FTextLabel.Layout := tlCenter;
FTextLabel.WordWrap := true;
end;
procedure TMyPreviewHandler.DoPreview(const FilePath: string);
begin
TextLabel.Caption := GetPackageDescription(PWideChar(FilePath));
end;
procedure TMyPreviewHandler.Unload;
begin
TextLabel.Caption := '';
inherited;
end;
initialization
{ Register your PreviewHandler with the ClassID, name, descripton and file extension }
TMyPreviewHandler.Register(CLASS_MyPreviewHandler, 'bplfile', 'BPL Binary Preview Handler', '.bpl');
end.
library MyPreviewHandlerLib;
uses
ComServ,
PreviewHandler in 'PreviewHandler.pas' {PreviewHandler: CoClass},
MyPreviewHandler in 'MyPreviewHandler.pas';
exports
DllGetClassObject,
DllCanUnloadNow,
DllRegisterServer,
DllUnregisterServer,
DllInstall;
{$R *.RES}
begin
end.
You may be interested in this article in my blog describing some more details on that framework.
I have never seen such a thing, but since the whole thing is build in COM, you would start by importing the type library, and implementing the required interfaces, including IPreviewHandlerFrame. [Sorry, not very helpful. But this is a pretty obscure area, so I'm not surprised that Delphi hasn't got a prebuilt component set for this.]
I can't find any references to using IPreviewHandlerFrame in Delphi, but did manage to come up with a C# example here - maybe it'll give you a starting point.
I think you have to write a COM-Server yourself, which provides the described IPreviwHandler-Interfacees. (There is no type library to import...) I am very interested in such a code as well and I am searching for quite a while now. I am not very experienced with COM-Server-writing... If you find something, let me know please! As I will share my code also, if I get some...
Andreas

Delphi 7 and Vista/Windows 7 common dialogs - events do not work

I'm trying to modify the Delphi 7 Dialogs.pas to access the newer Windows 7 Open/Save dialog boxes (see Creating Windows Vista Ready Applications with Delphi). I can display the dialogs using the suggested modifications; however, events such as OnFolderChange and OnCanClose no longer function.
This appears to be related to changing the Flags:= OFN_ENABLEHOOK to Flags:=0. When Flags is set to 0 the TOpenDialog.Wndproc is bypassed and the appropriate CDN_xxxxxxx messages are not trapped.
Can anyone suggest further code modifications to the D7 Dialogs.pas that will both display the newer common dialogs and maintain the event features of the original controls?
Thanks...
You should use the IFileDialog Interface and call its Advise() method with an implementation of the IFileDialogEvents Interface. The Delphi 7 Windows header units won't contain the necessary declarations, so they must be copied (and translated) from the SDK header files (or maybe there's already another header translation available?), but apart from that additional effort there shouldn't be any problem to call this from Delphi 7 (or even earlier Delphi versions).
Edit:
OK, since you didn't react in any way to the answers I'll add some more information. A C sample on how to use the interfaces can be had here. It's easy to translate it to Delphi code, provided you have the necessary import units.
I threw together a small sample in Delphi 4. For simplicity I created a TOpenDialog descendant (you would probably modify the original class) and implemented the IFileDialogEvents directly on it:
type
TVistaOpenDialog = class(TOpenDialog, IFileDialogEvents)
private
// IFileDialogEvents implementation
function OnFileOk(const pfd: IFileDialog): HResult; stdcall;
function OnFolderChanging(const pfd: IFileDialog;
const psiFolder: IShellItem): HResult; stdcall;
function OnFolderChange(const pfd: IFileDialog): HResult; stdcall;
function OnSelectionChange(const pfd: IFileDialog): HResult; stdcall;
function OnShareViolation(const pfd: IFileDialog;
const psi: IShellItem; out pResponse: DWORD): HResult; stdcall;
function OnTypeChange(const pfd: IFileDialog): HResult; stdcall;
function OnOverwrite(const pfd: IFileDialog; const psi: IShellItem;
out pResponse: DWORD): HResult; stdcall;
public
function Execute: Boolean; override;
end;
function TVistaOpenDialog.Execute: Boolean;
var
guid: TGUID;
Ifd: IFileDialog;
hr: HRESULT;
Cookie: Cardinal;
Isi: IShellItem;
pWc: PWideChar;
s: WideString;
begin
CLSIDFromString(SID_IFileDialog, guid);
hr := CoCreateInstance(CLSID_FileOpenDialog, nil, CLSCTX_INPROC_SERVER,
guid, Ifd);
if Succeeded(hr) then begin
Ifd.Advise(Self, Cookie);
// call DisableTaskWindows() etc.
// see implementation of Application.MessageBox()
try
hr := Ifd.Show(Application.Handle);
finally
// call EnableTaskWindows() etc.
// see implementation of Application.MessageBox()
end;
Ifd.Unadvise(Cookie);
if Succeeded(hr) then begin
hr := Ifd.GetResult(Isi);
if Succeeded(hr) then begin
Assert(Isi <> nil);
// TODO: just for testing, needs to be implemented properly
if Succeeded(Isi.GetDisplayName(SIGDN_NORMALDISPLAY, pWc))
and (pWc <> nil)
then begin
s := pWc;
FileName := s;
end;
end;
end;
Result := Succeeded(hr);
exit;
end;
Result := inherited Execute;
end;
function TVistaOpenDialog.OnFileOk(const pfd: IFileDialog): HResult;
var
pszName: PWideChar;
s: WideString;
begin
if Succeeded(pfd.GetFileName(pszName)) and (pszName <> nil) then begin
s := pszName;
if AnsiCompareText(ExtractFileExt(s), '.txt') = 0 then begin
Result := S_OK;
exit;
end;
end;
Result := S_FALSE;
end;
function TVistaOpenDialog.OnFolderChange(const pfd: IFileDialog): HResult;
begin
Result := S_OK;
end;
function TVistaOpenDialog.OnFolderChanging(const pfd: IFileDialog;
const psiFolder: IShellItem): HResult;
begin
Result := S_OK;
end;
function TVistaOpenDialog.OnOverwrite(const pfd: IFileDialog;
const psi: IShellItem; out pResponse: DWORD): HResult;
begin
Result := S_OK;
end;
function TVistaOpenDialog.OnSelectionChange(
const pfd: IFileDialog): HResult;
begin
Result := S_OK;
end;
function TVistaOpenDialog.OnShareViolation(const pfd: IFileDialog;
const psi: IShellItem; out pResponse: DWORD): HResult;
begin
Result := S_OK;
end;
function TVistaOpenDialog.OnTypeChange(const pfd: IFileDialog): HResult;
begin
Result := S_OK;
end;
If you run this on Windows 7 it will show the new dialog and accept only files with the txt extension. This is hard-coded and needs to be implemented by going through the OnClose event of the dialog. There's lots more to be done, but the provided code should suffice as a starting point.
Here's the framework for a Delphi 7 Vista/Win7 dialog component (and a unit that calls it). I've tried to duplicate the TOpenDialog's events (e.g., OnCanClose). The type definitions are not included in the component, but can be found in some newer ShlObj and ActiveX units out there on the net.
I had a problem trying to convert an old style Filter string to a FileTypes array (see below). So for now, you can set the FileTypes array as shown. Any help on filter conversion issue or other improvements are welcome.
Here's the code:
{Example of using the TWin7FileDialog delphi component to access the
Vista/Win7 File Dialog AND handle basic events.}
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Win7FileDialog;
type
TForm1 = class(TForm)
btnOpenFile: TButton;
btnSaveFile: TButton;
procedure btnOpenFileClick(Sender: TObject);
procedure btnSaveFileClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
procedure DoDialogCanClose(Sender: TObject; var CanClose: Boolean);
procedure DoDialogFolderChange(Sender: TObject);
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{Using the dialog to open a file}
procedure TForm1.btnOpenFileClick(Sender: TObject);
var
i: integer;
aOpenDialog: TWin7FileDialog;
aFileTypesArray: TComdlgFilterSpecArray;
begin
aOpenDialog:=TWin7FileDialog.Create(Owner);
aOpenDialog.Title:='My Win 7 Open Dialog';
aOpenDialog.DialogType:=dtOpen;
aOpenDialog.OKButtonLabel:='Open';
aOpenDialog.DefaultExt:='pas';
aOpenDialog.InitialDir:='c:\program files\borland\delphi7\source';
aOpenDialog.Options:=[fosPathMustExist, fosFileMustExist];
//aOpenDialog.Filter := 'Text files (*.txt)|*.TXT|
Pascal files (*.pas)|*.PAS|All Files (*.*)|*.*';
// Create an array of file types
SetLength(aFileTypesArray,3);
aFileTypesArray[0].pszName:=PWideChar(WideString('Text Files (*.txt)'));
aFileTypesArray[0].pszSpec:=PWideChar(WideString('*.txt'));
aFileTypesArray[1].pszName:=PWideChar(WideString('Pascal Files (*.pas)'));
aFileTypesArray[1].pszSpec:=PWideChar(WideString('*.pas'));
aFileTypesArray[2].pszName:=PWideChar(WideString('All Files (*.*)'));
aFileTypesArray[2].pszSpec:=PWideChar(WideString('*.*'));
aOpenDialog.FilterArray:=aFileTypesArray;
aOpenDialog.FilterIndex:=1;
aOpenDialog.OnCanClose:=DoDialogCanClose;
aOpenDialog.OnFolderChange:=DoDialogFolderChange;
if aOpenDialog.Execute then
begin
showMessage(aOpenDialog.Filename);
end;
end;
{Example of using the OnCanClose event}
procedure TForm1.DoDialogCanClose(Sender: TObject;
var CanClose: Boolean);
begin
if UpperCase(ExtractFilename(TWin7FileDialog(Sender).Filename))=
'TEMPLATE.SSN' then
begin
MessageDlg('The Template.ssn filename is reserved for use by the system.',
mtInformation, [mbOK], 0);
CanClose:=False;
end
else
begin
CanClose:=True;
end;
end;
{Helper function to get path from ShellItem}
function PathFromShellItem(aShellItem: IShellItem): string;
var
hr: HRESULT;
aPath: PWideChar;
begin
hr:=aShellItem.GetDisplayName(SIGDN_FILESYSPATH,aPath);
if hr = 0 then
begin
Result:=aPath;
end
else
Result:='';
end;
{Example of handling a folder change}
procedure TForm1.DoDialogFolderChange(Sender: TObject);
var
aShellItem: IShellItem;
hr: HRESULT;
aFilename: PWideChar;
begin
hr:=TWin7FileDialog(Sender).FileDialog.GetFolder(aShellItem);
if hr = 0 then
begin
// showmessage(PathFromShellItem(aShellItem));
end;
end;
{Using the dialog to save a file}
procedure TForm1.btnSaveFileClick(Sender: TObject);
var
aSaveDialog: TWin7FileDialog;
aFileTypesArray: TComdlgFilterSpecArray;
begin
aSaveDialog:=TWin7FileDialog.Create(Owner);
aSaveDialog.Title:='My Win 7 Save Dialog';
aSaveDialog.DialogType:=dtSave;
aSaveDialog.OKButtonLabel:='Save';
aSaveDialog.DefaultExt:='pas';
aSaveDialog.InitialDir:='c:\program files\borland\delphi7\source';
aSaveDialog.Options:=[fosNoReadOnlyReturn, fosOverwritePrompt];
//aSaveDialog.Filter := 'Text files (*.txt)|*.TXT|
Pascal files (*.pas)|*.PAS';
{Create an array of file types}
SetLength(aFileTypesArray,3);
aFileTypesArray[0].pszName:=PWideChar(WideString('Text Files (*.txt)'));
aFileTypesArray[0].pszSpec:=PWideChar(WideString('*.txt'));
aFileTypesArray[1].pszName:=PWideChar(WideString('Pascal Files (*.pas)'));
aFileTypesArray[1].pszSpec:=PWideChar(WideString('*.pas'));
aFileTypesArray[2].pszName:=PWideChar(WideString('All Files (*.*)'));
aFileTypesArray[2].pszSpec:=PWideChar(WideString('*.*'));
aSaveDialog.FilterArray:=aFileTypesArray;
aSaveDialog.OnCanClose:=DoDialogCanClose;
aSaveDialog.OnFolderChange:=DoDialogFolderChange;
if aSaveDialog.Execute then
begin
showMessage(aSaveDialog.Filename);
end;
end;
end.
{A sample delphi 7 component to access the
Vista/Win7 File Dialog AND handle basic events.}
unit Win7FileDialog;
interface
uses
SysUtils, Classes, Forms, Dialogs, Windows,DesignIntf, ShlObj,
ActiveX, CommDlg;
{Search the internet for new ShlObj and ActiveX units to get necessary
type declarations for IFileDialog, etc.. These interfaces can otherwise
be embedded into this component.}
Type
TOpenOption = (fosOverwritePrompt,
fosStrictFileTypes,
fosNoChangeDir,
fosPickFolders,
fosForceFileSystem,
fosAllNonStorageItems,
fosNoValidate,
fosAllowMultiSelect,
fosPathMustExist,
fosFileMustExist,
fosCreatePrompt,
fosShareAware,
fosNoReadOnlyReturn,
fosNoTestFileCreate,
fosHideMRUPlaces,
fosHidePinnedPlaces,
fosNoDereferenceLinks,
fosDontAddToRecent,
fosForceShowHidden,
fosDefaultNoMiniMode,
fosForcePreviewPaneOn);
TOpenOptions = set of TOpenOption;
type
TDialogType = (dtOpen,dtSave);
type
TWin7FileDialog = class(TOpenDialog)
private
{ Private declarations }
FOptions: TOpenOptions;
FDialogType: TDialogType;
FOKButtonLabel: string;
FFilterArray: TComdlgFilterSpecArray;
procedure SetOKButtonLabel(const Value: string);
protected
{ Protected declarations }
function CanClose(Filename:TFilename): Boolean;
function DoExecute: Bool;
public
{ Public declarations }
FileDialog: IFileDialog;
FileDialogCustomize: IFileDialogCustomize;
FileDialogEvents: IFileDialogEvents;
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
function Execute: Boolean; override;
published
{ Published declarations }
property DefaultExt;
property DialogType: TDialogType read FDialogType write FDialogType
default dtOpen;
property FileName;
property Filter;
property FilterArray: TComdlgFilterSpecArray read fFilterArray
write fFilterArray;
property FilterIndex;
property InitialDir;
property Options: TOpenOptions read FOptions write FOptions
default [fosNoReadOnlyReturn, fosOverwritePrompt];
property Title;
property OKButtonLabel: string read fOKButtonLabel write SetOKButtonLabel;
property OnCanClose;
property OnFolderChange;
property OnSelectionChange;
property OnTypeChange;
property OnClose;
property OnShow;
// property OnIncludeItem;
end;
TFileDialogEvent = class(TInterfacedObject, IFileDialogEvents,
IFileDialogControlEvents)
private
{ Private declarations }
// IFileDialogEvents
function OnFileOk(const pfd: IFileDialog): HResult; stdcall;
function OnFolderChanging(const pfd: IFileDialog;
const psiFolder: IShellItem): HResult; stdcall;
function OnFolderChange(const pfd: IFileDialog): HResult; stdcall;
function OnSelectionChange(const pfd: IFileDialog): HResult; stdcall;
function OnShareViolation(const pfd: IFileDialog; const psi: IShellItem;
out pResponse: DWORD): HResult; stdcall;
function OnTypeChange(const pfd: IFileDialog): HResult; stdcall;
function OnOverwrite(const pfd: IFileDialog; const psi: IShellItem;
out pResponse: DWORD): HResult; stdcall;
// IFileDialogControlEvents
function OnItemSelected(const pfdc: IFileDialogCustomize; dwIDCtl,
dwIDItem: DWORD): HResult; stdcall;
function OnButtonClicked(const pfdc: IFileDialogCustomize;
dwIDCtl: DWORD): HResult; stdcall;
function OnCheckButtonToggled(const pfdc: IFileDialogCustomize;
dwIDCtl: DWORD; bChecked: BOOL): HResult; stdcall;
function OnControlActivating(const pfdc: IFileDialogCustomize;
dwIDCtl: DWORD): HResult; stdcall;
public
{ Public declarations }
ParentDialog: TWin7FileDialog;
end;
procedure Register;
implementation
constructor TWin7FileDialog.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
end;
destructor TWin7FileDialog.Destroy;
begin
inherited Destroy;
end;
procedure TWin7FileDialog.SetOKButtonLabel(const Value: string);
begin
if Value<>fOKButtonLabel then
begin
fOKButtonLabel := Value;
end;
end;
function TWin7FileDialog.CanClose(Filename: TFilename): Boolean;
begin
Result := DoCanClose;
end;
{Helper function to get path from ShellItem}
function PathFromShellItem(aShellItem: IShellItem): string;
var
hr: HRESULT;
aPath: PWideChar;
begin
hr:=aShellItem.GetDisplayName(SIGDN_FILESYSPATH,aPath);
if hr = 0 then
begin
Result:=aPath;
end
else
Result:='';
end;
function TFileDialogEvent.OnFileOk(const pfd: IFileDialog): HResult; stdcall
var
aShellItem: IShellItem;
hr: HRESULT;
aFilename: PWideChar;
begin
{Get selected filename and check CanClose}
aShellItem:=nil;
hr:=pfd.GetResult(aShellItem);
if hr = 0 then
begin
hr:=aShellItem.GetDisplayName(SIGDN_FILESYSPATH,aFilename);
if hr = 0 then
begin
ParentDialog.Filename:=aFilename;
if not ParentDialog.CanClose(aFilename) then
begin
result := s_FALSE;
Exit;
end;
end;
end;
result := s_OK;
end;
function TFileDialogEvent.OnFolderChanging(const pfd: IFileDialog;
const psiFolder: IShellItem): HResult; stdcall
begin
{Not currently handled}
result := s_OK;
end;
function TFileDialogEvent.OnFolderChange(const pfd: IFileDialog):
HResult; stdcall
begin
ParentDialog.DoFolderChange;
result := s_OK;
end;
function TFileDialogEvent.OnSelectionChange(const pfd: IFileDialog):
HResult; stdcall
begin
ParentDialog.DoSelectionChange;
result := s_OK;
end;
function TFileDialogEvent.OnShareViolation(const pfd: IFileDialog;
const psi: IShellItem;out pResponse: DWORD): HResult; stdcall
begin
{Not currently handled}
result := s_OK;
end;
function TFileDialogEvent.OnTypeChange(const pfd: IFileDialog):
HResult; stdcall;
begin
ParentDialog.DoTypeChange;
result := s_OK;
end;
function TFileDialogEvent.OnOverwrite(const pfd: IFileDialog;
const psi: IShellItem;out pResponse: DWORD): HResult; stdcall;
begin
{Not currently handled}
result := s_OK;
end;
function TFileDialogEvent.OnItemSelected(const pfdc: IFileDialogCustomize;
dwIDCtl,dwIDItem: DWORD): HResult; stdcall;
begin
{Not currently handled}
// Form1.Caption := Format('%d:%d', [dwIDCtl, dwIDItem]);
result := s_OK;
end;
function TFileDialogEvent.OnButtonClicked(const pfdc: IFileDialogCustomize;
dwIDCtl: DWORD): HResult; stdcall;
begin
{Not currently handled}
result := s_OK;
end;
function TFileDialogEvent.OnCheckButtonToggled(const pfdc: IFileDialogCustomize;
dwIDCtl: DWORD; bChecked: BOOL): HResult; stdcall;
begin
{Not currently handled}
result := s_OK;
end;
function TFileDialogEvent.OnControlActivating(const pfdc: IFileDialogCustomize;
dwIDCtl: DWORD): HResult; stdcall;
begin
{Not currently handled}
result := s_OK;
end;
procedure ParseDelimited(const sl : TStrings; const value : string;
const delimiter : string) ;
var
dx : integer;
ns : string;
txt : string;
delta : integer;
begin
delta := Length(delimiter) ;
txt := value + delimiter;
sl.BeginUpdate;
sl.Clear;
try
while Length(txt) > 0 do
begin
dx := Pos(delimiter, txt) ;
ns := Copy(txt,0,dx-1) ;
sl.Add(ns) ;
txt := Copy(txt,dx+delta,MaxInt) ;
end;
finally
sl.EndUpdate;
end;
end;
//function TWin7FileDialog.DoExecute(Func: Pointer): Bool;
function TWin7FileDialog.DoExecute: Bool;
var
aFileDialogEvent: TFileDialogEvent;
aCookie: cardinal;
aWideString: WideString;
aFilename: PWideChar;
hr: HRESULT;
aShellItem: IShellItem;
aShellItemFilter: IShellItemFilter;
aComdlgFilterSpec: TComdlgFilterSpec;
aComdlgFilterSpecArray: TComdlgFilterSpecArray;
i: integer;
aStringList: TStringList;
aFileTypesCount: integer;
aFileTypesArray: TComdlgFilterSpecArray;
aOptionsSet: Cardinal;
begin
if DialogType = dtSave then
begin
CoCreateInstance(CLSID_FileSaveDialog, nil, CLSCTX_INPROC_SERVER,
IFileSaveDialog, FileDialog);
end
else
begin
CoCreateInstance(CLSID_FileOpenDialog, nil, CLSCTX_INPROC_SERVER,
IFileOpenDialog, FileDialog);
end;
// FileDialog.QueryInterface(
// StringToGUID('{8016B7B3-3D49-4504-A0AA-2A37494E606F}'),
// FileDialogCustomize);
// FileDialogCustomize.AddText(1000, 'My first Test');
{Set Initial Directory}
aWideString:=InitialDir;
aShellItem:=nil;
hr:=SHCreateItemFromParsingName(PWideChar(aWideString), nil,
StringToGUID(SID_IShellItem), aShellItem);
FileDialog.SetFolder(aShellItem);
{Set Title}
aWideString:=Title;
FileDialog.SetTitle(PWideChar(aWideString));
{Set Options}
aOptionsSet:=0;
if fosOverwritePrompt in Options then aOptionsSet:=
aOptionsSet + FOS_OVERWRITEPROMPT;
if fosStrictFileTypes in Options then aOptionsSet:=
aOptionsSet + FOS_STRICTFILETYPES;
if fosNoChangeDir in Options then aOptionsSet:=
aOptionsSet + FOS_NOCHANGEDIR;
if fosPickFolders in Options then aOptionsSet:=
aOptionsSet + FOS_PICKFOLDERS;
if fosForceFileSystem in Options then aOptionsSet:=
aOptionsSet + FOS_FORCEFILESYSTEM;
if fosAllNonStorageItems in Options then aOptionsSet:=
aOptionsSet + FOS_ALLNONSTORAGEITEMS;
if fosNoValidate in Options then aOptionsSet:=
aOptionsSet + FOS_NOVALIDATE;
if fosAllowMultiSelect in Options then aOptionsSet:=
aOptionsSet + FOS_ALLOWMULTISELECT;
if fosPathMustExist in Options then aOptionsSet:=
aOptionsSet + FOS_PATHMUSTEXIST;
if fosFileMustExist in Options then aOptionsSet:=
aOptionsSet + FOS_FILEMUSTEXIST;
if fosCreatePrompt in Options then aOptionsSet:=
aOptionsSet + FOS_CREATEPROMPT;
if fosShareAware in Options then aOptionsSet:=
aOptionsSet + FOS_SHAREAWARE;
if fosNoReadOnlyReturn in Options then aOptionsSet:=
aOptionsSet + FOS_NOREADONLYRETURN;
if fosNoTestFileCreate in Options then aOptionsSet:=
aOptionsSet + FOS_NOTESTFILECREATE;
if fosHideMRUPlaces in Options then aOptionsSet:=
aOptionsSet + FOS_HIDEMRUPLACES;
if fosHidePinnedPlaces in Options then aOptionsSet:=
aOptionsSet + FOS_HIDEPINNEDPLACES;
if fosNoDereferenceLinks in Options then aOptionsSet:=
aOptionsSet + FOS_NODEREFERENCELINKS;
if fosDontAddToRecent in Options then aOptionsSet:=
aOptionsSet + FOS_DONTADDTORECENT;
if fosForceShowHidden in Options then aOptionsSet:=
aOptionsSet + FOS_FORCESHOWHIDDEN;
if fosDefaultNoMiniMode in Options then aOptionsSet:=
aOptionsSet + FOS_DEFAULTNOMINIMODE;
if fosForcePreviewPaneOn in Options then aOptionsSet:=
aOptionsSet + FOS_FORCEPREVIEWPANEON;
FileDialog.SetOptions(aOptionsSet);
{Set OKButtonLabel}
aWideString:=OKButtonLabel;
FileDialog.SetOkButtonLabel(PWideChar(aWideString));
{Set Default Extension}
aWideString:=DefaultExt;
FileDialog.SetDefaultExtension(PWideChar(aWideString));
{Set Default Filename}
aWideString:=FileName;
FileDialog.SetFilename(PWideChar(aWideString));
{Note: Attempting below to automatically parse an old style filter string into
the newer FileType array; however the below code overwrites memory when the
stringlist item is typecast to PWideChar and assigned to an element of the
FileTypes array. What's the correct way to do this??}
{Set FileTypes (either from Filter or FilterArray)}
if length(Filter)>0 then
begin
{
aStringList:=TStringList.Create;
try
ParseDelimited(aStringList,Filter,'|');
aFileTypesCount:=Trunc(aStringList.Count/2)-1;
i:=0;
While i <= aStringList.Count-1 do
begin
SetLength(aFileTypesArray,Length(aFileTypesArray)+1);
aFileTypesArray[Length(aFileTypesArray)-1].pszName:=
PWideChar(WideString(aStringList[i]));
aFileTypesArray[Length(aFileTypesArray)-1].pszSpec:=
PWideChar(WideString(aStringList[i+1]));
Inc(i,2);
end;
FileDialog.SetFileTypes(length(aFileTypesArray),aFileTypesArray);
finally
aStringList.Free;
end;
}
end
else
begin
FileDialog.SetFileTypes(length(FilterArray),FilterArray);
end;
{Set FileType (filter) index}
FileDialog.SetFileTypeIndex(FilterIndex);
aFileDialogEvent:=TFileDialogEvent.Create;
aFileDialogEvent.ParentDialog:=self;
aFileDialogEvent.QueryInterface(IFileDialogEvents,FileDialogEvents);
FileDialog.Advise(aFileDialogEvent,aCookie);
hr:=FileDialog.Show(Application.Handle);
if hr = 0 then
begin
aShellItem:=nil;
hr:=FileDialog.GetResult(aShellItem);
if hr = 0 then
begin
hr:=aShellItem.GetDisplayName(SIGDN_FILESYSPATH,aFilename);
if hr = 0 then
begin
Filename:=aFilename;
end;
end;
Result:=true;
end
else
begin
Result:=false;
end;
FileDialog.Unadvise(aCookie);
end;
function TWin7FileDialog.Execute: Boolean;
begin
Result := DoExecute;
end;
procedure Register;
begin
RegisterComponents('Dialogs', [TWin7FileDialog]);
end;
end.
JeffR - The problem with your filtering code was related to the casting to a PWideChar of a conversion to WideString.
The Converted widestring was not assigned to anything, so would have been on the stack or heap, saving a pointer to a temporary value on the stack or heap is inherently dangerous!
As suggested by loursonwinny, you could use StringToOleStr, but this alone will cause a memory leak, as the memory containing the created OleStr would never be released.
My reworked version of this section of the code is:
{Set FileTypes (either from Filter or FilterArray)}
if length(Filter)>0 then
begin
aStringList:=TStringList.Create;
try
ParseDelimited(aStringList,Filter,'|');
i:=0;
While i <= aStringList.Count-1 do
begin
SetLength(aFileTypesArray,Length(aFileTypesArray)+1);
aFileTypesArray[Length(aFileTypesArray)-1].pszName:=
StringToOleStr(aStringList[i]);
aFileTypesArray[Length(aFileTypesArray)-1].pszSpec:=
StringToOleStr(aStringList[i+1]);
Inc(i,2);
end;
FileDialog.SetFileTypes(length(aFileTypesArray),aFileTypesArray);
finally
for i := 0 to Length(aFileTypesArray) - 1 do
begin
SysFreeString(aFileTypesArray[i].pszName);
SysFreeString(aFileTypesArray[i].pszSpec);
end;
aStringList.Free;
end;
end
else
begin
FileDialog.SetFileTypes(length(FilterArray),FilterArray);
end;
Many thanks for you code sample as it saved me a lot of work!!
I was looking around a bit, and made this quick patch for FPC/Lazarus, but of course you can use this as basis for D7 upgrading too:
(Deleted, use current FPC sources, since bugfixes were applied to this functionality)
Note: untested, and might contain symbols not in D7.

Resources