wxSpinCtrl Enter should do what Tab does Topic is solved

If you are using the main C++ distribution of wxWidgets, Feel free to ask any question related to wxWidgets development here. This means questions regarding to C++ and wxWidgets, not compile problems.
Post Reply
mael15
Super wx Problem Solver
Super wx Problem Solver
Posts: 430
Joined: Fri May 22, 2009 8:52 am
Location: Bremen, Germany

wxSpinCtrl Enter should do what Tab does

Post by mael15 » Tue Jan 14, 2020 5:41 pm

I have a wxSpinCtrl and want to leave the control when the user presses Enter, just like what happens when the user pressed Tab. The wxEVT_COMMAND_SPINCTRL_UPDATED should be triggered and the next control selected.
I am surprised that seems to be difficult. How can I achieve this?

ONEEYEMAN
Part Of The Furniture
Part Of The Furniture
Posts: 4147
Joined: Sat Apr 16, 2005 7:22 am
Location: USA, Ukraine

Re: wxSpinCtrl Enter should do what Tab does

Post by ONEEYEMAN » Tue Jan 14, 2020 7:48 pm

Hi,
Do you have it inside wxFrame or wxDialog?
What does hapend when you press Enter?
And usual stanza:
wx version?
OS version?
Toolkit?

Thank you.

mael15
Super wx Problem Solver
Super wx Problem Solver
Posts: 430
Joined: Fri May 22, 2009 8:52 am
Location: Bremen, Germany

Re: wxSpinCtrl Enter should do what Tab does

Post by mael15 » Tue Jan 14, 2020 7:50 pm

I have it in a wxPanel.
Nothing happens when I press Enter.
wxWidgets 3.1.3 on Windows 10 Pro with Visual Studio v142 build tools (10.0.18362.0).

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

Re: wxSpinCtrl Enter should do what Tab does

Post by doublemax » Tue Jan 14, 2020 7:59 pm

Without trying, i'd say: Catch wxEVT_TEXT_ENTER event and call Navigate() in the event handler.
Use the source, Luke!

mael15
Super wx Problem Solver
Super wx Problem Solver
Posts: 430
Joined: Fri May 22, 2009 8:52 am
Location: Bremen, Germany

Re: wxSpinCtrl Enter should do what Tab does

Post by mael15 » Tue Jan 14, 2020 8:08 pm

doublemax wrote:
Tue Jan 14, 2020 7:59 pm
Without trying, i'd say: Catch wxEVT_TEXT_ENTER event and call Navigate() in the event handler.
That sounds perfect and was what I was looking for, unfortunately nothing happens.

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

Re: wxSpinCtrl Enter should do what Tab does

Post by doublemax » Tue Jan 14, 2020 8:39 pm

Does the event handler get called? If not, did you set the wxTE_PROCESS_ENTER style flag?

If it does, try calling Navigate delayed using CallAfter:

Code: Select all

    CallAfter( [this] {
      Navigate();
    });
Use the source, Luke!

PB
Part Of The Furniture
Part Of The Furniture
Posts: 2422
Joined: Sun Jan 03, 2010 5:45 pm

Re: wxSpinCtrl Enter should do what Tab does

Post by PB » Tue Jan 14, 2020 8:39 pm

mael15 wrote:
Tue Jan 14, 2020 8:08 pm
That sounds perfect and was what I was looking for, unfortunately nothing happens.
Seems to work fine for me with the master on MSW, did you create the spin control with wxTE_PROCESS_ENTER flag?

Code: Select all

#include <wx/wx.h>
#include <wx/spinctrl.h>

class MyFrame : public wxFrame
{
public:
    MyFrame () : wxFrame(nullptr, wxID_ANY, "Test")
    {
        wxPanel* mainPanel = new wxPanel(this);        
        wxBoxSizer* mainPanelSizer = new wxBoxSizer(wxVERTICAL);

        wxSpinCtrl* spinCtrl = new wxSpinCtrl(mainPanel, wxID_ANY, "5", wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS | wxTE_PROCESS_ENTER);
        spinCtrl->SetRange(1, 10);
        spinCtrl->Bind(wxEVT_TEXT_ENTER, &MyFrame::OnSpinCtrlTextEnter, this);
        
        mainPanelSizer->Add(spinCtrl, wxSizerFlags().Expand().DoubleBorder());
        mainPanelSizer->Add(new wxTextCtrl(mainPanel, wxID_ANY, "blah"), wxSizerFlags().Expand().DoubleBorder());
        mainPanelSizer->Add(new wxButton(mainPanel, wxID_ANY, "Button"), wxSizerFlags().Expand().DoubleBorder());
        mainPanel->SetSizer(mainPanelSizer);
    }   
private:
    void OnSpinCtrlTextEnter(wxCommandEvent& evt)
    {
        wxWindow* win = dynamic_cast<wxWindow*>(evt.GetEventObject());

        win->Navigate();
    }
}; 

