AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Tutorials Delphi Wie erstelle ich einen Texteditor?
Tutorial durchsuchen
Ansicht
Themen-Optionen

Wie erstelle ich einen Texteditor?

Ein Tutorial von fkerber · begonnen am 11. Apr 2004 · letzter Beitrag vom 16. Sep 2007
Antwort Antwort
Seite 2 von 3     12 3      
Benutzerbild von fkerber
fkerber
Registriert seit: 9. Jul 2003
Hi!

Ich dachte mir, da in letzter Zeit vermehrt Fragen im Forum auftauchten, die man sozusagen, bei der Erstellung eines einfachen Texteditors „lernt“, schreibe ich dazu mal ein Tutorial.

Da es mein erstes Tutorial ist, bitte ich um Nachsicht und bin für konstruktive Kritik natürlich dankbar.

Das Tutorial wurde nur mit Komponenten erstellt, die in Delphi 7 PE enthalten sind.


1. Vorüberlegung

Ich möchte zuerst zu bedenken geben, dass wir nicht Word nachprogrammieren wollen (oder zumindest nicht können), sondern einen einfachen kleinen Texteditor schaffen wollen.
Ich möchte aber gerne gewisse grundlegende Funktionen wie z.B. Kopieren, Einfügen, Schriftformatierung etc. implementieren.

So genug der grauen Theorie, fangen wir an:



2. Erste Schritte: Das Grundlegende


Ich nehme ein Form, benenne es als MainForm und speichere es als Mainfrm.pas ab. Ich gebe ihm die Caption Texteditor.
Als nächsten Schritt werde ich die oberste Menüzeile (Datei, Bearbeiten, Hilfe) integrieren.
Dazu nehme ich die Komponente MainForm vom Register Standard und platziere sie auf dem Form. Durch einen Doppelklick auf die eben platzierte Komponente gelange ich in den Editor für das MainMenu. Ich denke die Bedienung dürft intuitiv sein, ich weise den Feldern in der oberen Zeile jeweils Name und Caption zu (z.B.: Name: Datei, Caption: &Datei, das & bewirkt, dass der folgende Buchstabe einen Unterstrich erhält, sodass dieser Eintrag durch Drücken der Taste des unterstrichenen Buchstabens selektiert werden kann.)
Ich hoffe, die Vorgehensweise ist klar geworden. Den genauen Aufbau kann man ja dem Programm entnehmen.

Irgendwie fehlt den Menüs allerdings noch etwas das gewohnte Aussehen: die Icons vor den Einträgen fehlen. Um diese hinzubekommen nehme ich eine ImageList von der Registerkarte Win32. In Sie lade ich die Icons in der Reihenfolge, wie ich sie in den Menüs verwenden werde. (Die Icons kann man mit diversen Programmen extrahieren, oder sie auch mit einem Grafikprogramm aus einem Screenshot rausschneiden.)
Damit die Einträge auch wissen, welche Icons sie nutzen sollen, weise ich dem MainMenu in der Eigenschaft Images die eben erstellte ImageList1 zu.
Jetzt weise ich nur noch jedem Eintrag die entsprechende Nummer aus der ImageList in der Eigenschaft ImageIndex zu.
Jetzt sieht es doch schon besser aus, oder?

Als nächstes erstelle ich die Menüs mit den schönen Buttons unterhalb der eben erstellten Menüleiste.
Hierfür verwende ich die Komponente Toolbar aus dem Register Win32. Ihr Kontextmenü ermöglicht mir das Hinzufügen von Buttons und Trennbereichen.
Ich füge 2 dieser Toolbars ein, erstelle jeweils einige Buttons auf ihnen und weise ihnen wiederum die ImageList1 in ihrer Eigenschaft Images zu.
Es steht jedem nun frei, welche Buttons er in die Toolbars einfügt, ich habe mich für die unten gezeigte Möglichkeit entschieden. (Dabei habe ich der ImageList noch weitere Bilder zugefügt) Die beiden Comboboxen dienen der späteren Auswahl von Schriftart und Größe, die Schaltfläche ohne Bild für die Farbwahl.
(Zum zuweisen der Images kann die Eigenschaft ImageIndex des jeweiligen Buttons genutzt werden.)
Das Wichtigste fehlt natürlich noch, das Fenster zum Anzeigen und Editieren des Text, wir wollen ja schließlich einen Texteditor erstellen. Dazu nehme ich die Komponente Richedit vom Registerblatt Win32 und platziere sie auf dem Formular. Ich stelle die Eigenschaft Align auf alClient, was bewirkt, dass das Richedit den gesamten verfügbaren Platz auf dem Formular ausfüllt.
Als letztes Element füge ich noch eine Statusleiste ein, die sich ebenfalls auf dem Registerblatt Win32 befindet.
Sie rundet das Bild ab und das eben erstellte Fenster erinnert doch schon recht stark an ähnliche Programme wie z.B. WordPad.

