2

Three classes: TTntMemo, TTntEdit and TEdit have a common ancestor - TCustomEdit, but I can't use Color and ShowHint properties of TCustomEdit because they are protected and are reintroduced as public only in TTntMemo, TTntEdit and TEdit. I am not allowed to change any of these classes because they belong either to VCL or to widely used controls libraries.

Following code is a PITA because it has to repeat itself three times - one time for each type:

class procedure TCommon.ValidateEdit(edit: TCustomEdit; condition: Boolean;
  failHint: WideString);
var m: TTntMemo;
    te: TTntEdit;
    e: TEdit;
begin
  if edit is TTntMemo then begin
    m := edit as TTntMemo;
    if condition then begin
      m.Color := clWindow;
      m.Hint := '';
      m.ShowHint := False;
    end
    else begin
      m.Color := $AAAAFF;
      m.Hint := failHint;
      m.ShowHint := True;
    end;
  end
  else
  if edit is TTntEdit then begin
    te := edit as TTntEdit;
    if condition then begin
      te.Color := clWindow;
      te.Hint := '';
      te.ShowHint := False;
    end
    else begin
      te.Color := $AAAAFF;
      te.Hint := failHint;
      te.ShowHint := True;
    end;
  end;
  if edit is TEdit then begin
    e := edit as TEdit;
    if condition then begin
      e.Color := clWindow;
      e.Hint := '';
      e.ShowHint := False;
    end
    else begin
      e.Color := $AAAAFF;
      e.Hint := failHint;
      e.ShowHint := True;
    end;
  end;
end;

Unfortunately Delphi6 doesn't have reflection.

Do you have some ideas how this code could be optimized?

Sir Rufo
  • 18,395
  • 2
  • 39
  • 73
Paul
  • 25,812
  • 38
  • 124
  • 247
  • 1
    Delphi 6 has RTTI and as far as I can see these are all properties that are published so they can be shown in the object inspector. – Stefan Glienke Dec 18 '13 at 11:56
  • @Rob Kennedy: It is not so clear there. – Paul Dec 20 '13 at 10:41
  • What's not clear? Declare the class, type cast, access whatever protected members you want. If there's something missing from my answer, please tell me. I want it to be good. – Rob Kennedy Dec 20 '13 at 13:36
  • @Rob Kennedy: Your answer is good. The problem is: if I get to that question page from search engine I can not say quickly that it is related to my issue - I have to use imagination. Probably I would have closed that page quickly. – Paul Dec 23 '13 at 13:26
  • But you didn't get there from a search engine; you got there from me telling you that's the page you need. With that in mind, do you still close it immediately, or do you actually read and understand what it's saying? Also note that closing your question as a duplicate doesn't make your question page go away. People can still find it in search engines and be directed to the page with the original question. Closing is to avoid duplicate answers accumulating in separate places, not to avoid duplicate questions. – Rob Kennedy Dec 23 '13 at 14:23
  • @Rob Kennedy: Sure, when that question had been pointed out as one that relates to my question, I checked it carefully and found that it is about the same things. What I say is that the question (not the answer) may not been identified properly if my question will be deleted. I understood about closing. I think that both questions should be closed as they contain both comprehensive answers. – Paul Dec 23 '13 at 17:49

1 Answers1

7

Use a hacked class of TCustomEdit

unit uCommon;

interface

  uses
    StdCtrls;

  type
    TCommon = class
      class procedure ValidateEdit( AEdit : TCustomEdit; ACondition : Boolean; AFailHint : string );
    end;

implementation

  uses
    Graphics;

  type
    // hacked TCustomEdit class to get access to protected properties
    THackedCustomEdit = class( TCustomEdit )
    published
      property ShowHint;
      property Color;
    end;

    { TCommon }

  class procedure TCommon.ValidateEdit( AEdit : TCustomEdit; ACondition : Boolean; AFailHint : string );
    var
      LEdit : THackedCustomEdit;
    begin
      LEdit := THackedCustomEdit( AEdit );
      if ACondition
      then
        begin
          LEdit.Color := clWindow;
          LEdit.Hint  := '';
        end
      else
        begin
          LEdit.Color := $AAAAFF;
          LEdit.Hint  := AFailHint;
        end;
      LEdit.ShowHint := not ACondition;
    end;

end.

or you can use TypInfo unit and

uses
  Graphics,
  TypInfo;

  class procedure TCommon.ValidateEdit( AEdit : TCustomEdit; ACondition : Boolean; AFailHint : string );
      procedure SetPublishedPropValue( Instance : TObject; const PropName : string; const Value : Variant );
        begin
          if IsPublishedProp( Instance, PropName )
          then
            SetPropValue( Instance, PropName, Value );
        end;

    begin
      if ACondition
      then
        begin
          SetPublishedPropValue( AEdit, 'Color', clWindow );
          AEdit.Hint := '';
        end
      else
        begin
          SetPublishedPropValue( AEdit, 'Color', $AAAAFF );
          AEdit.Hint := AFailHint;
        end;
      SetPublishedPropValue( AEdit, 'ShowHint', not ACondition );
    end;

UPDATE

Because all of the properties are declared in TControl you can also use this as your base class instead of TCustomEdit

Suggestion to get very DRY

If I would implement such a validator, I would prefer to use a function to get back the ACondition value

unit uCommon;

interface

  uses
    Controls;

  type
    TCommon = class
      class function ValidateControl( AControl : TControl; ACondition : Boolean; AFailHint : string ) : Boolean;
    end;

implementation

  uses
    Graphics;

  type
    THackedControl = class( TControl )
    published
      property ShowHint;
      property Color;
    end;

    { TCommon }

  class function TCommon.ValidateControl( AControl : TControl; ACondition : Boolean; AFailHint : string ) : Boolean;
    var
      LControl : THackedControl;
    begin
      // Return Condition as Result
      Result   := ACondition;
      LControl := THackedControl( AControl );
      if ACondition
      then
        begin
          LControl.Color := clWindow;
          LControl.Hint  := '';
        end
      else
        begin
          LControl.Color := $AAAAFF;
          LControl.Hint  := AFailHint;
        end;
      LControl.ShowHint := not ACondition;
    end;

end.

In my form I would use this (and it will become very DRY)

function BoolAnd( AValues : array of Boolean ) : Boolean;
var
  LIdx : Integer;
begin
  Result := True;
  for LIdx := Low( AValues ) to High( AValues ) do
  begin
    Result := Result and AValues[LIdx];
    if not Result then
      Break;
  end;
end;

procedure TForm1.Validate;
begin
  SaveButton.Enabled :=
    BoolAnd( [
      TCommon.ValidateControl( Edit1, Edit1.Text <> '', 'must not be empty' ),
      TCommon.ValidateControl( Memo1, Memo1.Text <> '', 'must not be empty' ),
      TCommon.ValidateControl( SpinEdit1, SpinEdit1.Value >= 10, 'must not be below 10' ),
      TCommon.ValidateControl( ComboBox1, ComboBox1.ItemIndex >= 0, 'must not be empty' )
    ] );
end;
Sir Rufo
  • 18,395
  • 2
  • 39
  • 73