Need help writing a custom event Topic is solved

Are you writing your own components and need help with how to set them up or have questions about the components you are deriving from ? Ask them here.
spamiam
I live to help wx-kind
I live to help wx-kind
Posts: 180
Joined: Wed Apr 22, 2009 1:40 am

Need help writing a custom event

Post by spamiam » Wed May 13, 2009 2:19 am

Hello!

I an trying to write a custom event, and I am trying to follow "Creating a Custom Event - Method 4" in the wiki page http://wiki.wxwidgets.org/Custom_Events.

I am having trouble, I think, trying to translate the example to my own use. First it is a "command event" example, and I think I want a "regular" event.

Here is my code for the header file:

Code: Select all

#ifndef CAPTUREEVENT_H
#define CAPTUREEVENT_H

#include <wx/event.h>
#include "CapturedData.h"

 
DECLARE_EVENT_TYPE( DataReadyEvent, -1 )

 
 
 
class CaptureEvent : public wxEvent
{
	public:
		// class constructor
		CaptureEvent();
		// class destructor
		~CaptureEvent();
		
		
	CaptureEvent( const CaptureEvent &event );
 
	virtual wxEvent *Clone() const	{ return new CaptureEvent(*this); };
 
	wxString GetText() const { return m_Text; };
	void SetText( const wxString& text ) { m_Text = text; };
	
	void SetData( CapturedData* cd) { data = cd; };
	CapturedData* GetData(void) const { return data; };
 
private:
	wxString m_Text;
	CapturedData* data;

		
		
//** the following line was commented-out because it caused a linker error
//DECLARE_DYNAMIC_CLASS( CaptureEvent );		
};

#endif // CAPTUREEVENT_H
In one of the other examples it indicates that I need the "DECLARE_DYNAMIC_CLASS...." macro, but I get a linker error when I use it.


The example also includes the following suggested code:

Code: Select all

typedef void (wxEvtHandler::*MyFooEventFunction)(MyFooEvent &);
 
// This #define simplifies the one below, and makes the syntax less
// ugly if you want to use Connect() instead of an event table.
#define MyFooEventHandler(func)                                         \
	(wxObjectEventFunction)(wxEventFunction)(wxCommandEventFunction)\
	wxStaticCastEvent(MyFooEventFunction, &func)                    
 
// Define the event table entry. Yes, it really *does* end in a comma.
#define EVT_MYFOO(id, fn)                                            \
	DECLARE_EVENT_TABLE_ENTRY( MyFooCommandEvent, id, wxID_ANY,  \
	(wxObjectEventFunction)(wxEventFunction)                     \
	(wxCommandEventFunction) wxStaticCastEvent(                  \
	MyFooEventFunction, &fn ), (wxObject*) NULL ),
 
// Optionally, you can do a similar #define for EVT_MYFOO_RANGE.
#define EVT_MYFOO_RANGE(id1,id2, fn)                                 \
	DECLARE_EVENT_TABLE_ENTRY( MyFooCommandEvent, id1, id2,      \
	MyFooEventHandler(fn), (wxObject*) NULL ),
But I am not sure how to translate these macros to fit my event as written above.

I plan on handling the events by using the Connect() function. Also, I am uncertain what code needs to appear in the constructor and destructor methods (I did not include the CPP file because the constructor and destructor methods are empty...).

Can anyone help me out with the this so that I can have it function properly? Thanks in advance!

-Tony

DavidHart
Site Admin
Site Admin
Posts: 3881
Joined: Thu Jan 12, 2006 6:23 pm
Location: IoW, UK

Post by DavidHart » Wed May 13, 2009 11:05 am

