Вставьте СТИЛИ в TWebBrowser

Я использую TWebBrowser в качестве графического интерфейса редактора для пользователей. Я хочу иметь возможность вставлять веб-элементы управления в документ. Простым примером может быть флажок. (могу пояснить почему, если нужно). У меня все это работает, когда я сначала собираю HTML-документ (с его разделами STYLE и SCRIPTS), а затем передаю его блоком в TWebBrowser. Но теперь я хочу иметь возможность вставлять свои элементы в существующий документ.

У меня есть этот код ниже, но он вызывает ошибку OLE (см. комментарии в коде):

procedure THTMLTemplateDocument.EnsureStylesInWebDOM;
var StyleBlock : IHTMLElement;
    StyleText: string;
begin
  StyleBlock := FWebBrowser.GetDocStyle;
  if not assigned(StyleBlock) then
    raise Exception.Create('Unable to access <STYLE> block in web document');
  StyleText := FCumulativeStyleCodes.Text;
  StyleBlock.InnerText := StyleText; <--- generates "OLE ERROR 800A0258"
end;

Вызываемые функции из приведенного выше кода выглядят следующим образом:

function THtmlObj.GetDocStyle: IHTMLElement;
//Return pointer to <STYLE> block, creating this if it was not already present.
var
  Document:    IHTMLDocument2;         // IHTMLDocument2 interface of Doc
  Elements:    IHTMLElementCollection; // all tags in document body
  AElement:    IHTMLElement;           // a tag in document body
  Style, Head: IHTMLElement;
  I:           Integer;                // loops thru Elements in document body
begin
  Result := nil;
  if not Supports(Doc, IHTMLDocument2, Document) then
    raise Exception.Create('Invalid HTML document');
  Elements := Document.all;
  for I := 0 to Pred(Elements.length) do begin
    AElement := Elements.item(I, EmptyParam) as IHTMLElement;
    if UpperCase(AElement.tagName) <> 'STYLE' then continue;
    result := AElement;
    break;
  end;
  if not assigned(Result) then begin
    Head := GetDocHead;
    if assigned(Head) then begin
      Style := Document.CreateElement('STYLE');
      (Head as IHTMLDOMNode).AppendChild(Style as IHTMLDOMNode);
      Result := Style;
    end;
  end;
end;

а также

function THtmlObj.GetDocHead: IHTMLElement;
//Return pointer to <HEAD> block, creating this if it was not already present.
var
  Document:    IHTMLDocument2;         // IHTMLDocument2 interface of Doc
  Elements:    IHTMLElementCollection; // all tags in document body
  AElement:    IHTMLElement;           // a tag in document body
  Body:        IHTMLElement2;          // document body element
  Head:        IHTMLElement;
  I:           Integer;                // loops thru Elements in document body
begin
  Result := nil;
  if not Supports(Doc, IHTMLDocument2, Document) then
    raise Exception.Create('Invalid HTML document');
  if not Supports(Document.body, IHTMLElement2, Body) then
    raise Exception.Create('Can''t find <body> element');
  Elements := Document.all;
  for I := 0 to Pred(Elements.length) do begin
    AElement := Elements.item(I, EmptyParam) as IHTMLElement;
    if UpperCase(AElement.tagName) <> 'HEAD' then continue;
    Result := AElement;
    break;
  end;
  if not assigned(Result) then begin
    Head := Document.CreateElement('HEAD');
    (Body as IHTMLDOMNode).insertBefore(Head as IHTMLDOMNode, Body as IHTMLDOMNode);
    //now look for it again
    Elements := Document.all;
    for I := 0 to Pred(Elements.length) do begin
      AElement := Elements.item(I, EmptyParam) as IHTMLElement;
      if UpperCase(AElement.tagName) <> 'HEAD' then continue;
      Result := AElement;
      break;
    end;
  end;
end;

Когда я запускаю это, StyleText =

