How to prevent a wxTextCtrl from loosing focus?

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
User avatar
Parduz
I live to help wx-kind
I live to help wx-kind
Posts: 188
Joined: Fri Jan 30, 2015 1:48 pm
Location: Bologna, Italy

How to prevent a wxTextCtrl from loosing focus?

Post by Parduz »

I want that the user can't "leave" a wxTextCtrl until the value inside it is "valid".
Which means that what i want is to prevent a wxTextCtrl to loose the Focus, no matter what causes it (mouse, keystrokes).

How can i obtain this?

*EDIT:
While the whole application is a fullscreen app, i don't want to prevent the user to go to another app; all i need i that the focused wxTextCtrl stay focused while my app is active. The user should not be able to anything else than typing on the text or press ESC or ENTER
PB
Part Of The Furniture
Part Of The Furniture
Posts: 4193
Joined: Sun Jan 03, 2010 5:45 pm

Re: How to prevent a wxTextCtrl from loosing focus?

Post by PB »

TBH, I consider such behaviour not only rather unusual but also quite user hostile.

Anyway, have you tried to handle wxEVT_KILL_FOCUS and when needed, from this handler (making sure to call wxEvent::Skip()), use CallAfter() (this is needed) where you return the focus to the control using SetFocus().
Kvaz1r
Super wx Problem Solver
Super wx Problem Solver
Posts: 357
Joined: Tue Jun 07, 2016 1:07 pm

Re: How to prevent a wxTextCtrl from loosing focus?

Post by Kvaz1r »

PB wrote: Anyway, have you tried to handle wxEVT_KILL_FOCUS and when needed, from this handler (making sure to call wxEvent::Skip()), use CallAfter() (this is needed) where you return the focus to the control using SetFocus().
Why there is need to use CallAfter? Code below works for me:

Code: Select all

#include <wx/wx.h>

class MyFrame : public wxFrame
	{
	public:
		MyFrame() : wxFrame(NULL, wxID_ANY, "Test", wxDefaultPosition, wxSize(600, 400))
			{                                       
			wxBoxSizer* Sizer = new wxBoxSizer( wxHORIZONTAL );

			m_textCtrl1 = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 );
			m_textCtrl1->Bind(wxEVT_KILL_FOCUS,[&](wxFocusEvent& event)
				{
				if(m_textCtrl1->GetValue() != "stop")
				    m_textCtrl1->SetFocus();
				else
					event.Skip();
				});
			Sizer->Add( m_textCtrl1, 0, wxALL, 5 );

			m_textCtrl2 = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 );
			Sizer->Add( m_textCtrl2, 0, wxALL, 5 );

			this->SetSizer( Sizer );
			this->Layout();
			this->Centre( wxBOTH );
			}
	protected:
		wxTextCtrl* m_textCtrl1;
		wxTextCtrl* m_textCtrl2;
	};

class MyApp : public wxApp
	{
	public:   
		bool OnInit()
			{
			(new MyFrame)->Show();
			return true;
			}
	}; 
wxIMPLEMENT_APP(MyApp);
Btw, I also don't see advantages for user in this behaviour.
PB
Part Of The Furniture
Part Of The Furniture
Posts: 4193
Joined: Sun Jan 03, 2010 5:45 pm

Re: How to prevent a wxTextCtrl from loosing focus?

Post by PB »

Kvaz1r wrote:Why there is need to use CallAfter?
wxWidgets docs for wxFocusEvent has this
Also note that wxEVT_KILL_FOCUS handler must not call wxWindow::SetFocus() as this, again, is not supported by all native controls. If you need to do this, consider using the Delayed Action Mechanism described in wxIdleEvent documentation.
I believe CallAfter() is the modern equivalent of the delayed action mechanism using wxIdleEvent.

It seems to work but handling focus is tricky and what works one one platform may not work on another. Here's the version with CallAfter():

Code: Select all

#include <wx/wx.h>