Hi Tony,
In one of the other examples it indicates that I need the "DECLARE_DYNAMIC_CLASS...." macro, but I get a linker error when I use it.
You probably forgot the matching IMPLEMENT_DYNAMIC_CLASS statement in the cpp file. But you shouldn't need to use these anyway (despite what it says in Method 3): I have custom events working without them.
First it is a "command event" example, and I think I want a "regular" event.
It's unlikely to matter if you keep it a commandevent; the main difference is that these propagate to parent controls if unhandled, while non-commandevents don't. But as the parent controls won't have handlers for a custom event-type, no harm will be done.
But if you do want to derive direct from wxEvent: apart from the obvious changes in the class declaration, afaik you just need to remove the (wxCommandEventFunction) casts from the macro definitions.
Also, I am uncertain what code needs to appear in the constructor and destructor methods (I did not include the CPP file because the constructor and destructor methods are empty...)
Well, the ctors (that you use) shouldn't be empty! You must copy the event's data in the copy ctor (because it's used by Clone()), and call the base class ctor. And (unless you mess about with a default ctor followed by wxEvent::SetEventType and wxEvent::SetId) you also need a non-default ctor as in the example, which must call the base class ctor, and presumably do SetData() after.
Can anyone help me out with the this so that I can have it function properly?
You'll need to tell us what isn't working first ;) .

Regards,

David

spamiam
I live to help wx-kind
I live to help wx-kind
Posts: 180
Joined: Wed Apr 22, 2009 1:40 am

Post by spamiam » Wed May 13, 2009 12:38 pm

DavidHart wrote:Hi Tony,
.
.
.
You'll need to tell us what isn't working first ;) .

Regards,

David
David,

Thanks for the help. Your encouragement is helping me get this event handler written.

re: removing the command event cast from the handler macro definition, Does this look correct?

Code: Select all

#define CaptureEventHandler(func)\ 
        (wxObjectEventFunction)(wxEventFunction)\ 
        wxStaticCastEvent(CaptureEventFunction, &func)      
It seems from the tutorial that only this macro is required of the three listed in my original post. Is this correct? Again, I plan on using Connect() to allow the handling class see the event happen.

Here is the CPP implementation of the event class. I wrote this following the tutorial like a cookbook: without understanding what it all is SUPPOSED to be doing. I have some guesses, but they are probably wrong. Can you enlighten me and tell me where I got the code wrong?

Code: Select all

#include "captureevent.h" // class's header file
//CaptureEvent.cpp

DEFINE_EVENT_TYPE( DATAREADYEVENT );



CaptureEvent::CaptureEvent() : m_eventType(DATAREADYEVENT) { }

CaptureEvent::CaptureEvent( const CaptureEvent &event )
{
         //What is this doing?
         //what else should be in the constructor, considering that I have the SetData method implemented in the header file
	this->m_eventType = event.m_eventType;
}


// class destructor: IS THIS REQUIRED??
CaptureEvent::~CaptureEvent()
{
	//IS ANYTHING REQUIRED HERE?
}
Thank you again!
-Tony

EDIT: P.S. when I compile the file, I get an error regarding m_eventType. I just used this from the tutorial, and did not see where it was coming from in the tutorial. What should I have instead of m_eventType?
-T

DavidHart
Site Admin
Site Admin
Posts: 3881
Joined: Thu Jan 12, 2006 6:23 pm
Location: IoW, UK

Post by DavidHart » Wed May 13, 2009 2:23 pm

Does this look correct?
#define CaptureEventHandler(func)\
(wxObjectEventFunction)(wxEventFunction)\
wxStaticCastEvent(CaptureEventFunction, &func)
As far as I know, yes.
It seems from the tutorial that only this macro is required of the three listed in my original post. Is this correct?
Again, yes afaik.
I wrote this following the tutorial like a cookbook: without understanding what it all is SUPPOSED to be doing
You need ctors that will do what you want. So in method 4 (which is what I use), there's a standard ctor that sets the event-type and (optionally) the event id, and passes these to the wxEvent ctor; and a copy ctor that copies any data that the event stores i.e. for you, this->SetData(event.GetData());
You must provide a copy ctor that creates a new instance of your custom event, and copies the data from old to new. This is because, when you post the event, wxWidgets internally calls your customevent::Clone method to create a copy of the event to post; and Clone() calls the copy ctor. Getting this wrong is the commonest reason for custom events not working properly.
// class destructor: IS THIS REQUIRED??
Not by me ;) . If the data inside the event will need some special deletion, this is where to do it. If you're only carrying ints, wxStrings, you don't need one.
when I compile the file, I get an error regarding m_eventType.
It's method 3 that uses m_eventType, but it's better practice to use wxEvent::[GS]etEventType.

So your code should resemble:

Code: Select all

