Coding responses to user actions in the Code Editor (IDE Tutorial)

From RAD Studio
Jump to: navigation, search

Go Up to Starting your first RAD Studio application Index (IDE Tutorial)


By following the instructions in this section, you will make your application interactive and provide it with the functionality you want. You will code the event handlers, that is, the responses to clicking the various items in the main menu and other types of user interaction.

Beginning the code

Begin writing code by defining a String variable that you use throughout the execution of your application to retain the name of the currently opened text file.

In Delphi, select the Code tab in the status bar to open the Code Editor. Use the Code Editor to define a String variable called CurrentFile in the private section of the TTextEditorForm class in the interface part:


Code Editor for Delphi
Defining the CurrentFile private variable (Delphi view)

In C++Builder, select the TextEditorUnit.h tab in the status bar to open the unit header file in the Code Editor. Use the Code Editor to declare a String variable called CurrentFile in the private section of TTextEditorForm:

Code Editor for C++
Defining the CurrentFile private variable (C++Builder view)

To toggle between the Form Designer and the Code Editor, you can press F12.

Updating the status bar in response to actions on the text editor

When users move the caret (text cursor) or modify the content of your TMemo component, you must update the status bar. Since you must update the status bar in response to many different events, you can define a single procedure in your code and later call that procedure from any event handler that needs to update the status bar.

In Delphi, to define this new procedure in your application, add the following procedure signature to the private section of the TTextEditorForm class in the interface part, right below the CurrentFile variable that you previously defined:

procedure UpdateStatusBar;

Right-click UpdateStatusBar in your code, and select Complete Class at Cursor so that RAD Studio adds a skeleton definition for your procedure to the implementation part of your code:

procedure TTextEditorForm.UpdateStatusBar;
begin

end;

Define the logic of the UpdateStatusBar procedure between these begin and end keywords as follows:

LineNumber.Text := 'L: ' + (Editor.CaretPosition.Line+1).ToString;
ColumnNumber.Text := 'C: ' + (Editor.CaretPosition.Pos+1).ToString;
LineCount.Text := 'Lines: ' + Editor.Lines.Count.ToString;

In C++, to define this new function in your application, add the following function signature to the private section of the TTextEditorForm class in the TextEditorUnit.h file, right below the CurrentFile variable that you previously defined:

void UpdateStatusBar();

Select the implementation file, TextEditorUnit.cpp, and implement this function as follows:

void TTextEditorForm::UpdateStatusBar()
{
    LineNumber->Text = L"L: " + String(Editor->CaretPosition.Line+1);
    ColumnNumber->Text = L"C: " + String(Editor->CaretPosition.Pos+1);
    LineCount->Text = L"Lines: " + String(Editor->Lines->Count);
}

You can now call UpdateStatusBar from any event handler to update your status bar.

The first time that your application should update the information displayed on the status bar is when your application starts. You can use the OnCreate event of your form for this:

  1. Select the Design tab to get back to the Form Designer.
  2. On the Structure view select your form component, which you named TextEditorForm.
  3. On the Object Inspector open the Events tab.
  4. On the Events tab, double-click the value field of the OnCreate event. RAD Studio switches to the Code Editor and adds a skeleton definition for your new event handler. Use that event handler to call your UpdateStatusBar procedure:

Delphi:

procedure TTextEditorForm.FormCreate(Sender: TObject);
begin
  Editor.Lines.Add('');
  UpdateStatusBar;
end;

C++:

void __fastcall TTextEditorForm::FormCreate(TObject *Sender)
{
        TextEditorForm->Editor->Lines->Add(L"");
        UpdateStatusBar();
}

Notice how before calling UpdateStatusBar, this code adds an empty line to your TMemo component. This initializes the line count of the memo, so that the line count label in the status bar shows "1" line from the beginning instead of "0" lines.

Repeat the steps above with the OnKeyUp and OnMouseUp events of your TMemo component, which you named Editor:

Delphi:

procedure TTextEditorForm.EditorKeyUp(Sender: TObject; var Key: Word;
  var KeyChar: Char; Shift: TShiftState);
begin
  UpdateStatusBar;
end;

procedure TTextEditorForm.EditorMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Single);
begin
  UpdateStatusBar;
end;

C++:

void __fastcall TTextEditorForm::EditorKeyUp(TObject *Sender, WORD &Key, System::WideChar &KeyChar,
          TShiftState Shift)
{
    UpdateStatusBar();
}

void __fastcall TTextEditorForm::EditorMouseUp(TObject *Sender, TMouseButton Button,
          TShiftState Shift, float X, float Y)
{
    UpdateStatusBar();
}

Creating event handlers for the File menu entries

You are now ready to define the responses to clicking menu items. With the Form Designer open, select the NewAction in the Structure view:

Selecting New Action

Then select the Events tab in the Object Inspector, and double-click the edit box corresponding to the OnExecute event. The Code Editor opens and displays a skeleton for your new event handler. Write in this event handler the code that executes when users select File > New.

