How to call a method of a container control from the window.controls() array

I’m attempting to call a method of each container control on a window by accessing them via the controls array. For example, if I put the following code in a PushButton action method on the parent window:

For Each oControl As Control In Self.Controls
  If oControl IsA MyCC Then
    MyCC( oControl ).SaveData
  End If
Next

It fails, as it never finds a control that IsA MyCC. Looking at the array and other posts I find that I can do the following instead:

For Each oControl As Control In Self.Controls
  If oControl IsA EmbeddedWindowControl Then
    MyCC( oControl ).SaveData
  End If
Next

I now spot the container controls but I get an IllegalCastException on the MyCC( oControl ), even though the container is of that type.

So, thinking laterally I tried creating a Class Interface and assigning it to the container control and casting to the interface and calling an interface method as follows:

For Each oControl As Control In Self.Controls
  If oControl IsA EmbeddedWindowControl Then
    SaveDataInterface( oControl ).SaveData
  End If
Next

Still no joy, the same illegalCastException arises.

So, what do I have to do to allow me to call a method of the Container Control from this Controls() reference.

The window in the example consists of two items. The ContainerControl and PushButton. When I stop the debugger in the PushButton Action event the Window.controls() array has two elements:

PushButton
EmbeddedWindowControl

However, when I look at the Controls link in the Debugger it has three elements:

PushButton
_Window1._wrapper_ContainerControl11
EmbeddedWindowControl

Clicking on the _Window1._wrapper_ContainerControl11 yields the object I want to deal with, however, that doesn’t show up in the iteration of the Controls() array. The Name of the EmbeddedWindowControl is also the _Window1… element. So it would seem that the Debugger is somehow using this to access the ‘Real container’. How can I emulate this.

Have you tried to cast to ContainerControl and then to your ContainerControl?

If I understand what you’re trying to do, then it’s the same as when I want to embed a container control in a window and then use it by referencing controls and methods in the container.

For starters, you have to embed the container and create a reference to it. So, I start by embedding a container from my project Nav panel (cntMainData in my case) into the MainWindow and calling it “MainData”. I then place MainData in an array (dataContainers) and create a reference to it in another array (dataContainerIndex) so I can reference it later.

// Add the "MainData" container to the MainWindow
  MainData = New cntMainData
  MainData.EmbedWithin(MainWindow, 5, 20)

  dataContainers.Append MainData   // place container in array for future control
  dataContainerIndex.Append("MainData")  // add ability to ID each container in the array inContainers later

Now, when I need to reference any control, method, etc. from the embedded container (such as your saveData method), I use:

  for ndx As Integer = 0 to Ubound(dataContainerIndex)  // point @ MainData container control

if dataContainerIndex(ndx) = "MainData" then
  
  cntMainData(dataContainers(ndx)).saveData
  
// anything else you want to do with an item in the container

    end if
    
  next

Not sure if that’s the same as you’re asking, but thought I’d share it just in case.

Unfortunately, this isn’t as straight forward as you would think.

See Normans blog for more info and a solution:
https://www.great-white-software.com/blog/2019/08/21/embeddedwindowcontrol/

The problem of converting an EmbeddedWindowControl to a ContainerControl has been discussed a few times before. I know a few of us worked the problem a while back, and here’s code from one of my projects.

I put this in a module, as it extends EmbeddedWindowControl:

Public Function getContainer(extends ewc as EmbeddedWindowControl) as ContainerControl
  if ewc = nil then Return Nil
  if ewc.Handle = 0 then Return Nil
  
  var objects as Runtime.ObjectIterator = Runtime.IterateObjects
  
  var currentObject as Object
  var currentType as Introspection.TypeInfo
  var currentProps() as Introspection.PropertyInfo
  
  while Objects.MoveNext
    currentObject = Objects.Current
    if currentObject isa ContainerControl then
      currentType = Introspection.GetType(currentObject)
      currentProps = currentType.GetProperties
      
      for each prop as Introspection.PropertyInfo in currentProps
        if prop.Name = "handle" and prop.Value(currentObject) = ewc.Handle then Return ContainerControl( currentObject )
      next
    end if
  wend