CaptureEvent::CaptureEvent( wxEventType commandType = DATAREADYEVENT, int id = 0 )
 :  wxEvent(id, commandType) { } // NB wxEvent's parameters are in the opposite order to wxCommandEvent's


CaptureEvent::CaptureEvent( const CaptureEvent &event )
  : wxEvent(event.GetId(), DATAREADYEVENT)
{ this->SetData(event.GetData()); }

spamiam
I live to help wx-kind
I live to help wx-kind
Posts: 180
Joined: Wed Apr 22, 2009 1:40 am

Post by spamiam » Wed May 13, 2009 4:08 pm

David,

Thank you again for your help. I think I am almost there. I am getting a few errors as I compile the code. I think it has something to do with the 'const' qualifiers. I thought I had them done correctly, but it would appear tha tsomething is not quite correct. Here are the header and CPP files. I modified them and clarified stuff. I probably clarified things too much. I moved the code for some of the methods out of the header file and into the CPP file. Maybe this is where the problem lies?

Code: Select all

//captureevent.h

#ifndef CAPTUREEVENT_H
#define CAPTUREEVENT_H

#include <wx/wx.h>
#include <wx/event.h>
#include "CapturedData.h"
#include <wx/string.h>


DECLARE_EVENT_TYPE( DATAREADYEVENT, -1 )


class CaptureEvent : public wxEvent
{
	public:
		
		//Constructor
        CaptureEvent( wxEventType commandType = DATAREADYEVENT, int id = 0 );
        
        //Copy method: required
        CaptureEvent( const CaptureEvent &event ); 
        
        // class destructor
		~CaptureEvent();
        
        
 
        virtual wxEvent *Clone() const{ return new CaptureEvent(*this); }
 
        wxString GetText(void);
        void SetText(const wxString text );
	
        void SetData(const CapturedData* cd);
        CapturedData* GetData(void);
 
    private:
        wxString eventText;
        CapturedData* data;
};


#define CaptureEventHandler(func)           \
	(wxObjectEventFunction)(wxEventFunction)\
	wxStaticCastEvent(CaptureEventFunction, &func)   


#endif // CAPTUREEVENT_H

Code: Select all

//captureevent.cpp
#include "captureevent.h" // class's header file

DEFINE_EVENT_TYPE( DATAREADYEVENT );



//*****************************************
//Constructor
// NB wxEvent's parameters are in the opposite order to wxCommandEvent's 
CaptureEvent::CaptureEvent( wxEventType commandType, int id) 
 :  wxEvent(id, commandType) { }



//*****************************************
//Copy method: REQUIRED
CaptureEvent::CaptureEvent( const CaptureEvent &event ):wxEvent(event.GetId(), DATAREADYEVENT) 
{ 
    this->SetText(event.GetText());
    this->SetData(event.GetData());    
}



//****************************************
// class destructor: 
CaptureEvent::~CaptureEvent()
{
	//Nothing Required... YET
}



//*****************************************
wxString CaptureEvent::GetText(void)
{
    return this->eventText;
}


//*****************************************
void CaptureEvent::SetText( const wxString text )
{ 
   eventText = text; 
}
        
        
//*****************************************        
void CaptureEvent::SetData(const CapturedData* cd)
{ 
    data = cd;
}
        
        
//*****************************************        
CapturedData* CaptureEvent::GetData(void)
{ 
    return data; 
}
The compiler gives these errors:
captureevent.cpp: In copy constructor `CaptureEvent::CaptureEvent(const CaptureEvent&)':
captureevent.cpp:20: error: passing `const CaptureEvent' as `this' argument of `wxString CaptureEvent::GetText()' discards qualifiers

captureevent.cpp:21: error: passing `const CaptureEvent' as `this' argument of `CapturedData* CaptureEvent::GetData()' discards qualifiers
captureevent.cpp: In member function `void CaptureEvent::SetData(const CapturedData*)':
captureevent.cpp:52: error: invalid conversion from `const CapturedData*' to `CapturedData*'

mingw32-make.exe: *** [Objects/MingW/captureevent.o] Error 1

Execution terminated
Compilation Failed. Make returned 2
Do you see my error(s)? I bet it is a rookie mistake, since I am a definite rookie!!

-Tony

EDIT: P.S.