class MyApp : public wxApp
{
public:   
    bool OnInit() override
    {
        (new MyFrame())->Show();
        return true;
    }
}; wxIMPLEMENT_APP(MyApp);
EDIT
As I tested now, it would not work for me if I just called Navigate() for the panel which is the parent of the spin control, it must be wxSpinCtrl's Navigate() as shown in the code above.

mael15
Super wx Problem Solver
Super wx Problem Solver
Posts: 430
Joined: Fri May 22, 2009 8:52 am
Location: Bremen, Germany

Re: wxSpinCtrl Enter should do what Tab does

Post by mael15 » Wed Jan 15, 2020 7:42 am

doublemax wrote:
Tue Jan 14, 2020 8:39 pm
delayed using CallAfter
unfortunately it still does nothing.
PB wrote:
Tue Jan 14, 2020 8:39 pm
void OnSpinCtrlTextEnter(wxCommandEvent& evt)
{
wxWindow* win = dynamic_cast<wxWindow*>(evt.GetEventObject());

win->Navigate();
}
};
YES! Thank you!

mael15
Super wx Problem Solver
Super wx Problem Solver
Posts: 430
Joined: Fri May 22, 2009 8:52 am
Location: Bremen, Germany

Re: wxSpinCtrl Enter should do what Tab does

Post by mael15 » Wed Jan 15, 2020 9:47 am

A little follow up question:
Can I somehow put this in a class that a lot of different spin controls can inherit from?

Code: Select all

class DECLDIR_CONF spinCtrlBasis {
public:
	spinCtrlBasis(int winId){
		getEvtHandler()->Connect(winId, wxEVT_TEXT_ENTER, wxCommandEventHandler(spinCtrlBasis::onEnterKey));
		getEvtHandler()->Connect(winId, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler(spinCtrlBasis::handleValueChange));
	}
private:
	virtual void handleValueChange(wxCommandEvent& evt) = 0;
	virtual wxEvtHandler *getEvtHandler() = 0;

	void onEnterKey(wxCommandEvent& evt) {
		wxWindow* win = dynamic_cast<wxWindow*>(evt.GetEventObject());
		win->Navigate();
	}
};
I would like to have inheriting wxSpinCtrl and wxSpinCtrlDouble with this onEnterKey functionality plus whatever they have to do when the value changes.

PB
Part Of The Furniture
Part Of The Furniture
Posts: 2422
Joined: Sun Jan 03, 2010 5:45 pm

Re: wxSpinCtrl Enter should do what Tab does

Post by PB » Wed Jan 15, 2020 12:05 pm

I do not know if subclassing the controls for that is the best idea. E.g, in wxFormBuilder support for custom controls, even those deriving from the supported ones with the same public interface is not the best.

Seeing as wxSpinCtrl and wxSpinCtrl double do not have a common "spin" ancestor, I would probably consider using the curiously recurring template pattern. The basic implementation of a spin control handling <Enter> could look like this

Code: Select all

#include <wx/wx.h>
#include <wx/spinctrl.h>

template <typename BaseSpinCtrl>
class wxCommonSpinCtrlHandlingEnter : public BaseSpinCtrl
{
public:
    wxCommonSpinCtrlHandlingEnter(wxWindow* parent, wxWindowID id = wxID_ANY,  const wxString& value = wxEmptyString) 
        : BaseSpinCtrl(parent, id, value, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS | wxTE_PROCESS_ENTER)     
    {
        Bind(wxEVT_TEXT_ENTER, [this](wxCommandEvent&){ Navigate(); });        
    }
};
 
typedef wxCommonSpinCtrlHandlingEnter<wxSpinCtrl> wxSpinCtrlHandlingEnter;
typedef wxCommonSpinCtrlHandlingEnter<wxSpinCtrlDouble> wxSpinCtrlDoubleHandlingEnter;

class MyFrame : public wxFrame
{
public:
    MyFrame () : wxFrame(nullptr, wxID_ANY, "Test")
    {
        wxPanel* mainPanel = new wxPanel(this);        
        wxBoxSizer* mainPanelSizer = new wxBoxSizer(wxVERTICAL);

        auto spinCtrl = new wxSpinCtrlHandlingEnter(mainPanel, wxID_ANY, "5");
        spinCtrl->SetRange(1, 10);
        
        auto spinCtrlDouble = new  wxSpinCtrlDoubleHandlingEnter(mainPanel, wxID_ANY, "5");
        spinCtrlDouble->SetRange(1, 10);        
        
        mainPanelSizer->Add(spinCtrl, wxSizerFlags().Expand().DoubleBorder());
        mainPanelSizer->Add(spinCtrlDouble, wxSizerFlags().Expand().DoubleBorder());
        mainPanelSizer->Add(new wxTextCtrl(mainPanel, wxID_ANY, "blah"), wxSizerFlags().Expand().DoubleBorder());
        mainPanelSizer->Add(new wxButton(mainPanel, wxID_ANY, "Button"), wxSizerFlags().Expand().DoubleBorder());
        mainPanel->SetSizer(mainPanelSizer);
    }
}; 

