Entwickler-Ecke
Open Source Units - HTML-Editor im Eigenbau
MSCH - Di 25.10.05 20:02
Titel: HTML-Editor im Eigenbau
Hallo Community,
ich möcht' euch die Verwendung des HTML-Editors, welcher beim IE verwendet wird, vorstellen und einige Anregungen bieten.
habt Verständnis, dass hier nicht der gesamte Quelltext steht.(Admins, verzeiht mir).
das Ganze funzt nachweislich unter D5, D6 und D2000. Andere Umgebungen stehen mir nicht zur Verfügung.
Sodele, nun gehts los.
Als erstes benötigt Ihr das OCX und die TLB des DHTML-Editors, welches Ihr in der Regel und auf deutschen Systemen unter dem Verzeichnis C:\Programme\Gemeinsame Dateien\Microsoft Shared\Triedit als DHTMLED.OCX findet. Wie ihr diese einbaut, beschreibe ich jetzt nicht, ist in der Hilfe gut beschrieben.
Als nächstes patchen wir die Olectrls.pas, da MS mit der KB891781 eine Sicherheitslücke gestopft hat, die in Delphi leider zu der bekannten Meldung "Schnittstelle nicht unterstützt" führt. Diese Lösung findet ihr auch bei Google, ist nicht auf meinem Mist gewachsen.
Achtung: Macht vorher eine Sicherheitskopie!!
Fügt im Interface der OleCltrs.pas folgendes ein:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25:
| Type TImpIOleContainer = class(TObject, IOleContainer) protected m_cRef: DWORD; public function _AddRef: Integer; overload; stdcall; function _Release: Integer; overload; stdcall;
constructor Create; virtual; destructor Destroy; override;
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function ParseDisplayName(const bc: IBindCtx; pszDisplayName: POleStr; out chEaten: Longint; out mkOut: IMoniker): HResult; stdcall;
function EnumObjects(grfFlags: Longint; out Enum: IEnumUnknown): HResult; stdcall; function LockContainer(fLock: BOOL): HResult; stdcall; end;
const IID_IUnknown : TGUID = '{00000000-0000-0000-C000-000000000046}'; IID_IOleContainer : TGUID = '{0000011B-0000-0000-C000-000000000046}'; |
Im Implementationsteil:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64:
| constructor TImpIOleContainer.Create; begin inherited; m_cRef := 0; end;
destructor TImpIOleContainer.Destroy; begin inherited; end;
function TImpIOleContainer.EnumObjects(grfFlags: Integer; out Enum: IEnumUnknown): HResult; begin Result := E_NOINTERFACE; end;
function TImpIOleContainer.LockContainer(fLock: BOOL): HResult; begin Result := E_NOINTERFACE; end;
function TImpIOleContainer.ParseDisplayName(const bc: IBindCtx; pszDisplayName: POleStr; out chEaten: Integer; out mkOut: IMoniker): HResult; begin Result := E_NOINTERFACE; end;
function TImpIOleContainer.QueryInterface(const IID: TGUID; out Obj): HResult;
begin If (IsEqualGUID(IID, IID_IOleContainer)) Then begin GetInterface(IID_IOleContainer, Obj); Result := S_OK; Exit; end else If (IsEqualGUID(IID, IID_IUnknown)) Then begin GetInterface(IID_IUnknown, Obj); Result := S_OK; Exit; end;
Result := E_NOINTERFACE; end;
function TImpIOleContainer._AddRef: Integer; begin Inc(m_cRef); Result := m_cRef; end;
function TImpIOleContainer._Release: Integer; begin If m_cRef > 0 Then Dec(m_cRef) else m_cRef := 0; Result := m_cRef; end; |
Jetzt sucht bitte folgende Funktion und ergänzt sie wie dargestellt:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:
| function TOleControl.GetContainer(out container: IOleContainer): HResult; Var pIContainer : TImpIOleContainer;
begin pIContainer := TImpIOleContainer.Create; If (pIContainer <> NIL) Then begin Result := pIContainer.QueryInterface(IID_IOleContainer, container); Exit; end; container := NIL; Result := E_NOINTERFACE; end; |
so, nun basteln wir den HTML-Editor
Ich habe dazu eine Form gebastet mit verschiedenen Buttons wie Fett, Kursiv etc. sowie dem importierten OCX-Control.
Das control hat hier den Namen
WebEdit
Folgende Units werden u.a. benötigt:
Delphi-Quelltext
1: 2: 3: 4:
| uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, OleServer, ExtDlgs, OleCtrls, DHTMLEDLib_TLB, ActiveX, ExtCtrls, SHDocVw, MSHTML_TLB, ActnList, StdCtrls, Menus, ComCtrls; |
Nun erweitert bitte mal die Klassendeklaration
wie folgt:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50:
| TMain = class(TForm,IHTMLEditDesigner,IHTMLEditHost) ... function SnapRect(const pIElement: IHTMLElement; var prcNew: tagRECT; eHandle: _ELEMENT_CORNER): HResult; stdcall; function PreHandleEvent(inEvtDispId: Integer; const pIEventObj: IHTMLEventObj): HResult; stdcall; function PostHandleEvent(inEvtDispId: Integer; const pIEventObj: IHTMLEventObj): HResult; stdcall; function TranslateAccelerator(inEvtDispId: Integer; const pIEventObj: IHTMLEventObj): HResult; stdcall; function PostEditorEventNotify(inEvtDispId: Integer; const pIEventObj: IHTMLEventObj): HResult; stdcall; ... end
function TMain.PostHandleEvent(inEvtDispId: Integer; const pIEventObj: IHTMLEventObj): HResult; begin Result := S_FALSE; end;
function TMain.PreHandleEvent(inEvtDispId: Integer; const pIEventObj: IHTMLEventObj): HResult; begin Result := S_FALSE; end;
function TMain.TranslateAccelerator(inEvtDispId: Integer; const pIEventObj: IHTMLEventObj): HResult; begin Result := S_FALSE; end;
function TMain.PostEditorEventNotify(inEvtDispId: Integer; const pIEventObj: IHTMLEventObj): HResult; begin if inEvtDispId = -606 then begin sb.Panels[0].Text := IntToStr(pIEventObj.clientX) + ':' + IntToStr(pIEventObj.clientY); end; Result := S_FALSE; end;
function TMain.SnapRect(const pIElement: IHTMLElement; var prcNew: tagRECT; eHandle: _ELEMENT_CORNER): HResult; begin prcNew.left := 20 * (prcNew.left div 20); prcNew.top := 20 * (prcNew.top div 20); prcNew.right := 20 * (prcNew.right div 20); prcNew.bottom := 20 * (prcNew.bottom div 20); Result := S_OK; end; |
am Ende des Programms muss stehen:
Delphi-Quelltext
1: 2: 3: 4: 5:
| initialization OleInitialize(nil);
finalization OleUninitialize; |
In der Create() Methode der Form ergänzen wir folgende Einträge:
Delphi-Quelltext
1: 2: 3: 4: 5:
| procedure TMain.FormCreate(Sender: TObject); begin WebEdit.DefaultInterface._AddRef; ... end; |
Um eine Datei zu Laden:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7:
| var O:OleVariant; begin O:='test.html'; WebEdit.LoadDocument(O); ... end; |
Um eine Datei zu Sichern, gehe ich hier jetzt einen alternativen Weg via Textfile:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10:
| var W:WideString; F:TextFile; begin S:=WebEdit.DocumentHTML; AssignFile(F,'Test.html'); Rewrite(F); Writeln(F,S); closeFile(F); end; |
Alternativ wäre zum Beispiel auch sinnvoll:
Delphi-Quelltext
1: 2:
| StringToWideChar(Filename,Wide,Sizeof(Wide)); (WebEdit.DOM as IPersistFile).Save(Wide,false); |
oder via Dialog zum Beispiel:
Delphi-Quelltext
1: 2: 3: 4:
| procedure TMain.SaveAsClick(Sender: TObject); begin WebEdit.DOM.execCommand('SaveAs',false,0); end; |
Damit diverse Buttons (Fett, Kursiv) nur aktiv sind, wenn der Nutzer auch sinnvoll das anwenden kann, habe ich eine Funktion "UpdateButton", die genau prüft, was kann, was kann nicht.
Zur Lesart:
ak... sind TAktions, die mit den Buttons gleichnamiger Bedeutung verknüpft sind.
die cmdID findet ihr auch im MSDN
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44:
| function TMain.GetProperties(const Name: String): OLEVariant; var V: OleVariant; begin V := WebEdit.DOM.selection.createRange; Result := V.queryCommandValue(Name); end;
procedure TMain.UpdateButtons; function QueryStatus(cmdID: OleVariant): OleVariant; begin Result := WebEdit.QueryStatus(cmdID); end;
begin akAusschneiden.Enabled := (QueryStatus(DECMD_CUT) and DECMDF_ENABLED) = DECMDF_ENABLED; akKopieren.Enabled := (QueryStatus(DECMD_COPY)and DECMDF_ENABLED) = DECMDF_ENABLED; akEinfuegen.Enabled:= (QueryStatus(DECMD_PASTE) and DECMDF_ENABLED) = DECMDF_ENABLED; akEinfuegenAlsText.Enabled:= (QueryStatus(DECMD_PASTE) and DECMDF_ENABLED) = DECMDF_ENABLED; aBold.Enabled := (QueryStatus(DECMD_BOLD) and DECMDF_ENABLED) = DECMDF_ENABLED; aBold.Checked := GetProperties('Bold'); aUnderline.Enabled := (QueryStatus(DECMD_UNDERLINE) and DECMDF_ENABLED) = DECMDF_ENABLED; aUnderline.Checked := GetProperties('Underline'); aItalic.Enabled := (QueryStatus(DECMD_ITALIC) and DECMDF_ENABLED) = DECMDF_ENABLED; aItalic.Checked := GetProperties('Italic'); aLeft.Checked := (GetProperties('JustifyLeft')); aCenter.Checked := (GetProperties('JustifyCenter')); aRight.Checked := (GetProperties('JustifyRight')); aUndo.Enabled:= (QueryStatus(DECMD_UNDO) and DECMDF_ENABLED) = DECMDF_ENABLED; aRedo.Enabled:= (QueryStatus(DECMD_REDO) and DECMDF_ENABLED) = DECMDF_ENABLED; aNumber.Checked:= (QueryStatus(DECMD_ORDERLIST) and DECMDF_LATCHED) = DECMDF_LATCHED; aBullet.Checked:= (QueryStatus(DECMD_UNORDERLIST) and DECMDF_LATCHED) = DECMDF_LATCHED; aIndent.Enabled:= (QueryStatus(DECMD_INDENT) and DECMDF_ENABLED) = DECMDF_ENABLED; aOutdent.Enabled:= (QueryStatus(DECMD_OUTDENT) and DECMDF_ENABLED) = DECMDF_ENABLED; aURL.Enabled:= WebEdit.Dom.queryCommandEnabled('CreateLink'); aUp.Checked:=(GetProperties('Superscript')); aDown.Checked:=(GetProperties('Subscript')); end; |
nun wollen wir aber auch, dass der Anwender Text z.b. Fett machen kann
hier die zugehörige TAction.Das gilt übrigens für alle Aktions. Ihr müsst nur die CMDID
wie Bold, Underline etc. entsprechend verwenden.
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26:
| procedure TMain.SetProperties(const Name: String; Value: OleVariant); var V: OleVariant; S: OleVariant; begin if GetProperties(Name) <> Value then begin V := WebEdit.DOM.selection.createRange; S := Value; V.execCommand(Name , False, S); end; end;
procedure TMain.aBoldExecute(Sender: TObject); begin SetProperties('Bold',iBold.Down); UpdateButtons; end; ...
procedure TMain.btnUnderlineClick(Sender: TObject); begin SetProperties('Underline',iUnderline.Down); UpdateButtons; end; |
Damit der Anwender auch immer weiss welche Funktionen er nutzen kann, verknüpfen wir die
Ereignisse ONonClick(), ONonKeyDown() und ONonKeyUp() mit der Funktion UpdateButtons:
z.b.
Delphi-Quelltext
1: 2: 3: 4:
| procedure TMain.WebEditonclick(Sender: TObject); begin UpdateButtons; end; |
Noch ein Problem haben wir, welcher Zeichensatz soll verwendet werden?
Ist nicht grad unwichtig beim Speichern etc.
Ich habe dazu einfach in der Create() Methode geschrieben :
Delphi-Quelltext
1: 2: 3:
| while Webedit.Busy do Application.ProcessMessages; if not WebEdit.Busy then WebEdit.Dom.charset:='iso-8859-1'; |
fertig.
Ich bastle hier noch etwas rum, kommen also noch ein paar Ergänzungen.
grez
MSCH
LH_Freak - Di 25.10.05 20:08
das klingt super. Könntest du vll. noch eine Demo posten (also im Anhang, und mit Quelltext)?
F34r0fTh3D4rk - So 30.10.05 17:19
ich würde es ja gerne verwenden, aber wie weiß ich auch nicht, ne demo wäre schon das non-plus-ultra ;)
um das einfach mal so zu testen, extra sich einarbeiten selbst n demo programm schreiben ist net immer so das wahre finde ich 8)
MSCH - So 30.10.05 19:20
oki doki,
ich setz das morgen mal rein, muss erstmal wieder mein D installieren, :-( isch abgekackt.
grez
msch
oje, immer diese ausdrücke.
MSCH - Mo 31.10.05 18:12
sodele, hier kömmt das versprochene Progrämmchen.
Es besteht aus 3 teilen, die Exe, eine Konfig und eine Hilfedatei.
Viel Spass beim probieren.
Hier noch ein paar infos:
Das Programm bietet die Möglichkeit, Sonderzeichen jedweder Art (auch Unicode) einzufügen.
Damit ihr das richtig seht, müßt ihr beim IE den Default-Zeichensatz auf Arial Unicode MS einstellen.
(extras-Optionen beim IE).
Leider kann ich die Quelltexte nur in Auszügen wiedergeben; ich verwendete auf mich lizensierte Kompos
von DevEx deren Weitergabe untersagt ist. Allerdings lässt sich das auch mit den Standard-Kompos von D
bewerkstelligen.
Die Unicode-Teile sind aus den TNT-Controls übernommen.
Und das übliche, die Verwendung ist frei jedoch auf eigene Verantwortung, Haftung und Schadensersatz
wird nicht übernommen. Ich hab nen Virenscanner am laufen, sollte also auch keine "Erreger" beinhalten.
Wird mich über Feedback freuen.
Grez
MSch
smiegel - Fr 11.11.05 13:57
Hallo,
entweder bin ich blind oder habe Tomaten auf den Augen! Wo ist der angekündigte Download-Link? Ich kann keinen finden :(
raziel - Fr 11.11.05 14:38
Er meint den Anhang, du musst wohl kurz "F5" drücken, damit der angezeigt wird. Ist ein bereits bekannter Bug.
stifflersmom - Fr 11.11.05 14:52
Auch wenn ich F5 länger drücke,
den Anhang finde ich nicht.
Moin
raziel - Fr 11.11.05 14:55
Dann klick auf "Seite neu laden", am Ende des letzten Beitrags von MSCH ist dann ein Anhang zu finden.
Bennle - Fr 11.11.05 16:05
Hallo,
Kann denn keiner mal den Quellcode anhängen, dann wäre das nur halbsoviel arbeit :D
MfG
Bennle
MSCH - Fr 11.11.05 18:45
Delete - Di 14.02.06 00:21
Hi,
wie ich schon in meiner PN geschrieben hab, würde ich gerne Funktionen wie Folgende ans laufen bekommen:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9:
| function TMain.PostEditorEventNotify(inEvtDispId: Integer; const pIEventObj: IHTMLEventObj): HResult; begin if inEvtDispId = -606 then begin sb.Panels[0].Text := IntToStr(pIEventObj.clientX) + ':' + IntToStr(pIEventObj.clientY); end; Result := S_FALSE; end; |
Allerdings fehlt mir jeglicher Ansatz wie ich das bewerkstelligen soll, da die entsprechenden Ereignisse nicht aufgerufen werden. Ich hab zwar Lösungen für den TWebBrowser gefunden, allerdings es nicht geschafft das auf das TDhtmlEdit zu übertragen.
Wär deshalb echt nett, falls du oder wer anders wissen würden wie ich die Funktionen dafür integriere, das die Ereignisse aufgerufen und genutzt werden können (z.B. eben für X/Y-Anzeige oder dergleichen).
Danke und Gruß
Benedikt
Delete - Mi 15.02.06 00:51
Eine Frage bleibt mir dabei irgendwie noch, was ist die Entsprechung von TDHTMLEdit zufolgender, auf den TWebBrowser bezogenen Funktion:
Delphi-Quelltext
1: 2: 3: 4:
| function Tform1.GetHTMLDocument2Ifc: IHTMLDocument2; begin Result := WebBrowser1.Document as IHTMLDocument2; end; |
Irgendwie steh ich da grad voll aufm Schlauch, aber ich denke ich benötige diese Funktion? Lieg ich da falsch bzw. wenn es doch richtig ist, was muss ich statt WebBrowser1.Document nutzen?
Oder denk ich das Ganze komplett falsch?
MfG Benedikt
MSCH - Mi 15.02.06 18:36
im DHTML-Editor wäre die Entsprechung:
result:= Webedit.DOM;
grez
msch
Delete - Mi 15.02.06 18:42
Mhhh verdammt, das hatte ich auch schon aber trotzdem irgendeinen Fehler (Schwerwiegender Fehler: TOleException) - naja, ich werd nochmal genauer schauen was sich da machen lässt - trotzdem Danke, evtl. kann ich mein Problem ja auch so irgendwie lösen :wink:
//EDIT: Hat sich erledigt...
Entwickler-Ecke.de based on phpBB
Copyright 2002 - 2011 by Tino Teuber, Copyright 2011 - 2024 by Christian Stelzmann Alle Rechte vorbehalten.
Alle Beiträge stammen von dritten Personen und dürfen geltendes Recht nicht verletzen.
Entwickler-Ecke und die zugehörigen Webseiten distanzieren sich ausdrücklich von Fremdinhalten jeglicher Art!