I put the [GS]et... methods back into the header and it worked for some reason. I then tried to add code to create the event. The code creating the event in the main program was written like this:

Code: Select all

capturedData = new CapturedData(filename);
//capturedData was allocated as: "CapturedData* capturedData" in the header.
        
        //now if it was successful, let everybody know!
        CaptureEvent *ce = new CaptureEvent();
        ce->SetData(capturedData);
and I got no compilation errors. Now to see if I can Connect() to it.

-Tony

User avatar
doublemax
Moderator
Moderator
Posts: 13993
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Post by doublemax » Wed May 13, 2009 4:54 pm

BTW: if you intend to use this event to send data from a secondary thread to your main thread, your Clone() implementation is no good.

If you have a wxString (or any other reference counted class) in your class, you must enfore a deep copy of it in your Clone() method.

Code: Select all

wxString s1( wxT("test") );
wxString s2=s1;    // will use reference counting

wxString s3( s1.c_str() );    // will create a deep copy of the string data
Use the source, Luke!

spamiam
I live to help wx-kind
I live to help wx-kind
Posts: 180
Joined: Wed Apr 22, 2009 1:40 am

Post by spamiam » Wed May 13, 2009 5:35 pm

doublemax wrote:BTW: if you intend to use this event to send data from a secondary thread to your main thread, your Clone() implementation is no good.

If you have a wxString (or any other reference counted class) in your class, you must enfore a deep copy of it in your Clone() method.

Code: Select all

wxString s1( wxT("test") );
wxString s2=s1;    // will use reference counting

wxString s3( s1.c_str() );    // will create a deep copy of the string data
Thanks for the heads-up.

So the method should look like this?

Code: Select all

void CaptureEvent::SetText( const wxString text ) 
{ 
   eventText( text.c_str() ); 
} 

spamiam
I live to help wx-kind
I live to help wx-kind
Posts: 180
Joined: Wed Apr 22, 2009 1:40 am

Post by spamiam » Wed May 13, 2009 5:49 pm

OK, I think we are almost there!

I have now put the Connect() code in the object which will handle the event. The code looks like this:

Code: Select all

    mainframe->Connect(DATAREADYEVENT,
                    CaptureEventHandler(GraphicsWindow::OnNewData),  //THIS IS LINE # 42
                    NULL, this);
mainframe is a reference to the object in which the event is created as shown in a previous post.

I get the following compiler error:
GraphicsWindow.cpp:42: error: expected primary-expression before ')' token
GraphicsWindow.cpp:42: error: expected primary-expression before ')' token
I presume it is referring to a problem with the macro which is written as:

Code: Select all

#define CaptureEventHandler(func)           \
	(wxObjectEventFunction)(wxEventFunction)\
	wxStaticCastEvent(CaptureEventFunction, &func)   
Since I have no idea what this macro is actually supposed to be doing especially as it pertains to the Connect() function


I think I needed to add

Code: Select all

typedef void (wxEvtHandler::*CaptureEventFunction)(CaptureEvent &);
just abefore the macro listed above. WHen I added that, then I got it to compile, but it instantly crashed as it tried to run. Where did I go wrong? It is really hard to use the tutorial as a pure cookbook when I can not really tell what is going on! I am sorry to ask so many questions. Hopefully others will be able to learn from this thread!
-Tony

DavidHart
Site Admin
Site Admin
Posts: 3881
Joined: Thu Jan 12, 2006 6:23 pm
Location: IoW, UK

Post by DavidHart » Wed May 13, 2009 6:57 pm

WHen I added that, then I got it to compile, but it instantly crashed as it tried to run. Where did I go wrong?
What happens when you run a debug build of your app in a debugger? What does the backtrace say after the crash?

Having asked that, the commonest reason for Connect() failing/crashing is an incorrect eventSink parameter. In your Connect(), is 'this' a pointer to a GraphicsWindow object?
It is really hard to use the tutorial as a pure cookbook when I can not really tell what is going on!
Well, you could have just replicated method 4 to get it working, then altered things to do what you want. (Or you could have worked through it bit by bit, learning what each bit does...)

spamiam
I live to help wx-kind
I live to help wx-kind
Posts: 180
Joined: Wed Apr 22, 2009 1:40 am

