解决Windows Webbrowser控件抢焦点问题

介绍

如果你是一名MFC程序员,你几乎肯定会希望使用Webbrowser ActiveX控件在应用程序中显示HTML。它包装了Internet Explorer的所有功能,因为它已经存在于所有Windows安装中,即使是Windows 10,也不需要安装任何其他库或浏览器。

然而,在多文档界面(MDI)应用程序中使用它却有一个致命的缺陷 – 它不断从其他窗口窃取焦点。可悲的是,这确实使得在许多情况下使用它几乎是不可能的 – 没有别的方法可以正常工作。如果我们能够防止这种情况发生,那么我们拥有一个有用且可扩展的控制 – 否则,这是棘手的选择。

背景

我一直在维护一个大型的C ++ MFC pre .NET程序,并且拥有大量的用户作为我十多年的日常工作。虽然这是所有“传统”代码,但它运行良好,重写它实质上不是商业上可行的命题。我认为在类似情况下肯定会有很多人:如果这种解决方案在网络上的任何地方都可以使用,它可以为我节省3天的工作时间,并且如果它帮助其他人在相同的情况下工作,那么这将是大。

我只会在这里主要介绍我的解决方案,因为这几乎可以肯定你为什么要阅读这篇文章:我已经了解了一些Windows中比较晦涩的方面,但是这本质上是一个错误修复 – 控制不应该是’ t的行为就像它一样,我们需要将其排除。

但是有一个问题!很多与这个问题相关的文档都很旧(大多数搜索提供的相关信息都是从2003年或更早的文章中找到的),而且似乎最近MS已经退出了很多旧代码示例等等,这可能有助于。我们能找到足够的线索来解决这个问题吗?

顺便说一句,我在Visual C ++ 2010上使用MFC,但该解决方案的工作原理已经在Windows 7和Windows 10上进行了测试,从VC6开始应该没问题,所以请继续阅读…

解决方案

一个Active X控件具有一个托管它的程序的接口,由一个名为MFC的类来表示COleControlSite。这又包含一个IUnknown接口的实现,它是一个通用接口,可以使用API​​中定义的机制来查询它支持的服务。在这种情况下,“服务”意味着控制可以做的任何事情:在网络浏览器的情况下,可以调整窗口大小,缩放,浏览网页地址,上一页等。因为服务由GUID标识,该系统几乎可以无限扩展,因此可以添加任何类型的控件来提供任意数量的服务。

从网上搜索的高低来看,我设法将这个解决方案的焦点问题放在这个机制中。有一个IProtectFocus来自IUnknown我们需要支持的接口。至少,这给了我们一个门槛。

并且自MFC7以来,Webbrowser控件包装器(MFC CHTMLDialogCHtmlView类)具有一个虚拟函数CreateControlSite,它允许派生类覆盖默认控制站点并将其替换为我们自己定义的一个。

下一步

正如我们所知道的,MFC试图通过将Win32小部件包装在C ++类中来简化Windows编程,目的是使它们更容易用来构造有用的程序。然而,很好的说,使用MFC进行编程主要是为了使框架能够完成它没有设计的事情。然而,让这成为可能的事情,我们可以使用这些东西来做玩具系统的唯一原因是我们可以访问MFC类的源代码。

在这里,这个访问是有帮助的,因为我们只需要IProtectFocus在我们的一个源文件中输入“ ”,然后在上下文菜单中点击“转到定义”来找出它的组成部分。在这个和非常粗略的MSDN文档之间,我们发现它是(像C ++中所有这样的’接口’)一个abstract只有一个纯虚方法的类AllowFocusChange,它接受一个指向BOOL的指针(MFC-speak for int used作为一个布尔值) – 函数只需要将它指向FALSE,神奇的是,我们的小部件不会窃取焦点。

到目前为止这么好,但不幸的是它并不那么简单。我们必须让托管程序知道Webbrowser支持其IProtectFocus子类IUnknown,并且事实证明这需要实现另一个接口。这一次是IServiceProvider:这有一个成员函数QueryService,它可以做我们想要的。

然而,就在我们看起来我们正在到达某个地方时,我们发现IServiceProvider通过返回一个指向所请求的接口指针来响应查询,而不是直接用我们想要与之通信的BOOL来响应请求。但是,我们必须返回这个指针到底是什么?有关此主题的信息很少; 它是一个指向界面的指针,但这究竟意味着什么?尽管如此,我们现在还不会放弃。

在一个IUnknown实例上实现多个接口

我无法在网络上的任何地方找到这样的例子。我们唯一的帮助是在Microsoft技术说明TN038:MFC / OLE IUnknown实现中。希望在阅读本文时仍然存在,但即使不是,解决方案仍然存在。

多部分接口是使用宏定义的,这些宏定义整个接口的各个部分的地址。这些宏创建内部类,它继而从您正在实现的特定接口继承。他们还提供了许多支持该IUnknown机制的样板代码,但由于他们声明了许多对读者不可见的内容,因此会使代码看起来“错误” – 实际上,您必须仔细查看如何将宏定义为对代码的工作原理有任何了解。这是MFC’帮助’的一个很好的例子。除非你要在表面之下挖掘,否则你必须遵守规则。

因此,简而言之,我终于把所有这些都拼凑在一起,但接着是残酷的打击。它确实解决了焦点问题,但是当托管视图Webbrowser关闭时,视图崩溃了。这次崩溃在框架的核心是深刻的,但我一直怀疑这种事情可能会发生; 该IProtectFocus接口返回一个指向堆的指针,当它被释放时会留下潜在的致命情况。框架似乎依赖于引用计数的方法来试图避免这种情况,但是我没有正确实现它,或者它没有按预期工作。

