1
votes

I'm currently having a major headache with typecasting of component properties.

On my form, i have a TPanel called "scene". Also on the form, i have a button that creates a TSelection, and within that TSelection creates a TImage and then load a picture into that TImage. The name for the TSelection is assigned via a TEdit known as "ImgObjName". It then writes this name to an inifile. The events for the TSelection are assigned to procedures elsewhere in the code. As you know, the TSelection component can be moved around (and resized) at runtime. The TImage has it's HitTest turned off while the TSelection has it turned on.

The above works as i want it to, but the next part is where i'm stuck. Essentially, on a timer, i want to write a select few of the properties of each child component to an TMemIniFile. There's 2 ways i'm willing to do this;
1) Write the properties of each child to seperate TMemInifiles.
2) Write the properties of each child to a single TMemIniFile, but make the section identify which component the values in that section relate to.

I've tried a few different methods, but all of them have caused me some major problems (usually "index out of bounds").

My current method is as such;

ChgPos is a global boolean variable which is TRUE when the the mousedown event on one of the TSelection objects is fire, and FALSE when the MouseUp event is fired. This boolean procedure works perfectly for these purposes so no changes are needed there.

TimerBar is a TTrackBar that's been created at designtime. It's value changes based on a timer.

AnimIni is the TMemIniFile which has been assigned earlier in the code. For this purpose, i've set it not to free up the file (so as there's no access violations).

var
  i: Integer;
  PosX, PosY: Integer;
begin
  for i := 0 to Scene.ChildrenCount - 1 do
  begin
    if Scene.Components[i] is TSelection then
    begin
      PosX := AnimIni.ReadInteger(IntToStr(Round(TimerBar.Value)) + '_Object' +
        IntToStr(i), 'PosX', PosX);
      PosY := AnimIni.ReadInteger(IntToStr(Round(TimerBar.Value)) + '_Object' +
        IntToStr(i), 'PosY', PosY);
    end;
  end;
  if ChgPos = False then
  begin
    if Scene.Components[i] is TSelection then
    begin
      (Scene.Components[i] as TSelection).Position.X := PosX;
      (Scene.Components[i] as TSelection).Position.Y := PosY;
    end;
  end
  else if ChgPos = True then
  begin
    AnimIni.WriteInteger(IntToStr(Round(TimerBar.Value)) + '_Object' +
      IntToStr(i), 'PosX', Round((Scene.Children[i] as TSelection).Position.X));
    AnimIni.WriteInteger(IntToStr(Round(TimerBar.Value)) + '_Object' +
      IntToStr(i), 'PosY', Round((Scene.Children[i] as TSelection).Position.Y));
  end;
end;

I'm struggling to figure out where to go with this. I'm getting "Index out of range" errors with it. I'll also need to save the TImage component properties (particularly the parent and Bitmap location, but i feel it's important for me to get the code working with at least one component at the moment).

I'm somewhat new to typecasting (as all my previous projects worked without the need for it), but all my experience with it so far has been pleasently successful. It's just in this particular case it's proven to become more complicated than i can work out without some assistance.

I did try WriteComponent and ReadComponent and using multiple files to stream the data in realtime in relation to the value of TimerBar but it's much too slow for what i want to achieve (particularly on the write function). The inifile method does work from my earlier testing, but it's actually working with typecasting of multiple components that are created at runtime that i'm having issues with.

Can anyone shed some light on a potential solution or the direction i should head in?

2
Your i loop ends too soon. The state of i in your if ChgPos (and the rest) is undetermined.LU RD

2 Answers

6
votes
  1. You are mixing components and children. If you loop through all children of the panel, do not use that index on the Components property, but the Children property. (I assume your code compiled and XE2 has a Children property, otherwise I think you mean Controls and ControlCount).
  2. Like LU RD already commented, you are using the for-loop variable i outside the for-loop. I am sure you want it inside. You are also warned by the compiler for this:

    FOR-loop variable 'i' may be undefined after loop

    Always make sure you have zero! compiler errors, warnings and hints.

  3. I do not understand your routine logic for it loads the settings even if you actually want to write them. I think you only want to load the settings from the MemIniFile when ChgPos is false.

No guarantees given, but I think the routine should look like this (including a few syntax improvements):

var
  i: Integer;
  Selection: TSelection;
  PosX, PosY: Integer;
begin
  for i := 0 to Scene.ChildrenCount - 1 do
    if Scene.Children[i] is TSelection then
    begin
      Selection := Scene.Children[i] as TSelection;
      if ChgPos then
      begin
        AnimIni.WriteInteger(IntToStr(Round(TimerBar.Value)) + '_Object' +
          IntToStr(i), 'PosX', Round(Selection.Position.X));
        AnimIni.WriteInteger(IntToStr(Round(TimerBar.Value)) + '_Object' +
          IntToStr(i), 'PosY', Round(Selection.Position.Y));
      end
      else
      begin
        PosX := AnimIni.ReadInteger(IntToStr(Round(TimerBar.Value)) +
          '_Object' + IntToStr(i), 'PosX', PosX);
        PosY := AnimIni.ReadInteger(IntToStr(Round(TimerBar.Value)) +
          '_Object' + IntToStr(i), 'PosY', PosY);
        Selection.Position.X := PosX;
        Selection.Position.Y := PosY;
      end;
    end;
end;

Although I seriously doubt the default values for the AnimIni.ReadInteger function, PosX and PosY, which are unassigned. If no section in the ini file is found, then PosX and PosY will have arbitrary values. You should initialize them to whatever makes sense.

3
votes

Your loop counter and indexed properties don't match. ComponentCount and Components[] go together. And ChildrenCount and Children[] go together. You want to work with the latter pair since you are interesting in the children of the controls. The ComponentCount and Components[] properties refer to ownership which is a different concept.

What's more your loop ends but you continue using the loop variable after the loop variable. That's clearly wrong. It looks like it needs to be inside the loop and inside the Scene.Children[i] is TSelection test.

As an aside ChildrenCount is grammatically incorrect alongside ComponentCount and ControlCount. This property should have been named ChildCount.