Post by spamiam » Wed May 13, 2009 7:13 pm

DavidHart wrote:Well, you could have just replicated method 4 to get it working, then altered things to do what you want. (Or you could have worked through it bit by bit, learning what each bit does...)
I thought that is what I am doing! Trying to work it out!

I tried to keep the code as close as possible to the tutorial while actually getting it to compile, and then to actually work!

One of the difficulties of following the implementation of the tutorial is that it includes enough variations, it is hard to keep straight which part is necessary to be with another part.

re: the reference to the eventSink. Yes, "this" is a pointer to a GraphicsWindow object.

I am pretty sure that the code in the Connect() is reasonably correct, except as it specifically referrs to the custom event handler. The reason I think so is that I have a Connect() just before this one which connects to a standard wxWidgets scrollbar, and it works perfectly.

It is something about the implementation of the custom event that is causing the problem.

-Tony

spamiam
I live to help wx-kind
I live to help wx-kind
Posts: 180
Joined: Wed Apr 22, 2009 1:40 am

Post by spamiam » Thu May 14, 2009 12:36 am

David,

I feel like I am beating a dead horse, but I am trying to get this to work. I took your advice and went back to the example of method #4. I just pasted it into the header file, and changes text to fit my needs. But I did not change anything else, like the command event type.

I then placed the rest of the material in my main GUI class as follows:

Code: Select all

            // excerpted from the mainframe object which is an instance of wxFrame
            CaptureEvent event( DataReadyEvent );
            event.SetData(capturedData);
            wxPostEvent(graphPanel2,  event );
            //ProcessEvent(DataReadyEvent);
And I can get it to WORK when I use the wxPostEvent() command, specifying which object should handle the event.

and using this in the event-handling class:

Code: Select all

    Connect( wxID_ANY, DataReadyEvent,
	CaptureEventHandler(GraphicsWindow::OnNewData), NULL, this );
But there are a few objects that all need to handle the event. And I tried to use this:

Code: Select all

    mainframe->Connect(DataReadyEvent,
                    CaptureEventHandler(GraphicsWindow::OnNewData),
                    NULL, this);
With this technique, The program crashes again.

I have 2 questions: 1) How do I process the event without specifying the event-handling object. Is it with ProcessEvent()? And 2) why is it crashing?

DavidHart
Site Admin
Site Admin
Posts: 3881
Joined: Thu Jan 12, 2006 6:23 pm
Location: IoW, UK

Post by DavidHart » Thu May 14, 2009 10:17 am

And I can get it to WORK when I use the wxPostEvent() command, specifying which object should handle the event
That's encouraging :) .
1) How do I process the event without specifying the event-handling object?
You can't. That would be like posting a letter without writing the destination address on the envelope. But all of ProcessEvent/AddPendingEvent/wxPostEvent do specify the event-handling object: e.g. pFoo->AddPendingEvent(..) specifies the object pointed to by pFoo.
You say there are several objects that need to receive the event. If so, you either need to post the event to each object (if you mean that they all need to be notified every time); or else add logic somewhere to select which object should be the destination, and send the event there. That logic could be inside the posting function, or it can be in the receiving class, in which case the event itself will need to contain information that can be used to determine its correct destination.
And 2) why is it crashing?
As I said before, it's probably that you're passing a wrong eventSink. You do:
mainframe->Connect(DataReadyEvent,...,NULL, this);
which is the same as writing:
mainframe->Connect(DataReadyEvent,...,NULL, mainframe);
Unless you have a strange naming policy, mainframe is a wxFrame. You should be supplying a pointer to a GraphicsWindow object.

spamiam
I live to help wx-kind
I live to help wx-kind
Posts: 180
Joined: Wed Apr 22, 2009 1:40 am

Post by spamiam » Thu May 14, 2009 12:40 pm

mainframe->Connect(DataReadyEvent,...,NULL, mainframe);
Unless you have a strange naming policy, mainframe is a wxFrame. You should be supplying a pointer to a GraphicsWindow object.
Yes, mainframe is the main wxFrame. But the line "mainframe->Connect(...) is written inside the GraphicsWindow class. Therefore the "this", I think, refers to the current GraphicsWindow object. What makes me virtually positive that this is true is that one line above this in the GraphicsWindow class, I have a similar line connecting to a scrollbar event. This event does work in all of the instances of the GraphicsWindow class.