Damit in Zukunft das Formular maximiert gestartet wird, stelle ich seine Eigenschaft WindowState auf wsMaximized.


3. Die Programmierung


Der erste Schritt sollte nochmal etwas Theorie sein. Sie ist vielleicht im Moment etwas nervig, erleichtert die spätere Arbeit aber ungemein. Bei unserer Programmplanung ist es so, dass sowohl über den Menüpunkt als auch über den Button Neu ja die selbe Funktion ausgeführt werden soll. Diese also zweimal zu programmieren ist unnötig und erhöht nur die Programmgröße. Um dies zu vermeiden, stellt uns Delphi eine schöne Komponente zur Seite: die ActionList (Registerkarte Standard).
Die Actionlist ist eine nicht sichtare Komponente, die es uns ermöglicht sogenannte Actions zu erstellen und diese dann mehreren Buttons oder Menüeinträgen zuzuweisen.

Als erstes wollen wir die Action für das Öffnen eines Dokumentes erzeugen. Allerdings stören wir uns noch daran, dass beim Starten des Programmes bereits Richedit1 in unserem Fenster drinsteht. Um dies zu beheben, klicken wir auf die 3 Punkte der Eigenschaft Lines des Richedits und löschen den Text dann aus dem erscheinenden Fenster raus. Somit ist dieses „Problem“ behoben.
Nun aber zum Öffnen einen Dokuments. Wir klicken doppelt auf die ActionList und wählen oben NewAction (erstes Icon) aus. Als Caption geben wir Ö&ffnen an, als Name Aoeffnen (A für Action), als ShortCut Strg+O, als ImageIndex 1, (dafür muss bei der ActionList die Eigenschaft Images wieder auf ImageList1 gestellt sein).
Bei Category gebe ich Datei an, dies dient aber nur der Übersichtlichkeit und hat keine weiteren Auswirkungen.
Ein Doppelklick auf die eben erstellte Action bringt mich in meinen Quellcode-Editor. An dieser Stelle gebe ich jetzt ein, was ich auch sonst beim Buttonklick eingegeben hätte.
Zum Auswählen einer Datei zum Öffnen stellt uns Delphi wiederum eine Hilfe zur Verfügung, den OpenDialog (Registerkarte Dialoge). Wir ziehen ihn auf unser Form und kehren dann zum Quellcode-Editor zurück.
Hier geben wir nun ein:

Delphi-Quellcode:
If Opendialog1.Execute then
  begin
    if FileExists(OpenDialog1.Filename) then
    begin
      Richedit1.Lines.LoadfromFile(OpenDialog1.Filename);
    end;
  end;
