|
Antwort |
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:
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.
If Opendialog1.Execute then
begin if FileExists(OpenDialog1.Filename) then begin Richedit1.Lines.LoadfromFile(OpenDialog1.Filename); end; end; 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:
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.
if SaveDialog1.Execute then
begin if SaveDialog1.Filename<>’’ then // ist Dateiname angegeben? begin Richedit1.Lines.SavetoFile(SaveDialog1.Filename); end; end; 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:
Nachdem nun auch dies geht, erstelle ich eine Action für Neu, die folgenden Code enthält.
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; 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:
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.
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; 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:
Anstelle von fsBold verwende ich bei Kursiv fsItalic und bei Unterstreichen fsUnderline.
begin
if ToolButton16.Down then Richedit1.SelAttributes.Style:=Richedit1.SelAttributes.Style + [fsBold] else Richedit1.SelAttributes.Style:=Richedit1.SelAttributes.Style - [fsBold] end; 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:
Sie überprüft, ob das Zeichen fett ist und setzt dementsprechend den Status des ToolButtons.
if fsBold in Richedit1.SelAttributes.Style then
begin ToolButton16.Down:=true; end else begin ToolButton16.Down:=false; end; 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:
Auch hier ist wieder das Überprüfen des Status bei SelectionChange erforderlich:
ToolButton20.Down:=False;
ToolButton21.Down:=False;
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:
Als Text weise ich der Combobox1 MS Sans Serif zu.
procedure TForm1.FormShow(Sender: TObject);
begin EnumFontFamilies(Canvas.Handle, nil, @EnumFontClBack, Integer(Self)); end; 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.) |
kohennigs
|
#2
Ich wollte hier mal als meinen ersten Eintrag ein dickes Lob los werden !
Sehr gute Anleitung ! Ich bin ein totalter Delphi Neuling, bring mir das gerade im Betrieb (bin Azubi) selber bei, um mir eine Grundlage zu schaffen. Hab auch Schulungsordner, die mir helfen sollen, aber "Wir schreiben einen Mini Editor" Aufgabe daraus ist eher weniger schön formuliert ! P.S.: Ich hoffe das ich irgendwann auch mal blutigen Anfängern mit sinnvollen Beträgen helfen kann ! MfG KAIO |
Zitat |
|
#3
Nicht, das ich die Idee schlecht finde so ein Tut zu bauen, aber eine Klick-für-Klick Anleitung ist imho nichts, wobei man viel lernen kann.
Ich denke, jeder der Delphi aufmachen kann kann ein Richedit auf ein Form ziehen, das ding kompilieren, laufenlassen und da was reinschreiben. Es geht beim Lernen doch eher darum zu verstehen, warum ich etwas mache und als Schmankerl nebenbei auch noch wie. Nur dieses 'warum' kommt mir in dem Tut etwas zu kurz. Sorry für die Kritik, aber wenn Du das Tut erweiterst wäre es vielleicht eine Idee da drauf zu achten
Sebastian Gingter
|
Zitat |
Delphi XE Professional |
#4
Hi!
Ok, werde ich beim nächsten Mal mehr drauf achten. Aber etwas mehr als nur Richedit aufs Form ziehen ist es ja schon... Und was sollte ich z.B. bei der Erstellung eines MainMenus mehr zu erläutern? Ciao fkerber
Frederic Kerber
|
Zitat |
|
#5
naja. keiner wird den Editor genauso nachbauen. Sondern jeder wird den Code nehmen, und sein eigenes Werk (mehr oder weniger stark angelehnt) darum bauen. Da, wo es einem drauf ankommt, wird man ein wenig mehr machen, an anderen Stellen vielleicht weniger. Aber wenn man ein funktionierendes Grundgerüst hat, an das man sich halten kann, ist das immer gut. Und die Teile, in deren Nähe man etwas verändern will, die wird man sich genau angucken und dann auch verstehen. (so learning by doing effekt). Und der Rest ist da und klappt.
Dabei lernt man vielleicht nicht ganz so viel, wie wenn man alles alleine macht, aber dafür viel schneller, weil man einfach was Funktionierendes vor sich hat. So macht es (mir) mehr Spass und geht alles flotter von der Hand, und auf Dauer kann man es dann auch! nailor
Michael N.
|
Zitat |
Delphi XE Professional |
#6
Hi!
@Nailor: Ja, so geht es mir auch und solche Tutorials fand ich an sich besser, als wenn man mit zuviel Theorie "vollgestopft" wird. Und so in etwa habe ich versucht mein Tutorial aufzubauen: Relativ schnell ein vorzeigbares Ergebnis und dabei etwas lernen. So hats mir immer am meisten Spaß gemacht. Ciao fkerber
Frederic Kerber
|
Zitat |
kohennigs
|
#7
Tach auch !
@ Nailer & fkerber Stimmt genau Lernen mit Erfolg motiviert ! Da mir diese kleinen Hinweise "für Interessierte" immmer recht gut weitergeholfen haben, wollte ich auch mal einen solchen beisteuern: Bei den Buttons im Menü gibt es die nette Eigenschaft "Grouped". Die bewirkt bei 'true' das nur einer der markierten Buttons gedrückt sein kann. Das lässt sich bei unseren Buttons für die Schriftausrichtung recht nett anwenden. Ok, das spart nur ein paar Zeilen, aber immerhin. MfG KaiO |
Zitat |
|
#8
Hallo,
mit dieser Zeile: Richedit1.SelAttributes.Style:=Richedit1.SelAttrib utes.Style + [fsBold] markierst du den gesamten markierten Text fett. Allerdings gibt es ein Problem, wenn innerhalb dieser Markierung (nicht am Anfang und nicht am Ende) bereits Text z. B. kursiv formatiert ist. Die Kursiv-Formatierung geht dann verloren, weil das RichEdit die Formatierung des ersten (oder letzten?) markierten Zeichens nimmt, dort fsBold "hinzufügt" und diese Formatierung dann für alle markierten Zeichen speichert. Beim erstenZeichen war aber fsItalic noch nicht gesetzt. Gibt es eine bessere Möglichkeit, als per for-Schleife alle markierten Zeichen einzeln zu formatieren? In C# habe ich eine Methode geschrieben, die vom markierten Text (umgewandelt in RTF-Code) die RTF-Tags per String.Replace hinzufügt / löscht. Dies ist aber eigentlich nur eine Notlösung. Im "Win32 Developer's Reference" habe ich nichts passendes gefunden... MfG, Andreas |
Zitat |
Andreas Hartmann |
Öffentliches Profil ansehen |
Mehr Beiträge von Andreas Hartmann finden |
Delphi XE Professional |
#9
Hi!
Das stimmt allerdings... Eine Lösung kenne ich nicht. Stell die Frage am besten mal in einem eigenen Thread. Ciao fkerber
Frederic Kerber
|
Zitat |
|
#10
Hi, hab jetzt weitergeforscht - eine bessere Lösung als eine for-Schleife gibt es nicht. Dort wäre aber eine Optimierungsmöglichkeit, dass man nicht jedes Zeichen einzeln formatiert (die SelLength immer auf 1 setzt), sondern man verlängert mit einer while-Schleife die Sellength, um möglichst lange Passagen innerhalb der Markierung zu verändern (also Passagen, in denen sich der Style bis auf die zu verändernde Eigenschaft nicht ändert).
MfG, Andreas |
Zitat |
Andreas Hartmann |
Öffentliches Profil ansehen |
Mehr Beiträge von Andreas Hartmann finden |
Ansicht |
Linear-Darstellung |
Zur Hybrid-Darstellung wechseln |
Zur Baum-Darstellung wechseln |
ForumregelnEs 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
|
|
Nützliche Links |
Heutige Beiträge |
Sitemap |
Suchen |
Code-Library |
Wer ist online |
Alle Foren als gelesen markieren |
Gehe zu... |
LinkBack |
LinkBack URL |
About LinkBacks |