class MyDialog : public wxDialog
{
public:
    MyDialog () : wxDialog(NULL, wxID_ANY, "Test",  wxDefaultPosition, wxSize(800, 600))
    {                                                      
        wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL);

        mainSizer->Add(new wxTextCtrl(this, wxID_ANY), wxSizerFlags().Expand().Border());

        mainSizer->Add(new wxStaticText(this, wxID_ANY, "The next wxTextCtrl must contain \"ABC\""),
            wxSizerFlags().Expand().Border(wxTOP | wxLEFT));
        
        wxTextCtrl* textCtrl = new wxTextCtrl(this, wxID_ANY);
        textCtrl->Bind(wxEVT_KILL_FOCUS, &MyDialog::OnKillFocus, this);
        mainSizer->Add(textCtrl, wxSizerFlags().Expand().Border());

        mainSizer->Add(new wxTextCtrl(this, wxID_ANY), wxSizerFlags().Expand().Border());                        
        mainSizer->Add(new wxButton(this, wxID_ANY, "Button"), wxSizerFlags().Expand().Border());
        
        wxTextCtrl* logCtrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 
            wxTE_MULTILINE | wxTE_READONLY | wxTE_RICH2);     
        wxLog::SetActiveTarget(new wxLogTextCtrl(logCtrl)); 
        mainSizer->Add(logCtrl, wxSizerFlags().Expand().Border().Proportion(1));

        
        SetSizer(mainSizer);                 
    }	
private:    
    void OnKillFocus(wxFocusEvent& event)
    {
        event.Skip();
        
        wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>(event.GetEventObject());                
        const wxString value = textCtrl->GetValue();
        
        if ( value != "ABC" )
        {
            wxLogMessage("\"%s\" is not a valid value!", value);
            CallAfter([textCtrl] { textCtrl->SetFocus(); } );           
        }
        else
        {
            wxLogMessage("Value is valid!");        
        }
    }
};

class MyApp : public wxApp
{
public:	
    bool OnInit()
    {
        MyDialog().ShowModal();
        return false;
    }
}; wxIMPLEMENT_APP(MyApp);
Kvaz1r
Super wx Problem Solver
Super wx Problem Solver
Posts: 357
Joined: Tue Jun 07, 2016 1:07 pm

Re: How to prevent a wxTextCtrl from loosing focus?

Post by Kvaz1r »

PB wrote: wxWidgets docs for wxFocusEvent has this
Also note that wxEVT_KILL_FOCUS handler must not call wxWindow::SetFocus() as this, again, is not supported by all native controls. If you need to do this, consider using the Delayed Action Mechanism described in wxIdleEvent documentation.
I believe CallAfter() is the modern equivalent of the delayed action mechanism using wxIdleEvent.
Right, thanks for the explanation.
PB
Part Of The Furniture
Part Of The Furniture
Posts: 4193
Joined: Sun Jan 03, 2010 5:45 pm

Re: How to prevent a wxTextCtrl from loosing focus?

Post by PB »

The code created by Kvaz1r and myself is very similar and thus shares the same bug. At least on MSW, it crashes when the "protected" wxTextCtrl has focus and you try to exit the program by closing its window.

Therefore it seems that such situation must be handled, perhaps by disconnecting the event handler when the control is being destroyed.
User avatar
Parduz
I live to help wx-kind
I live to help wx-kind
Posts: 188
Joined: Fri Jan 30, 2015 1:48 pm
Location: Bologna, Italy

Re: How to prevent a wxTextCtrl from loosing focus?

Post by Parduz »

Wow, thanks.
I'll try your suggestions right now.
Kvaz1r wrote:Btw, I also don't see advantages for user in this behaviour.
PB wrote:TBH, I consider such behaviour not only rather unusual but also quite user hostile.
This is true, and desired: the app is the control interface (running on a BBB with a touchscreen) for some devices on an automatic machine.
The value of the textctrl will be sent to the device, and the user have to finish one "edit action" before do anything else.
The edit action ends (and the data sent) when the user aborts (ESC), or confirms (ENTER) a validated value.
Problems are:
  • there's multiple textctrl per page, multiple pages, and we want that each "edit action" ends correctly (the beginning of the edit requires the device to stop doing real-time tasks)
  • Boss don't like popup/modal msgbox for data entry