最后一次尝试修复这个幸运的工作:而不是返回一个指向堆上的接口的指针,我做了一个静态存根接口,并返回一个指针。这个指针显然总是有效的,尽管非传统,这个想法似乎工作。任务完成!

使用代码

大部分代码来自MFC CHtmlView(MFC目录中的viewhtml.cpp)。该文件包含默认接口的声明和定义,但对于非内联使用,它需要分解为.h.cpp文件。

我推荐完全按照所提供的内容来使用这些内容:我花了好几个小时试图找出为什么以前的版本无法链接,结果是与我甚至不知道存在的库类的名称冲突,所以我建议你保留它们的名字。

重要提示:我的代码中的派生类被调用CHtmlCtrl,因此文件中对CHtmlCtrl的所有引用都需要更改为派生类的名称。在这个例子中,我用过了CHtmlDerived

HtmlControlSiteEx.cppHtmlControlSiteEx.h添加到您的项目中。

然后,在你的类派生自CHtmlView,重写虚CreateControlSite函数:

示例.h文件:

#pragma once

//CHtmlDerived - subclass of CHtmlView fixing the focus-stealing bug

class CHtmlDerived : public class CHtmlView
{
public:
    CHtmlDerived(/*params*/);
    //...
    //declarations
    //...
    virtual BOOL CreateControlSite(COleControlContainer* pContainer,
       COleControlSite** ppSite, UINT nID, REFCLSID clsid);
    //...
    //declarations
    //...

    //ALSO ADD THE FOLLOWING which declare functions defined in HtmlControlSiteEx.cpp
    //...simply copied from CHtmlView's class declaration
        // DocHostUIHandler overrideables
    virtual HRESULT OnShowContextMenu(DWORD dwID, LPPOINT ppt,
        LPUNKNOWN pcmdtReserved, LPDISPATCH pdispReserved);
    virtual HRESULT OnGetExternal(LPDISPATCH *lppDispatch);
    virtual HRESULT OnGetHostInfo(DOCHOSTUIINFO *pInfo);
    virtual HRESULT OnShowUI(DWORD dwID,
        LPOLEINPLACEACTIVEOBJECT pActiveObject,
        LPOLECOMMANDTARGET pCommandTarget, LPOLEINPLACEFRAME pFrame,
        LPOLEINPLACEUIWINDOW pDoc);
    virtual HRESULT OnHideUI();
    virtual HRESULT OnUpdateUI();
    virtual HRESULT OnEnableModeless(BOOL fEnable);
    virtual HRESULT OnDocWindowActivate(BOOL fActivate);
    virtual HRESULT OnFrameWindowActivate(BOOL fActivate);
    virtual HRESULT OnResizeBorder(LPCRECT prcBorder,
        LPOLEINPLACEUIWINDOW pUIWindow, BOOL fFrameWindow);
    virtual HRESULT OnTranslateAccelerator(LPMSG lpMsg,
        const GUID* pguidCmdGroup, DWORD nCmdID);
    virtual HRESULT OnGetOptionKeyPath(LPOLESTR* pchKey, DWORD dwReserved);
    virtual HRESULT OnFilterDataObject(LPDATAOBJECT pDataObject,
        LPDATAOBJECT* ppDataObject);
    virtual HRESULT OnTranslateUrl(DWORD dwTranslate,
        OLECHAR* pchURLIn, OLECHAR** ppchURLOut);
    virtual HRESULT OnGetDropTarget(LPDROPTARGET pDropTarget,
        LPDROPTARGET* ppDropTarget);
};

示例.cpp文件:

//CHtmlDerived - subclass of CHtmlView fixing the focus-stealing bug

#include "stdafx.h"
#include "htmlctrl.h"
#include "htmlcontrolsite.h"
//etc

CHtmlDerived::CHtmlDerived(/*params*/) : CHtmlView()
{
    //do stuff
}

//...
//definitions
//...

BOOL CHtmlDerived::CreateControlSite(COleControlContainer* pContainer, 
    COleControlSite** ppSite, UINT nID, REFCLSID clsid)
{
    //uncomment below to use default site:
    //return CHtmlView::CreateControlSite(pContainer, ppSite, nID, clsid);

    ASSERT(ppSite != NULL);
    *ppSite = new CHtmlControlSiteEx(pContainer);
    return TRUE;
}

建立和享受!

结论

在CodeProject上已经有很多有用的文章处理Webbrowser控件的各个方面,但我在网上找到的任何东西似乎都提供了有关如何解决此特定问题的精确说明。实现这里描述的修复相对比较简单,一旦你开始使用这个控件(提示:开玩笑PreTranslateMessage,你会发现你可以做的其他各种有用的事情。

网络上的大部分其他材料都会帮助实现这一目标,这在很大程度上已经成为一个断链的海洋。也许我错了,几乎没有其他人仍然使用老派的MFC,但在我看来,传统应用程序仍然有生命力(尽管如果你正在做新的事情,有很多更好的选择),这是一个耻辱微软似乎有意删除旧代码样本等,这对我们仍然有用。

未经允许不得转载:技术啦 - 关注IT,建站和运维,分享最新教程,资源 » 解决Windows Webbrowser控件抢焦点问题

赞 (0) 打赏

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址