Delphi:

Note: Add the FMX.DialogService.Sync unit to the uses clause in the implementation section, equivalent to the snippet below:
implementation

{$R *.fmx}

uses
 FMX.DialogService.Sync;
procedure TTextEditorForm.NewActionExecute(Sender: TObject);
var
  UserResponse: Integer;
begin
  // Ask for confirmation if the memo is not empty.
  if not Editor.Text.IsEmpty then
  begin
    UserResponse := TDialogServiceSync.MessageDialog(
      'This will clear the current document. Do you want to continue?',
      TMsgDlgType.mtInformation, mbYesNo, TMsgDlgBtn.mbYes, 0);
    if UserResponse = mrYes then
    begin
      Editor.Text := '';
      Editor.Lines.Add(''); // Initialize the memo line count to "1".
      UpdateStatusBar;
      CurrentFile := ''; // New files have no file name until saved.
    end;
  end;
end;

C++:

Note: Add the FMX.DialogService.Sync.hpp header to the include list in the implementation section, equivalent to the snippet below:
//---------------------------------------------------------------------------

#include <fmx.h>
#pragma hdrstop

#include "TextEditorForm.h"
#include <FMX.DialogService.Sync.hpp>
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.fmx"
void __fastcall TTextEditorForm::NewActionExecute(TObject *Sender)
{
    if (!TextEditorForm->Editor->Text.IsEmpty()) {
        int UserResponse = TDialogServiceSync::MessageDialog(
            L"This will clear the current document. Do you want to continue?",
            TMsgDlgType::mtInformation, mbYesNo, TMsgDlgBtn::mbYes, 0);
        if (UserResponse == mrYes) {
            TextEditorForm->Editor->Text = L"";
            TextEditorForm->Editor->Lines->Add(L"");  // Initialize the memo line count to "1".
            UpdateStatusBar();
            CurrentFile = L"";  // New files have no file name until saved.
        }
    }
}

Click the Design tab to get back to the Form Designer, and repeat the process for the remaining actions used by the File menu entries: OpenAction, SaveAction, SaveAsAction, ExitAction. These are the implementations of the event handlers of the OnExecute event of each of these actions:

Delphi:

// File > Open
procedure TTextEditorForm.OpenActionExecute(Sender: TObject);
var
  FileName: String;
begin
  if OpenFileDialog.Execute then
  begin
    FileName := OpenFileDialog.FileName;
    if FileExists(FileName) then
    begin
      Editor.Lines.LoadFromFile(FileName);
      CurrentFile := FileName;
      Caption := 'Text Editor - ' + ExtractFileName(FileName);
    end;
  end;
end;

// File > Save
procedure TTextEditorForm.SaveActionExecute(Sender: TObject);
begin
  if CurrentFile = '' then
    SaveAsAction.Execute()
  else
    Editor.Lines.SaveToFile(CurrentFile);
end;

// File > Save As
procedure TTextEditorForm.SaveAsActionExecute(Sender: TObject);
var
  FileName: String;
  UserResponse: TModalResult;
begin
  if SaveFileDialog.Execute then
  begin
    FileName := SaveFileDialog.FileName;
    if FileExists(FileName) then
    begin
      UserResponse := TDialogServiceSync.MessageDialog(
        'File already exists. Do you want to overwrite?',
        TMsgDlgType.mtInformation, mbYesNo, TMsgDlgBtn.mbYes, 0);
      if UserResponse = mrNo then
        Exit;
    end;
    Editor.Lines.SaveToFile(FileName);
    CurrentFile := FileName;
    Caption := 'Text Editor - ' + ExtractFileName(FileName);
  end;
end;

// File > Exit
procedure TTextEditorForm.ExitActionExecute(Sender: TObject);
begin
  Application.Terminate;
end;

C++:

// File > Open
void __fastcall TTextEditorForm::OpenActionExecute(TObject *Sender)
{
    if (TextEditorForm->OpenFileDialog->Execute()) {
        String FileName = TextEditorForm->OpenFileDialog->FileName;
        if (FileExists(FileName)) {
            TextEditorForm->Editor->Lines->LoadFromFile(FileName);
            CurrentFile = FileName;
            Caption = L"Text Editor - " + ExtractFileName(FileName);
        }
    }
}

// File > Save
void __fastcall TTextEditorForm::SaveActionExecute(TObject *Sender)
{
    if (CurrentFile == L"") {
        TextEditorForm->SaveAsAction->Execute();
    } else {
        TextEditorForm->Editor->Lines->SaveToFile(CurrentFile);
    }
}

