我来赚你五百分!
拖动我这里一切正常,而快捷键的失效却百思不得其解。
这几天,我辛辛苦苦搜索了五湖四海,总算功夫不负有心人。
请仔细看,肯定有部分需要改正,假若改对了源代码,请贴上来。
Lost keys in the IE5 ActiveX controls
Abstract:Hooking the IWebBrowser and DHTML controls to properly handle keystrokes
The problem: If you import the Internet Explorer or Microsoft DHTML ActiveX controls into Delphi or C++Builder, the control does not respond to certain keystrokes (Tab, in the case of IE, and delete, in the case of DHTML).
The reasons for the problem: The problem occurs because of a combination of peculiar behavior on the part of the controls, and peculiar behavior on the part of the Delphi and C++Builder IDEs.
How the control is strange: The control believes that the keys in question should be treated as accelerators and wishes to handle them as such, rather than as Windows key down messages.
What Delphi and C++Builder are doing: TApplication catches all windows messages. As part of the processing of such messages, it allows windows contained within the application to respond to key messages themselves rather than use the default processing To allow VCL-wrapped windows to distinguish between messages passed to them by TApplication in the middle of message processing from messages which have been passed on after TApplication has given up on processing them, the VCL modifies the message and passes the modified message to the window via SendMessage. (The relevant code is in forms.pas: Application.IsKeyMsg).
When an ActiveX control is imported, it is wrapped by a descendant class of TOleControl (olectrls.pas). TOleControl hooks the window procedure
the hook catches the messages thrown by Application, modifies them back to their original state, and calls IOleInPlaceActiveObject.TranslateAccelerator, allowing the code to respond to accelerators.
With most controls, this works. Unfortunately, the dynamic nature of the IE controls causes a problem, in that the window which is recieving the messages is either a child or a grandchild of the window which has been subclassed. Their window procedures don't understand the messages they are recieving from TApplication (and so ignore them)
the accelerators never get translated
and the message that was intended by pressing tab or delete gets lost.
How Delphi 5 solves the problem: The problem was solved in Delphi 5 by modifying the behavior of TApplication.IsKeyMsg to walk the parent chain of a non-VCL window until a VCL window is encountered, and then throw the modified message at it. This allows the TOleControl wrapper to ask the control to translate the accelerator.
How you can solve the problem in C++Builder 4 or Delphi 4: The problem can be solved on an ad-hoc basis by dynamically subclassing the window procedures of the controls in question. (An earlier fix to the problem in Delphi5 involved modifying TOleControl to subclass the windows of all children created by the embedded Ole control
the problem with this approach was that, while the controls notify you verify their event interfaces when their children are destroyed and recreated, they do not pass such a notification through a standard Ole interface, so it was difficult to find a general-case mechanism for the container to know when to hook the child windows).
When to hook the window procedure: In the case of the WebBrowser control, a new child window is created whenever a new web page is accessed (and so the window procedure should be hooked in the OnNavigatComplete2 or the OnNewWindow2 event handlers). In the case of the DHTML control, there is usually only one child window whose creation is delayed until well past the creation of the control itself
manually hooking its window procedure in the form's OnActivate method should work).
How to get the handle for the window: The handle for the window that you want to subclass can be retrieved in one of two ways (one is generic, the other is not). You can get the handle for the window which was subclassed at the creation of the control by accessing the Handle method of the TOleControl descendant, using code that looks more or less like the following pascal code:
{these are members of Form1}
WebBrowser1 : TWebBrowser;
FFrameWndProcInstance: Pointer;
FFrameHwnd : HWND;
FFrameDefWndProc : Pointer;
function Form1.SubClassFrame;
var
Child : HWND;
begin
Child := GetWindow(WebBrowser1.Handle, GW_CHILD);
if Child <> 0 then
begin
Child := GetWindow(Child, GW_CHILD);
if (Child <>0) and (Child <> FFrameHwnd) then
{ don't bother subclassing if you've already got it subclassed }
begin
if FFrameWndProcInstance <> nil then
UnSubClassFrame;
FFrameHwnd := Child;
FFrameWndProcInstance := MakeObjectInstance(FrameWndProc);
FFrameDefWndProc := Pointer(GetWindowLong(FFrameHwnd, GWL_WNDPROC));
SetWindowLong(FFrameHwnd, GWL_WNDPROC, LongInt(FFrameWndProcInstance));
end;
end;
end;
function Form1.UnSubClassFrame;
begin
if (FFrameWndProcInstance <> nil and (FDefFrameWndProc) <> nil and (FFrameHwnd <> 0) then
begin
SetWindowLong(FFrameHwnd, GWL_WNDPROC, LongInt(FDefFrameWndProc));
FFrameWndProcInstance := nil;
FFrameHwnd := nil;
{ require the caller to set FDefFrameWndProc to nil so you can unsubclass and then call the default
proc when responding to WM_DESTROY events }
end;
end;
Alternately, you can ask the control to return to you an IOleInPlaceActiveObject interface, and query it for its window handle:
function Form1.SubClassFrame;
var
ActiveObject : IOleInPlaceActiveObject;
Child : HWND;
begin
if (WebBrowser1.DefaultDispatch.QueryInterface(IOleInPlaceActiveObject, ActiveObject) = S_OK) then
try
Child := ActiveObject.GetWindow;
if (Child <> nil) and (Child <> FFrameHwnd) then
begin
if FFrameWndProcInstance <> nil then
UnSubClassFrame;
FFrameHwnd := Child;
FFrameWndProcInstance := MakeObjectInstance(FrameWndProc);
FFrameDefWndProc := Pointer(GetWindowLong(FFrameHwnd, GWL_WNDPROC));
SetWindowLong(FFrameHwnd, GWL_WNDPROC, LongInt(FFrameWndProcInstance));
end;
finally
ActiveObject.Release;
end;
end;
function Form1.UnSubClassFrame;
var
ActiveObject: IOleInPlaceActiveObject;
begin
if (WebBrowser1.DefaultDispatch.QueryInterface(IOleInPlaceActiveObject, ActiveObject) = S_OK) then
try
if (FFrameWndProcInstance <> nil and (FDefFrameWndProc) <> nil and (FFrameHwnd <> 0) then
begin
SetWindowLong(FFrameHwnd, GWL_WNDPROC, LongInt(FDefFrameWndProc));
FFrameWndProcInstance := nil;
FFrameHwnd := nil;
{ require the caller to set FDefFrameWndProc to nil so you can unsubclass and then call the default
proc when responding to WM_DESTROY events }
end;
finally
ActiveObject.Release;
end;
end;
What the window procedure should do. The window procedure hook you install should, at a minimum, pass the key messages on to the ole control's accelerator treanslator, properly unhook itself in response to a WM_DESTROY message,and pass all other messages along to the default window procedure, as per the following example:
procedure Form1.FrameWndProc(var Message: TMessage);
var
WinMsg: TMsg;
ActiveObject : IOleInPlaceActiveObject;
begin
if (Message.Msg >= CN_BASE + WM_KEYFIRST)
and (Message.Msg <= CN_BASE + WM_KEYLAST) then
begin
WinMsg.HWnd := WebBrowser1.Handle;
WinMsg.Message := Message.Msg - CN_BASE;
WinMsg.WParam := Message.WParam;
WinMsg.LParam := Message.LParam;
WinMsg.Time := GetMessagTime;
WinMsg.Pt.X := $115DE1F1;
WinMsg.Pt.Y= $115DE1F1;
if (WebBrowser1.DefaultDispatch.QueryInterface(IOleInPlaceActiveObject, ActiveObject) = S_OK) then
try
if ActiveObject.TranslateAccelerator(WinMsg) = S_OK then
begin
Message.Result := 1;
WinMsg.Pt.X := $5ACC355;
end;
finally
ActiveObject.Release;
end;
if WinMsg.Pt.X = $5ACC355 then
Exit;
end;
with Message do
begin
case Msg of
WM_DESTROY:
begin
UnSubClassFrame;
CallWindowProc(FDefFrameWndProc, FFrameHwnd, Message.Msg, Message.WParam, Message.LParam);
FDefFrameWndProc := nil;
end;
end;
Result := CallWindowProc(FDefFrameWndProc, FFrameHwnd, Msg, WParam, LParam);
end;
end;