How do I use a string in TRttiMethod.Invoke as parameter properly?
Asked Answered
S

1

6

I'm trying to generalize the content validation of visual components with the Text-property using RTTI but when I try to pass a string value into TRttiMethod.Invoke, I get the Message "Invalid Typecast". (Actually "Ungültige Typumwandlung" but I guess, that was a fitting translation.)

The code below is stripped of all security measures, assertions and so on, assuming all passed objects are just perfect.

procedure ValidateTextFieldAndSetFocus(const Field: TObject; const Validator: TObject; const errorStates: array of TStringValidationResult; const sErrorMessage: string);
var
  context  : TRttiContext;
  objField : TRttiType;
  objValid : TRttiType;
  prop     : TRttiProperty;
  execute  : TRttiMethod;
  I        : Integer;
  validResult : TStringValidationResult;
  value    : TValue;
begin
  context  := TRttiContext.Create;
  objField := context.GetType(Field.ClassInfo);
  objValid := context.GetType(Validator.ClassInfo);
  prop     := objField.GetProperty('Text');
  value    := prop.GetValue(Field);
  execute  := objValid.GetMethod('Execute');
  for I := 0 to High(errorStates) do
    if execute.Invoke(Validator,[value]).TryAsType<TStringValidationResult>(validResult) then
      if validResult = errorStates[I] then
      begin
        SetFocusIfCan(Field);
        raise Exception.Create(sErrorMessage);
      end;
end;

The Validator's Execute only has one string-Parameter. I've seen examples where strings were passed directly into the array of TValue, but then I get the same typecast error.

edit:

The actual error appears in execute.Invoke(Validator,[value]).

Example

TNoSemicolonNullValidator = class
  class function Execute(const aStr: string): TStringValidationResult;
end;

procedure TestValidation;
var
  Validator : TNoSemicolonNullValidator;
begin
  Validator := TNoSemicolonNullValidator.Create;
  try
    ValidateTextFieldAndSetFocus(Edit1,Validator,[svInvalid],'Edit1 is invalid!');
  finally
    Validator.Free;
  end;
end;
Solarism answered 4/9, 2015 at 8:32 Comment(4)
Please provide a minimal reproducible example so that we don't have to guess the parts that are missingEudoxia
As it stands, this question should be closed as being off topic. Because you have not provided a reproduction. Once you remedy that (see the link in the comment above), we will be able to answer.Eudoxia
Actually the code provides enough information to spot the mistake.Mckay
@StefanGlienke So you don't believe in submitting complete reproductions? Personally I like to encourage best practices and teach people how to isolate problems and submit minimal reproductions. I'm quite sure you feel the same way when people submit issues to Spring4D. I feel very strongly that we should be educating people of the importance of complete code to reproduce problems. I'm disappointed that you don't seem to share that view.Eudoxia
M
14

You are calling a class function here but you are passing a TObject as first parameter (which is the hidden Self argument of non static methods). On a class method the Self parameter must not be an instance but the class of it. So the correct call would be:

execute.Invoke(validator.ClassType, [value]);

Here is a minimal example to prove that:

program Project1;

{$APPTYPE CONSOLE}

uses
  Rtti,
  SysUtils;

type
  TValidator = class
    class function Execute(const s: string): Boolean;
  end;

class function TValidator.Execute(const s: string): Boolean;
begin
  Writeln(s);
end;

var
  ctx: TRttiContext;
  v: TValidator;
begin
  v := TValidator.Create;
  try
    ctx.GetType(TValidator).GetMethod('Execute').Invoke(v, ['test']);
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  try
    ctx.GetType(TValidator).GetMethod('Execute').Invoke(v.ClassType, ['test']);
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  Readln;
end.
Mckay answered 4/9, 2015 at 9:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.