'.selected {'#$D#$A' вес шрифта: полужирный;'#$D#$A' //цвет фона: желтый;'#$D#$A'}'#$D#$A'.unselected { '#$D#$A' вес шрифта: нормальный;'#$D#$A' //фоновый цвет: белый;'#$D#$A'}'#$D#$A#$D#$A

Но я попытался сделать StyleText чем-то простым, например, «привет», и он все равно завис.

Поиск в Google по запросу «OLE ERROR 800A0258» выявляет несколько других людей, у которых были похожие проблемы, например здесь и здесь -- этот более поздний пользователь, кажется, указывает, что он решил проблему с помощью .OuterHTML, но я попробовал это с аналогичной ошибкой. Этот поток указывает, что .InnerText доступен только для чтения . Но в объявлении интерфейса (см. ниже), похоже, есть метод для настройки (т.е. не только для чтения).

// *********************************************************************//
// Interface: IHTMLElement
// Flags:     (4416) Dual OleAutomation Dispatchable
// GUID:      {3050F1FF-98B5-11CF-BB82-00AA00BDCE0B}
// *********************************************************************//
  IHTMLElement = interface(IDispatch)
    ['{3050F1FF-98B5-11CF-BB82-00AA00BDCE0B}']
...
    procedure Set_innerHTML(const p: WideString); safecall;
    function Get_innerHTML: WideString; safecall;
    procedure Set_innerText(const p: WideString); safecall;
    function Get_innerText: WideString; safecall;
    procedure Set_outerHTML(const p: WideString); safecall;
    function Get_outerHTML: WideString; safecall;
    procedure Set_outerText(const p: WideString); safecall;
    function Get_outerText: WideString; safecall;
...
    property innerHTML: WideString read Get_innerHTML write Set_innerHTML;
    property innerText: WideString read Get_innerText write Set_innerText;
    property outerHTML: WideString read Get_outerHTML write Set_outerHTML;
    property outerText: WideString read Get_outerText write Set_outerText;
...
  end;

Может ли кто-нибудь помочь выяснить, как настроить СТИЛИ в разделе <STYLE> существующего HTML-документа в TWebBrowser?


person kdtop    schedule 09.03.2016    source источник
comment
У меня был эксперт Delphi, который пытался помочь мне использовать встроенный хром. Он сказал мне, что он отлично работает, если браузер находится на главной форме. Но когда это было на вторичных формах, это не работало правильно.   -  person kdtop    schedule 10.03.2016
comment
Р.э. почему бы не сделать это в Javascript... У меня есть приложение Delphi, и я хочу вставить шаблон в область редактирования (TWebBrowser). В настоящее время существует механизм шаблонов, который выводит окно, отображает шаблон и по завершении сворачивает его в текст, который вставляется в документ. Я пытаюсь встроить этот шаблон непосредственно в область редактирования. HTML-документ не знает, что потребуется во время вставки, я не думаю, что Javascript сможет это сделать.   -  person kdtop    schedule 10.03.2016
comment
Тогда вы не понимаете javascript. Проведите с ним немного времени. Вы можете вставить любой объект в DOM и управлять этими объектами, их видимостью и местоположением с помощью JavaScript. Вы можете получить этот запрос javascript и загрузить фрагменты html во время выполнения и вставить его на свою страницу. Проверьте jQuery. Если вы хотите, чтобы пользователь мог взаимодействовать с результирующим HTML, вам в любом случае понадобятся некоторые знания javascript. В конце концов, я думаю, вы упретесь в стену, если не научитесь делать это внутри блоков javascript, выполняемых внутри родительского документа.   -  person Warren P    schedule 10.03.2016
comment
@WarrenP Спасибо за ваш пост. Я знаю, что javascript может манипулировать DOM. Но я думаю, что моя установка отличается от того, что вы думаете. Этот браузер не взаимодействует с Интернетом. Он отображает только содержимое, переданное ему приложением Delphi. Если бы javascript должен был манипулировать DOM, тогда код Delphi должен был бы сначала настроить код javascript и передать его. Цепочка событий начинается с кода Delphi. Так что я могу установить STYLES напрямую или создать для этого javascript. Оба действия инициируются из кода Delphi.   -  person kdtop    schedule 13.03.2016
comment
Я бы не рекомендовал, чтобы он взаимодействовал с Интернетом. Но он должен взаимодействовать с локальным сервером, на котором вы работаете, и я думаю, что JavaScript + JQUERY-подобный слой + загрузка со страницы лучше, чем обращаться к интерфейсам IShellDocView/IHTMLStyleSheet (разработанным в 1995 году) и пытаться сделать все сюда.   -  person Warren P    schedule 14.03.2016