Die erste Zeile bewirkt das Ausführen des OpenDialogs, die 3. überprüft das Vorhandensein der ausgewählten Datei und mit der 5. Zeile wird die Datei in das Richedit geladen. Ich gehe davon aus, dass die Funktionen eigentlich selbsterklärend sind.
Jetzt weise ich der Eigenschaft Action des entsprechenden Buttons und des entsprechenden Menüeintrags den Wert Aoeffnen zu und probiere das Programm aus.
Es funktioniert, aber was mir natürlich noch nicht gefällt ist, dass ich alle Dateien auswählen kann, was zum Teil unschöne Nebeneffekte hat. Um dies zu verhindern nutze ich die Eigenschaft Filter des OpenDialogs und gebe auf der linke Seite an, Textdateien (*.txt, *.rtf), auf der rechten *.txt, *.rtf. Falls jemand doch andere Dateitypen auswählen gebe ich ihm die Möglichkeit, indem ich in der zweiten Zeile eintrage: Alle Dateien und *.*
Somit kann man schon Dateien öffnen. Wenn wir diese dann verändert haben wollen wir sie natürlich auch speichern. Dazu nehme ich dann den SaveDialog von der Registerkarte Diaogs stelle die selben Filter ein und setze zusätzlich noch die Option ofOwerridePrompt auf true. Dies bewirkt die übliche Sicherheitsabfrage, wenn eine Datei mit gleichem Dateinamen bereits existiert und sie überschrieben würde. Außerdem gebe ich noch bei DefaultExt *.rtf an, damit die Endung automatisch angefügt wird, wenn der User dies nicht tut.
Dann erschaffe ich eine neue Action in der Actionlist, nenne sie Aspeichernu (da es ja für die Speichern unter Funktion ist (mit dem Dialog)) gebe ihr die entsprechende Caption, weise sie dem Menüeintrag Speichern unter zu und klicke doppelt auf sie, um in den Quellcode-Editor zu gelangen. Dort gebe ich analog zum Öffnen ein:

Delphi-Quellcode:
if SaveDialog1.Execute then
  begin
    if SaveDialog1.Filename<>’’ then // ist Dateiname angegeben?
    begin
      Richedit1.Lines.SavetoFile(SaveDialog1.Filename);
    end;
  end;
Auch dies funktioniert. Nun möchte ich die Speichern-Funktion implementieren. Der einzige Unterschied ist, dass sie direkt speichert ohne den Dialog, allerdings nur dann, wenn eine Datei geöffnet wurde oder bereits gespeichert wurde.
Deswegen ist es wichtig, dem oberen Code der Speichern unter Funktion noch folgende Zeile anzufügen:

OpenDialog1.Filename:=’’; // kommt unter RichEdit1.Lines. Save......... Es bewirkt, dass beim Speichern ohne Dialog nicht mehr der Dateiname vom Öffnen übernommen wird, wenn bereits unter einem anderen Namen gespeichert wurde.
Die Speichern-Action wird genauso wie die anderen angelegt und erhält folgenden Code:

Delphi-Quellcode:
if OpenDialog1.Filename<>’’ then
  begin
    Richedit1.Lines.SavetoFile(OpenDialog1.Filename);
  end else
  begin
    if SaveDialog1.Filename<>’’ then
      begin
         Richedit1.Lines.SavetoFile(SaveDialog1.Filename);
      end else
      begin
        Aspeichernu.Execute; //falls kein Name zum Speichern vorhanden, Speichern unter aufrufen.
      end;
Nachdem nun auch dies geht, erstelle ich eine Action für Neu, die folgenden Code enthält.

Richedit1.Lines.Clear; An dieser Stelle möchte ich einschieben, dass es natürlich erforderlich wäre, zu überprüfen, ob Änderungen am Text vorgenommen wurden und dann den User zum speichern aufzufordern. Selbiges tritt beim Öffnen eines anderen Dokumentes auf oder beim Verlassen des Programms. Ich denke aber, dass das hier zu weit führt. Für Interessierte als Denkanstoß: Es gibt folgende Boolean-Variable: Richedit1.Modified.

Zur Drucken-Funktion erstelle ich ebenfalls eine neue Action und weise ihr folgenden Code zu:

Richedit1.Print(''); //der String in den Klammern dient als Überschrift über dem Ausdruck. Auch hier bleibt anzumerken, dass dieser Ausdruck der allereinfachste, aber auch am Schlimmsten aussehendste ist, da keine Zeilenumbrüche gemacht werden, in der obersten linken Ecke angefangen wird etc. Aber auch das würde zu weit führen, das richtig zu machen.
(Auch hier bietet Delphi einen vorgefertigten Dialog an).