User avatar
Parduz
I live to help wx-kind
I live to help wx-kind
Posts: 188
Joined: Fri Jan 30, 2015 1:48 pm
Location: Bologna, Italy

Re: How to prevent a wxTextCtrl from loosing focus?

Post by Parduz »

PB wrote: It seems to work but handling focus is tricky and what works one one platform may not work on another. Here's the version with CallAfter():
Adding an identical textctrl (all my textctrl behave the same) fires an infinite loop of Set/KillFocus.

My solution is to store the pointer to the ctrl that will gain and immediatly loose the focus to make it skip the validation.
I don't like this so much.
Do you have better ideas?

Code: Select all

#include <wx/wx.h>

class MyDialog : public wxDialog
{
private:
	wxWindow* SkipFocusCtrl;


public:
    MyDialog () : wxDialog(NULL, wxID_ANY, "Test",  wxDefaultPosition, wxSize(800, 600))
	{
		wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL);

		mainSizer->Add(new wxTextCtrl(this, wxID_ANY), wxSizerFlags().Expand().Border());

		mainSizer->Add(new wxStaticText(this, wxID_ANY, "The next wxTextCtrl must contain \"ABC\""),
		wxSizerFlags().Expand().Border(wxTOP | wxLEFT));

		wxTextCtrl* textCtrl1 = new wxTextCtrl(this, wxID_ANY);
		textCtrl1->Bind(wxEVT_KILL_FOCUS, &MyDialog::OnKillFocus, this);
		mainSizer->Add(textCtrl1, wxSizerFlags().Expand().Border());
		textCtrl1->SetValue("123");

		wxTextCtrl* textCtrl2 = new wxTextCtrl(this, wxID_ANY);
		textCtrl2->Bind(wxEVT_KILL_FOCUS, &MyDialog::OnKillFocus, this);
		mainSizer->Add(textCtrl2, wxSizerFlags().Expand().Border());
		textCtrl2->SetValue("456");

		mainSizer->Add(new wxTextCtrl(this, wxID_ANY), wxSizerFlags().Expand().Border());
		mainSizer->Add(new wxButton(this, wxID_ANY, "Button"), wxSizerFlags().Expand().Border());


		wxTextCtrl* logCtrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize,
		wxTE_MULTILINE | wxTE_READONLY | wxTE_RICH2);
		wxLog::SetActiveTarget(new wxLogTextCtrl(logCtrl));
		mainSizer->Add(logCtrl, wxSizerFlags().Expand().Border().Proportion(1));

		SetSizer(mainSizer);

		SkipFocusCtrl = nullptr;
	}
private:

    void OnKillFocus(wxFocusEvent& event)
    {
        event.Skip();

		wxWindow* nextFocus = event.GetWindow();
		wxObject* currFocus = event.GetEventObject();
		wxLogMessage("%p is loosing focus for %p", currFocus, nextFocus);

		if (SkipFocusCtrl==currFocus) {
            wxLogMessage("Skipping focus control");
			SkipFocusCtrl = nullptr;
			return;
		}


        wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>(currFocus);
        const wxString value = textCtrl->GetValue();

        if ( value != "ABC" )
        {
            wxLogMessage("\"%s\" is not a valid value!", value);
            SkipFocusCtrl = nextFocus;
            CallAfter([textCtrl] { textCtrl->SetFocus(); } );
        }
        else
        {
            wxLogMessage("Value is valid!");
        }
    }
};

class MyApp : public wxApp
{
public:
    bool OnInit()
    {
        MyDialog().ShowModal();
        return false;
    }
};
wxIMPLEMENT_APP(MyApp);
PB
Part Of The Furniture
Part Of The Furniture
Posts: 4193
Joined: Sun Jan 03, 2010 5:45 pm

