wxPanel derived component: asking help for problems and tips 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.
Post Reply
Glitch
Earned a small fee
Earned a small fee
Posts: 13
Joined: Fri Mar 27, 2020 9:54 am

wxPanel derived component: asking help for problems and tips

Post by Glitch »

I'm buildin a component called "wxActiveArea". I want it to act like a wxPanel, but with an animated frame that "glows" when the mouse "press" on it.
I'll copy all the code at the end of the post.

I'm at the beginning of it and i already have problems:
wxAArea.png
wxAArea.png (18.71 KiB) Viewed 19707 times
The upper is my component, the lower is a panel: they're built in the same manned but the label in my component is'nt centered.
What am i doing wrong?

2nd problem: if i "connect" function to the left mouse down event (not in the code), my component cease to react to it. I guess the new function "steals" it. How should i ensure that the wxActiveArea ALWAYS get the mousedown before, and then eventually it is passed to user functions?

Tips:
1) How can my wxActiveArea get the mouse click even if it contains other components? if in the example i click on the label the ActiveArea does'nt react to the mousedown, while i want it ALWAYS get the event no matter what's the child component under the mouse.
2) is the event table still a thing for components? should i use "connect" or "bind" instead?
3) Is it ok to use a wxTimer to make animations inside my component?

Thanks.

wxActiveArea.h

Code: Select all

#ifndef WXACTIVEAREA_H
#define WXACTIVEAREA_H

#include <wx/panel.h>
#include <wx/dcbuffer.h>

// forward declarations
class wxActiveArea;

#define ID_WXACTIVEAREA 10101

class wxActiveArea: public wxPanel //wxWindow //wxControl
{
    typedef wxPanel inherited;

    DECLARE_DYNAMIC_CLASS( wxActiveArea )
    DECLARE_EVENT_TABLE()

public:
    wxActiveArea(){};
    wxActiveArea(
            wxWindow *parent,
            wxWindowID winid = ID_WXACTIVEAREA,
            const wxPoint& pos = wxDefaultPosition,
            const wxSize& size = wxDefaultSize,
            long style = wxTAB_TRAVERSAL | wxSIMPLE_BORDER,
            const wxString& name = wxPanelNameStr
    );
    virtual ~wxActiveArea();
    wxActiveArea(const wxActiveArea& other);

protected:
    // Pseudo ctor
    bool Create(wxWindow *parent,
                wxWindowID winid = ID_WXACTIVEAREA,
                const wxPoint& pos = wxDefaultPosition,
                const wxSize& size = wxDefaultSize,
                long style = wxTAB_TRAVERSAL | wxSIMPLE_BORDER,
                const wxString& name = wxPanelNameStr);

    void doDraw(wxBufferedPaintDC& dc);
    virtual bool AcceptsFocus() const {return false;};

private:
    virtual void		OnSize( wxSizeEvent& event );
    virtual void		OnPaint( wxPaintEvent& event );
    virtual void		OnLeftDown( wxMouseEvent& event );
    virtual void		OnLeftUp( wxMouseEvent& event );
};
#endif // WXACTIVEAREA_H
wxActiveArea.cpp

Code: Select all

#include "wxActiveArea.h"
#include <iostream>

#include <wx/graphics.h>

IMPLEMENT_DYNAMIC_CLASS( wxActiveArea, inherited )

BEGIN_EVENT_TABLE( wxActiveArea, inherited )
    EVT_SIZE( wxActiveArea::OnSize )
    EVT_PAINT( wxActiveArea::OnPaint )
    EVT_LEFT_DOWN( wxActiveArea::OnLeftDown )
    EVT_LEFT_UP( wxActiveArea::OnLeftUp )
END_EVENT_TABLE()

wxActiveArea::~wxActiveArea()
{
}
//---------------------------------------------------------------------------------------------------
wxActiveArea::wxActiveArea(
    wxWindow            *parent     ,
    wxWindowID          winid       ,
    const wxPoint&      pos         ,
    const wxSize&       size        ,
    long                style       ,
    const wxString&     name
)
{
	m_parent = parent;
	if (m_parent) {
		Create(
			m_parent	,
			winid		,
			pos			,
			size		,
			style       ,
			name
		);
	}
}
bool wxActiveArea::Create(
    wxWindow            *parent     ,
    wxWindowID          winid       ,
    const wxPoint&      pos         ,
    const wxSize&       size        ,
    long                style       ,
    const wxString&     name
)
{
    inherited::Create(parent, winid, pos, size, style, name);

	if (m_parent!=nullptr) {
		this->SetBackgroundColour(m_parent->GetBackgroundColour());
		this->SetForegroundColour(m_parent->GetForegroundColour());
		this->SetFont(m_parent->GetFont());
	}else{
		this->SetBackgroundColour(wxColour(0,   0,  64));
		this->SetForegroundColour(wxColour(0, 200, 255));
		this->SetFont(wxFont(8, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxT("Tahoma")));
	}

    return true;
}





