Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>You're touching a few concepts and problems with this question. First of all you've mixed in some record types and some properties, and I'd like to handle this first. Then I'll give you some short info on how to read the "Left" and "Top" fields of a record when that record is part of an field in a class... Then I'll give you suggestions on how to make this work generically. I'm probably going to explain a bit more then it's required, but it's midnight over here and I can't sleep!</p> <p>Example:</p> <pre><code>TPoint = record Top: Integer; Left: Integer; end; TMyClass = class protected function GetMyPoint: TPoint; procedure SetMyPoint(Value:TPoint); public AnPoint: TPoint; property MyPoint: TPoint read GetMyPoint write SetMyPoint; end; function TMyClass.GetMyPoint:Tpoint; begin Result := AnPoint; end; procedure TMyClass.SetMyPoint(Value:TPoint); begin AnPoint := Value; end; </code></pre> <p>Here's the deal. If you write this code, at runtime it will do what it seems to be doing:</p> <pre><code>var X:TMyClass; x.AnPoint.Left := 7; </code></pre> <p>But this code will not work the same:</p> <pre><code>var X:TMyClass; x.MyPoint.Left := 7; </code></pre> <p>Because that code is equivalent to:</p> <pre><code>var X:TMyClass; var tmp:TPoint; tmp := X.GetMyPoint; tmp.Left := 7; </code></pre> <p>The way to fix this is to do something like this:</p> <pre><code>var X:TMyClass; var P:TPoint; P := X.MyPoint; P.Left := 7; X.MyPoint := P; </code></pre> <p>Moving on, you want to do the same with RTTI. You may get RTTI for both the "AnPoint:TPoint" field and for the "MyPoint:TPoint" field. Because using RTTI you're essentially using a function to get the value, you'll need do use the "Make local copy, change, write back" technique with both (the same kind of code as for the X.MyPoint example).</p> <p>When doing it with RTTI we'll always start from the "root" (a TExampleClass instance, or a TMyClass instance) and use nothing but a series of Rtti GetValue and SetValue methods to get the value of the deep field or set the value of the same deep field.</p> <p>We'll assume we have the following:</p> <pre><code>AnPointFieldRtti: TRttiField; // This is RTTI for the AnPoint field in the TMyClass class LeftFieldRtti: TRttiField; // This is RTTI for the Left field of the TPoint record </code></pre> <p>We want to emulate this:</p> <pre><code>var X:TMyClass; begin X.AnPoint.Left := 7; end; </code></pre> <p>We'll brake that into steps, we're aiming for this:</p> <pre><code>var X:TMyClass; V:TPoint; begin V := X.AnPoint; V.Left := 7; X.AnPoint := V; end; </code></pre> <p>Because we want to do it with RTTI, and we want it to work with anything, we will not use the "TPoint" type. So as expected we first do this:</p> <pre><code>var X:TMyClass; V:TValue; // This will hide a TPoint value, but we'll pretend we don't know begin V := AnPointFieldRtti.GetValue(X); end; </code></pre> <p>For the next step we'll use the GetReferenceToRawData to get a pointer to the TPoint record hidden in the V:TValue (you know, the one we pretend we know nothing about - except the fact it's a RECORD). Once we get a pointer to that record, we can call the SetValue method to move that "7" inside the record.</p> <pre><code>LeftFieldRtti.SetValue(V.GetReferenceToRawData, 7); </code></pre> <p>This is allmost it. Now we just need to move the TValue back into X:TMyClass:</p> <pre><code>AnPointFieldRtti.SetValue(X, V) </code></pre> <p>From head-to-tail it would look like this:</p> <pre><code>var X:TMyClass; V:TPoint; begin V := AnPointFieldRtti.GetValue(X); LeftFieldRtti.SetValue(V.GetReferenceToRawData, 7); AnPointFieldRtti.SetValue(X, V); end; </code></pre> <p>This can obviously be expanded to handle structures of any depth. Just remember that you need to do it step-by-step: The first GetValue uses the "root" instance, then the next GetValue uses an Instance that's extracted from the previous GetValue result. For records we may use TValue.GetReferenceToRawData, for objects we can use TValue.AsObject!</p> <p>The next tricky bit is doing this in a generic way, so you can implement your bi-directional tree-like structure. For that, I'd recommend storing the path from "root" to your field in the form of an TRttiMember array (casting will then be used to find the actual runtype type, so we can call GetValue and SetValue). An node would look something like this:</p> <pre><code>TMemberNode = class private FMember : array of TRttiMember; // path from root RootInstance:Pointer; public function GetValue:TValue; procedure SetValue(Value:TValue); end; </code></pre> <p>The implementation of GetValue is very simple:</p> <pre><code>function TMemberNode.GetValue:TValue; var i:Integer; begin Result := FMember[0].GetValue(RootInstance); for i:=1 to High(FMember) do if FMember[i-1].FieldType.IsRecord then Result := FMember[i].GetValue(Result.GetReferenceToRawData) else Result := FMember[i].GetValue(Result.AsObject); end; </code></pre> <p>The implementation of SetValue would be a tiny little bit more involved. Because of those (pesky?) records we'll need to do <em>everything</em> the GetValue routine does (because we need the Instance pointer for the very last FMember element), then we'll be able to call SetValue, but we might need to call SetValue for it's parent, and then for it's parent's parent, and so on... This obviously means we need to KEEP all the intermediary TValue's intact, just in case we need them. So here we go:</p> <pre><code>procedure TMemberNode.SetValue(Value:TValue); var Values:array of TValue; i:Integer; begin if Length(FMember) = 1 then FMember[0].SetValue(RootInstance, Value) // this is the trivial case else begin // We've got an strucutred case! Let the fun begin. SetLength(Values, Length(FMember)-1); // We don't need space for the last FMember // Initialization. The first is being read from the RootInstance Values[0] := FMember[0].GetValue(RootInstance); // Starting from the second path element, but stoping short of the last // path element, we read the next value for i:=1 to Length(FMember)-2 do // we'll stop before the last FMember element if FMember[i-1].FieldType.IsRecord then Values[i] := FMember[i].GetValue(Values[i-1].GetReferenceToRawData) else Values[i] := FMember[i].GetValue(Values[i-1].AsObject); // We now know the instance to use for the last element in the path // so we can start calling SetValue. if FMember[High(FMember)-1].FieldType.IsRecord then FMember[High(FMember)].SetValue(Values[High(FMember)-1].GetReferenceToRawData, Value) else FMember[High(FMember)].SetValue(Values[High(FMember)-1].AsObject, Value); // Any records along the way? Since we're dealing with classes or records, if // something is not a record then it's a instance. If we reach a "instance" then // we can stop processing. i := High(FMember)-1; while (i &gt;= 0) and FMember[i].FieldType.IsRecord do begin if i = 0 then FMember[0].SetValue(RootInstance, Values[0]) else if FMember[i-1].FieldType.IsRecord then FMember[i].SetValue(FMember[i-1].GetReferenceToRawData, Values[i]) else FMember[i].SetValue(FMember[i-1].AsObject, Values[i]); // Up one level (closer to the root): Dec(i) end; end; end; </code></pre> <p>... And this should be it. Now some warnings:</p> <ul> <li>DON'T expect this to compile! I actually wrote every single bit of code in this post in the web browser. For technical reasons I had access to the Rtti.pas source file to look up method and field names, but I don't have access to an compiler.</li> <li>I'd be VERY careful with this code, especially if PROPERTIES are involved. A property can be implemented without an backing field, the setter procedure might not do what you expect. You might run into circular references!</li> </ul>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload