How to implement radio buttons in C++ with vtkButtonWidget using VTK 9.3?

Using VTK 9.3, C++. For various reasons I want to use only VTK widgets rather than use QT or other toolkits. VTK doesn’t provide a radio button widget, so trying to implement that but having a heck of a time. I have searched in vain for a human-written implementation, so have resorted to AI, which provides the attached. There are some compilation errors, and I’m not sure how to solve them with VTK 9.3. Specifically the compiler error gives "class vtkWidgetRepresentation’ has no member named ‘SetState’ on lines 39, 46, 152, and “class vtkWidgetRepresentation’ has no member named ‘SetState’” on line 36. I see that vtkButtonWidget have a GetButtonRepresentation() method which has SetState()/GetState() in VTK 9.5, but VTK 9.3 only has GetRepresentation().
Can someone please point me to a radio button example implemented with vtkButtonWidget that actually compiles and works?
Any help much appreciated! Thanks!

#include <vtkButtonWidget.h>
#include <vtkTexturedButtonRepresentation2D.h>
#include <vtkImageData.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkRenderer.h>
#include <vtkCommand.h>
#include <vtkSmartPointer.h>
#include <vector>

// Forward declaration
class RadioButtonCallback;

class RadioButtonCallback : public vtkCommand
{
public:
  static RadioButtonCallback* New() { return new RadioButtonCallback; }
  void Execute(vtkObject* caller, unsigned long event, void* calldata) override
  {
    if (event == vtkCommand::StateChangedEvent)
    {
      vtkButtonWidget* pressedButton = static_cast<vtkButtonWidget*>(caller);
      int pressedButtonIndex = -1;

      // Find the index of the pressed button
      for (size_t i = 0; i < this->Buttons.size(); ++i)
      {
        if (this->Buttons[i] == pressedButton)
        {
          pressedButtonIndex = i;
          break;
        }
      }

      // If the pressed button is not already "on", turn it "on" and others "off"
      if (pressedButton->GetRepresentation()->GetState() == 0) // Assuming 0 is "off" and 1 is "on"
      {
        // Turn "on" the pressed button
        pressedButton->GetRepresentation()->SetState(1);

        // Turn "off" all other buttons in the group
        for (size_t i = 0; i < this->Buttons.size(); ++i)
        {
          if (i != pressedButtonIndex)
          {
            this->Buttons[i]->GetRepresentation()->SetState(0);
          }
        }
        
        // --- Add your radio button specific actions here ---
        // For example, you might print a message:
        std::cout << "Radio Button " << pressedButtonIndex << " selected." << std::endl;
      }
      
      // Force a render to update the button appearances
      if (this->Interactor)
      {
          this->Interactor->GetRenderWindow()->Render();
      }
    }
  }

  // Add the buttons that belong to this radio group
  void AddButton(vtkButtonWidget* button) { this->Buttons.push_back(button); }
  void SetInteractor(vtkRenderWindowInteractor* interactor) { this->Interactor = interactor; }

private:
  std::vector<vtkSmartPointer<vtkButtonWidget>> Buttons;
  vtkSmartPointer<vtkRenderWindowInteractor> Interactor;
};

int main(int, char*[])
{
  // 1. Create a renderer and render window
  vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New();
  vtkSmartPointer<vtkRenderWindow> renderWindow = vtkSmartPointer<vtkRenderWindow>::New();
  renderWindow->AddRenderer(renderer);

  // 2. Create an interactor
  vtkSmartPointer<vtkRenderWindowInteractor> renderWindowInteractor =
      vtkSmartPointer<vtkRenderWindowInteractor>::New();
  renderWindowInteractor->SetRenderWindow(renderWindow);

  // 3. Create textures for button states (on and off)
  vtkSmartPointer<vtkImageData> imageOn = vtkSmartPointer<vtkImageData>::New();
  vtkSmartPointer<vtkImageData> imageOff = vtkSmartPointer<vtkImageData>::New();
  
  // (Assuming you have functions like CreateButtonOn and CreateButtonOff to generate textures)
  // For simplicity, we'll create simple filled squares as textures.
  // You would typically load image files for your desired radio button appearance.
  imageOn->SetDimensions(10, 10, 1);
  imageOn->AllocateScalars(VTK_UNSIGNED_CHAR, 3);
  unsigned char* pixelsOn = static_cast<unsigned char*>(imageOn->GetScalarPointer());
  for (int i = 0; i < 10 * 10 * 3; i += 3)
  {
    pixelsOn[i] = 0;     // Red
    pixelsOn[i + 1] = 0; // Green
    pixelsOn[i + 2] = 255; // Blue (for "on" state)
  }

  imageOff->SetDimensions(10, 10, 1);
  imageOff->AllocateScalars(VTK_UNSIGNED_CHAR, 3);
  unsigned char* pixelsOff = static_cast<unsigned char*>(imageOff->GetScalarPointer());
  for (int i = 0; i < 10 * 10 * 3; i += 3)
  {
    pixelsOff[i] = 128; // Gray (for "off" state)
    pixelsOff[i + 1] = 128;
    pixelsOff[i + 2] = 128;
  }
  
  // 4. Create multiple vtkButtonWidget instances
  std::vector<vtkSmartPointer<vtkButtonWidget>> radioButtons;
  const int numRadioButtons = 3;
  for (int i = 0; i < numRadioButtons; ++i)
  {
    vtkSmartPointer<vtkTexturedButtonRepresentation2D> buttonRepresentation =
        vtkSmartPointer<vtkTexturedButtonRepresentation2D>::New();
    buttonRepresentation->SetNumberOfStates(2); // Two states: on and off
    buttonRepresentation->SetButtonTexture(0, imageOff); // State 0: off
    buttonRepresentation->SetButtonTexture(1, imageOn);  // State 1: on

    // Place the buttons in the scene
    double bounds[6];
    bounds[0] = 50.0 + i * 60.0; // Adjust for spacing
    bounds[1] = bounds[0] + 50.0;
    bounds[2] = 50.0;
    bounds[3] = 100.0;
    bounds[4] = 0.0;
    bounds[5] = 0.0;
    buttonRepresentation->PlaceWidget(bounds);

    vtkSmartPointer<vtkButtonWidget> buttonWidget = vtkSmartPointer<vtkButtonWidget>::New();
    buttonWidget->SetInteractor(renderWindowInteractor);
    buttonWidget->SetRepresentation(buttonRepresentation);
    buttonWidget->EnabledOn(); // Enable the button

    radioButtons.push_back(buttonWidget);
  }

  // 5. Create a callback and associate it with the radio buttons
  vtkSmartPointer<RadioButtonCallback> callback = vtkSmartPointer<RadioButtonCallback>::New();
  callback->SetInteractor(renderWindowInteractor);
  for (int i = 0; i < numRadioButtons; ++i)
  {
    callback->AddButton(radioButtons[i]);
    radioButtons[i]->AddObserver(vtkCommand::StateChangedEvent, callback);
  }

  // Initialize the first radio button to "on"
  if (!radioButtons.empty())
  {
    radioButtons[0]->GetRepresentation()->SetState(1);
  }

  // 6. Start the interaction
  renderWindow->Render();
  renderWindowInteractor->Start();

  return 0;
}

Down cast the representation to a vtkButtonRepresentation.

#include <vtkButtonRepresentation.h>

if (auto* buttonRep = vtkButtonRepresentation::SafeDownCast(button->GetRepresentation())
{
  buttonRep->SetState(...)
}

@Tomasso I have done a couple of examples:

  • The C++ example is RadioButton, it demonstrates how to setup multicoloured buttons to change the properties of an actor, and the use of #ifdef to account for different versions of VTK. Have a play with the interactive example.
  • The Python example RadioButton only works for VTK 9.5 and greater. This is because vtkButtonRepresentation is an abstract class and I have never found a way of getting the representation. Now SetState works perfectly here! The other approach used here is to use a dataclass to hold the buttons. This ensures that the list buttons are implemented only once, preventing reassignment when we pass it to the RadioButtonCallback class.