Delphi

From miki
Jump to navigation Jump to search

A meager attempt to recover my old knowledge on Delphi 7...

References

  • The ultimate reference on Delphi with lots of examples at [1]

Keyboard shortcuts

Some frequently used keyboard shortcuts that any decent programmer should always use:

Shortcut Description
Ctrl+Shift+↑ Go to method declaration
Ctrl+Shift+↓ Go to method definition
Ctrl+Shift-C Auto-complete class (eg. use it when declaring new class method, and cursor still on method declaration)
Ctrl+J Insert code template
Ctrl+Space Code auto-complete
Ctrl+K+I Indent code
Ctrl+K+U Unindent code
Ctrl+left click Go to identifier definition

Basics

Some basic stuff on the Delphi language.

  • A function to print the address of a pointer:
  function PtrToStr(p:pointer):String;
  begin
    Result := '0x'+IntToHex(Integer(p),8);
  end;

  writeln('@myObject:',PtrToStr(@myObject));
  myObject := TMyClass.Create;
  writeln('myObject:',PtrToStr(myObject));
  • Exception - Use CreateFMT to build complex error messages with parameters...:
  if(...)
    then raise Exception.CreateFMT('Something failed because "%s" has less that %d characters',[someString,minLen]);
  • Date and Time - Use FormatDateTime to format date/time to custom format easily (but note that locale information may still influence the output):
  label1.Caption := 'Current date and time is ',FormatDateTime('yyyy/mm/dd hh:nn',now);

Class Basics

Some very basic stuff on classes in Delphi.

Constructors and Destructors

  • All classes in Delphi always derives by default from the class TObject. The 2 declarations below are identical:
type
  TMyFirstClass = class;
  TMySecondClass = class(TObject);
  • Constructors and destructors are declared like this:
type
  TMyClass = class;
  public
    constructor Create;
    destructor Destroy; override;
  end;
  • Constructors are not virtual function, but destructors usually are (so that it can be called by Free declared in TObject.
  • Constructors are never automatically called for objects declared as global variables or as member of other classes. They must be called explicitly...
type
  TMyClass = class;
  public
    constructor Create;
  end;

var
  myObject: TMyClass;

procedure TFrmForm1.FormCreate(Sender: TObject);
begin
  myObject := TMyClass.Create;        // objects are assigned result of constructor call!
end;
  • Same applies to destructors. However never call destructors directly. Instead call Free which test for nil before calling the destructor:
  myObject := TMyClass.Create;
  // ...
  myObject.Destroy;            //Dangerous. Fail if myObject is nil
  myObject.Free;               //Safer. Doesn't fail if myObject is nil
  • This is also true for arrays of objects. Arrays will only contain the reference to the object. These references must be initialized with a call to the constructors for each element in the array. Elements must be destroyed

Class Copy

There is no default copy constructor in Delphi. Usual method is to define a procedure called Assign to copy the content of a source object into another destination object of same class. For instance:

procedure TMyClass.Assign(const source: TMyClass);
var index:Integer;
begin
  if(source = Self)
    then exit;                               //Avoid copying itself - necessary if arrays must be freed first
  Self.someMember := source.someMember;
  Self.someObject.Assign(source.someObject); //Deep copy
  Self.SomeArray := Copy(source.SomeArray);  //Note that this is not correct for array of objects
end;

This method can be extended to deal with other classes than the destination classes. Also a further extension is to use a virtual method AssignTo as described for TPersistent objects:

  procedure AssignTo(Dest: TPersistent); virtual;
  // The Assign method of TPersistent calls AssignTo if the descendant object does not succeed 
  // in copying the properties of a source object.

One can easily define a copy constructor from the method Assign:

constructor TMyClass.CreateCopy(const source: TMyClass);
begin
  Create();
  Assign(source);
end;

Typical bug! - A typical bug is to forget to update the Assign method when adding a new data member in the class. To prevent this, a solution is to store all data member in a record instead, and use the built-in record-copy from Delphi.

GUI Basics

Basic and general GUI-related stuff.

  • Use procedures BeginUpdate and EndUpdate to prevent update of a control while changing its content (to prevent flickering). These procedures are defined for:
    • the Items property of TCheckListBox, TComboBox, TListBox, TListView, TRadioGroup, TStatusPanel, TTreeView
    • the Lines property of TMemo, TRichEdit
    • the properties Cols and Rows of TStringGrid

Dialog box

  • Use InputBox to bring up an input dialog box ready for the user to enter a string in its edit box. InputQuery offers similar functionality.
procedure TForm1.Button1Click(Sender: TObject);
begin
  Label1.Caption:=InputBox('Question', 'Enter string', 'Default');
end;
  • Use MessageDlg to display a message dialog box in the center of the screen
  • Use ShowMessage to display a message box with an OK button.
  • See help on QDialogs routines for more.
  • Add a Don't show this message again to a standard dialog box:
procedure TForm1.Button1Click(Sender: TObject) ;
var
  AMsgDialog: TForm;
  ACheckBox: TCheckBox;
begin
  AMsgDialog := CreateMessageDialog('This is a test message.', mtWarning, [mbYes, mbNo]) ;
  ACheckBox := TCheckBox.Create(AMsgDialog) ;
  with AMsgDialog do
  try
    Caption := 'Dialog Title' ;
    Height := 169;

    with ACheckBox do
    begin
      Parent := AMsgDialog;
      Caption := 'Don''t show me again.';
      Top := 121;
      Left := 8;
    end;

    if (ShowModal = ID_YES) then
    begin
      if ACheckBox.Checked then
        //do if checked
      else
        //do if NOT checked 
    end;
  finally
    Free;
  end;
end;

TStringGrid, TDBGrid

  • Check this great post to know how to use colors in TDBGrid [2].
  • Here an example on how to format cells in TStringGrid. Use the OnDrawCell event. Note that property DefaultDrawing is set to True so that cells are drawn with default formatting (incl. focus and background).
procedure TFrmMultiply.StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer; Rect: TRect; State: TGridDrawState);
begin
  if(ACol=5) then
  begin
    if(StringGrid1.Cells[6,ARow]='') then
    begin
      StringGrid1.Canvas.Font.Color := clGreen;
      StringGrid1.Canvas.Font.Style := [fsBold,fsStrikeOut]
    end;
    //Redraw the current cell - code took from TStringGrid.DrawCell
    StringGrid1.Canvas.TextRect(Rect, Rect.Left+2, Rect.Top+2, StringGrid1.Cells[ACol, ARow]);
  end;