Als Letztes fehlt im Menü Datei noch der Eintrag Beenden. Für ihn erzeuge ich keine Action, da er nur einmal verwendet wird. Ich klicke ihn an und gebe ein:

MainForm.Close; Somit ist unser Datei-Menü vollständig.


Im Menü bearbeiten verfahre ich genauso, indem ich Actions definiere (außer für Alles markieren, da sie auch nur einmal vorkommt).

Die dabei zur Verwendung kommenden Funktionen sind folgende:

Delphi-Quellcode:
Richedit1.Undo;

// Für Redo gibt es leider keine direkt implementierte Funktion, ich werde dies später aufgreifen.

Richedit1.CuttoClipboard; //fürs Ausschneiden
Richedit1.CopytoClipboard; // fürs Kopieren
Richedit1.PasteFromClipboard; // fürs Einfügen

Richedit1.Selectall;
Auch hier fehlen natürlich Überprüfungen, ob überhaupt Text zum Ausschneiden markiert ist oder ob Rückgängig bzw. Wiederholen möglich sind, aber auch das führt zu weit.


Als letzter großer Schritt fehlt jetzt die Schriftformatierung (also Fett, Kursiv, Schriftart etc.).
Dazu stelle ich als Erstes die Eigenschaft Style der ToolButtons für Fett, Kursiv, Unterstrichen, Links-, Rechtsbündig und Zentriert auf tbscheck, was bewirkt, dass die Buttons sozusagen einrasten, wenn man sie drückt. Zusätzlich stelle ich bei dem Button für Linksbündig die Eigenschaft Down auf true, was bedeutet, dass der Button bereits bei Programmstart als gedrückt angesehen wird, da ja linksbündig die Standardeigenschaft im Richedit ist.

Ich weise dabei den Toolbuttons folgenden Code zu:

Delphi-Quellcode:
begin
if ToolButton16.Down then
Richedit1.SelAttributes.Style:=Richedit1.SelAttributes.Style + [fsBold]
else
Richedit1.SelAttributes.Style:=Richedit1.SelAttributes.Style - [fsBold]
end;
Anstelle von fsBold verwende ich bei Kursiv fsItalic und bei Unterstreichen fsUnderline.

Warum mit + und - ?
Richedit1.SelAttributes.Style ist sozusagen eine Liste, die den Status des Zeichen speichert. Deswegen kann man ihr den entsprechenden Status zufügen bzw. wegnehmen.

Die if-Abfrage stellt fest, ob es erforderlich ist, die Formatierung zu „entfernen“ oder dazu zu machen.

Wie ihr sicherlich feststellen werdet fehlt etwas entscheidendes. Das Programm bekommt gar nicht mit, wenn man beim Bewegen des Cursors an eine Stelle im Text kommt, wo die Formatierung anders ist. Das ist relativ unschön.
Also bauen wir in OnSelectionChange folgende Abfrage ein:

Delphi-Quellcode:
if fsBold in Richedit1.SelAttributes.Style then
  begin
    ToolButton16.Down:=true;
  end else
   begin
    ToolButton16.Down:=false;
  end;
Sie überprüft, ob das Zeichen fett ist und setzt dementsprechend den Status des ToolButtons.
Diese Abfrage mache ich ebenfalls für die beiden anderen Buttons.

Um den Buttons zum Ausrichten des Textes Leben einzuhauchen verwende ich den folgenden Code:

Richedit1.Paragraph.Alignment:=taLeftJustify; Für zentrierte Ausrichtung ist taCenter zuzuweisen, für rechtsbündig taRightJustify.

Natürlich muss hier beachtet werden, dass die jeweils anderen Buttons nicht selektiert bleiben dürfen, deshalb dieser Code (anhand des Bsp. Left):

Delphi-Quellcode:
ToolButton20.Down:=False;
ToolButton21.Down:=False;
Auch hier ist wieder das Überprüfen des Status bei SelectionChange erforderlich:

