Search code examples
castingc++-clieventhandler

Typecasting EventHandler in C++/CLI


I have a form (which I'll call MainForm) embedded with a TabControl. Every time the user creates a new tab it is filled with an instance of a pre-built Panel (which I'll call MyPanel) which contains many controls.

My MyPanel class has a private variable bool save_state which is set to false every time one of the (editable) controls is edited and set to true when the user "saves" the state of the panel.

I want a visual flag to keep track of tabs that have unsaved changes (e.g. the tab "Tab1" will instead display the text "Tab1 *" if it has unsaved changes). So I want to set up the event handler in my MainForm which can call a method in MyPanel to add the handler to each control.

Since not all my controls use the same EventHandler type (for example, I also need to track DataGridViewRowsAddedEvent, among others), I currently have several methods adding the appropriate handler to the corresponding controls (one for each type of Event Handler), each of which is running the same code (i.e. set the save_state bit to false and append " *" to the tab text.

For example, in MainForm.cpp I have:

#include "MyPanel.h"

void markUnsaved(void) {
  // set panel bit to false
  // append " *" to tab text if we haven't already
}

void MainForm::handler1(Object ^sender, EventArgs ^e) {
  markUnsaved();
}

void MainForm::handler2(Object ^sender, DataGridViewRowsAddedEventArgs ^e) {
  markUnsaved();
}

void Main::FormaddNewPanelToTab(int tab_index) {
  // check index is valid ...


  // make the new panel
  MyPanel ^new_panel = gcnew MyPanel();
  new_panel->addEventHandlerToControls(gcnew EventHandler(this, &MainForm::handler1));
  new_panel->addDgvEventHandlerToControls(gcnew DataGridViewRowsAddedEventHandler(this, &MainForm::handler2));

  // rest of code...
}

Though this currently works as intended, this (along with the several other Event Handler types I have to manage) makes my code look really silly.

I am hoping to be able to have have a single event handler in MainForm and a single method in MyPanel which type-casts the Event Handler passed and adds it to all the controls with the appropriate types.

I have tried doing simple casts such as:

void MyPanel::addHandlerToControls(EventHandler ^handler) {
  control_NUD->ValueChanged += handler; // this works because ValueChanged is of type EventHandler
  control_DGV->RowsAdded += (DataGridViewRowsAddedEventHandler ^)handler; // this compiles but throws an exception

  // rest of the code...
}

to no avail.

Any help would be greatly appreciated!


Solution

  • I know this is maybe a bit late for answer but I'd want to show how would I solve this.

    Firs of all I suggest to get rid from idea of casting event handlers. Kind of such approach may work in C# (with some adjustments) but as far as I know it's not possible in C++ /CLI.

    I'd go for adding new event to a MyPanel class that will be invoked every time when the data on a panel is changed. But to avoid adding a lot of different handlers to a control events in a MyPanel class it's better to create one generic method that will handle all the neccessary control's events and fire new event. Maybe this sounds messy, let me show the code:

    public ref class MyPanel 
    {
        // Add a new event
        public: 
            event EventHandler^ DataChanged;
    
        // Add a method that will fire new event 
        // this methid will be invoked on every control's event that you'll subscribe
        private: 
            generic <typename T>
            void DataChangedHandler(System::Object^ sender, T e)
            {
                // Fire the event
                DataChanged(this, EventArgs::Empty);
            }
    
        // Once the controls are initialized you may add the event handlers
        // I put it in a constructor only for example
        MyPanel()
        {
            control_NUD->ValueChanged += gcnew EventHandler(this, &MyPanel::DataChangedHandler<EventArgs^>);
            control_DGV->RowsAdded += gcnew DataGridViewRowsAddedEventHandler(this, &MyPanel::DataChangedHandler<DataGridViewRowsAddedEventArgs^>);
            // and so on...
        }
    }
    
    
    /// And now in a main form we only need to subscribe to a DataChanged event
    public ref class MainForm
    {
        //...
    
        // the handler
        void MyHandler(Object^ sender, EventArgs^ e) 
        {
            markUnsaved();
        }
    
        void FormaddNewPanelToTab(int tab_index) 
        {
            // make the new panel
            MyPanel ^new_panel = gcnew MyPanel();
            new_panel->DataChanged += gcnew EventHandler(this, &MainForm::MyHandler);
        }
    
        //...
    }
    

    Hope this helps.