LazActiveX/TEvsWebBrowser/WYSIWYG Editor Problem mit MSHMTL

Antworten
musicones
Beiträge: 35
Registriert: Di 8. Sep 2009, 09:13
OS, Lazarus, FPC: Win 10 (L 1.6.2 FPC 3.0.0)
CPU-Target: 64Bit

LazActiveX/TEvsWebBrowser/WYSIWYG Editor Problem mit MSHMTL

Beitrag von musicones »

Hi,

Ich arbeite an einem kleinen WYSIWYG HTML Editor. Ich nutze ActiveX und TEvsWebBrowser in Lazarus 1.1/FPC 2.6.1.
und die Möglichkeit den Browser in den Editmodus zu setzen.
EInfach Formatierungen (Fett, Kursiv, Text ausrichten) funktionieren bereits.

Ich wollte vorgegebene Überschriften (Blockformatierung) implementieren. Da die unterschiedlichen Browserversionen,
aber unterschiedliche Namen für die Überschriften verwenden, ist es möglich diese vorher auszulesen.
Die Idee war diese auszulesen und in einer Combobox auswählbar zu machen.

Wie das grundsätzlice funktioniert kann man in der Dokumentation zu mshtml nachlesen (c-style). Es kursieren dazu
eine Menge nachfragen und mögliche Lösungen im Netz für Delphi. Eine von diesen möglichen Lösungen wollte ich
für fpc und lazarus umsetzen und sehen was sie bewirken, bzw. ob sie funktioniert.

Ein erstes grundsätzliches Problem erlaubt mir nicht meine Anwendung zu debuggen. Wenn ich die Testanwendung in
der IDE starte und ich während der Laufzeit auf den Editor klicke, um Eingaben zu machen, dann erhalte ich eine Fehlermeldung
und das Programm bricht ab. Wenn ich allerdings die erstellte Anwendung ausserhalb der IDE starte, dann funktioniert soweit alles.
Ich vermute hier erstmal ein Problem mit der ActiveX oder TEvsWebbroser Implementation, da Lazarus 1.1 ja noch nicht released ist.
Soweit als Vorabbemerkung.

Jetzt zum eigentlichen Problem. Ich kann also meine Anwendung nur ausserhalb der IDE starten und nicht debuggen.
Der Quellcode zum Auslesen der Überschriften lautet:

Code: Alles auswählen

 
procedure THTMLEditorView.FillStylesCombo;
const
  IDM_GETBLOCKFMTS = 2233;
  GUID_TriEditCommandGroup: TGUID = '{2582F1C0-084E-11d1-9A0E-006097C9B344}';
  CMDSETID_Forms3: TGUID = '{DE4BA900-59CA-11CF-9592-444553540000}';
var
  CommandTarget: IOleCommandTarget;
  MyHResult: HRESULT;
  MyVariant, varRange, oNULL: OleVariant;
  Ps: PSafeArray;
  Warr: array of WideString;
  sCommands: string;
  StrCount, I: integer;
  Pw: PWideChar;
begin
  ONull := Null;
 
  // making the variant a VariantArray
  TVariantArg(MyVariant).vt := VT_ARRAY;
 
  //if that doesn't works try @CMDSETID_Forms3 in stead of @GUID_TriEditCommandGroup.
  {
  TVariantArg(vo).vt := vt_bstr;
  TVariantArg(vo).bStrVal := nil;
  Hr := CommandTarget.Exec(@CMDSETID_Forms3, IDM_BLOCKFMT,
  OLECMDEXECOPT_DONTPROMPTUSER, NullVariant, Va);
  }

 
  MyHResult := CommandTarget.Exec(
    CMDSETID_Forms3,
    IDM_GETBLOCKFMTS,
    OLECMDEXECOPT_DONTPROMPTUSER,
    ONull,
    MyVariant);
 
 