Re: How to prevent a wxTextCtrl from loosing focus?

Post by PB »

Parduz wrote:Adding an identical textctrl (all my textctrl behave the same) fires an infinite loop of Set/KillFocus.
Well, I am not surprised. You have multiple invalid text controls in the code and the condition that an invalid control must not lose focus.

In other words:
What the code actually does is not preventing losing focus, the control loses the focus but tries to get it back. However, in your case the text control from which the focus needs to be taken back is invalid as well and thus wants to take focus back as well. This is a situation which must be dealt with. I assumed it was clear that the code I posted is just a technical proof of concept showing a possible scenario.

In a real world application, the handling needs to be more complex, depending on the form design and logic.

It is also possible that this way is not the way to approach the problem at all, but I am not sure which other ways there are.

EDIT
Sorry, I have somehow missed that you do know why the infinite loop happens, so my explanation was not needed.
ONEEYEMAN
Part Of The Furniture
Part Of The Furniture
Posts: 7459
Joined: Sat Apr 16, 2005 7:22 am
Location: USA, Ukraine

Re: How to prevent a wxTextCtrl from loosing focus?

Post by ONEEYEMAN »

Hi,
If the user still needs to either confirm with pressing Enter or deny with pressing ESC, why not leave this and do the check on the (presumably default) button click handler?
Or whatever Enter action is handled by.

This will be more natural...

Thank you.
User avatar
Parduz
I live to help wx-kind
I live to help wx-kind
Posts: 188
Joined: Fri Jan 30, 2015 1:48 pm
Location: Bologna, Italy

Re: How to prevent a wxTextCtrl from loosing focus?

Post by Parduz »

ONEEYEMAN wrote:Hi,
If the user still needs to either confirm with pressing Enter or deny with pressing ESC, why not leave this and do the check on the (presumably default) button click handler?
Or whatever Enter action is handled by.

This will be more natural...
'cause (as example) the user could press a "change page" button, an action that should send a series of command to the device to exit from the edit mode, switch page, notifiy the new page to the device, get all the data, etc...
It's feasible but it involve a whole lot of management, timing issue (that device is SLOW and the communication with it is via serial, etc) so SEEMS to me that forcing the user to finish what he's doing is better and simpler.
User avatar
doublemax
Moderator
Moderator
Posts: 19116
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: How to prevent a wxTextCtrl from loosing focus?

Post by doublemax »

I'm still not 100% sure how your GUI works, i'll assume something like this:
- there are several pages with text controls (any maybe other controls)
- in order for a certain function to get executed, all the controls on all pages must have valid data

In that case, i would solve it like this:
When the user clicks "execute" or "next page", the content of the text controls is checked. If they are not valid, display a wxRichToolTip pointing at the first text control with wrong data *and* give focus to that text control. I think it's better to show a tooltip instead of a message box, because it doesn't require an additional action by the user.

Additionally, you could mark all text controls that have invalid values with a different colored background or border.
Use the source, Luke!
User avatar
Parduz
I live to help wx-kind
I live to help wx-kind
Posts: 188
Joined: Fri Jan 30, 2015 1:48 pm
Location: Bologna, Italy

Re: How to prevent a wxTextCtrl from loosing focus?

Post by Parduz »

doublemax wrote:I'm still not 100% sure how your GUI works, i'll assume something like this:
- there are several pages with text controls (any maybe other controls)
- in order for a certain function to get executed, all the controls on all pages must have valid data

In that case, i would solve it like this:
When the user clicks "execute" or "next page", the content of the text controls is checked. If they are not valid, display a wxRichToolTip pointing at the first text control with wrong data *and* give focus to that text control. I think it's better to show a tooltip instead of a message box, because it doesn't require an additional action by the user.

Additionally, you could mark all text controls that have invalid values with a different colored background or border.
This is true, but the real situation is much more complex (or messed up, if you want) and until prooven impossible or just unbelievable hard, i'll stick to the focus management :)
Post Reply