|
Hello, I hope I'm not posting my question to the wrong forum as it might rather be a language question concerning C++/CLI. With the Delegate::CreateDelegate() method, it is possible to bind a method to a delegate that does not strictly have the same signature as the delegate but does have compatible parameter and return types. Compatible means that: - delegate parameters can be converted to method's parameters and - the method return value can be converted to delegate's return value. (See http://msdn.microsoft.com/en-us/library/74x8f551.aspx for reference). For example, consider the following code: using namespace System; using namespace System::Windows::Forms; namespace myapp { public ref class MyForm : public Form { [...] void MyPaint( Object ^sender, EventArgs ^args ); // MyPaint signature makes it a candidate for a System::EventHandler public: MyForm() { // Call designer-generated code... InitializeComponent(); // Retrieve MyPaint method... using namspace System::Reflection; MethodInfo ^mi = MyForm::typeid->GetMethod( "MyPaint", BindingFlags::NonPublic | BindingFlags::Instance ); // Bind MyPaint to the Paint event (which is a System::Windows::Forms::PaintEventHandler^, not a System::EventHandler^)... Paint += (PaintEventHandler^)Delegate::CreateDelegate( PaintEventHandler::typeid, this, mi ); // Works just fine! } }; // MyClass } // myapp Note that MyForm::MyPaint's arguments do not match the PaintEventHandler delegate. However this works because type PaintEventArgs^ can be converted to EventArgs^ (and void to Void). Now the question is: is there a simpler way to do that, if possible something that can be done directly in the designer? Paint += gcnew PaintEventArgs( this, &MyForm::MyPaint ) would not compile as MyPaint signature does not match the delegate. Paint += (PaintEventArgs^)gcnew EventArgs( this, &MyForm::MyPaint ) would compile but throw an invalid cast exception because there is no inheritance among delegates. Now, changing MyPaint's signature is out of the question, because I also want to bind it to simple EventHandlers (and by the way this is not actual code, just an example). In C# I think the solution would be to use an anonymous method for adapting the parameters, e.g. Paint += delegate(Object sender,PaintEventArgs args) { MyPaint(sender,args); } but there is no such thing in C++/CLI (or is there?). Now I could use non-anonymous methods, e.g. void MyPaint2(Object ^sender, PaintEventArgs ^args) { MyPaint(sender,args); } but this is just ugly. A cleaner solution would be greatly appreciated. Thanks in advance, -- Emmanuel - Moved byeryangMSFTWednesday, September 23, 2009 9:04 AMC++/CLI issue. (From:Common Language Runtime)
-
| | Emmanuel Raulo-Kumagai | Also consider this approach:
template<typename TEventHandler, typename TEventArgs>
ref class Adapter
{
public:
Adapter(EventHandler ^ h) : mHandler(h)
{
}
operator TEventHandler ^ ()
{
return gcnew TEventHandler(this, &Adapter::Handler);
}
private:
EventHandler ^ const mHandler;
void Handler(Object ^ sender, TEventArgs ^ e)
{
mHandler(sender, e);
}
};
Usage:
Paint += Adapter<PaintEventHandler, PaintEventArgs>(gcnew EventHandler(this, &MyForm::MyPaint));
- Marked As Answer byEmmanuel Raulo-Kumagai 18 hours 35 minutes ago
-
| | Viorel_ | OK, so I finally came up with the following class which helps with binding a simple EventHandler to a EventHandler<SomethingExtendingEventArgs^> :
generic<typename TEventArgs> where TEventArgs : EventArgs
ref class Adapter
{
public:
static EventHandler<TEventArgs>^ Adapt( EventHandler^ del ) {
Adapter ^a = gcnew Adapter<TEventArgs>(del);
Reflection::MethodInfo ^mi = Adapter<TEventArgs>::typeid->GetMethod(
"Invoke",
Reflection::BindingFlags::Public|Reflection::BindingFlags::Instance );
return (EventHandler<TEventArgs>^)
Delegate::CreateDelegate( EventHandler<TEventArgs>::typeid, a, mi );
}
Adapter( EventHandler^ adapted ): Adapted(adapted) {}
void Invoke( Object^ sender, TEventArgs args ) { Adapted(sender,args); }
EventHandler^ Adapted;
};
Now when a delegate was cleverly declared using EventHandler<SomethingExtendingEventArgs^>, I can bind things this way:
SomeEvent += Adapter<SomethingExtendingEventArgs^>::Adapt(
gcnew EventHandler(target,&MyClass::MyMethodConsumingEventArgs) );
So this works with the delegates specifically defined for my application which are declared as typedef EventHandler<DomainLogicEventArgs^> DomainLogicEventHandler; and I can bind handlers on the UI level to events triggered by my domain logic. However it seems some people were not clever when developping .NET so that e.g. PaintEventHandler is NOT an EventHandler<PaintEventArgs^>. So, why put a generic EventHandler<TEventArgs> in the framework in the first place if it's not gonna be used?! As delegate types can't be used as argument to generics, my solution can't be used with event delegates defined in Windows Forms. Does anybody have a cleaner solution than having the same methods with different signatures for each delegate, please? -- Emmanuel | | Emmanuel Raulo-Kumagai | OK, I was not being very clever either... If a delegate can't be used as an argument to a generic class, its Type can still be passed as method argument, so I came up with the following:
generic<typename TEventArgs> where TEventArgs : EventArgs
ref class Adapter
{
public:
/// Adapt an EventHandler into a handler for a delegate with the same
/// signature as EventHandler<TEventArgs>.
/// Requires explicit cast to delegate type.
static Delegate^ Adapt( EventHandler^ del, Type ^DelegateType ) {
Adapter ^a = gcnew Adapter<TEventArgs>(del);
Reflection::MethodInfo ^mi = Adapter<TEventArgs>::typeid->GetMethod(
"Invoke",
Reflection::BindingFlags::Public|Reflection::BindingFlags::Instance );
return Delegate::CreateDelegate( DelegateType, a, mi );
}
/// Adapt an EventHandler into a handler for an EventHandler<TEventArgs>.
static EventHandler<TEventArgs>^ Adapt( EventHandler^ del ) {
return (EventHandler<TEventArgs>^)
Adapt( del, EventHandler<TEventArgs>::typeid );
}
Adapter( EventHandler^ adapted ): Adapted(adapted) {}
void Invoke( Object^ sender, TEventArgs args ) { Adapted(sender,args); }
EventHandler^ Adapted;
};
When the event delegate to which I want to bind a compatible method is declared as EventHandler<SomeEventArgs^>, I can still call:
SomeEvent += Adapter<SomethingExtendingEventArgs^>::Adapt(
gcnew EventHandler(target,&MyClass::MyMethodConsumingEventArgs) );
When it's one of the Windows Forms event delegates, the following works:
SomeFormsEvent += (SomeFormsEventHandler^)
Adapter<SomeFormsEventArgs^>::Adapt(
gcnew EventHandler(target,&MyClass::MyMethodConsumingEventArgs),
SomeFormsEventHandler::typeid );
'hope this snippet is useful to somebody hitting the same limitation... -- Emmanuel | | Emmanuel Raulo-Kumagai | Also consider this approach:
template<typename TEventHandler, typename TEventArgs>
ref class Adapter
{
public:
Adapter(EventHandler ^ h) : mHandler(h)
{
}
operator TEventHandler ^ ()
{
return gcnew TEventHandler(this, &Adapter::Handler);
}
private:
EventHandler ^ const mHandler;
void Handler(Object ^ sender, TEventArgs ^ e)
{
mHandler(sender, e);
}
};
Usage:
Paint += Adapter<PaintEventHandler, PaintEventArgs>(gcnew EventHandler(this, &MyForm::MyPaint));
- Marked As Answer byEmmanuel Raulo-Kumagai 18 hours 35 minutes ago
-
| | Viorel_ | So a delegate can be passed in as a template parameter? I can't believe I did not try this... Thanks alot for bringing this solution! It works like a charm. Again, it would be nice if such functionality was built in the Framework and usable in the designer... -- Emmanuel
| | Emmanuel Raulo-Kumagai |
|