(*
  if (MyHResult = S_OK) then
  begin
    //get a pointer to the SafeArray
    Ps := TVariantArg(VarRange).pArray;
 
    //Get number of strings in the SafeArray
 
    StrCount := TSAFEARRAYBOUND(Ps^.rgsabound).cElements;
 
    //make room fore all the strings in our WideString array
    SetLength(Warr, StrCount);
    try
      //lock the SafeArray = no risk of memory reallocation during
      Inc(Ps^.cLocks);
      //copy the SafeArray-Strings to the WideString array
      //Ps.pvData points to start of the SafeArrays Data-segment
      //Ps.cbElements = size of each record = a PWideChar
      CopyMemory(@Warr[0], Ps^.pvData, I * 4 {Ps.cbElements});
    finally
      Dec(Ps^.cLocks); //unlock the SafeArray
    end;
 
    sCommands := '';
 
    //get each string from the WideString array
    for I := 0 to StrCount do sCommands := sCommands + Warr[I] + #13#10;
  end
  else
    sCommands := 'GetBlockFmts error';
 
  showInfo(sCommands);
*)

 
(*
  //then the API-call way
  if (MyHResult = S_OK) then
  begin
    //get a pointer to the SafeArray
    Ps := TVariantArg(VarRange).pArray;
 
    sCommands := '';
 
    for I := VarArrayLowBound(VarRange, 1) to VarArrayHighBound(VarRange, 1) do
    begin
      //get each string from the SafeArray
      SafeArrayGetElement(Ps, PLongInt(I), Pw);
      sCommands := sCommands + Pw + #13#10;
    end;
  end
  else
    sCommands := 'GetBlockFmts error';
 
  showInfo(sCommands);
 
  //----------------------------------------------------------
 
 
*)

 
end;       
 


Problematischer Code, der zur Fehlermeldung führt:

Code: Alles auswählen

 
  MyHResult := CommandTarget.Exec(
    CMDSETID_Forms3,
    IDM_GETBLOCKFMTS,
    OLECMDEXECOPT_DONTPROMPTUSER,
    ONull,
    MyVariant);
 


Das Ergebnis ist eine "Access violation".


Evt. liest jemand ja bereits aus dem Quellcode meinen Fehler, bzw.
hat bereits mal jemand mit CommandTarget gearbeitet oder
könnte mit einen Fehler in der eigentlichen Implementation bestätigten.

Vielen Dank
Antonio
Zuletzt geändert von Lori am Mo 14. Jan 2013, 15:24, insgesamt 2-mal geändert.
Grund: Bitte den richtigen Highlighter verwenden.

Soner
Beiträge: 623
Registriert: Do 27. Sep 2012, 00:07
OS, Lazarus, FPC: Win10Pro-64Bit, Immer letzte Lazarus Release mit SVN-Fixes
CPU-Target: x86_64-win64
Wohnort: Hamburg

Re: LazActiveX/TEvsWebBrowser/WYSIWYG Editor Problem mit MSH

Beitrag von Soner »

Es ist normal dass es Abstürzt. Du definierst "CommandTarget" als lokale Variable und benutzt in ohne zu initialisieren. Ich glaube vor dem benutzen muß du ihn auf dein Editor/Browser setzen.

musicones
Beiträge: 35
Registriert: Di 8. Sep 2009, 09:13
OS, Lazarus, FPC: Win 10 (L 1.6.2 FPC 3.0.0)
CPU-Target: 64Bit

Re: LazActiveX/TEvsWebBrowser/WYSIWYG Editor Problem mit MSH

Beitrag von musicones »

Ja, richtig. Vielen Dank.

Der Code muss dann wie folgt aussehen:

Code: Alles auswählen

 
...
  CommandTarget := (Document as IOleCommandTarget);
 
  MyHResult := CommandTarget.Exec(
  CMDSETID_Forms3,
  IDM_GETBLOCKFMTS,
  OLECMDEXECOPT_DONTPROMPTUSER,
  EmptyParam,
  ValOUT);
...
 



Die nachfolgende Auswertung des OleVariants bereitet mir noch Kopfschmerzen.

Code: Alles auswählen

 
var
  CommandTarget: IOleCommandTarget;
  ValOUT, sBuffer: OleVariant;
  Val: PSafeArray;
  MyHResult: HResult;
  i, iMin, iMax: Integer;
  PWChar: PWideChar;
  S: string;
begin
...CommandTarget.Exec....
 
  if (MyHResult = S_OK) then
  begin
    TVariantArg(ValOUT).VT := VT_BYREF or VT_SAFEARRAY;
 