The mainframe pointer and the scrollwindow pointer are global variables handled identically. I printed out the mainframe pointer's value, and it is not NULL, but I can't be sure that it is not a wild pointer. It ought to be fine since the pointer's value is never touched once it is instantiated.

What do you think? I am as sure as I can be that the "this" pointer is valid since the scrollbar "Connect()" works fine.

Regarding the necessity of specifing the destination of the event when posting it.... If I post it with a certain object as the destination, does this preclude other objects from dynamically connecting to the event as well? I was thinking that while I may not know ahead of time which objects will be connecting to an event, I can certainly post it somewhere, like the posting class itself. It ends up not having an event handler, but maybe it doesn't matter, since other objects may be linked dynamically.

-Tony

DavidHart
Site Admin
Site Admin
Posts: 3881
Joined: Thu Jan 12, 2006 6:23 pm
Location: IoW, UK

Post by DavidHart » Thu May 14, 2009 1:48 pm

But the line "mainframe->Connect(...) is written inside the GraphicsWindow class. Therefore the "this", I think, refers to the current GraphicsWindow object.
Yes, I'm sorry, my previous statement was wrong. I was thinking of the (common) situation where the eventSink parameter isn't supplied; then it would have defaulted to the 'this' of mainframe. But here you should be OK.
So you need to use your debugger to find out why the crash occurs; look at the backtrace.
Regarding the necessity of specifing the destination of the event when posting it.... If I post it with a certain object as the destination, does this preclude other objects from dynamically connecting to the event as well? I was thinking that while I may not know ahead of time which objects will be connecting to an event, I can certainly post it somewhere, like the posting class itself. It ends up not having an event handler, but maybe it doesn't matter, since other objects may be linked dynamically.
I'm not sure I understand that paragraph. You can safely post an event to an object that can't handle it, but you can't post to an object that doesn't yet exist.

If you want post events to an unknown number of dynamically-created objects, the easiest way is: Post all events to your GraphicsWindow class. When you create a new receiving object, use wxWindow::PushEventHandler to push the receiving object's eventhandler onto the GraphicsWindow object.
This means that an event will be passed first to the handler of the (most recent) receiving object. In that handler, do whatever you need, then event.Skip(). This means that the event will be passed up the chain to the next receiving object, etc, until finally it gets to the GraphicsWindow object. So one event will traverse all the possible receivers, and each can react as appropriate.

spamiam
I live to help wx-kind
I live to help wx-kind
Posts: 180
Joined: Wed Apr 22, 2009 1:40 am

Post by spamiam » Thu May 14, 2009 4:12 pm

DavidHart wrote: So you need to use your debugger to find out why the crash occurs; look at the backtrace.
I did look at the wxDevC++ backtrace and it was not enlightening (to me). It seemed to be crashing very very early in the run. Certainly it was before any part of the GUI was drawn on the screen. I can post the back trace it it might be helpful. I even tried placing breakpoints very early in the processing, and I never got it to stop at the breakpoint. The crash preceeded it!
I'm not sure I understand that paragraph. You can safely post an event to an object that can't handle it, but you can't post to an object that doesn't yet exist.

If you want post events to an unknown number of dynamically-created objects, the easiest way is: Post all events to your GraphicsWindow class. When you create a new receiving object, use wxWindow::PushEventHandler to push the receiving object's eventhandler onto the GraphicsWindow object.
This means that an event will be passed first to the handler of the (most recent) receiving object. In that handler, do whatever you need, then event.Skip(). This means that the event will be passed up the chain to the next receiving object, etc, until finally it gets to the GraphicsWindow object. So one event will traverse all the possible receivers, and each can react as appropriate.
Yes, this is mostly what I meant. I was asking if it is OK to post to an object which can not by itself handle the event. The object I had in mind has NO event handler at all! Maybe it needs some sort of a handler for the event.Skip() to be used?. For the mainframe class, the only object that is sure to be present and an immediately available pointer is the mainframe instance itself! It would be nice to post events to the GraphWindow class, but this class is actually the one which is going to have the multiple instances, and will need the dynamic event handling!

-Tony

Post Reply