class MyApp : public wxApp
{
public:   
    bool OnInit() override
    {
        (new MyFrame())->Show();
        return true;
    }
}; wxIMPLEMENT_APP(MyApp);
BTW, the official documentation for wxSpinCtrlDouble, unlike that for wxSpinCtrl, has nothing about processing enter. On MSW it works (the control is a generic one combining wxTextCtrl and wxSpinCtrl) but it may not work elsewhere.

EDIT
When using virtual functions is required, it gets tricky and the CRTP may not be that useful. The best I could come with was

Code: Select all

#include <wx/wx.h>
#include <wx/spinctrl.h>

#include <type_traits>

template <typename BaseSpinCtrl>
class wxCommonSpinCtrlHandlingEnter : public BaseSpinCtrl
{
public:
    wxCommonSpinCtrlHandlingEnter(wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& value = wxEmptyString) 
        : BaseSpinCtrl(parent, id, value, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS | wxTE_PROCESS_ENTER)     
    {                
        Bind(wxEVT_TEXT_ENTER, [this](wxCommandEvent&){ Navigate(); });        

        if ( std::is_same<BaseSpinCtrl, wxSpinCtrl>::value )
        {
            Bind(wxEVT_SPINCTRL, [this](wxSpinEvent& evt){ ProcessValue(evt.GetPosition()); });
        }
        else
        {
            Bind(wxEVT_SPINCTRLDOUBLE, [this](wxSpinDoubleEvent& evt){ ProcessValue(evt.GetValue()); });
        }
    }
protected:
    virtual void ProcessValue(int value)
    {    
        wxLogMessage("Processing INT value: %d", value);
    }

    virtual void ProcessValue(double value)
    {    
        wxLogMessage("Processing DOUBLE value: %g", value);
    }
};
 
typedef wxCommonSpinCtrlHandlingEnter<wxSpinCtrl> wxSpinCtrlHandlingEnter;
typedef wxCommonSpinCtrlHandlingEnter<wxSpinCtrlDouble> wxSpinCtrlDoubleHandlingEnter;

class MySpinCtrlHandlingEnter : public wxSpinCtrlHandlingEnter
{
public:
    MySpinCtrlHandlingEnter(wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& value = wxEmptyString)
        : wxSpinCtrlHandlingEnter(parent, id, value) {}
protected:
    void ProcessValue(int value) override
    {    
        wxLogMessage("Hello from MySpinCtrlHandlingEnter::ProcessValue value: %d", value);
    }
};

class MyFrame : public wxFrame
{
public:
    MyFrame () : wxFrame(nullptr, wxID_ANY, "Test", wxDefaultPosition, wxSize(600, 600))
    {
        wxPanel* mainPanel = new wxPanel(this);        
        wxBoxSizer* mainPanelSizer = new wxBoxSizer(wxVERTICAL);

        auto spinCtrl = new wxSpinCtrlHandlingEnter(mainPanel, wxID_ANY, "5");
        spinCtrl->SetRange(1, 10);
        mainPanelSizer->Add(spinCtrl, wxSizerFlags().Expand().DoubleBorder());
        
        auto spinCtrlDouble = new wxSpinCtrlDoubleHandlingEnter(mainPanel, wxID_ANY, "5");
        spinCtrlDouble->SetRange(1, 10);                        
        mainPanelSizer->Add(spinCtrlDouble, wxSizerFlags().Expand().DoubleBorder());

        auto mySpinCtrl = new MySpinCtrlHandlingEnter(mainPanel, wxID_ANY, "5");
        mySpinCtrl->SetRange(1, 10);                        
        mainPanelSizer->Add(mySpinCtrl, wxSizerFlags().Expand().DoubleBorder());

         wxTextCtrl* logCtrl = new wxTextCtrl(mainPanel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 
            wxTE_MULTILINE | wxTE_READONLY | wxTE_RICH2);     
        wxLog::SetActiveTarget(new wxLogTextCtrl(logCtrl));   
        wxLog::DisableTimestamp();
        mainPanelSizer->Add(logCtrl, wxSizerFlags().Proportion(1).Expand().DoubleBorder());
        
        mainPanel->SetSizer(mainPanelSizer);
    }
}; 

class MyApp : public wxApp
{
public:   
    bool OnInit() override
    {
        (new MyFrame())->Show();
        return true;
    }
}; wxIMPLEMENT_APP(MyApp);
I would investigate if it this is not possible to do that some other way, e.g. using validators.

mael15
Super wx Problem Solver
Super wx Problem Solver
Posts: 430
Joined: Fri May 22, 2009 8:52 am
Location: Bremen, Germany

Re: wxSpinCtrl Enter should do what Tab does

Post by mael15 » Thu Jan 16, 2020 1:55 pm

PB wrote:
Wed Jan 15, 2020 12:05 pm
On MSW it works (the control is a generic one combining wxTextCtrl and wxSpinCtrl) but it may not work elsewhere.
That is okay, my program will always be limited to windows.
Really helpful answer, thank you! I also learned something new about how to use templates. =D> :D

Post Reply