打印本文 打印本文 关闭窗口 关闭窗口
The Delphi Object Model (PART II)
作者:武汉SEO闵涛  文章来源:敏韬网  点击数4134  更新时间:2009/4/23 18:43:51  文章录入:mintao  责任编辑:mintao
''''s type cannot be a dynamic array. Records and arrays can be nested, and you can even use variant records. Example 2-9 shows an extended rectangle type, similar to the Windows TRect type, but because it is a class, it has properties and methods.

Example 2-9: Properties Readers and Writers
TRectEx = class(TPersistent)
  private
    R: TRect;
    function GetHeight: Integer;
    function GetWidth: Integer;
    procedure SetHeight(const Value: Integer);
    procedure SetWidth(const Value: Integer);
  public
    constructor Create(const R: TRect); overload;
    constructor Create(Left, Top, Right, Bottom: Integer); overload;
    constructor Create(const TopLeft, BottomRight: TPoint); overload;
 
    procedure Assign(Source: TPersistent); override;
    
    procedure Inflate(X, Y: Integer);
    procedure Intersect(const R: TRectEx);
    function IsEmpty: Boolean;
    function IsEqual(const R: TRectEx): Boolean;
    procedure Offset(X, Y: Integer);
    procedure Union(const R: TRectEx);
 
    property TopLeft: TPoint read R.TopLeft write R.TopLeft;
    property BottomRight: TPoint read R.BottomRight write R.BottomRight;
    property Rect: TRect read R write R;
    property Height: Integer read GetHeight write SetHeight;
    property Width: Integer read GetWidth write SetWidth;
  published
    property Left: Integer read R.Left write R.Left default 0;
    property Right: Integer read R.Right write R.Right default 0;
    property Top: Integer read R.Top write R.Top default 0;
    property Bottom: Integer read R.Bottom write R.Bottom default 0;
  end;

Array properties

Properties come in scalar and array flavors. An array property cannot be published, but they have many other uses. The array index can be any type, and you can have multidimensional arrays, too. For array-type properties, you must use read and write methods--you cannot map an array-type property directly to an array-type field.

You can designate one array property as the default property. You can refer to the default property by using an object reference and an array subscript without mentioning the property name, as shown in Example 2-10.

Example 2-10: Using a Default Array Property

type
  TExample = class
    ...
    property Items[I: Integer]: Integer read GetItem write SetItem;
    property Chars[C: Char]: Char read GetChar write SetChar; default;
  end;
var
  Example: TExample;
  I: Integer;
  C: Char;
begin
  Example := TExample.Create;
  I := Example.Items[4];     // Must mention property name explicitly
  C := Example[''''X''''];         // Array property is default
  C := Example.Chars[''''X''''];   // Same as previous line

Indexed properties

You can map many properties to a single read or write method by specifying an index number for each property. The index value is passed to the read and write methods to differentiate one property from another.

You can even mix array indices and an index specifier. The reader and writer methods take the array indices as the first arguments, followed by the index specifier.

Default values

A property can also have stored and default directives. This information has no semantic meaning to the Delphi Pascal language, but Delphi''''s IDE uses this information when storing form descriptions. The value for the stored directive is a Boolean constant, a field of Boolean type, or a method that takes no arguments and returns a Boolean result. The value for the default directive is a constant value of the same type as the property. Only enumerated, integer, and set-type properties can have a default value. The stored and default directives have meaning only for published properties.

To distinguish a default array from a default value, the default array directive comes after the semicolon that ends the property declaration. The default value directive appears as part of the property declaration. See the default directive in Chapter 5 for details.

Using properties