//---------------------------------------------------------------------------------------------------
void wxActiveArea::OnPaint( wxPaintEvent& event )
{
    wxBufferedPaintDC dc(this);
	doDraw(dc);
}
//---------------------------------------------------------------------------------------------------
void wxActiveArea::doDraw(wxBufferedPaintDC& dc)
{
    bool Cliccato = (GetCapture() == this) && this->IsEnabled();
    wxPen bPen(GetForegroundColour());
    const double rr = 10;
    const double bb = 10;

    wxGraphicsContext *gc;
	wxRect clientRect = GetClientRect();
	wxRect in = clientRect;
    in.Deflate(bb);

	dc.SetPen			(wxPen		(GetBackgroundColour()) );
	dc.SetBrush			(wxBrush	(GetBackgroundColour()) );
	dc.DrawRectangle	(clientRect);

    if (Cliccato) {
        gc = wxGraphicsContext::Create( dc );
        if(gc) {
            bPen.SetColour( wxColor(255,255,0,128) );
            bPen.SetWidth(2);

            gc->SetPen(bPen);
            gc->SetBrush(*wxTRANSPARENT_BRUSH);
            gc->DrawRoundedRectangle(in.GetLeft(), in.GetTop(), in.GetWidth(),in.GetHeight(),rr);
            delete gc;
        }   // if gc
    }else{
        bPen.SetWidth(2);
        bPen.SetColour(*wxWHITE);

        dc.SetPen(bPen);
        dc.SetBrush(*wxTRANSPARENT_BRUSH);
        dc.DrawRoundedRectangle(in,rr);
    }
}
//---------------------------------------------------------------------------------------------------
void wxActiveArea::OnLeftDown( wxMouseEvent& event )
{
	if(GetCapture() != this)
	{
		CaptureMouse();
		Refresh();
	}
}
void wxActiveArea::OnLeftUp( wxMouseEvent& event )
{
	if(GetCapture() == this)
	{
		ReleaseMouse();
		if(GetClientRect().Contains(event.GetPosition()))
		{
			wxCommandEvent evt(wxEVT_COMMAND_BUTTON_CLICKED, GetId());
			evt.SetEventObject(this);
			GetEventHandler()->AddPendingEvent(evt);
		}
		Refresh();
	}
}
void wxActiveArea::OnSize( wxSizeEvent& event )
{
	Refresh();
}
TEST_wxAA_App.cpp

Code: Select all

#include "wx/wx.h"
class TEST_wxAA_App : public wxApp
{
public:
    virtual bool OnInit() wxOVERRIDE;
};

// Define a new frame type: this is going to be our main frame
class MyFrame : public wxFrame
{
public:
    MyFrame(const wxString& title);

