I'm using TChromium and I need to navigate to a specific page and save a specific image of this page to a file.
I know how to navigate and extract the HTML source to get the image's address, but I don't know how to save the image to my local filesystem.
How can I do it using some TChromium method ?
I don't want to use another component (such TIdHTTP) to do it because the site requires login and the image relies on the active session.
Thanks in advance !
From CEF forums:
"CEF does not currently support the extraction of cached resources.
You can identify the request that originally returned the content by
overriding CefRequestHandler::OnBeforeResourceLoad() and then execute
the request yourself using CefWebURLRequest the retrieve and save the
contents."
Another approach is to add a context menu as asked here - TChromium how to add "Save Picture" item in Context Menu? and where TLama has made a code snippet:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtDlgs, IdHTTP, cefvcl, ceflib;
const
MENU_ID_SAVE_IMAGE_AS = Ord(MENU_ID_USER_FIRST) + 1;
type
TDownloader = class(TThread)
private
FURL: string;
FFileName: string;
protected
procedure Execute; override;
public
constructor Create(const URL, FileName: string); reintroduce;
end;
type
TForm1 = class(TForm)
Button1: TButton;
Chromium1: TChromium;
SavePictureDialog1: TSavePictureDialog;
procedure FormCreate(Sender: TObject);
procedure Chromium1BeforeContextMenu(Sender: TObject; const browser: ICefBrowser;
const frame: ICefFrame; const params: ICefContextMenuParams; const model: ICefMenuModel);
procedure Chromium1ContextMenuCommand(Sender: TObject; const browser: ICefBrowser;
const frame: ICefFrame; const params: ICefContextMenuParams; commandId: Integer;
eventFlags: TCefEventFlags; out Result: Boolean);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ TDownloader }
constructor TDownloader.Create(const URL, FileName: string);
begin
inherited Create(False);
FreeOnTerminate := True;
FURL := URL;
FFileName := FileName;
end;
procedure TDownloader.Execute;
var
HTTPClient: TIdHTTP;
FileStream: TFileStream;
begin
try
HTTPClient := TIdHTTP.Create;
try
FileStream := TFileStream.Create(FFileName, fmCreate);
try
HTTPClient.Get(FURL, FileStream);
finally
FileStream.Free;
end;
finally
HTTPClient.Free;
end;
except
// error handling ignored for this example
end;
end;
{ TForm1 }
procedure TForm1.FormCreate(Sender: TObject);
begin
Chromium1.Load('http://www.google.com/');
end;
procedure TForm1.Chromium1BeforeContextMenu(Sender: TObject; const browser: ICefBrowser;
const frame: ICefFrame; const params: ICefContextMenuParams; const model: ICefMenuModel);
begin
if (CM_TYPEFLAG_MEDIA in params.TypeFlags) and (params.MediaType = CM_MEDIATYPE_IMAGE) then
model.AddItem(MENU_ID_SAVE_IMAGE_AS, 'Save image as...');
end;
procedure TForm1.Chromium1ContextMenuCommand(Sender: TObject; const browser: ICefBrowser;
const frame: ICefFrame; const params: ICefContextMenuParams; commandId: Integer;
eventFlags: TCefEventFlags; out Result: Boolean);
var
SaveDialog: TSavePictureDialog;
begin
if (commandId = MENU_ID_SAVE_IMAGE_AS) then
begin
SaveDialog := TSavePictureDialog.Create(nil);
try
// SaveDialog.FileName := <here you can extract file name from params.SourceUrl>;
// SaveDialog.DefaultExt := <here you can extract file ext from params.SourceUrl>;
if SaveDialog.Execute then
TDownloader.Create(params.SourceUrl, SaveDialog.FileName);
finally
SaveDialog.Free;
end;
end;
end;
end.
Another approach is to identify all the images from the page (take a look at How can I use Javascript to get a list of all picture URLs available on a site?) and download the image link by using CefBrowserHost.StartDownload.
Add to Form1:
public
var file_download_finished : boolean;
procedure TForm1.Chromium1BeforeDownload(Sender: TObject;
const browser: ICefBrowser; const downloadItem: ICefDownloadItem;
const suggestedName: ustring; const callback: ICefBeforeDownloadCallback);
begin
callback.Cont('FileName.jpg', false);
end;
procedure TForm1.Chromium1DownloadUpdated(Sender: TObject;
const browser: ICefBrowser; const downloadItem: ICefDownloadItem;
const callback: ICefDownloadItemCallback);
var i : nativeint;
begin
if downloadItem.IsComplete then
file_download_finished := true
else
i := downloadItem.PercentComplete;
end;
procedure TForm1.Button1Click(Sender: TObject);
var M: TMsg;
begin
file_download_finished := false;
Chromium1.Browser.Host.StartDownload('https://www.gravatar.com/avatar/7ffbdc105c382c0070cdd29d073725b5?s=48&d=identicon&r=PG&f=1');
repeat // wait until download is finished
while PeekMessage(M, 0, 0, 0, pm_Remove) do
begin
TranslateMessage(M);
DispatchMessage(M);
end;
until file_download_finished;
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);
Hi I'm studying sockets on how to send and receive files, I'm using the component ServerSocket1 to do this I have the following code I found searching google.
the client
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ScktComp, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
ClientSocket1: TClientSocket;
OpenDialog1: TOpenDialog;
procedure Button1Click(Sender: TObject);
procedure ClientSocket1Connect(Sender: TObject;
Socket: TCustomWinSocket);
procedure ClientSocket1Error(Sender: TObject; Socket: TCustomWinSocket;
ErrorEvent: TErrorEvent; var ErrorCode: Integer);
procedure Button2Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure ClientSocket1Read(Sender: TObject; Socket: TCustomWinSocket);
private
{ Private declarations }
Stream: TMemoryStream;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject);
begin
ClientSocket1.Address:= '127.0.0.1';
ClientSocket1.Port:= 2500;
ClientSocket1.Open;
end;
procedure TForm1.ClientSocket1Connect(Sender: TObject;
Socket: TCustomWinSocket);
begin
ShowMessage('Connected.. Now go load a file!');
end;
procedure TForm1.ClientSocket1Error(Sender: TObject;
Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
var ErrorCode: Integer);
begin
ShowMessage('Did you startup the server? I cannot find it!');
end;
procedure TForm1.Button2Click(Sender: TObject);
var
Size: Integer;
begin
if OpenDialog1.Execute Then
begin
Stream.LoadFromFile(OpenDialog1.Filename);
Size:= Stream.Size;
ClientSocket1.Socket.SendBuf(Size,SizeOf(Size));
ClientSocket1.Socket.SendStream(Stream);
End;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Stream:= TMemoryStream.Create;
end;
procedure TForm1.ClientSocket1Read(Sender: TObject;
Socket: TCustomWinSocket);
var
S: String;
begin
S:= Socket.ReceiveText;
Socket.Close;
ShowMessage('Client: '+S);
end;
end.
the server
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ScktComp;
type
TForm1 = class(TForm)
ServerSocket1: TServerSocket;
SaveDialog1: TSaveDialog;
procedure FormCreate(Sender: TObject);
procedure ServerSocket1ClientConnect(Sender: TObject;
Socket: TCustomWinSocket);
procedure ServerSocket1Listen(Sender: TObject;
Socket: TCustomWinSocket);
procedure ServerSocket1ClientRead(Sender: TObject;
Socket: TCustomWinSocket);
private
{ Private declarations }
Stream: TMemoryStream;
FSize: Integer;
writing: Boolean;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
begin
ServerSocket1.Port:= 2500;
ServerSocket1.Active:= True;
Stream:= TMemoryStream.Create;
writing:= False;
end;
procedure TForm1.ServerSocket1ClientConnect(Sender: TObject;
Socket: TCustomWinSocket);
begin
ShowMessage('A client has connected');
end;
procedure TForm1.ServerSocket1Listen(Sender: TObject;
Socket: TCustomWinSocket);
begin
ShowMessage('I''m listening');
end;
procedure TForm1.ServerSocket1ClientRead(Sender: TObject;
Socket: TCustomWinSocket);
var
BytesReceived: Longint;
CopyBuffer: Pointer; { buffer for copying }
ChunkSize: Integer;
TempSize: Integer;
const
MaxChunkSize: Longint = 8192; { copy in 8K chunks }
begin
If FSize=0 then
begin
If Socket.ReceiveLength>SizeOf(TempSize) then
begin
Socket.ReceiveBuf(TempSize,SizeOf(TempSize));
Stream.SetSize(TempSize);
FSize:= TempSize //Threadsafe code!
End;
End;
If (FSize>0) and not(writing) then
begin
GetMem(CopyBuffer, MaxChunkSize); { allocate the buffer }
writing:= True;
While Socket.ReceiveLength>0 do
Begin
ChunkSize:= Socket.ReceiveLength;
If ChunkSize > MaxChunkSize then ChunkSize:= MaxChunkSize;
BytesReceived:= Socket.ReceiveBuf(CopyBuffer^,ChunkSize);
Stream.Write(CopyBuffer^, BytesReceived); { ...write chunk }
Dec(FSize,BytesReceived);
End;
If FSize=0 then
If SaveDialog1.Execute then
begin
If FileExists(SaveDialog1.Filename) then
DeleteFile(SaveDialog1.Filename);
Stream.SaveToFile(SaveDialog1.Filename);
Socket.SendText('File received!');
Stream.SetSize(0);
FSize:= 0;
End;
FreeMem(CopyBuffer, MaxChunkSize); { allocate the buffer }
Writing:= False;
End;
end;
end.
The problem in this code that eh had is that I can only send one I can send a file because when I try to re-send other file errors throws me as 'Access violation at address' or 'Stream read error'.
that I can do to fix this code and you can send multiple files after each?
there is a reference of how to do it with indy sockets?
This is because memorystream used to open the file is not free. You have to free the stream variable before loading the next file to be sent.
I modified your code a bit and it is now working perfectly, I request various files and is ok.
the server
procedure TForm1.ServerClientRead(Sender: TObject;
Socket: TCustomWinSocket);
var
BytesReceived: Longint;
CopyBuffer: Pointer; { buffer for copying }
ChunkSize: Integer;
TempSize: Integer;
FileName: array [0..255] of char;
const
MaxChunkSize: Longint = 8192; { copy in 8K chunks }
begin
If FSize=0 then
begin
If Socket.ReceiveLength>SizeOf(TempSize) then
begin
Socket.ReceiveBuf(TempSize,SizeOf(TempSize));
Socket.ReceiveBuf(FileName, sizeOf(FileName));
Save.FileName:= FileName; //I added
Stream:= TMemoryStream.Create;
Stream.SetSize(TempSize);
FSize:= TempSize; //Threadsafe code!
writing:= True;
End;
End;
If (FSize>0) and (writing) then
{before not(writing) -> because in big files, ServerClientRead is call more than one time and the transfer was stopped after the first call, but now it continues.}
begin
GetMem(CopyBuffer, MaxChunkSize); { allocate the buffer }
While Socket.ReceiveLength>0 do
Begin
ChunkSize:= Socket.ReceiveLength;
If ChunkSize > MaxChunkSize then ChunkSize:= MaxChunkSize;
BytesReceived:= Socket.ReceiveBuf(CopyBuffer^,ChunkSize);
Stream.Write(CopyBuffer^, BytesReceived); { ...write chunk }
Dec(FSize,BytesReceived);
End;
FreeMem(CopyBuffer, MaxChunkSize); { free allocated buffer, now here }
If FSize
Client button click:
procedure TForm1.Button1Click(Sender: TObject);
var ms: TMemoryStream;
size: Integer;
FileName: array [0..255] of char;
begin
if Open.Execute then
begin
ms:= TMemoryStream.Create;
try
ms.LoadFromFile(open.FileName);
ms.Position:= 0;
Size:= MS.Size;
Client.Socket.SendBuf(Size,SizeOf(Size));
StrPLCopy(FileName, ExtractFileName(Open.FileName), High(FileName));
Client.Socket.SendBuf(FileName, SizeOf(FileName));
client.Socket.SendStream(ms);
except
ms.Free;
end;
end;
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;
Is it possible to disable view source option in Delphi Chromium Embedded ?
I haven't found anything suitable in properties/methods list.
There are no direct settings or events allowing to hide Chromium popup menu items. However you have at least few options how to continue, you can for instance:
1. Tell user that the View source option is forbidden and decline the action
You can decide what action will you allow or decline in the OnMenuAction event handler, where if you assign True to the Result parameter the action is declined. The following code checks that you have performed the view source action and if so, decline the action and show the information message:
type
TCefMenuId = TCefHandlerMenuId;
procedure TForm1.Chromium1MenuAction(Sender: TObject;
const browser: ICefBrowser; menuId: TCefMenuId; out Result: Boolean);
begin
if menuId = MENU_ID_VIEWSOURCE then
begin
Result := True;
ShowMessage('View page source is not allowed!');
end;
end;
2. Fake the menu item to something custom by changing menu item's caption with its action
You can take advantage of the menu item for something else by changing the menu item's caption and executing some custom action. The following sample code shows how to change the view source menu item into the about box menu item:
type
TCefMenuId = TCefHandlerMenuId;
procedure TForm1.Chromium1GetMenuLabel(Sender: TObject;
const browser: ICefBrowser; menuId: TCefMenuId; var caption: ustring;
out Result: Boolean);
begin
if menuId = MENU_ID_VIEWSOURCE then
caption := 'About my application...';
end;
procedure TForm1.Chromium1MenuAction(Sender: TObject;
const browser: ICefBrowser; menuId: TCefMenuId; out Result: Boolean);
begin
if menuId = MENU_ID_VIEWSOURCE then
begin
Result := True;
ShowMessage('About box...!');
end;
end;
3. Create you own custom page (frame) popup menu
You can create your own popup menu, but you need to consider that this menu is quite hardcoded, so you will need to maintain it if you'll need to have it the same with each new version of Delphi Chromium wrapper. Here is the code how to create the page menu without view source menu item:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Menus, cefvcl, ceflib;
type
PCefMenuInfo = PCefHandlerMenuInfo;
type
TForm1 = class(TForm)
Chromium1: TChromium;
procedure FormCreate(Sender: TObject);
procedure Chromium1BeforeMenu(Sender: TObject; const browser: ICefBrowser;
const menuInfo: PCefMenuInfo; out Result: Boolean);
private
PageMenu: TPopupMenu;
procedure OnNavigateBackMenuItemClick(Sender: TObject);
procedure OnNavigateForwardMenuItemClick(Sender: TObject);
procedure OnPrintMenuItemClick(Sender: TObject);
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.OnNavigateBackMenuItemClick(Sender: TObject);
begin
Chromium1.Browser.GoBack;
end;
procedure TForm1.OnNavigateForwardMenuItemClick(Sender: TObject);
begin
Chromium1.Browser.GoForward;
end;
procedure TForm1.OnPrintMenuItemClick(Sender: TObject);
begin
Chromium1.Browser.GetFocusedFrame.Print;
end;
procedure TForm1.Chromium1BeforeMenu(Sender: TObject;
const browser: ICefBrowser; const menuInfo: PCefMenuInfo;
out Result: Boolean);
begin
if menuInfo.typeFlags = MENUTYPE_PAGE then
begin
Result := True;
PageMenu.Items[0].Enabled := browser.CanGoBack;
PageMenu.Items[1].Enabled := browser.CanGoForward;
PageMenu.Popup(menuInfo^.x, menuInfo^.y);
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
MenuItem: TMenuItem;
begin
PageMenu := TPopupMenu.Create(Self);
MenuItem := TMenuItem.Create(PageMenu);
MenuItem.Caption := 'Back';
MenuItem.OnClick := OnNavigateBackMenuItemClick;
PageMenu.Items.Add(MenuItem);
MenuItem := TMenuItem.Create(PageMenu);
MenuItem.Caption := 'Forward';
MenuItem.OnClick := OnNavigateForwardMenuItemClick;
PageMenu.Items.Add(MenuItem);
MenuItem := TMenuItem.Create(PageMenu);
MenuItem.Caption := '-';
PageMenu.Items.Add(MenuItem);
MenuItem := TMenuItem.Create(PageMenu);
MenuItem.Caption := 'Print';
MenuItem.OnClick := OnPrintMenuItemClick;
PageMenu.Items.Add(MenuItem);
Chromium1.Load('www.stackoverflow.com');
end;
end.
Footnote
The type definitions used in all code samples are there because I've noticed that some version of Delphi Chromium has wrong event handler definitions.
Probably things changed over years, today a direct method exists:
uses
ceflib;
[..]
implementation
procedure TForm1.Chromium1BeforeContextMenu(Sender: TObject;
const browser: ICefBrowser; const frame: ICefFrame;
const params: ICefContextMenuParams; const model: ICefMenuModel);
begin
//model.Clear;
model.Remove(Integer(MENU_ID_VIEW_SOURCE));
end;
You can use model.Clear if you want to completely get rid of popup menu.
I have a TWebBrowser component that show a Google maps page. The problem is that when user press F5 the page refresh and page reloads. This cause javascript variables to reinitialize and get out of sync with Delphi and a scripting error dialog appear,
'undefined' is null or not an object.
I want to stop refresh from the user.
I tried this event for OnBeforeNavigate2:
procedure TNewOrganizationForm.mapAddressBeforeNavigate2(ASender: TObject;
const pDisp: IDispatch; var URL, Flags, TargetFrameName, PostData,
Headers: OleVariant; var Cancel: WordBool);
begin
inherited;
Cancel := Assigned(fMapEngine) and not fMapEngine.Loading;
end;
But when I set a breakpoint it is not even called. Is there another way ?
Ronald you can use the IHTMLDocument2.onkeydown event to intercept and block a key.
to assign an event handler first you must create a procedure type using the IHTMLEventObj as parameter.
THTMLProcEvent = procedure(Sender: TObject; Event: IHTMLEventObj) of object;
then you must create an class descendent from InterfacedObject and IDispatch to pass and process the events .
finally you can process the intercepted key in the onkeydown event in this way
Var
HTMLDocument2 : IHTMLDocument2;
begin
if Not Assigned(WebBrowser1.Document) then Exit;
HTMLDocument2:=(WebBrowser1.Document AS IHTMLDocument2);
if HTMLDocument2.parentWindow.event.keyCode=VK_F5 then //compare the key
begin
HTMLDocument2.parentWindow.event.cancelBubble:=True; //cancel the key
HTMLDocument2.parentWindow.event.keyCode :=0;
end;
end;
//check the full source code
unit Unit55;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, OleCtrls, SHDocVw, MSHTML;
type
//Create the procedure type to assign the event
THTMLProcEvent = procedure(Sender: TObject; Event: IHTMLEventObj) of object;
//Create a new class for manage the event from the twebbrowser
THTMLEventLink = class(TInterfacedObject, IDispatch)
private
FOnEvent: THTMLProcEvent;
private
constructor Create(Handler: THTMLProcEvent);
function GetTypeInfoCount(out Count: Integer): HResult; stdcall;
function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult; stdcall;
function GetIDsOfNames(const IID: TGUID; Names: Pointer;
NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; stdcall;
function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer;
Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; stdcall;
public
property OnEvent: THTMLProcEvent read FOnEvent write FOnEvent;
end;
TForm55 = class(TForm)
WebBrowser1: TWebBrowser;
procedure FormShow(Sender: TObject);
procedure WebBrowser1NavigateComplete2(ASender: TObject; const pDisp: IDispatch; var URL: OleVariant);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
FOnKeyDownConnector: THTMLEventLink; //pointer to the event handler
procedure WebBrowser1OnKeyDown(Sender: TObject; EventObjIfc: IHTMLEventObj);//the event handler
public
{ Public declarations }
end;
var
Form55: TForm55;
implementation
{$R *.dfm}
constructor THTMLEventLink.Create(Handler: THTMLProcEvent);
begin
inherited Create;
_AddRef;
FOnEvent := Handler;
end;
function THTMLEventLink.GetIDsOfNames(const IID: TGUID; Names: Pointer; NameCount, LocaleID: Integer; DispIDs: Pointer): HResult;
begin
Result := E_NOTIMPL;
end;
function THTMLEventLink.GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult;
begin
Result := E_NOTIMPL;
end;
function THTMLEventLink.GetTypeInfoCount(out Count: Integer): HResult;
begin
Result := E_NOTIMPL;
end;
function THTMLEventLink.Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult;
var
HTMLEventObjIfc: IHTMLEventObj;
begin
Result := S_OK;
if Assigned(FOnEvent) then FOnEvent(Self, HTMLEventObjIfc);
end;
procedure TForm55.FormCreate(Sender: TObject);
begin
FOnKeyDownConnector := THTMLEventLink.Create(WebBrowser1OnKeyDown); //assign the address of the event handler
end;
procedure TForm55.WebBrowser1NavigateComplete2(ASender: TObject; const pDisp: IDispatch; var URL: OleVariant);
var
HTMLDocument2 : IHTMLDocument2;
begin
HTMLDocument2:=(WebBrowser1.Document AS IHTMLDocument2);
HTMLDocument2.onkeydown := FOnKeyDownConnector as IDispatch; //assign the event handler
end;
procedure TForm55.WebBrowser1OnKeyDown(Sender: TObject; EventObjIfc: IHTMLEventObj);
Var
HTMLDocument2 : IHTMLDocument2;
begin
//finally do your stuff here, in this case we will intercept and block the F5 key.
if Not Assigned(WebBrowser1.Document) then Exit;
HTMLDocument2:=(WebBrowser1.Document AS IHTMLDocument2);
if HTMLDocument2.parentWindow.event.keyCode=VK_F5 then
begin
HTMLDocument2.parentWindow.event.cancelBubble:=True;
HTMLDocument2.parentWindow.event.keyCode :=0;
end;
end;
procedure TForm55.FormShow(Sender: TObject);
begin
WebBrowser1.Navigate('www.google.com');
end;
end.
I did not find an easy way to do this. I could not find any event or anything similar on TWebBrowser, that would dissable refresh. Maybe you should check TEmbededWB as it has more events and is more capable than the default TWebBrowser. Otherwise they are very similar.
But I found a way to prevent refresh. Now it is funny that even with KeyPreview set to "True" on the main form I could not recieve key notifications. It seems that TWebBrowser eats them up somehow. But this worked:
procedure TForm1.FormCreate(Sender: TObject);
begin
Application.OnMessage := OnAppMessage;
end;
procedure TForm1.OnAppMessage(var Msg: TMsg; var Handled: Boolean);
begin
if Msg.message = WM_KEYDOWN then
if Msg.wParam = VK_F5 then
Handled := True;
end;
Not the most elegant way but at least it works. I have not found a better solution yet.