Class Property Scope

I have a TCue class with a boolean variable named Selected. There is also a TCueList class (TObjectList based) that holds the list of TCue items.

I’d like all of the processing for setting TCue.Selected to be done via the TCueList object, so as TCue items are selected and deselected the holding list can update other properties or fire an event.

Is there a way of limiting the scope of TCue.Selected so that it can only be accessed via the TCueList object via the SelectAll, ClearSelection, etc procedures I’ve added?

I want to prevent direct calls such as TCueList.Items[x].Selected := True. I realise I can just avoid using those direct calls, but is it possible to limit the scope?

1 Like

Hi David,

I guess it depends on what scope you want it limited to. If you want to prevent users in other units from acecssing the TCue.Select property you could define it in the same unit as TCueList but set the Select property to private visibility.

unit UCue;

interface

uses
  System.Generics.Collections;

type
  TCue = class(TObject)
  private
    FSelected: Boolean;
    property Selected: Boolean read FSelected write FSelected;
  end;

  TCueList = class(TObjectList<TCue>)
  private
    function GetSelected(Index: Integer): Boolean;
    procedure SetSelected(Index: Integer; const Value: Boolean);
  public
    property Selected[Index: Integer]: Boolean read GetSelected write SetSelected;
  end;

implementation

function TCueList.GetSelected(Index: Integer): Boolean;
begin
  Result := Items[Index].Selected;
end;

procedure TCueList.SetSelected(Index: Integer; const Value: Boolean);
begin
  Items[Index].Selected := Value;
end;

end.
program CueProject;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  UCue in 'UCue.pas';

begin
  try
    var CueList := TCueList.Create;
    try
      var cue := TCue.Create;
      CueList.Add(cue);
      Cuelist.Selected[0] := True;
    finally
      CueList.Free;
    end;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

That was one thought, but the classes are in separate units and I really want to keep it that way.

It’s not a big issue about the scope as I’m the only developer for the project, but I thought I’d ask anyway.

Hi,
You could make it protected, and then use a protected hack subclass to access the member.
Not sure if this is better than just leaving it public, but it’s hidden a bit.

eg.
in CueList file

type THackCue= class(TCue);

THackCue(cue).Selected := true;

I guess it depends if you’re just trying to avoid someone using it inadvertently, or if you’re trying to stop someone who’s determined.

If the former, then going back to the earlier thread about interfaces, it might be interesting to have that Selected member be private, but have the class implement an interface that exposes it. “Normal” class clients won’t even see it, but casting to the interface will let you access it.

Note, if you go down this path, best to have the list hold a reference to the TCue via an interface as well (ie. so all references to Cue’s are interfaces). You can get yourself in a tangle holding a reference to the class instance and an interface at the same time.

(Updated to clarify my clumsy working.)

Modifying Jared’s code a bit.

unit UCue;

interface

type
  TCue = class(TObject)
  protected
    FSelected : boolean;
  end;

implementation

end.
unit UCueList;

interface

uses
  System.Generics.Collections, UCue;

type
  TCueList = class(TObjectList<TCue>)
  private
    function GetSelected(Index: Integer): Boolean;
    procedure SetSelected(Index: Integer; const Value: Boolean);
  public
    property Selected[Index: Integer]: Boolean read GetSelected write SetSelected;
    function AddNew : TCue;
  end;

implementation

type
  TSelectableCue = class(TCue)
  public
    property Selected : boolean read FSelected write FSelected;
  end;

function TCueList.AddNew : TCue;
begin
  Result := TSelectableCue.Create;
  Add(Result);
end;

function TCueList.GetSelected(Index: Integer): Boolean;
begin
  Result := (Items[Index] as TSelectableCue).Selected;
end;

procedure TCueList.SetSelected(Index: Integer; const Value: Boolean);
begin
  (Items[Index] as TSelectableCue).Selected := Value;
end;

end.

So new TCue instances are created by calling TCueList.AddNew. If you need parameters for the TCue constructor just pass them through the AddNew function as well.

You’d also replace that direct property access to the ancestor protected TCue.FSelectable with a getter/setter that can do the TCue based processing you want.

program CueProject;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  UCue in 'UCue.pas',
  UCueList in 'UCueList.pas';

begin
  try
    var CueList := TCueList.Create;
    try
      var cue := CueList.AddNew;
      Cuelist.Selected[0] := True;
    finally
      CueList.Free;
    end;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

I ended up going with a read only property and a public setter for the TCue class. This makes it obvious if I try and use the normal assignment.

Unless you’re holding a long term interface reference to the list, aren’t you running the risk that the list will be freed when your temporary interface reference goes out of scope?

I guess that was the point I was clumsily trying to make, but I can see now it reads badly. Updated.