Page 1 of 1

wxSpinCtrl Enter should do what Tab does

Posted: Tue Jan 14, 2020 5:41 pm
by mael15
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?

Re: wxSpinCtrl Enter should do what Tab does

Posted: Tue Jan 14, 2020 7:48 pm
by ONEEYEMAN
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.

Re: wxSpinCtrl Enter should do what Tab does

Posted: Tue Jan 14, 2020 7:50 pm
by mael15
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).

Re: wxSpinCtrl Enter should do what Tab does

Posted: Tue Jan 14, 2020 7:59 pm
by doublemax
Without trying, i'd say: Catch wxEVT_TEXT_ENTER event and call Navigate() in the event handler.

Re: wxSpinCtrl Enter should do what Tab does

Posted: Tue Jan 14, 2020 8:08 pm
by mael15
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.

Re: wxSpinCtrl Enter should do what Tab does

Posted: Tue Jan 14, 2020 8:39 pm
by doublemax
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();
    });

Re: wxSpinCtrl Enter should do what Tab does

Posted: Tue Jan 14, 2020 8:39 pm
by PB
mael15 wrote: Tue Jan 14, 2020 8:08 pmThat 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.

Re: wxSpinCtrl Enter should do what Tab does

Posted: Wed Jan 15, 2020 7:42 am
by mael15
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!

Re: wxSpinCtrl Enter should do what Tab does

Posted: Wed Jan 15, 2020 9:47 am
by mael15
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.

Re: wxSpinCtrl Enter should do what Tab does

Posted: Wed Jan 15, 2020 12:05 pm
by PB
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.

Re: wxSpinCtrl Enter should do what Tab does

Posted: Thu Jan 16, 2020 1:55 pm
by mael15
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