//    Val := PSafeArray(TVariantArg(ValOUT).ppArray);
    Val := PSafeArray(TVariantArg(ValOUT).pArray);
 
    S := '';
 
    // Die Anzahl der Elemente im Array bestimmen
   ActiveX.SafeArrayGetLBound(Val, 1, iMin);
   ActiveX.SafeArrayGetUBound(Val, 1, iMax);
 
    for I := iMin to iMax - 1 do
    begin
      //Jedes Element aus dem SafeArray erhalten
      MyHResult := SafeArrayGetElement(Val, PLongInt(I), sBuffer);
 
      case MyHResult of
        S_OK: S := S + sBuffer + #13#10;
        DISP_E_BADINDEX: S := S + 'The specified index is invalid: ' + IntToStr(I) + #10#13;
        E_INVALIDARG: S := S + 'One of the arguments is invalid: ' + IntToStr(I) + #10#13;
        E_OUTOFMEMORY: S := S + 'Memory could not be allocated for the element: ' + IntToStr(I) + #10#13;
        else S := S + 'other error: ' + IntToStr(I) + #10#13;
      end;
    end;
  end
  else
    S := 'GetBlockFmts error';
 
  showInfo(S);
 


Das ganze läuft dann immer in E_INVALIDARG und keine Ahnung warum

Danke und Gruß
Antonio

Soner
Beiträge: 623
Registriert: Do 27. Sep 2012, 00:07
OS, Lazarus, FPC: Win10Pro-64Bit, Immer letzte Lazarus Release mit SVN-Fixes
CPU-Target: x86_64-win64
Wohnort: Hamburg

Re: LazActiveX/TEvsWebBrowser/WYSIWYG Editor Problem mit MSH

Beitrag von Soner »

Es soll ja irgendwelche Parameter falsch sein. Bei Welche Zeile taucht der Fehler auf?

musicones
Beiträge: 35
Registriert: Di 8. Sep 2009, 09:13
OS, Lazarus, FPC: Win 10 (L 1.6.2 FPC 3.0.0)
CPU-Target: 64Bit

Re: LazActiveX/TEvsWebBrowser/WYSIWYG Editor Problem mit MSH

Beitrag von musicones »

Mittlerweile habe ich die Prozedur angepasst und sieht so aus:

Code: Alles auswählen

 
procedure THTMLEditorView.FillStylesCombo2;
const
  IDM_GETBLOCKFMTS = 2233;
  CMDSETID_Forms3: TGUID = '{DE4BA900-59CA-11CF-9592-444553540000}';
var
  CommandTarget: IOleCommandTarget;
  ListOfFormats: OleVariant;
  SaveListOfFormats: PSafeArray;
  MyHResult: HResult;
  WSElement: WideString;
  S: string;
  i, iMin, iMax: Integer;
begin
  { Variablen initialisieren }
  S := EmptyString; iMin := 0; iMax := 0;
 
  { Document als IOleCommandTarget casten }
  CommandTarget := (Document as IOleCommandTarget);
 
  { OleVariant ListOfFormats vorbereiten }
  TVariantArg(ListOfFormats).VT := VT_BYREF or VT_SAFEARRAY;
  TVariantArg(ListOfFormats).ppArray := nil;
 
  { Liste der Formate in ListOfFormats einlesen }
  MyHResult := CommandTarget.Exec(
    CMDSETID_Forms3,
    IDM_GETBLOCKFMTS,
    OLECMDEXECOPT_DONTPROMPTUSER,
    EmptyParam,
    ListOfFormats);
 
  { Wenn das CommandTarget.Exec erfolgreich durchlaufen wurde,
    die Liste der Formate parsen }

  if (MyHResult = S_OK) then
  begin
    // VARTYPE NOCHMAL SETZEN!
    TVariantArg(ListOfFormats).VT := VT_BYREF or VT_SAFEARRAY;
 
    { OleVariant als SaveArray interpretieren }
    SaveListOfFormats := PSafeArray(TVariantArg(ListOfFormats).ppArray);
 
    { Die Anzahl der Elemente im Array bestimmen }
    ActiveX.SafeArrayGetLBound(SaveListOfFormats, 1, iMin);
    ActiveX.SafeArrayGetUBound(SaveListOfFormats, 1, iMax);
 
    for i := iMin to iMax - 1 do
    begin
      { Werte aus SafeArray auslesen }
      MyHResult := SafeArrayGetElement(SaveListOfFormats, PLongInt(i), WSElement);
 
      case MyHResult of
        S_OK: S := S + WSElement + #13#10;
 
        DISP_E_BADINDEX:
        begin
          S := S + 'The specified index is invalid: ' + IntToStr(I) + #10#13;
          break;
        end;
 
        E_INVALIDARG:
        begin
          S := S + 'One of the arguments is invalid: ' + IntToStr(I) + #10#13;
          break;
        end;
 
        E_OUTOFMEMORY:
        begin
          S := S + 'Memory could not be allocated for the element: ' + IntToStr(I) + #10#13;
          break;
        end
 
        else begin
          S := S + 'other error: ' + IntToStr(I) + #10#13;
          break;
        end;
      end;
    end;
  end else
    S := 'GetBlockFmts error';
 
  ActiveX.SafeArrayDestroyData(SaveListOfFormats);
  ActiveX.SafeArrayDestroy(SaveListOfFormats);
 
 
  if (S <> EmptyStr)
  then showInfo(S)
  else showInfo('Keine Meldungen');