    //void OnQuit(wxCommandEvent& event);

private:
    //wxDECLARE_EVENT_TABLE();
};
//==============================================================
wxIMPLEMENT_APP(TEST_wxAA_App);
//==============================================================
bool TEST_wxAA_App::OnInit()
{
    if ( !wxApp::OnInit() )
        return false;

    MyFrame *frame = new MyFrame("wxActiveArea TEST");
    frame->Show(true);
    //SetTopWindow(frame);
    return true;
}
//==============================================================
#include "wxActiveArea.h"
//==============================================================
/*
wxActiveArea* m_ActiveArea;
wxStaticText* m_st_label;
*/
//==============================================================
MyFrame::MyFrame(const wxString& title)
       : wxFrame(NULL, wxID_ANY, title)
{
    this->SetBackgroundColour(wxColour(wxT("rgb(255,128,0)")));

    wxBoxSizer* szr_main = new wxBoxSizer(wxVERTICAL);
    this->SetSizer(szr_main);

    wxActiveArea* m_ActiveArea = new wxActiveArea(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
    m_ActiveArea->SetBackgroundColour(wxColour(wxT("rgb(0,0,128)")));
    m_ActiveArea->SetForegroundColour(wxColour(wxT("rgb(255,255,255)")));

    szr_main->Add(m_ActiveArea, 1, wxALL|wxEXPAND, 15);

    wxBoxSizer* szr_AA_V = new wxBoxSizer(wxVERTICAL);
    m_ActiveArea->SetSizer(szr_AA_V);

    wxBoxSizer* szr_AA_H = new wxBoxSizer(wxHORIZONTAL);

    szr_AA_V->Add(szr_AA_H, 1, wxALIGN_CENTER_HORIZONTAL, 5);

    wxStaticText* m_st_label = new wxStaticText(m_ActiveArea, wxID_ANY, _("This should be a centered label"), wxDefaultPosition, wxDefaultSize, 0);
    m_st_label->SetBackgroundColour(wxColour(wxT("rgb(0,0,255)")));

    szr_AA_H->Add(m_st_label, 0, wxALIGN_CENTER_VERTICAL, 5);




    wxPanel* m_ComparePanel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
    m_ComparePanel->SetBackgroundColour(wxColour(wxT("rgb(0,0,128)")));
    m_ComparePanel->SetForegroundColour(wxColour(wxT("rgb(255,255,255)")));

    szr_main->Add(m_ComparePanel, 1, wxALL|wxEXPAND, 15);

    wxBoxSizer* szr_CP_V = new wxBoxSizer(wxVERTICAL);
    m_ComparePanel->SetSizer(szr_CP_V);

    wxBoxSizer* szr_CP_H = new wxBoxSizer(wxHORIZONTAL);

    szr_CP_V->Add(szr_CP_H, 1, wxALIGN_CENTER_HORIZONTAL, 5);

    wxStaticText* m_st_label_cp = new wxStaticText(m_ComparePanel, wxID_ANY, _("This is a centered label"), wxDefaultPosition, wxDefaultSize, 0);
    m_st_label_cp->SetBackgroundColour(wxColour(wxT("rgb(0,0,255)")));

    szr_CP_H->Add(m_st_label_cp, 0, wxALIGN_CENTER_VERTICAL, 5);


















    SetBackgroundColour(wxColour(wxT("rgb(255,128,0)")));
    CentreOnScreen(wxBOTH);
}
User avatar
doublemax
Moderator
Moderator
Posts: 19115
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxPanel derived component: asking help for problems and tips

Post by doublemax »

The upper is my component, the lower is a panel: they're built in the same manned but the label in my component is'nt centered.
What am i doing wrong?
You need to all event.Skip() in the OnSize handler, otherwise the default behavior which handles the sizer layout is not executed.
2nd problem: if i "connect" function to the left mouse down event (not in the code), my component cease to react to it. I guess the new function "steals" it. How should i ensure that the wxActiveArea ALWAYS get the mousedown before, and then eventually it is passed to user functions?
I don't understand this. Please rephrase.
1) How can my wxActiveArea get the mouse click even if it contains other components? if in the example i click on the label the ActiveArea does'nt react to the mousedown, while i want it ALWAYS get the event no matter what's the child component under the mouse.
There is no easy way to do this. You need to recursively catch the mousedown event from all descendants and redirect them to your control. Here's some sample code that does this for a key event: https://wiki.wxwidgets.org/Catching_key ... ve_connect
2) is the event table still a thing for components? should i use "connect" or "bind" instead?
There is nothing wrong with using a static event table. But if you use dynamic binding, use Bind(), not Connect()
3) Is it ok to use a wxTimer to make animations inside my component?
Yes, how else would you do it?
Use the source, Luke!
Glitch
Earned a small fee
Earned a small fee
Posts: 13
Joined: Fri Mar 27, 2020 9:54 am

Re: wxPanel derived component: asking help for problems and tips

Post by Glitch »

doublemax wrote: Sun Apr 19, 2020 3:52 pm You need to all event.Skip() in the OnSize handler, otherwise the default behavior which handles the sizer layout is not executed.
Damn! Thanks. It may be a language issue but i still read "skip" as "don't look at this event anymore" :oops:


doublemax wrote: Sun Apr 19, 2020 3:52 pm
2nd problem: if i "connect" function to the left mouse down event (not in the code), my component cease to react to it. I guess the new function "steals" it. How should i ensure that the wxActiveArea ALWAYS get the mousedown before, and then eventually it is passed to user functions?
I don't understand this. Please rephrase.
If i add this line on the frame:

Code: Select all

    m_ActiveArea->Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(fMainForm::OnActiveareaLeftDown), NULL, this);
the event handler is executed, but my component does not get the mouse down anymore.
I want the final user be able to use any event he wants (actually, i'd want my component be used just like a wxPanel, so to be fully "compatible" and replaceable) so i need to get the mouse down even if he wants do something with it.


doublemax wrote: Sun Apr 19, 2020 3:52 pm
3) Is it ok to use a wxTimer to make animations inside my component?
Yes, how else would you do it?
Dunno, i'm worried about resources. What if i use 50 ActiveAreas instances in a form?
User avatar
doublemax
Moderator
Moderator
Posts: 19115
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxPanel derived component: asking help for problems and tips

Post by doublemax »

I want the final user be able to use any event he wants (actually, i'd want my component be used just like a wxPanel, so to be fully "compatible" and replaceable) so i need to get the mouse down even if he wants do something with it.
Then you user must explicitly call event.Skip() in his event handler. It's the same with any control, if you steal its mouse events, it stops working.
Dunno, i'm worried about resources. What if i use 50 ActiveAreas instances in a form?
I think any modern OS can handle that. And if it really becomes a problem, you can still refactor it and use a shared timer for all instances. Even if it means to write some management code for that.
Use the source, Luke!
Glitch
Earned a small fee
Earned a small fee
Posts: 13
Joined: Fri Mar 27, 2020 9:54 am

Re: wxPanel derived component: asking help for problems and tips

Post by Glitch »

Should i use a static timer (like in the "animate.cpp" component)?

Code: Select all

    wxtimer m_staticTimer;
    m_staticTimer.SetOwner(this);
or a dynamic one as said here?

Code: Select all

    wxtimer* m_DynamicTimer ;
    m_DynamicTimer = new wxTimer();
In the latter case, should i "wxdelete" it in the destructor of my class?


And:

why "Connect() works but if i use "Bind()" i never get the event?

Code: Select all

    m_staticTimer.Bind(wxEVT_TIMER, &this->OnsTimer, this);
    
    m_staticTimer.Connect(wxEVT_TIMER, wxTimerEventHandler(OnsTimer), NULL, this);
User avatar
doublemax
Moderator
Moderator
Posts: 19115
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxPanel derived component: asking help for problems and tips

Post by doublemax »

Should i use a static timer (like in the "animate.cpp" component)?
Doesn't make any difference.
In the latter case, should i "wxdelete" it in the destructor of my class?
Yes (a normal 'delete' works fine, too).
why "Connect() works but if i use "Bind()" i never get the event?
I have no explanation for this, they should do the same.
Use the source, Luke!
PB
Part Of The Furniture
Part Of The Furniture
Posts: 4193
Joined: Sun Jan 03, 2010 5:45 pm

Re: wxPanel derived component: asking help for problems and tips

Post by PB »

Glitch wrote: Tue Apr 21, 2020 4:50 pm why "Connect() works but if i use "Bind()" i never get the event?

Code: Select all

    m_staticTimer.Bind(wxEVT_TIMER, &this->OnsTimer, this);
    
    m_staticTimer.Connect(wxEVT_TIMER, wxTimerEventHandler(OnsTimer), NULL, this);
To me, it looks like neither line should even compile let alone work. For example, I believe that the Bind()-using line should look like this (assuming the oddly looking method name is correct, and this is a MyFrame instance)

Code: Select all

m_staticTimer.Bind(wxEVT_TIMER, &MyFrame::OnsTimer, this);
Glitch
Earned a small fee
Earned a small fee
Posts: 13
Joined: Fri Mar 27, 2020 9:54 am

Re: wxPanel derived component: asking help for problems and tips

Post by Glitch »

I'm sorry, i messed up things and got the wrong conclusion, so i posed the wrong question :oops: .

Pls let me rewrite part of my previous post and ask the new question, saying that "this" is the wxActiveArea class, derived from wxPanel, i'm writing.
(also changed some name so this should be more readable)

Timers declaration in the h file:

Code: Select all

    wxTimer  m_StaticTimer;
    wxTimer* m_DynamicTimer;
I've tried either with Connect()

Code: Select all

    m_DynamicTimer = new wxTimer();
    m_DynamicTimer->Connect(wxEVT_TIMER, wxTimerEventHandler(wxActiveArea::OnDynamicTimerTime), NULL, this);

    m_StaticTimer.SetOwner(this);
    m_StaticTimer.Connect(wxEVT_TIMER, wxTimerEventHandler(wxActiveArea::OnStaticTimerTime), NULL, this);
or with Bind()

Code: Select all

    m_DynamicTimer = new wxTimer();
    m_DynamicTimer->Bind(wxEVT_TIMER, &wxActiveArea::OnDynamicTimerTime, this);

    m_StaticTimer.SetOwner(this);
    m_StaticTimer.Bind(wxEVT_TIMER, &wxActiveArea::OnStaticTimerTime, this);
and the static timer never fired its event.

I've finally found that the m_StaticTimer.SetOwner(this); was the problem. Without that line everything works. Should it be used? if yes: why and how to get the event then?
Glitch
Earned a small fee
Earned a small fee
Posts: 13
Joined: Fri Mar 27, 2020 9:54 am

Re: wxPanel derived component: asking help for problems and tips

Post by Glitch »

doublemax wrote: Sun Apr 19, 2020 3:52 pm
1) How can my wxActiveArea get the mouse click even if it contains other components? if in the example i click on the label the ActiveArea does'nt react to the mousedown, while i want it ALWAYS get the event no matter what's the child component under the mouse.
There is no easy way to do this. You need to recursively catch the mousedown event from all descendants and redirect them to your control. Here's some sample code that does this for a key event: https://wiki.wxwidgets.org/Catching_key ... ve_connect
My problem is that i don't know WHEN do the recursion:
as my component is like a wxPanel, the user can add childrens whenever he wants.
How can I ("me" being my component) be aware that there's a new child component so i can catch the event i want from him?
User avatar
doublemax
Moderator
Moderator
Posts: 19115
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxPanel derived component: asking help for problems and tips