End Function

So, when iterating over the controls, I have this code:

for each currentControl as Control in self.Controls
  if currentControl isa EmbeddedWindowControl then
    var currentEWC as EmbeddedWindowControl = EmbeddedWindowControl( currentControl )
    var currentContainer as ContainerControl = currentEWC.getContainer()
    if currentContainer <> nil then
      currentContainer.SomeMethod()
    end if
  end if
next

So, I think you mean the following:

Dim oCC As ContainerControl
For Each oControl As Control In Self.Controls
  If oControl IsA EmbeddedWindowControl Then
    oCC = ContainerControl( oControl.Window )
  End If
Next

Which results in an IllegalCastException.

@Don_Lyttle
Thanks, but I only wanted to drop the controls on the window in the designer, but I see the direction.

@Anthony_G_Cyphers
So, looking at your function it appears to be iterating across every item that is created within the entire application. Wow, talk about using a sledgehammer to crack a walnut. If that’s what’s required I suppose it will be the answer. I suspect that’s what the debugger is doing, or something similar, to present the extra item in the controls view!

I’ll give it a try.
Thanks
Ian

Yeah, you basically have to. EmbeddedWindowControl(and thus ContainerControl) is a very special control. It really is, internally, like a window object that’s been embedded into another view.

You have to wonder why they didn’t simply add a ContainerInstance property to the EmbeddedWindowControl class that would simply return the container control to you. Sure you would still need the secret sauce but at least it would be nice simple sauce.

I’ll look for a Feedback Case for this, and see if we can get this put in. I too would rather just have it at my fingertips when needed.

This appears to be the feedback request : <https://xojo.com/issue/23030>

Nice. Adding my voice to this. It should be a simple addition, but without knowing the internals I’m probably shooting myself in the foot by saying that.

1 Like

I have now added a specific scenario that I encounter to the case, and asked for review.

We’ve handled this in a different way with great success.

Create a common super for your Windows.

Create a common super for your ContainerControls.

In the Window super, add methods to RegisterContainerControl, UnregisterContainerControl, and a function RegisteredContainerControls. Register and Unregister will record and remove WeakRefs from a private array, and the function will translate those WeakRefs back to ContainerControl in an array.

In the CC super, implement the Open event and have it check the class of TrueWindow. If it’s your Window super, call Register on itself. On close, call Unregister.

Now you will have easy access to your Window’s CC’s, and all you have to do is set the supers of the CC and Window respectively.

1 Like

For Each oControl As Control In Self.Controls
EmbeddedWindowControl

because this kludge i prefer to use my own property/array.
using a interface in this cc’s is a good idea.

I’ve switched to the Registration method. I’ve a array of container controls on the parent window and a method to allow the containers to register themselves. Then you can loop through the array rather than all controls on the window.

The cost of disentangling the control from it’s EmbeddedWindowControl was enormous, especially if you take into account that every variant is an element in the runtime. It was taking 12 full seconds to find which control I had, in extreme circumstances.

Private Property Panels() as LoadSaveDataInterface

Sub RegisterPanel(CC as LoadSaveDataInterface)
  Panels.Add CC
End Sub

In each panels open event:

WindowClass( Window ).RegisterPanel( Self )

You can then do things like this in the parent window.

Public Sub PerformValidation(lblError as Label, BtnAction0 as PushButton, BtnAction1 as PushButton)
  '
  ' Trigger each panels ValidData method
  '
  For Each Panel As LoadSaveDataInterface In Panels
    Panel.SomeMothod
  Next
End Sub

By using an array of Interface (rather than class names or CustomControl) you get the protection that you can’t register an control that doesn’t support the interface and also you don’t have to perform the cast to that interface when calling it

1 Like