end;
  • Another example of using TCanvas in OnDrawCell event to change text alignment in a TStringGrid (note that in Delphi CLX TextRect accepts an optional TextFlags parameters to specify align). It also assumes that property DefaultDrawing is set to True as above.
procedure TFrmMultiply.sgStatAnsweredDrawCell(Sender: TObject; ACol, ARow: Integer; Rect: TRect; State: TGridDrawState);
var
  W: Integer;
begin
  with (Sender as TStringGrid) do
    Canvas.TextRect(Rect,Rect.Left+2, Rect.Top+2, Cells[ACol,ARow]);                    //To left-align the text
    W := Canvas.TextWidth(Cells[ACol,ARow]);
    Canvas.TextRect(Rect,(Rect.Right+Rect.Left-W) div 2, Rect.Top+2, Cells[ACol,ARow]); //To center the text
    Canvas.TextRect(Rect,Rect.Right-2-W, Rect.Top+2, Cells[ACol,ARow]);                 //To right-align the text
end;

TEdit

  • Use OnKeyPress event to filter or react to some keypress
procedure TFrmForm1.Edit1KeyPress(Sender: Tobject; var Key: Char);
begin
  if( Key = #13 ) then
  begin
    //Some code here when Enter key is pressed
  end;
  if( not(Key in [#8, '0'..'9']) ) then Key:=#0;     //Ignore all key presses but Tab or digits

Some Examples

A Simple Class Container

type
  TMyClass = class(TObject);

  TMyContainer = class(TObject)
  private
    FObjects  : array of TMyClass;
    FCount    : Integer;
    FCapacity : Integer;
  protected
    function    GetObjects(index: Integer): TMyClass;
    procedure   SetCapacity(newCapacity: Integer);
  public
    destructor  Destroy; override;
    procedure   AddObject(object: TMyClass);            //takes ownership of problem (even on exception)
    procedure   Append(const source: TMyContainer);  	//Create new copies of TMyClass
    procedure   Assign(const source: TMyContainer);     //Create new copies of TMyClass
    procedure   Clear;
    procedure   Delete(index: Integer);
    property    Count: Integer read FCount;
    property    Objects[index: Integer]: TMyContainer read GetObjects; default;
  end;

{ TMyContainer }

function TMyContainer.GetObjects(index: Integer): TMyClass;
begin
  if( (index<0) or (index>=FCount) )
    then Result := nil
    else Result := FObjects[index];
end;

procedure TMyContainer.SetCapacity(newCapacity: Integer);
var
  i: Integer;
begin
  //If capacity is reduced, free objects in excess
  if(newCapacity<FCapacity) then
    for i := newCapacity to FCapacity-1 do
      FProblems[i].Free();

  SetLength(FObjects,newCapacity);
  FCapacity := newCapacity;

  // count must always be lower than or equal to capacity
  if(FCapacity<FCount)
    then FCount := FCapacity;
end;

destructor TMyContainer.Destroy;
begin
  Clear();
  inherited;
end;

procedure TMyContainer.AddObject(object: TMyClass);
begin
  try
    //Increase array capacity if needed (we double it)
    if(FCount >= FCapacity)
      then if(FCapacity=0)
        then SetCapacity(1)
        else SetCapacity(2*FCapacity);              //Implement your own strategy here (eg. constant extension...)
    FObjects[FCount] := problem;
    inc(FCount);
  except
    object.Free();                                  //Ownership was taken when method was called
    raise;
  end;
end;

procedure TMyContainer.Append(const source: TMyContainer);
var
  i: Integer;
begin
  if(source = Self)
    then exit;
  for i:= 0 to source.FCount-1 do
    AddObject(TMyClass.CreateCopy(source.FObjects[i]));
end;

procedure TMyContainer.Assign(const source: TMyContainer);
begin
  if(source = Self)
    then exit;
  Clear();
  Append(source);
end;

procedure TMyContainer.Clear;
begin
  SetCapacity(0);
end;

procedure TMyContainer.Delete(index: Integer);
begin
  if((index < 0) or (index >= FCount))
    then exit;
  FObjects[index].Free();
  Dec(FCount);
  if(index < FCount) then
    System.Move(FObjects[index + 1],FObjects[index],(FCount - index) * SizeOf(TMyClass));
end;

Using an object with no reference variable

source: http://delphi.about.com/cs/adptips2003/a/bltip0103_4.htm

procedure TForm1.FormCreate(Sender: TObject);
var RegPath : string;
begin
   with TRegistry.Create do
     try
       RegPath:='MyApp';
       RootKey := HKEY_CURRENT_USER;
       If OpenKey(RegPath, False) then
         // do stuff here
     finally
       Free
     end
end;