代码之家  ›  专栏  ›  技术社区  ›  Fabrizio RAHUL S R

检测可编辑TWebBrowser中的更改

  •  0
  • Fabrizio RAHUL S R  · 技术社区  · 6 年前

    我正在将HTML本地文件加载到 TWebBrowser 具体如下:

    procedure TForm1.FormCreate(Sender: TObject);
    begin
      WebBrowser1.Navigate('file:///C:\Tmp\input.html');
    end;
    

    在 TWebBrowser.OnDocumentComplete 事件处理程序我正在使其可编辑:

    procedure TForm1.WebBrowser1DocumentComplete(ASender: TObject;
      const pDisp: IDispatch; const URL: OleVariant);
    begin
      (WebBrowser1.Document as IHTMLDocument2).designMode := 'on';
    end;
    

    当用户通过应用任何更改时,需要立即通知我 TWebBrowser公司 OnChanged 或类似的事件处理程序。


    我试过捕捉 WM_PASTE 和 WM_KEYDOWN 但我的代码从未执行过:

      TMyWebBrowser = class(TWebBrowser)
      public
        procedure WM_Paste(var Message: TWMPaste); message WM_PASTE;
        procedure WM_KeyDown(var Message: TWMKeyDown); message WM_KEYDOWN;
      end;
    
    ...
    
    procedure TMyWebBrowser.WM_Paste(var Message: TWMPaste);
    begin
      inherited;
      ShowMessage('Paste');
    end;
    
    procedure TMyWebBrowser.WM_KEYDOWN(var Message: TWMKeyDown);
    begin
      inherited;
      ShowMessage('KeyDown');
    end;
    

    我还尝试设置 WindowProc 但没有任何成功。

    0 回复  |  直到 6 年前
        1
  •  2
  •   Peter Wolf    6 年前

    要在设计模式下捕获对文档的更改,您应该使用 IMarkupContainer2 注册的接口 IHTMLChangeSink 通过 RegisterForDirtyRange 方法这个过程非常简单-实现 获得 IMarkupContainer2 从…起 WebBrowser1.Document 并称其为 寄存器fordertyrange 方法,但有一个陷阱。

    当您更改 designMode 属于 IHTMLDocument2 , TWebBrowser 控件重新加载当前文档,并丢失所有已注册的更改接收器。因此,您应该在将文档置于设计模式后进行注册。之后,您将通过以下方式收到更改通知: IHTMLChangeSink.Notify 方法

    但还有一个陷阱。因为进入设计模式会导致重新加载文档,而这又会导致更改 readyState 文档的属性 'loading' 然后连续到 'complete' . 您的找零槽将收到这些 就绪状态 更改通知。请注意 TWebBrowser.OnDocumentComplete 不是

    另一个小问题是 寄存器fordertyrange 创建您需要维护的cookie,以便注销更改接收器。因为您需要一个类来实现 IHTMLChangeSink 无论如何,它也可以封装设计模式状态并更改注册。

    uses
      System.SysUtils, SHDocVw, MSHTML;
    
    const
      DesignMode: array[Boolean] of string = ('off', 'on');
    
    type
      TWebBrowserDesign = class(TInterfacedObject, IHTMLChangeSink)
      private
        FDirtyRangeCookie: LongWord;
        FDocumentComplete: Boolean;
        FHTMLDocument2: IHTMLDocument2;
        FMarkupContainer2: IMarkupContainer2;
        FOnChange: TProc;
        { IHTMLChangeSink }
        function Notify: HResult; stdcall;
      public
        constructor Create(WebBrowser: TWebBrowser; const AOnChange: TProc);
        destructor Destroy; override;
      end;
    
    constructor TWebBrowserDesign.Create(WebBrowser: TWebBrowser; const AOnChange: TProc);
    begin
      inherited Create;
      if not Assigned(WebBrowser) then
        raise Exception.Create('Web browser control missing.');
      if not Supports(WebBrowser.Document, IHTMLDocument2, FHTMLDocument2) then
        raise Exception.Create('No HTML document loaded.');
      FHTMLDocument2.designMode := DesignMode[True];
      if Supports(WebBrowser.Document, IMarkupContainer2, FMarkupContainer2) then
      begin
        if FMarkupContainer2.RegisterForDirtyRange(Self, FDirtyRangeCookie) <> S_OK then
          FDirtyRangeCookie := 0
        else
          _Release;
      end;
      FOnChange := AOnChange;
    end;
    
    destructor TWebBrowserDesign.Destroy;
    begin
      if Assigned(FMarkupContainer2) and (FDirtyRangeCookie <> 0) then
        FMarkupContainer2.UnRegisterForDirtyRange(FDirtyRangeCookie);
      if Assigned(FHTMLDocument2) then
        FHTMLDocument2.designMode := DesignMode[False];
      inherited;
    end;
    
    function TWebBrowserDesign.Notify: HResult;
    begin
      Result := S_OK;
      if not FDocumentComplete then
        FDocumentComplete := FHTMLDocument2.readyState = 'complete'
      else if Assigned(FOnChange) then
        FOnChange();
    end;
    

    注意呼叫 _Release 注册更改接收器后。这是为了“防止”标记容器包含对 TWebBrowserDesign 例子这允许您使用的生命周期来控制设计模式 Twebbrowser设计

    type
      TForm1 = class(TForm)
        { ... }
      private
        FWebBrowserDesign: IInterface;
        { ... }
      end;
    
    procedure TForm1.WebBrowser1DocumentComplete(ASender: TObject;
      const pDisp: IDispatch; const URL: OleVariant);
    begin
      { enter design mode }
      FWebBrowserDesign := TWebBrowserDesign.Create(WebBrowser1, procedure
        begin
          ButtonSave.Enabled := True;
        end);
    end;
    
    procedure TForm1.ButtonSave(Sender: TObject);
    begin
      { exit design mode }
      FWebBrowserDesign := nil;
      ButtonSave.Enabled := False;
    end;
    

    或者,您可以将change sink作为一个组件来实现。

    type
      TWebBrowserDesign = class(TComponent, IHTMLChangeSink)
      private
        FDirtyRangeCookie: LongWord;
        FDocumentComplete: Boolean;
        FHTMLDocument2: IHTMLDocument2;
        FMarkupContainer2: IMarkupContainer2;
        FOnChange: TNotifyEvent;
        FWebBrowser: TWebBrowser;
        procedure EnterDesignMode;
        procedure ExitDesignMode;
        function GetActive: Boolean;
        procedure SetActive(const Value: Boolean);
        procedure SetWebBrowser(const Value: TWebBrowser);
        { IHTMLChangeSink }
        function Notify: HResult; stdcall;
      protected
        procedure Notification(AComponent: TComponent; Operation: TOperation); override;
      public
        destructor Destroy; override;
      published
        property Active: Boolean read GetActive write SetActive;
        property OnChange: TNotifyEvent read FOnChange write FOnChange;
        property WebBrowser: TWebBrowser read FWebBrowser write SetWebBrowser;
      end;
    
    destructor TWebBrowserDesign.Destroy;
    begin
      ExitDesignMode;
      inherited;
    end;
    
    procedure TWebBrowserDesign.EnterDesignMode;
    begin
      if not Assigned(FWebBrowser) then
        raise Exception.Create('Web browser control missing.');
      if not Supports(FWebBrowser.Document, IHTMLDocument2, FHTMLDocument2) then
        raise Exception.Create('No HTML document loaded.');
      try
        FHTMLDocument2.designMode := DesignMode[True];
        if Supports(FWebBrowser.Document, IMarkupContainer2, FMarkupContainer2) then
        begin
          if FMarkupContainer2.RegisterForDirtyRange(Self, FDirtyRangeCookie) <> S_OK then
            FDirtyRangeCookie := 0;
        end;
      except
        ExitDesignMode;
        raise;
      end;
    end;
    
    procedure TWebBrowserDesign.ExitDesignMode;
    begin
      if Assigned(FMarkupContainer2) then
      begin
        if FDirtyRangeCookie <> 0 then
        begin
          FMarkupContainer2.UnRegisterForDirtyRange(FDirtyRangeCookie);
          FDirtyRangeCookie := 0;
        end;
        FMarkupContainer2 := nil;
      end;
      if Assigned(FHTMLDocument2) then
      begin
        FHTMLDocument2.designMode := DesignMode[False];
        if not (csDestroying in ComponentState) then
          FHTMLDocument2 := nil; { causes AV when its hosting TWebBrowser component is destroying; I didn't dig into details }
      end;
      FDocumentComplete := False;
    end;
    
    function TWebBrowserDesign.GetActive: Boolean;
    begin
      Result := Assigned(FHTMLDocument2);
    end;
    
    procedure TWebBrowserDesign.Notification(AComponent: TComponent;
      Operation: TOperation);
    begin
      inherited;
      if (Operation = opRemove) and (AComponent = FWebBrowser) then
        WebBrowser := nil;
    end;
    
    function TWebBrowserDesign.Notify: HResult;
    begin
      Result := S_OK;
      if not FDocumentComplete then
        FDocumentComplete := FHTMLDocument2.readyState = 'complete'
      else if Assigned(FOnChange) then
        FOnChange(Self);
    end;
    
    procedure TWebBrowserDesign.SetActive(const Value: Boolean);
    begin
      if Active <> Value then
      begin
        if Value then
          EnterDesignMode
        else
          ExitDesignMode;
      end;
    end;
    
    procedure TWebBrowserDesign.SetWebBrowser(const Value: TWebBrowser);
    begin
      if Assigned(FWebBrowser) then
      begin
        ExitDesignMode;
        FWebBrowser.RemoveFreeNotification(Self);
      end;
      FWebBrowser := Value;
      if Assigned(FWebBrowser) then
        FWebBrowser.FreeNotification(Self);
    end;
    

    如果您将这样一个组件放在设计时包中,并在IDE中注册它,那么您将能够将该组件与 TWebBrowser公司 并分配 OnChange 窗体设计器中的事件处理程序。使用 Active 属性以进入/退出设计模式。

    type
      TForm1 = class(TForm)
        { ... }
        WebBrowserDesign1: TWebBrowserDesign;
        { ... }
      end;
    
    procedure WebBrowserDesign1Change(Sender: TObject);
    begin
      ButtonSave.Enabled := True;
    end;
    
    procedure TForm1.WebBrowser1DocumentComplete(ASender: TObject;
      const pDisp: IDispatch; const URL: OleVariant);
    begin
      { enter design mode }
      WebBrowserDesign1.Active := True;
    end;
    
    procedure TForm1.ButtonSave(Sender: TObject);
    begin
      { exit design mode }
      WebBrowserDesign1.Active := False;
      ButtonSave.Enabled := False;
    end;
    

    注意:关于C#/WinForms也有类似的问题- How do I detect when the content of a WebBrowser control has changed (in design mode)?

    最后一点:我不认为在更改后启用保存按钮是最好的用户体验设计。如果你认为上面的代码值得实现你的目标,那么继续吧。这只是一个概念证明,代码尚未经过彻底测试。使用风险自负。

    推荐文章