ContainerControls And RectControls inconsistency

I’m on 2019r1.1 still so I don’t know if the following is still true.

If you walk the control hierarchy using RectControl.Parent, even if the the parent is a Container control, you can assign parent to a variable of type RectControl…

Which was fortuitous as i need to check up the hierarchy if any of the controls containing this one (whatever they might be) was a subclass that implemented a specific class interface.

Seeing that I thought great, that should mean that I can write an extension method to get the absolute coordinates for any control that can contain other controls for menu item.popup, regardless of if it was a RectControl or an embedded containerControl:

Public Sub AbsolutePosition(Extends Container as ContainerControl, byref X as integer, byref Y as Integer)
  X = X + Container.Left
  Y = Y + Container.Top
  
  Do 
    If Container.Window IsA EmbeddedWindowControl  Then
      Container = ContainerControl(Container.Window )
      X= X + Container.Left
      Y = Y + Container.Top
    Else
      X= X + Container.Window.Left
      Y = Y + Container.Window.Top
      Return
    End if
  Loop
  
End Sub

But then when I tried to do that (me is a container control here):

me.AbsolutePosition(MenuX,MenuY) ' Does not compile

It did not compile So then i tried:

EmbeddedWindowControl(me).AbsolutePosition(MenuX,MenuY) 'illegal cast
It did compile (And autocomplete!!!) but when I ran I got an illegal cast exception

So next i tried:
RectControl(EmbeddedWindowControl(me)).AbsolutePosition(MenuX,MenuY)

And again it compiled but on run I got an illegal cast exception again!

So why can I assign the RectControl.Parent to a variable of type RectControl even when it is container control but the above not work!

I wound up creating an overload method to handle container controls but even THAT was confusing. I first defined the overload method as:

Public Sub AbsolutePosition(Extends Container as EmbeddedWindowControl, Byref X as integer, Byref Y as integer)

But then I got a compile error because the compiler could not distinguish between a rectControl and and an Embedded window! That REALLY surprised me given that
EmbeddedWindowControl(me).AbsolutePosition(MenuX,MenuY)
Did not work

This is what finally worked for the overload:

Public Sub AbsolutePosition(Extends Container as ContainerControl, Byref X as integer, Byref Y as integer)

This is VERY inconsistent behavior… Whatever magic Xojo inc choses to do under the hood should be consistent IMO!

I would report this inconsistency as a bug, but I am 99.9% sure I would be told this was by design .

-Karen

Windows/ContainerControls aren’t RectControls. The behaviour is by design.

1 Like

I think you missed my point… Which I will state more succinctly below:

If that is so why can I do:
Dim R as RectControl = aRectControl.Parent
When it’s parent is a ContainerControl without any issues?

Try it yourself.

Put a RectControl in a containerControl and put an instance on a window

in the RectControl Open event put:

Dim MyParent as rectControl = me.parent
Break

And look at MyParent in the debugger… It’s an EmbeddedWindowControl (AKA ContainerControl) … which would imply it is a subclass of RectControl… But it’s not - which should not be possible

So in some cases container controls are seen to be RectControls and others not by the language…

It should always be or never be considered a RectControl… but as things are, sometime it is, and sometimes it is not.

-Karen

@Greg_O_Lone

Greg instead of just liking her post, maybe you can explain this inconsistency?

Obviously this is being special cased under the hood, but this design breaks language expectations, no?

What could have been done is that Parent could just be Object and
we would need to do an IsA to know to cast it as a Container Control or a RectControl.

While not as convenient, that would not beak the “rules” as it does now.

But I do know the history. RectControl.Parent existed before RB had ContainerControls and this was the Frankenstein Monster created to deal with not breaking existing code, back when that mattered more.

This type of special case is the kind of thing that needs MORE DETAIL in the language reference IMO.

-Karen

1 Like

I’m sorry that you are frustrated by this, but I liked her post because her explanation is correct.

ContainerControls are basically Windows behind the scenes, just without all the chrome.

Unfortunately this is a design decision that was made a very long time ago and one not easily changed because we and others have code which relies on it.

So file a bug report about that.

When I started using RB (V3) , there was not even a control hierarchy. I remember when that was introduced into the product. ContainerControls came later.

I knew ContainerControls were not normal controls but I had never needed do anything “fancy” with them until now… Most it was just using them like regular controls for groups of controls that act as one, or swapping Save And Cancel button positions by platform.