A common approach to writing Delphi classes is to make all fields private, and declare public properties to access the fields. Delphi imposes no performance penalty for properties that access fields directly. By using properties you get the added benefit of being able to change the implementation at a future date, say to add validation when a field''''s value changes. You can also use properties to enforce restricted access, such as using a read-only property to access a field whose value should not be changed. Example 2-11 shows some of the different ways to declare and use properties.

Example 2-11: Declaring and Using Properties

type
  TCustomer = record
    Name: string;
    TaxIDNumber: string[9];
  end;
  TAccount = class
  private
    fCustomer: TCustomer;
    fBalance: Currency;
    fNumber: Cardinal;
    procedure SetBalance(NewBalance: Currency);
  published
    property Balance: Currency read fBalance write SetBalance;
    property Number: Cardinal read fNumber; // Cannot change account #
    property CustName: string read fCustomer.Name;
  end;
  TSavingsAccount = class(TAccount)
  private
    fInterestRate: Integer;
  published
    property InterestRate: Integer read fInterestRate
        write fInterestRate default DefaultInterestRate;
  end;
  TLinkedAccount = class(TObject)
  private
    fAccounts: array[0..1] of TAccount;
    function GetAccount(Index: Integer): TAccount;
  public
    // Two ways for properties to access an array: using an index
    // or referring to an array element.
    property Checking: TAccount index 0 read GetAccount;
    property Savings:  TAccount read fAccounts[1];
  end;
  TAccountList = class
  private
    fList: TList;
    function GetAccount(Index: Integer): TAccount;
    procedure SetAccount(Index: Integer; Account: TAccount);
    function GetCount: Integer;
  protected
    property List: TList read fList;
  public
    property Count: Integer read GetCount;
    property Accounts[Index: Integer]: TAccount read GetAccount
        write SetAccount; default;
  end;
 
procedure TAccount.SetBalance(NewBalance: Currency);
begin
  if NewBalance < 0 then
    raise EOverdrawnException.Create;
  fBalance := NewBalance;
end;
 
function TLinkedAccount.GetAccount(Index: Integer): TAccount;
begin
  Result := fAccounts[Index]
end;
 
function TAccountList.GetCount: Integer;
begin
  Result := List.Count
end;
 
function TAccountList.GetAccount(Index: Integer): TAccount;
begin
  Result := List[Index]
end;
 
procedure TAccountList.SetAccount(Index: Integer; Account: TAccount);
begin
  fList[Index] := Account
end;

Class-type properties

Properties of class type need a little extra attention. The best way to work with class-type properties is to make sure the owner object manages the property object. In other words, don''''t save a reference to other objects, but keep a private copy of the property object. Use a write method to store an object by copying it. Delphi''''s IDE requires this behavior of published properties, and it makes sense for unpublished properties, too.

The only exception to the rule for class-type properties is when a property stores a reference to a component on a form. In that case, the property must store an object reference and not a copy of the component.

Delphi''''s IDE stores component references in a .dfm file by storing only the component name. When the .dfm is loaded, Delphi looks up the component name to restore the object reference. If you must store an entire component within another component, you must delegate all properties of the inner component.

Make sure the property''''s class inherits from TPersistent and that the class overrides the Assign method. Implement your property''''s write method to call Assign. (TPersistent--in the Classes unit--is not required, but it''''s the easiest way to copy an object. Otherwise, you need to duplicate the Assign method in whatever class you use.) The read method can provide direct access to the field. If the property object has an OnChange event, you might need to set that so your object is notified of any changes. Example 2-12 shows a typical pattern for using a class-type property. The example defines a graphical control that repeatedly displays a bitmap throughout its extent, tiling the bitmap as necessary. The Bitmap property stores a TBitmap object.

Example 2-12: Declaring and Using a Class-type Property

unit Tile; interface uses SysUtils, Classes, Controls, Graphics; type // Tile a bitmap TTile = class(TGraphicControl) private fBitmap: TBitmap; procedure SetBitmap(NewBitmap: TBitmap); procedure BitmapChanged(Sender: TObject); protected procedure Paint; override; public constructor Create(Owner: TComponent); override; destructor Destroy; override; published property Align; property Bitmap: TBitmap read fBitmap write SetBitmap; property OnClick; property OnDblClick; // Many other properties are useful, but were omitted to save space. // See TControl for a full list. end; implementation { TTile } // Create the bitmap when creating the control. constructor TTile.Create(Owner: TComponent); begin inherited; fBitmap := TBitmap.Create; fBitmap.OnChange := BitmapChanged; end; // Free the bitmap when destroying the control. destructor TTile.Destroy; begin FreeAndNil(fBitmap); inherited; end; // When the bitmap changes, redraw the control. procedure TTile.BitmapChanged(Sender: TObject); begin Invalidate; end; // Paint the control by tiling the bitmap. If there is no // bitmap, don''''t paint anything. procedure TTile.Paint; var X, Y: Integer; begin i

上一页  [1] [2] [3] [4]  下一页

打印本文 打印本文 关闭窗口 关闭窗口