Ответы (2)


Если у вас есть действительный IHTMLDocument2, вы можете вызвать его создатьStyleSheet(). Он вернет экземпляр IHTMLStyleSheet. Вы можете использовать его свойство cssText для установки стиля.

Убедитесь, что вы учитываете кодировку символов документа.

person Zamrony P. Juhara    schedule 10.03.2016
comment
Кажется, это как раз то, что я искал. Спасибо! - person kdtop; 14.03.2016

Основываясь на рекомендациях @Zamrony P. Juhara, я придумал следующий код. Я публикую на случай, если это может помочь кому-то еще в будущем.

procedure THtmlObj.AddStylesToExistingStyleSheet(StyleSheet: IHTMLStyleSheet; SelectorSL, CSSLineSL : TStringList);
//NOTE: There must be a 1:1 correlation between SelectorSL and CSSLineSL
//  The first SL will contain the selector text
//  the second SL will contain all the CSS in one line (divided by ";"'s)
var
  SLIdx, RuleIdx, p: integer;
  SelectorText, CSSText, OneCSSEntry : string;
begin
  if not assigned(StyleSheet) then begin
    raise Exception.Create('Invalid StyleSheet');
  end;
  for SLIdx := 0 to SelectorSL.Count - 1 do begin
    SelectorText := SelectorSL.Strings[SLIdx];
    if SlIdx > (CSSLineSL.Count - 1) then break;
    CSSText := CSSLineSL.Strings[SLIdx];
    while CSSText <> '' do begin
      p := Pos(';', CSSText);
      if p > 0 then begin
        OneCSSEntry := MidStr(CSSText, 1, p);
        CSSText := MidStr(CSSText, p+1, Length(CSSText));
      end else begin
        OneCSSEntry := CSSText;
        CSSText := '';
      end;
      RuleIdx := StyleSheet.Rules.length;
      StyleSheet.addRule(SelectorText, OneCSSEntry, RuleIdx);
    end;
  end;
end;


function THtmlObj.AddStyles(SelectorSL, CSSLineSL : TStringList) :     IHTMLStyleSheet;
//NOTE: There must be a 1:1 correlation between SelectorSL and CSSLineSL
//  The first SL will contain the selector text
//  the second SL will contain all the CSS in one line (divided by ";"'s)
var
  Document:      IHTMLDocument2;              // IHTMLDocument2 interface of Doc
  StyleSheets:   IHTMLStyleSheetsCollection;  // document's style sheets
  StyleSheet:    IHTMLStyleSheet;             // reference to a style sheet
  OVStyleSheet:  OleVariant;                  // variant ref to style sheet
  Idx:           integer;
begin
  Result := nil;
  if not Supports(Doc, IHTMLDocument2, Document) then begin
    raise Exception.Create('Invalid HTML document');
  end;
  StyleSheets := Document.styleSheets;
  Idx := Document.StyleSheets.length;
  OVStyleSheet := Document.createStyleSheet('',Idx);
  if not VarSupports(OVStyleSheet, IHTMLStyleSheet, StyleSheet) then begin
    raise Exception.Create('Unable to create valid style sheet');
  end;
  Result := StyleSheet;
  AddStylesToExistingStyleSheet(StyleSheet, SelectorSL, CSSLineSL);
end; //AddStyles
person kdtop    schedule 14.03.2016
comment
Хороший! Иногда я выкладываю подобные вещи и даже через год нахожу свой собственный код таким образом и использую его снова. - person Warren P; 15.03.2016