This was the first time I wanted to do more with them. And when I saw that a CC could be assigned to a RectControl variable, it seemed to me that whatever magic was done under the hood to allow that, would have worked more more broadly.

How and where you would document this behavior (under containerControl under RectControl.Parent or both) is not straightforward…

BTW If one searches the on-line docs for EmbeddedWindowControl,

http://documentation.xojo.com/index.php?search=EmbeddedWindowControl&searchToken=f3bk1ti1gegiz2vx8ma75g1o9

the only thing that comes up is a release note from 2015.

So in general the magic and it’s limitations are not well documented… Of course the best solution would be to make the magic work more generally, even if it was messy under the hood… but I suspect if that was practical it would already have been done.

-Karen

To leave something useful, here are the two methods I am using to get the Absolute X and Y positions for popup menus for a control. I need two, one for RectControls and one for container controls… If anyone sees an issue please let me know… In my testing these seem to work OK.

BTW given that this is not so straight forward because of how container controls have to be handled, maybe methods like these should be added to the framework?

Public Sub AbsolutePosition(Extends Item as RectControl, byref X as integer, byref Y as Integer)
  
  X = X + item.Left
  Y = Y + Item.Top
  
  Dim W as Window = Item.Window
  Do
    X = X + W.Left
    Y = Y + W.Top
    
    If  W IsA ContainerControl Then 
      W = ContainerControl(W).Window
    Else
      Return
    End if
  Loop
  
End Sub


Public Sub AbsolutePosition(Extends Container as ContainerControl, Byref X as integer, Byref Y as integer)
  X = X + Container.Left
  Y = Y + Container.Top
  
  Dim W as Window = Container.Window
  
  Do
    X = X + W.Left
    Y = Y + W.Top
    
    If  W IsA ContainerControl Then 
      W = ContainerControl(W).Window
    Else
      Return
    End if
  Loop
  
End Sub

The other thing I have to do was test to see if any control containing a specific Control implemented a certain class interface so it could call it’s methods.

Given that RectControl.Parent (and ContainerControl.Parent!) can be either a RectControl or a ContainerControl I thought it would be trivial to write a method to that could return the containing control as the interface or NIL like this:

Dim Item as RectControl = Me.parent 'Me can be either a RectControl or a ContainerControl

Do 
  If Item is  NIL Then
    If me.Window isA SpecificClassInterface Then
      Return SpecificClassInterface(me.Window)
    Else
      Return NIL
    End if
  ElseIf Item isA SpecificClassInterface Then
    Return SpecificClassInterface(item)
  End if
  
  Item = item.Parent
Loop

But that does not work as (I) expected with hierarchically embedded Container Controls in a specific case.

What I did to test:
I created 3 ContainerControls (call them CC1, CC2, CC3)

I put a pushbutton in CC3
then embedded CC3 in CC2
then I embedded CC2 in CC1
And put CC1 on a window (this is in the IDE not dynamically)

I basically put the above code in the Pushbutton to Walk the Control Hierarchy

What I expected Pushbutton.parent to be was CC3 and it was…

I then expected the the parent of CC3 to be CC2
the parent of CC2 to be CC1
and the parent CC1 to be NIL

BUT what happened was that the Parent of CC3 was NIL!

then I changed things up:

I put a canvas on CC2 and put CC3 inside the canvas on CC2

And then the hierarchy worked as expected.

Next I moved moved CC1 out of the canvas on CC2 but kept both CC1 and the canvas on CC2…

I expected this to return Nil for the parent of CC2, but it did not!

Conclusion: If I did not mess up my testing, as long as there is a RectControl on a ContainerControl, with need container controls the hierarchy is unbroken, BUT if you have a container control embedded into a ContainerControl with NO RectControls on it, the hierarchy breaks.

Looks like a bug, but a very obscure one! After all how often would someone have a Container Control embedded into a container control with no other controls on it?

-Karen

While I can now climb the hierarchy I just found out
Item isA SpecificClassInterface
fails if Item is a ContainerControl even though it does implement SpecificClassInterface

Come on Xojo inc!

Container Controls have been around for a long time (15+ years?) and are an important feature… Why have these limitation been allowed to exist for so long!!! Are the rough edges in the product like this EVER going to be addressed so it feels more solid?

Well another issue that needs a workaround for a design that should work if Xojo worked as it should…