end;
 


Das Ergebnis der Zeile: MyHResult := SafeArrayGetElement(SaveListOfFormats, PLongInt(i), WSElement);
liefert aktuell E_INVALIDARG.

Soner
Beiträge: 623
Registriert: Do 27. Sep 2012, 00:07
OS, Lazarus, FPC: Win10Pro-64Bit, Immer letzte Lazarus Release mit SVN-Fixes
CPU-Target: x86_64-win64
Wohnort: Hamburg

Re: LazActiveX/TEvsWebBrowser/WYSIWYG Editor Problem mit MSH

Beitrag von Soner »

In MSDN stheht dass SafeArrayGetElement Zeiger als Parameter verwendet (http://msdn.microsoft.com/en-us/library/ms891255.aspx):

Code: Alles auswählen

HRESULT SafeArrayGetElement(
  SAFEARRAY FAR* psa,
  long FAR* rgIndices,
  void FAR* pv
);

Versuch WSElement als PWideString zu definieren. (Eventuell muss es vorher initialisiert bzw. speicher reserviert werden).
Weiteres in Internet kannst du finden wenn du in Google nach "delphi SafeArrayGetElement" suchst. Weil Delphi mehr für Win32/64-Api verwendet wird, wirst du da mehr Informationen finden.
Viel Glück.

EDIT:
Schaumal hier http://www.supermemo.com/source/tweb.htm dort wird auch Explorer als Editor verwendet.

musicones
Beiträge: 35
Registriert: Di 8. Sep 2009, 09:13
OS, Lazarus, FPC: Win 10 (L 1.6.2 FPC 3.0.0)
CPU-Target: 64Bit

Re: LazActiveX/TEvsWebBrowser/WYSIWYG Editor Problem mit MSH

Beitrag von musicones »

Danke für deine Hilfe.

Hatte ich bereits verucht. Der Quellcode basiert auf vielen Einträgen, die ich über die Google Suche gefunden hatte.
Ich habe WSElement mit verschiedensten Datentypen ausprobiert, PWideString, PWideChar u.a.
Mit PWideChar hatte ich das auch im Netz gefunden. Dort wurde es auch nicht vorher initialisiert.

musicones
Beiträge: 35
Registriert: Di 8. Sep 2009, 09:13
OS, Lazarus, FPC: Win 10 (L 1.6.2 FPC 3.0.0)
CPU-Target: 64Bit

gelöst

Beitrag von musicones »

Endlich gelöst :P. Noch einmal Danke an Soner, der mich dazu ermuntert hat die Google Suche zum Xten mal anzuschmeißen.
Tatsächlich konnte ich etwas finden. Nach Tagen der Suche wird man ja betriebsblind.

Hier nun die fertige Prozedur, die die Blockformate ausliest:

Code: Alles auswählen

 
procedure THTMLEditorView.FillStylesCombo2;
type
  TWideStringArray = Array of WideString;
 
  function SafeArrayAsStringArray(ar: PSafeArray): TWideStringArray;
  var
    lb, ub, i: Integer;
    ind: array [0..0] of Integer;
  begin
    SafeArrayGetLBound(ar, 1, lb);
    SafeArrayGetUBound(ar, 1, ub);
    SetLength(result, ub-lb+1);
 
    for i := lb to ub do
    begin
      ind[0] := i;
      SafeArrayGetElement(ar, @ind, result[i-lb]);
    end;
  end;
 
const
  IDM_GETBLOCKFMTS = 2233;
  CMDSETID_Forms3: TGUID = '{DE4BA900-59CA-11CF-9592-444553540000}';
var
  CommandTarget: IOleCommandTarget;
  ListOfFormats: OleVariant;
  SafeListOfFormats: PSafeArray;
  StringListOfFormats: TWideStringArray;
  MyHResult: HResult;
  S: string;
  i: Integer;
begin
  { Variablen initialisieren }
  S := EmptyStr;
 
  { Document als IOleCommandTarget casten }
  CommandTarget := (Document as IOleCommandTarget);
 
  { OleVariant ListOfFormats vorbereiten }
  TVariantArg(ListOfFormats).VT := VT_BYREF or VT_SAFEARRAY;
  TVariantArg(ListOfFormats).ppArray := nil;
 
  { Liste der Formate in ListOfFormats einlesen }
  MyHResult := CommandTarget.Exec(
    CMDSETID_Forms3,
    IDM_GETBLOCKFMTS,
    OLECMDEXECOPT_DONTPROMPTUSER,
    EmptyParam,
    ListOfFormats
  );
 
  { Wenn das CommandTarget.Exec erfolgreich durchlaufen wurde,
    die Liste der Formate parsen }

  if (MyHResult = S_OK) then
  begin
    // VarType noch einmal setzen; sicher ist sicher
    TVariantArg(ListOfFormats).VT := VT_BYREF or VT_SAFEARRAY;
 
    { OleVariant als SafeArray interpretieren }
    SafeListOfFormats := PSafeArray(TVariantArg(ListOfFormats).ppArray);
 
    { Das SafeArray in einen StringArray kopieren - siehe Function oben }
    StringListOfFormats := SafeArrayAsStringArray(SafeListOfFormats);
 
    { Durchlaufen des StringArrays und auslesen der Elemente in einen String, zur Anzeige; Lediglich für den Test gedacht  }
    for i := Low(StringListOfFormats) to High(StringListOfFormats) do
    begin
       S := S + StringListOfFormats[i] + LineEnding;
    end;
 
    showMessage(S);
  end;
end;
 


Zur Erklärung: Man erhält einen OleVariant. Dieser wird als SafeArray interpretiert. Da wir im Grunde eine Stringliste erwarten, kopieren wir das SafeArray in einen WideString Array.
Dies wird mit der Function SafeArrayAsStringArray gemacht. Diese Funktion habe ich im beim Googlen gefunden. Interessant ist, dass anstatt den zweiten Parameter für das Indize als PLongInt zu casten, hier ein Array of Integer und mit @ als Pointer genutzt wird. Möglicherweise war also der zweite Parameter bei mir der Fehlerhafte.

Einziges bleibendes Problem, was aber sicher nicht mehr soo schwer zu lösen sein sollte ist, dass Umlaute nicht korrekt dargestellt werden (als ? ; Beispiel: "?berschrift 1").
Da muss ich jetzt erst einmal schauen, zumindest kommt die Liste der Blockformate heraus.

Soner
Beiträge: 623
Registriert: Do 27. Sep 2012, 00:07
OS, Lazarus, FPC: Win10Pro-64Bit, Immer letzte Lazarus Release mit SVN-Fixes
CPU-Target: x86_64-win64
Wohnort: Hamburg

Re: LazActiveX/TEvsWebBrowser/WYSIWYG Editor Problem mit MSH

Beitrag von Soner »

Da Lazarus mit Utf8 arbeiten soll versuchmal für die richtigen Umlaute Strings zukonvertieren. z.B.:

Code: Alles auswählen

 
 Edit1.Text:=UTF8Encode(StringListOfFormats[0]);
 


Du kannst auch mit reine Windows-Funktionen kontrollieren ob die Texte in dein StringListOfFormat richtige umlaute hat. Also Lazarus-LCL umgehen:

Code: Alles auswählen

 
//....
uses Windows;
//....
  windows.SendMessageW(Edit1.Handle,WM_SETTEXT,0,LPARAM(PWideChar(StringListOfFormats[0])));
//..
 

oder besser alles in in Widestring kopieren dann zu ein Memo hinzufügen:

Code: Alles auswählen

 
//....
uses Windows;
//....
var
  StringListOfFormats: TWideStringArray;
  i: integer;
  txt: WideString;
begin
   for i := Low(StringListOfFormats) to High(StringListOfFormats) do
      txt := txt + StringListOfFormats[i] + LineEnding;
 
   windows.SendMessageW(Edit1.Handle,WM_SETTEXT,0,LPARAM(PWideChar(txt )));
//..
 

 
Beachte auch in dein Code hier:

Code: Alles auswählen

 
 S := S + StringListOfFormats[i] + LineEnding;
 

Du hast StringListOfFormats als WidestringArray und die kopierst du einfach in die VAriable s was als normale String definiert ist.
Versuchmal das:

Code: Alles auswählen

 
 S := S + UTF8Encode(StringListOfFormats[i]) + LineEnding;
 

Antworten