// File > Save As
void __fastcall TTextEditorForm::SaveAsActionExecute(TObject *Sender)
{
    if (TextEditorForm->SaveFileDialog->Execute()) {
        String FileName = TextEditorForm->SaveFileDialog->FileName;
        if (FileExists(FileName)) {
            TModalResult UserResponse = TDialogServiceSync::MessageDialog(
                L"File already exists. Do you want to overwrite?",
                TMsgDlgType::mtInformation, mbYesNo, TMsgDlgBtn::mbYes, 0);
            if (UserResponse == mrNo) {
                return;
            }
        }
        TextEditorForm->Editor->Lines->SaveToFile(FileName);
        CurrentFile = FileName;
        Caption = L"Text Editor - " + ExtractFileName(FileName);
    }
}

// File > Exit
void __fastcall TTextEditorForm::ExitActionExecute(TObject *Sender)
{
    Application->Terminate();
}

Creating event handlers for the Edit and Format menu entries

To implement the actions for the menu entries in the Edit and Format menus, you must follow the same procedure that you followed for the File menu actions. These event handlers are very simple: they simply forward the action to your memo, as all the required functionality is already implemented in the TMemo class, and they call UpdateStatusBar if the action may have any effect in the caret position or the line count.

Delphi:

// Edit > Cut
procedure TTextEditorForm.CutActionExecute(Sender: TObject);
begin
  Editor.CutToClipboard;
  UpdateStatusBar;
end;

// Edit > Copy
procedure TTextEditorForm.CopyActionExecute(Sender: TObject);
begin
  Editor.CopyToClipboard;
end;

// Edit > Paste
procedure TTextEditorForm.PasteActionExecute(Sender: TObject);
begin
  Editor.PasteFromClipboard;
  UpdateStatusBar;
end;

// Edit > Select All
procedure TTextEditorForm.SelectAllActionExecute(Sender: TObject);
begin
  Editor.SelectAll;
  UpdateStatusBar;
end;

// Edit > Undo
procedure TTextEditorForm.UndoActionExecute(Sender: TObject);
begin
  Editor.UnDo;
  UpdateStatusBar;
end;
Note: Add the FMX.DialogService.Sync unit to the uses clause in the implementation section, equivalent to the snippet below:
implementation
 
{$R *.fmx}
 
uses
  FMX.DialogService.Sync, FMX.Memo.Types;
// Edit > Delete
procedure TTextEditorForm.DeleteActionExecute(Sender: TObject);
begin
  if Editor.SelLength > 0 then
    Editor.DeleteSelection
  else
    Editor.DeleteFrom(Editor.CaretPosition, 1, [TDeleteOption.MoveCaret]);
  UpdateStatusBar;
end;

// Format > Word Wrap
procedure TTextEditorForm.WordWrapActionExecute(Sender: TObject);
begin
  Editor.WordWrap := not Editor.WordWrap;
  WordWrapAction.Checked := Editor.WordWrap;
  UpdateStatusBar;
end;

C++:

// Edit > Cut
void __fastcall TTextEditorForm::CutActionExecute(TObject *Sender)
{
    TextEditorForm->Editor->CutToClipboard();
    UpdateStatusBar();
}

// Edit > Copy
void __fastcall TTextEditorForm::CopyActionExecute(TObject *Sender)
{
    TextEditorForm->Editor->CopyToClipboard();
}

// Edit > Paste
void __fastcall TTextEditorForm::PasteActionExecute(TObject *Sender)
{
    TextEditorForm->Editor->PasteFromClipboard();
    UpdateStatusBar();
}

// Edit > Select All
void __fastcall TTextEditorForm::SelectAllActionExecute(TObject *Sender)
{
    TextEditorForm->Editor->SelectAll();
    UpdateStatusBar();
}

// Edit > Undo
void __fastcall TTextEditorForm::UndoActionExecute(TObject *Sender)
{
    TextEditorForm->Editor->UnDo();
    UpdateStatusBar();
}
Note: Add the FMX.DialogService.Sync.hpp header to the include list in the implementation section, equivalent to the snippet below
//---------------------------------------------------------------------------
 
#include <fmx.h>
#pragma hdrstop
 
#include "TextEditorForm.h"
#include <FMX.DialogService.Sync.hpp>
#include <FMX.Memo.Types.hpp>
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.fmx"
// Edit > Delete
void __fastcall TTextEditorForm::DeleteActionExecute(TObject *Sender)
{
    if (TextEditorForm->Editor->SelLength > 0) {
        TextEditorForm->Editor->DeleteSelection();
    } else {
        TextEditorForm->Editor->DeleteFrom(TextEditorForm->Editor->CaretPosition, 1, TDeleteOptions() << TDeleteOption::MoveCaret);
    }
    UpdateStatusBar();
}

// Format > Word Wrap
void __fastcall TTextEditorForm::WordWrapActionExecute(TObject *Sender)
{
    TextEditorForm->Editor->WordWrap = !TextEditorForm->Editor->WordWrap;
    TextEditorForm->WordWrapAction->Checked = TextEditorForm->Editor->WordWrap;
    UpdateStatusBar();
}

Next

Compiling and running the application