-Karen

I just checked this in 2020r2.1 and it’s working fine. Please check and make sure that interface is still applied.

i check if the interface was assigned and it is… actually to 2 items in the hierarchy!
But my original code did not find the interface for me in either 2019.R1.1 or 2020r2.1

In the setup I described above , I Assigned SpecificClassInterface to both CC2 and CC1…

To test, I initially put Pushbutton.MouseDown in CC3:

Dim Item as RectControl = Me.parent 'Me can be either a RectControl or a ContainerControl

Do 
  If Item is  NIL Then
    If me.Window isA SpecificClassInterface Then
      MsgBox "Found SpecificClassInterface"
      Return  True
    Else
      MsgBox "SpecificClassInterface Not Found"
      Return True
    End if
    
  ElseIf Item isA  SpecificClassInterface Then
    MsgBox "Found SpecificClassInterface"
    Return  True
    
  ElseIf Item isA  EmbeddedWindowControl  ANd  EmbeddedWindowControl(item ) IsA SpecificClassInterface then
    MsgBox "Found SpecificClassInterface"
    
    Return true 
  End if
  
  Item = item.Parent
Loop

When I clicked on the button the MsgBox says “SpecificClassInterface Not Found”.

After you posted it works for you I changed my code to this:

Dim Item as RectControl = Me.parent 'Me can be either a RectControl or a ContainerControl

Do 
          If Item is  NIL Then
            If me.Window isA SpecificClassInterface Then
              MsgBox "Found SpecificClassInterface"
              Return  True
            Else
              MsgBox "SpecificClassInterface Not Found"
              Return True
            End if
          ElseIf Item isA  SpecificClassInterface Then
            MsgBox "Found SpecificClassInterface"
            Return  True
          Elseif NOT (Item.Parent is NIL) And Item.Parent IsA EmbeddedWindowControl And Item.Window IsA SpecificClassInterface Then
            MsgBox "Found SpecificClassInterface"
            Return  True
          End if
          Item = item.Parent
        Loop

And that worked for walking the hierarchy to find the interface… But needing to do that, while I can understand why it works, is certainly not very intuitive!

Maybe Xojo needs a built in “iterator” to walk the control hierarchy?

-karen

As i said above, I did get it work but not how I initially tried.

I just realized that your reply implied that my original code for walking the control hierarchy should have worked… But it did not.

Instead of being able to use IsA on item (Where item = Control.Parent) as can be seen above, If the Parent is a container control I had to use IsA on Item.Window so the container control was seen as a Window.

While I have code that does what I want now, just so I understand this better, can you share the code you used to check if a container control IsA SpecificClassInterface successfully when it is in a RectControl variable?

Maybe I am making this more complicated than it is, because I am missing something.

Thanks,

  • Karen

You’re not missing anything. The problem is that EmbeddedWindowControl is a dead end. You normally have to iterate through all the runtime objects to find the actual ContainerControl. In your specific use case, Item.Window happens to work. In other use cases, you have to search for it.

In what cases won’t it work?

Thanks,
-karen

Where you’re iterating over the controls on a window. window.control(i) will be an EmbeddedWindowControl and there is no way to access it other than searching through the runtime objects.

I see… I was thinking there was a way to get a reference to a RectControl on the ContainerControl, but when you have it as an EmbeddedWindowControl there is no direct way…

But what i was trying to do lends itself to another , admittedly a bit convoluted, way to get a reference to it AS a ContainerControl Or as a Window without having to iterate the runtime, which could be expensive in a large project.

  1. Create a ClassInterface
    ClassInterface ContainerControlHolder
    Method RegisterContainer( theContainer As ContainerControl)

  2. Have the window (or any RectControl in the hierarchy using my code above to walk it if needed) implement it.

  3. Subclass ContainerControl (so you can make any ContainerControl work this way by just changing it’s super)

3a) In it’s Open event you do:
If me.Window IsA ContainerControlHolder Then
ContainerControlHolder(me.Window).RegisterContainer(me)

Now the window has a reference to the ContainerControls as a container control

If you want it as a window, in the open event find it’s first RectControl and use it to get RectControl.Window instead and return that (changing the types to Window of course)

From Inside the embedded container control event in your subclass you have access to all the CC window methods/properties…

While the code is not so neatly packaged in a single method , this may be a better way than searching the Runtime.

-Karen