Delphi-Quellcode:
case Richedit1.Paragraph.Alignment of
taLeftJustify: begin Toolbutton19.Down:=true; Toolbutton20.Down:=false; Toolbutton21.Down:=false; end;
taCenter: begin Toolbutton20.Down:=true; Toolbutton19.Down:=false; Toolbutton21.Down:=false; end;
taRightJustify: begin Toolbutton21.Down:=true; Toolbutton19.Down:=false; Toolbutton20.Down:=false; end;
end;

Nun zur Schriftgröße. Zuerst gebe ich bei Items der Combobox2 Zahlenwerte an, die später zu Verfügung stehen sollen und gebe bei Text 8 an (Standardeinstellung).
Bei OnChange der Combobox gebe ich folgenden Code ein:

Richedit1.SelAttributes.Size:=Strtoint(Combobox2.text); Er weist dem selektierten Zeichen die entsprechende Größe zu.
Auch hier muss im OnSelectionChange wieder etwas hinzugefügt werden:

Combobox2.Text:=Inttostr(Richedit1.SelAttributes.Size);
Nun zur Schriftart. Um die Fonts in meine Combobox zu bekommen verwende ich folgenden Code von Swissdelphicenter.ch

Delphi-Quellcode:
function EnumFontClBack(var lp: TEnumLogFont;
  var tm: TNewTextMetric;
  dwType: DWORD;
  lpData: lParam): Integer; stdcall;
begin
  Result := 1;
  with TForm1(lpData), Combobox1 do
  begin
    Items.Add(lp.elfLogFont.lfFaceName);
  end;
end;

Delphi-Quellcode:
procedure TForm1.FormShow(Sender: TObject);
begin
  EnumFontFamilies(Canvas.Handle, nil, @EnumFontClBack, Integer(Self));
end;
Als Text weise ich der Combobox1 MS Sans Serif zu.

Auch hier der Code wieder bei OnChange:

Richedit1.SelAttributes.Name:=Combobox1.Text; Und bei OnSelectionChange des Richedits:

Combobox1.Text:=Richedit1.SelAttributes.Name;
So, jetzt sind wir mit den Grundfunktionen durch. Was fehlt jetzt noch?

Zuerst mal die Wiederholen-Funktion. Auch hier erstellen wir wieder eine Action und zwar mit folgendem Code:

Richedit1.Perform(EM_REDO,0,0) // bei Uses Richedit einbinden Sie führt zwar das gewollte Wiederholen aus, aber wie bereits oben erwähnt nützt das aber auch nicht viel für einen reibungslosen Ablauf von Rückgängig/Wiederholen.
Ich empfehle hier die Lektüre dieses Tutorials.


4. Ausblick

Ich hoffe, ich konnte vielleicht dem ein oder anderen weiterhelfen.
Bei sich bietender Gelegenheit werde ich das Tutorial weiterführen.


Ciao fkerber

Anhang: Die Projektdateien

Edit: Fehler beseitigt (Danke an Markus K.)
Angehängte Dateien
Dateityp: zip texted_491.zip (14,4 KB, 888x aufgerufen)
 
ehem. Benutzer
 
#11
  Alt 27. Sep 2004, 17:03
hallo,
hab da ein problem, wenn ich das zum speicher mache, also das :
Delphi-Quellcode:
if SaveDialog1.Execute then
  begin
    if SaveDialog1.Filename<>’’ then // ist Dateiname angegeben?
    begin
      Richedit1.Lines.SavetoFile(SaveDialog1.Filename);
    end;
  end;
steht das da:

Code:
Erzeugen
  [Fehler] Mainfrm.pas(88): Ungültiges Zeichen in Eingabedatei: '’' ($92)
  [Fehler] Mainfrm.pas(89): Operator oder Semikolon fehlt
  [Fataler Fehler] Project2.dpr(6): Verwendete Unit 'Mainfrm.pas' kann nicht compiliert werden
Wie bekomm ich das hin?

Danke


MfG
  Mit Zitat antworten Zitat
Benutzerbild von mirage228
mirage228

 
Delphi 2010 Professional
 
#12
  Alt 27. Sep 2004, 17:11
Hi,

du musst Apostrophe verwenden:

Delphi-Quellcode:
if SaveDialog1.Execute then
begin
  if SaveDialog1.Filename <> 'then // ist Dateiname angegeben?
    Richedit1.Lines.SavetoFile(SaveDialog1.Filename);
