views:

285

answers:

4

I am implementing a Boilerplate feature - allow users to Change descriptions of some components - like Tlabels - at run time. e.g.

TFooClass = Class ( TBaseClass)
 Label : Tlabel;
 ...
 End;

 Var FooClass : TFooClass;

...

At Design time, the value Label's caption property is say - 'First Name', when the application is run, there is a feature that allows the user to change the caption value to say 'Other Name'. Once this is changed, the caption for the label for the class instance of FooClass is updated immediately.

The problem now is if the user for whatever reason wants to revert back to the design time value of say 'First Name' , it seems impossible.

I can use the RTTIContext methods and all that but I at the end of the day, it seems to require the instance of the class for me to change the value and since this has already being changed - I seem to to have hit a brick wall getting around it.

My question is this - is there a way using the old RTTI methods or the new RTTIContext stuff to the property of a class' member without instantiating the class - i.e. getting the property from the ClassType definition.

This is code snippet of my attempt at doing that :

  c : TRttiContext;
   z : TRttiInstanceType;
   w : TRttiProperty;
 Aform : Tform;
  ....
 Begin
 .....

   Aform := Tform(FooClass);

   for vCount := 0 to AForm.ComponentCount-1 do begin
    vDummyComponent := AForm.Components[vCount];
    if IsPublishedProp(vDummyComponent,'Caption') then begin
      c := TRttiContext.Create;
       try
         z := (c.GetType(vDummyComponent.ClassInfo) as TRttiInstanceType);
         w := z.GetProperty('Caption');
          if w <> nil  then
             Values[vOffset, 1] := w.GetValue(vDummyComponent.ClassType).AsString
        .....
        .....

....
....

I am getting all sorts of errors and any help will be greatly appreciated.

+1  A: 

Sounds like what you're trying to do is get the value of a certain property as defined in the DFM. This can't be done using RTTI, since RTTI is based on inspecting the structure of an object as specified by its class definition. The DFM isn't part of the class definition; it's a property list that gets applied to the objects after they've been created from the class definitions.

If you want to get at the values of the properties of a form's controls, you'll probably have to cache them somewhere. Try putting something in the form's OnCreate that runs through all the controls and uses RTTI to populate a TDictionary<string, TValue> with the values of all the properties. Then you can look them up later on when you need them.

Mason Wheeler
You are exactly right about what I am trying to do. My Problem is -I have a lot of forms and I am sure implementing the TDictionary stuff will bloat up the run time size of the app.In order to make it persistent, I am actually storing the changed value in a DB - I may just have to create a column to store the original value before any value is changed the first time an attempt is made to change the value. I am still thinking that the ClassInfo pointer would have the values of the design time values for the controls though.
Kayode Yusuf
@Kayode: The ClassInfo pointer can't have design-time values for the objects, because they don't belong to the classes, they belong to specific instances. For example, what if you had two different TLabels on a form, with two different captions? Which one would you expect it to store?
Mason Wheeler
I was actually thinking of getting the classinfo of the owner which encapsulates those controls, so I have two TLabel controls at design time, their design time state would be encapsulated within the ClassInfo for the Owner class.
Kayode Yusuf
I see. But no, that's not included in the classinfo. It's in the DFM resource, which you can find if you know the class name of the form, but that's separate from the RTTI system.
Mason Wheeler
Caching is exactly the thing that needs to be done, and it should be simple. Assuming the system Kayode Yusuf is trying to implement is persistent, there must exist an place where, at run time, when the form is created, those properties values are changed. That's the perfect spot to CACHE the values, because right before changing the property value you've got all the RTTI stuff set up and you've got the OLD value available. The nice thing is, since the OLD value is not cached to persistent storage, when the user issues the "revert to default", the default is the default in the current version.
Cosmin Prund
A: 

If what you are trying to achieve it to restore the value that was set at design-time (i.e. that one that is saved in the DFM), I'd use InitInheritedComponent as a starting point.

It's possible to get the content of the DFM at runtime. Could be a pain to parse though.

Check also InternalReadComponentRes.

Both routine can be found in the classes unit.

Ken Bourassa
I considered putting that in my response, but those routines are for reading an entire DFM resource at once. And to save space, the compiler compresses the DFMs to binary format. Difficult to parse indeed!
Mason Wheeler
This is why someone who needs to "parse" the binary DFM will most likely need to use one of the lower level functions Delphi use to initialize forms, or maybe a simple TReader will do the job. I still think it might be the best path to follow if it works.
Ken Bourassa
+1  A: 

The RTTI System does not provide what you are after. Type information is currently only determined at compile time. Initial form values are set at Runtime using the DFM resource. You can change values in a DFM Resource in a compiled application because it's evaluated at runtime.

Parse and use the DFM Resource where it is stored, or make a copy of the original value at runtime. Possibly at the point of initial change to reduce your memory footprint.

Masons Suggestion of using TDictionary<string, TValue> is what I would use. I would be careful of storing this information in a database as keeping it in sync could become a real maintenance nightmare.

Robert Love
A: 

Well - I solved the problem. The trick is basically instantiating another instance of the form like so :

 procedure ShowBoilerPlate(AForm : TForm; ASaveAllowed : Boolean);
 var
    vCount           : Integer;
    vDesignTimeForm  : TForm;
    vDesignTimeComp  : TComponent;
    vDesignTimeValue : String;
    vCurrentValue    : String;
 begin
   ....
   ....
   vDesignTimeForm :=  TFormClass(FindClass(AForm.ClassName)).Create(AForm.Owner);

   try
     // Now I have two instances of the form - I also need to have at least one
     // overloaded constructor defined for the base class of the forms that will allow for 
     // boilerplating. If you call the default Constructor - no boilerplating
     // is done. If you call the overloaded constructor, then, boilerplating is done.
     // Bottom line, I can have two instances AForm - with boilerplated values and
     // vDesignForm without boilerplated values.
     for vCount := 0 to AForm.ComponentCount-1 do begin
       vDummyComponent := AForm.Components[vCount];
       if Supports (vDummyComponent,IdoGUIMetaData,iGetGUICaption)  then begin
          RecordCount := RecordCount + 1;
          Values[vOffset, 0] := vDummyComponent.Name;
          if IsPublishedProp(vDummyComponent,'Caption') then begin
           vDesignTimeComp := vDesignTimeForm.FindComponent(vDummyComponent.Name);
           if vDesignTimeComp <> nil then begin
             // get Design time values here
              vDesignTimeValue := GetPropValue(vDesignTimeComp,'Caption');
           end;
           // get current boilerplated value here
              vCurrentValue  := GetPropValue(vDummyComponent,'Caption');
         end;
        vOffset := RecordCount;;
       end;
     end;

  finally
    FreeAndNil(vDesignTimeForm);
  end;
end;

Anyway - thank you all for all your advice.

Kayode Yusuf