Post by doublemax »

My problem is that i don't know WHEN do the recursion:
as my component is like a wxPanel, the user can add childrens whenever he wants.
How can I ("me" being my component) be aware that there's a new child component so i can catch the event i want from him?
That could be tricky.

Can you describe a use-case for your custom control, i have a hard time imagining for what you need this?
Use the source, Luke!
Glitch
Earned a small fee
Earned a small fee
Posts: 13
Joined: Fri Mar 27, 2020 9:54 am

Re: wxPanel derived component: asking help for problems and tips

Post by Glitch »

doublemax wrote: Wed Apr 22, 2020 2:16 pm
My problem is that i don't know WHEN do the recursion:
as my component is like a wxPanel, the user can add childrens whenever he wants.
How can I ("me" being my component) be aware that there's a new child component so i can catch the event i want from him?
That could be tricky.

Can you describe a use-case for your custom control, i have a hard time imagining for what you need this?
I have a touchscreen and i want it full by a lot of "squares" contaning labels or images (think at a "more static version" of the win10 start menu). Anyway it can contains whatever.

Each square was a wxPanel, but since wxPanels does'nt have borders (frame?) around them, i'm making my own class (wxActiveArea) that draws a rounded rectangle on the client area of the panel.

I also need to have each "square" react to a "long-press" (you put your finger on it, his border "grows" as feedback, after a time an event is fired ....hence the ActiveArea name).
The "put your finger in it" is the actual problem: if the AArea contains other controls, the "mouse left down" event is cathed by the other controls and it does'nt arrive at the AArea. I can "patch" it by assigning the event to EACH child and call a function from there, but i'd like more to always get that even no matter what child is under the mouse cursor.
User avatar
doublemax
Moderator
Moderator
Posts: 19115
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxPanel derived component: asking help for problems and tips

Post by doublemax »

I see, thanks for the explanation.

Right now i can only think of two solutions:
1) Cooperative, add a dedicated method to the control for binding the event handler that the user has to call once he finished adding all children

2) (Untested, just an idea). Catch the wxWindowCreateEvent, as it propagates upwards, it should reach your control
https://docs.wxwidgets.org/trunk/classw ... event.html
For a totally clean version, you should also catch wxWindowDestroyEvent and detach your event handlers (I'm not 100% if this works or not, maybe that's too late. So, if this crashes, just don't do it ;) )
Use the source, Luke!
Post Reply