end;
mfG
mirage228
David F.
  Mit Zitat antworten Zitat
ehem. Benutzer
 
#13
  Alt 27. Sep 2004, 17:17
danke geht ^^



MfG
  Mit Zitat antworten Zitat
Benutzerbild von bjacke
bjacke

 
Delphi 7 Personal
 
#14
  Alt 18. Okt 2004, 19:22
Hi, erstmal danke für deine tolle Kollektion, hat mir echt geholfen!

Aber um die ComboBox mit den Schriftarten zu füllen würde ich diese kürzere Variante wählen:
ComboBox1.Items := Screen.Fonts; Und zum Nutzen im RichEdit:
Rich.SelAttributes.Name:=Combobox1.Text;
  Mit Zitat antworten Zitat
Benutzerbild von arbu man
arbu man

 
Delphi 7 Professional
 
#15
  Alt 22. Dez 2004, 20:28
Super Editor

Hier noch ein Link wie man mit Rand drucken kann.
Drucken
Björn
  Mit Zitat antworten Zitat
Benutzerbild von hanz666
hanz666

 
Delphi 4 Standard
 
#16
  Alt 9. Jan 2005, 14:28
hai, kann man die farbe von dem main menü ändern?
das grau is mir ein wenig zu öde...
  Mit Zitat antworten Zitat
ehem. Benutzer
 
#17
  Alt 9. Jan 2005, 14:34
Hallo, erstmal willkommen in der DP (das wollt ich schon immer mal sagen )
Das mit dem Mainmenü, da gibt's Komponenten mit denen man das verändern kann,
irgendwo hatt ich das mal, schau mal auf http://www.torry.net


MfG
  Mit Zitat antworten Zitat
deinmeister
 
#18
  Alt 18. Jan 2005, 15:47
mhhhhhhhhhhh kurze frage... hab alles gemacht was im tutor stand... aba als ich das dann testen wollte... WILL ES EINFACH NICHT STARTEN.... es wird auch keine fehlermeldung gezeigt... es wird ohne muckig zu werden compiliert .. dann klick ich auf die exe und NIX PASSIERT.... weiß einer was da los sein könnte?
  Mit Zitat antworten Zitat
Gabberman
 
#19
  Alt 18. Okt 2005, 23:08
Ich hab da mal ne Frage.
Ich hab folgenden Quellcode:
Delphi-Quellcode:
   AssignFile(H, 'daten/schluss.fbe');
    Rewrite(H);
    Writeln(H, :?: :?: :?:
    Closefile(H);
Mein Richeditfeld heißt 'schlusssatz'.
Gibt es irgend eine Möglichkeit den Inhalt von diesem in der Datei H zu speichern????




Wäre schön wenn ihr mir helfen könntet!!!
  Mit Zitat antworten Zitat
nki

 
Delphi 2006 Personal
 
#20
  Alt 24. Feb 2006, 21:59
Hallo, super tut, aber ab der letzten Procedure geht bei mir nichts mehr

Die Funktion hab ich grad noch so hinbekommen, aber die darauf folgende Prozedur bekomm ich folgende Fehler:

Delphi-Quellcode:
[Fehler] textedit_u.pas(321): Undefinierter Bezeichner: 'FormShow'
[Fehler] textedit_u.pas(323): Undefinierter Bezeichner: 'Canvas'
[Fehler] textedit_u.pas(323): 'END' erwartet, aber ',' gefunden
[Fataler Fehler] PRtextedit.dpr(5): Verwendete Unit 'textedit_u.pas' kann nicht compiliert werden
ich habe die Prozedur auch unter type geschrieben:

Delphi-Quellcode:
type
  ...
  procedure TMainForm.FormShow(Sender: TObject);
dann bekomme ich diesen Fehler: = erwartet aber ( gefunden

kann mir jemand helfen?

danke im Voraus

Gruß nki
  Mit Zitat antworten Zitat
Antwort Antwort
Seite 2 von 3     12 3      


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 04:20 Uhr.
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz