Changing the Superclass of a control per OS?

I have a window that has a HTMLViewer on it, but for reasons, I want to replace the HTMLViewer with WKWebViewControlMBS, but only on macOS builds. In other words, I want one control to have a different Superclass depending on the OS I’m building for.

I tried hacking into the text file format and setting the superclass of my control to a constant (#kMyClassName) which was defined in the class. The constant has a different value depending on the OS build. This didn’t seem to work, as the OS seemed confused about what the class was when I try to build.

Other ideas:

  • I could of course define two different subclasses (HTMLViewerMac and HTMLViewerWindows) and then have one of each control on my window, but I don’t like this design as I have to then duplicate code in all the events and methods.
  • Have a single subclass, which uses a “has a” rather than “is a” relationship - in the IDE it will just be an Object subclass, but at runtime it would pick a control of the right kind (HTMLViewer on windows, WKWebViewControlMBS on mac) and then use AddHandler to attatch methods to to the events of the control, and a bunch of #if TargetMacOS / #if TargetWin32 pragmas. I’ve used this design before, and it’s medium ugly.
  • Using an IDE Script pre-build and post-build scripts to actually change the superclass during build. I’m not sure that IDE Scripting can accomplish this, can it?
  • some sort of magic using class Interfaces?

Any other suggestions?

How about using separate container controls for Mac & MS-Windows that you embed dynamically at runtime when the window opens. The container controls could also implement a class interface to provide a consistent public interface.

That sounds possible, but wouldn’t I end up in the same place more or less? I’d have two different container controls and have to maintain code in both of them.

I would create one instance of WKWebViewControlMBS and one of HTMLViewer outside of the Window then bring the one into the Window and disable the one I do not use.

I imagine you will have to maintain some code for both platforms no matter what solution you choose. The benefit of the solution I proposed is that it should simplify the code in your window as the platform / control specific code will be held within the container control.

I don’t think there is any good way to do what you want. So instead I would abstract all your code into a separate class and insert it into whatever the parent viewer class is. Not a subclass, but a simple class that has all the code you want to run and some methods that look for a parent property that points to the viewer and calls the correct function in that based on what the parent is. Thats not a very good explanation, but I’ve had do similar things. You create a subclass of the 2 different viewers with just enough code to accept an instance of your class that does all the work. You push all the events from the viewer into methods of your class and you have methods in your class that will look at their parent property and call the appropriate methods in either one depending on what it is. So you have to write some stub code in both the viewer subclasses to hold your class and to know how to forward any events to it, but once you do that all the rest of your work can be only in the interface class.

Thanks for all the suggestions, and it’s good to know i’m not crazy in thinking this is not a trivial problem.

What I ended up doing is (I think) something like what James has suggested:

  • create a new class cHTMLViewer which has the exact same methods, properties and events of the actual HTMLViewer control.
  • I made cHTMLViewer a RectControl subclass, so you can position and size it in the IDE
  • In the Open event, added per-platform code which fires a GetControl() event which asks the instance to get the correct control.
  • The Open event hooks up all these events using AddHandler calls
  • The window then has 3 controls: the cHTMLViewer (which is invisible but sized and located properly) the HTMLViewer and HTMLViewerWK instances (which are both invisible and placed outside the window).

This seems to work OK - I can take an existing HTMLViewer on a window, change the Superclass to this new cHTMLViewer class, and then at runtime it does the right thing.

Here’s what the Open event looks like:

' gets the instances of the HTMLViewer or HTMLViewerWK
' and sets up handlers
#if TargetMacOS
  dim h as HTMLViewerWK = GetControlWK()
  if h = nil then
    LogError CurrentMethodName + " GetControlWK() = nil - please implement this event and return the HTMLViewerWK instance on the window"
  end if
  mWebWK = h
  AddHandler h.CancelLoad, WeakAddressOf CancelLoadWK
  AddHandler h.DocumentBegin, WeakAddressOf DocumentBeginWK
  AddHandler h.DocumentComplete, WeakAddressOf DocumentCompleteWK
  AddHandler h.DocumentProgressChanged, WeakAddressOf DocumentProgressChangedWK
  AddHandler h.Error, WeakAddressOf ErrorWK
  AddHandler h.NewWindow, WeakAddressOf NewWindowWK
  AddHandler h.StatusChanged, WeakAddressOf StatusChangedWK
  AddHandler h.TitleChanged, WeakAddressOf TitleChangedWK
#elseif TargetWin32
  dim h as HTMLViewer = GetControl()
  if h = nil then
    LogError CurrentMethodName + " GetControl() = nil - please implement this event and return the HTMLViewer instance on the window"
  end if
  mWeb = h
  AddHandler h.CancelLoad, WeakAddressOf CancelLoad
  AddHandler h.DocumentBegin, WeakAddressOf DocumentBegin
  AddHandler h.DocumentComplete, WeakAddressOf DocumentComplete
  AddHandler h.DocumentProgressChanged, WeakAddressOf DocumentProgressChanged
  AddHandler h.Error, WeakAddressOf Error
  AddHandler h.NewWindow, WeakAddressOf NewWindow
  AddHandler h.StatusChanged, WeakAddressOf StatusChanged
  AddHandler h.TitleChanged, WeakAddressOf TitleChanged

// locate the HTMLViewer at my location =
h.left = me.left
h.width = me.width
h.height = me.height