Discussion:
Problems with losing focus on wxTextCtrl in wxGTK
(too old to reply)
Marco DeFreitas
2011-08-21 20:31:18 UTC
Permalink
I have an application where I have multiple wxTextCtrls on a panel. I
would like to validate the contents to be sure they are valid (i.e,
positive floating point number). On an invalid entry, I would like to
display a wxMessageBox with the error. When the user dismisses the
message box I would like to set focus to the text field in question
and select the text in it.

It was no problem to validate when the user hits the enter key.

I also wanted to validate when the focus is lost on the (possibly bad)
text field, for example the user might enter text in one field and
then click on another text field.

I was having problems changing focus within a focus event handler;
that is, when focus is lost on a text field with bad data, I was
having problems setting the focus back to the text field with bad
data. However, I got things working reasonably well by just flagging
the error in the "Kill Focus" event handler and then changing the
focus in an "Idle" event handler that checked for the flagged error.

The problem is as follows:
1) I enter bad data in text field #1
2) I click on text field #2
3) I get a wxMessageBox with an error msg; I click OK
4) The focus is now on text field #1 with its text selected (as I
wanted), but the text appears and disappears on text field #2 as I
swipe the cursor back and forth on text field #2. This keeps happening
until I click on text field #2. It seems as though the system thinks
there is still some sort of "psuedo focus" on text field #2? Note that
this problem does not occur if I do not pop up the wxMessageBox.

I tried to recreate the problem in this test program. In this sample,
the text in text field #2 does not appear and disappears, it gets
highlighted/unhighlighted as I swipe the cursor over it; I assume this
is a symptom of the same problem.

Is this a bug? Am I doing something wrong? Is there a better way to
accomplish what I am trying to do?

The sample is as follows... it just have two text fields that take a
valid floating point number:

__________________________________


#include <iostream>
using namespace std;
#include "wx/wx.h"

#define TC_ID_1 wxID_HIGHEST + 1
#define TC_ID_2 TC_ID_1 + 1

class MyApp : public wxApp
{
public:
virtual bool OnInit();
};

class MyFrame : public wxFrame
{
public:
MyFrame(const wxString& title);

bool ValidateMe(int id);
void OnTextEntered(wxCommandEvent& e);
void OnIdle(wxIdleEvent& e);
void OnLostFocus(wxFocusEvent& e);

private:
wxTextCtrl *m_tc1;
wxTextCtrl *m_tc2;
wxTextCtrl *m_err_tc;
bool m_err_ack;
DECLARE_EVENT_TABLE()
};

DECLARE_APP(MyApp)
IMPLEMENT_APP(MyApp)

bool MyApp::OnInit()
{
MyFrame *frame = new MyFrame(wxT("Minimal App"));
frame->Show(true);
return true;
}

BEGIN_EVENT_TABLE(MyFrame, wxFrame)
EVT_TEXT_ENTER(TC_ID_1, MyFrame::OnTextEntered)
EVT_TEXT_ENTER(TC_ID_2, MyFrame::OnTextEntered)
EVT_IDLE( MyFrame::OnIdle)
END_EVENT_TABLE()


MyFrame::MyFrame(const wxString& title) : wxFrame(NULL, wxID_ANY,
title)
{
m_err_tc = 0;
m_err_ack = false;

m_tc1 = new wxTextCtrl(this, TC_ID_1, "0",
wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER);
m_tc2 = new wxTextCtrl(this, TC_ID_2, "0",
wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER);

m_tc1->Connect(wxEVT_KILL_FOCUS, wxFocusEventHandler(
MyFrame::OnLostFocus), NULL, this);
m_tc2->Connect(wxEVT_KILL_FOCUS, wxFocusEventHandler(
MyFrame::OnLostFocus), NULL, this);

wxBoxSizer *s = new wxBoxSizer(wxVERTICAL);
s->Add(m_tc1, 0, wxEXPAND|wxALL, 5);
s->Add(m_tc2, 0, wxEXPAND|wxALL, 5);
SetSizerAndFit(s);
}

void MyFrame::OnTextEntered(wxCommandEvent& e)
{
ValidateMe(e.GetId());
}

void MyFrame::OnLostFocus(wxFocusEvent& e)
{
ValidateMe(e.GetId());
e.Skip();
}

void MyFrame::OnIdle(wxIdleEvent& e)
{
if (m_err_ack) {
wxTextCtrl *tc = m_err_tc;
m_err_tc = 0;
m_err_ack = false;
tc->SetFocus();
tc->SetSelection(-1, -1);
}
e.Skip();
}

bool MyFrame::ValidateMe(int id)
{
bool ok = true;
wxTextCtrl *tc = 0;

if (id == TC_ID_1) {
tc = m_tc1;
} else if (id == TC_ID_2) {
tc = m_tc2;
}

if (tc) {
wxString s = tc->GetValue();
double d;
if (!s.ToDouble(&d)) {
ok = false;
tc->ChangeValue("0");
m_err_tc = tc;

// Comment out the next showing of the dialog box and the
// error does not occur
wxMessageDialog d(tc, "Not a valid float!", "Error",
wxOK|wxICON_ERROR);
d.ShowModal();

m_err_ack = true;
}
}

return ok;
}
Marco DeFreitas
2011-08-21 20:38:59 UTC
Permalink
I forgot to mention I am using wxGTK-2.8.12 on SUSE Linux.
Marco DeFreitas
2011-08-21 20:52:56 UTC
Permalink
Also, I am using gtk2-2.8.
Vadim Zeitlin
2011-08-23 12:20:09 UTC
Permalink
Post by Marco DeFreitas
I also wanted to validate when the focus is lost on the (possibly bad)
text field, for example the user might enter text in one field and
then click on another text field.
I was having problems changing focus within a focus event handler;
that is, when focus is lost on a text field with bad data, I was
having problems setting the focus back to the text field with bad
data. However, I got things working reasonably well by just flagging
the error in the "Kill Focus" event handler and then changing the
focus in an "Idle" event handler that checked for the flagged error.
Hello Marco,

This is indeed the right thing to do as you can't always (i.e. on all
platforms) switch focus from focus event handler itself. Moreover, I
suspect -- although I didn't test this so I could be wrong -- that your
remaining problem is also due to "doing too much" from this event handler.
It should basically just set some internal flags and return, in particular
it shouldn't result in any focus changes itself and showing a message box
definitely does change focus.

So I think things should work better if you call wxMessageBox() from your
EVT_IDLE handler too. Alternatively, you could use wxLogError() (or maybe
wxLogWarning()) as they're shown during idle time and not immediately as
well.

Good luck,
VZ
--
TT-Solutions: wxWidgets consultancy and technical support
http://www.tt-solutions.com/
Marco DeFreitas
2011-08-23 18:46:11 UTC
Permalink
 This is indeed the right thing to do as you can't always (i.e. on all
platforms) switch focus from focus event handler itself. Moreover, I
suspect -- although I didn't test this so I could be wrong -- that your
remaining problem is also due to "doing too much" from this event handler.
It should basically just set some internal flags and return, in particular
it shouldn't result in any focus changes itself and showing a message box
definitely does change focus.
 So I think things should work better if you call wxMessageBox() from your
EVT_IDLE handler too. Alternatively, you could use wxLogError() (or maybe
wxLogWarning()) as they're shown during idle time and not immediately as
well.
 Good luck,
VZ
I did try to put the wxMessageDialog in the Idle handler, but this did
not help. I tried to use wxLogError, but it was even worse, that is,
the message box was not centered near my text field (it was in the
center of the screen) and the text I wanted to select (in text field
#1) was not selected.

In both cases (wxMessageDialog or wxLogError in the Idle handler) text
field #2 gets highlighted/unhighlighted as I swipe the cursor across
text field #2.
Marco DeFreitas
2011-08-26 22:39:19 UTC
Permalink
Has anyone been able to reproduce the error with the sample i
provided?

Thanks...
e***@gmail.com
2012-06-12 19:23:06 UTC
Permalink
Post by Marco DeFreitas
Has anyone been able to reproduce the error with the sample i
provided?
Thanks...
Hello,
I can't reproduce the error on WinXP with wxWidgets-2.8.12 and MinGW GCC 4.6.1.
I found this disccussion when searching for an example of "Delayed Action Mechanism". I have updated your example to check the double value in the OnIdle handler after a kill focus event, hopefully it helps other people.
I don't known if the "dynamically connected" mode can be improved by passing the ID or object directly to the OnIdle handler (SetEventObject or SetId??)
Notice the difference in the counter behaviour in the status bar for the static and dynamically connected OnIdle.

#include "wx/wx.h"

#define EV_DYN // undef for static connected OnIdle

class MyApp : public wxApp
{
public:
virtual bool OnInit();
};

class MyFrame : public wxFrame
{
public:
MyFrame(const wxString& title);

private:
void ValidateMe(int id);
void OnTextEntered(wxCommandEvent& event);
void OnIdle(wxIdleEvent& event);
void OnLostFocus(wxFocusEvent& event);

wxTextCtrl *m_tc1;
wxTextCtrl *m_tc2;
int m_check_id;
long m_IdleCnt;
DECLARE_EVENT_TABLE()
};

enum
{
ID_TC_1 = 1,
ID_TC_2,
};

BEGIN_EVENT_TABLE(MyFrame, wxFrame)
EVT_TEXT_ENTER(ID_TC_1, MyFrame::OnTextEntered)
EVT_TEXT_ENTER(ID_TC_2, MyFrame::OnTextEntered)
#ifndef EV_DYN
EVT_IDLE( MyFrame::OnIdle)
#endif
END_EVENT_TABLE()

IMPLEMENT_APP(MyApp)

bool MyApp::OnInit()
{
MyFrame *frame = new MyFrame(wxT("Minimal App"));
frame->Show(true);
return true;
}

MyFrame::MyFrame(const wxString& title) : wxFrame(NULL, wxID_ANY, title)
{
m_check_id = 0;
m_IdleCnt = 0;

wxPanel * panel = new wxPanel(this, wxID_ANY); // Handy for using TAB
m_tc1 = new wxTextCtrl(panel, ID_TC_1, "0",
wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER);
m_tc2 = new wxTextCtrl(panel, ID_TC_2, "0",
wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER);

m_tc1->Connect(wxEVT_KILL_FOCUS, wxFocusEventHandler(
MyFrame::OnLostFocus), NULL, this);
m_tc2->Connect(wxEVT_KILL_FOCUS, wxFocusEventHandler(
MyFrame::OnLostFocus), NULL, this);

wxBoxSizer *s = new wxBoxSizer(wxVERTICAL);
s->Add(m_tc1, 0, wxEXPAND|wxALL, 5);
s->Add(m_tc2, 0, wxEXPAND|wxALL, 5);
panel->SetSizer(s);

// Create and enable status bar.
CreateStatusBar();
SetStatusText( wxString("Welcome to ") << title );

SetMinSize( ClientToWindowSize( s->CalcMin()));
}

void MyFrame::OnTextEntered(wxCommandEvent& event)
{
ValidateMe(event.GetId());
}

void MyFrame::OnLostFocus(wxFocusEvent& event)
{
// Ask OnIdle to check the value
// If ValidateMe is done here: unpredictable results can occur
// because this function calls SetFocus.
m_check_id = event.GetId();
// Calling ValidateMe in wxMSW 2.8.12 seems to work.
// ValidateMe(event.GetId());
#ifdef EV_DYN
Connect( wxEVT_IDLE, wxIdleEventHandler( MyFrame::OnIdle ) );
#endif
}

void MyFrame::OnIdle(wxIdleEvent& event)
{
m_IdleCnt++;
SetStatusText( wxString::Format(wxT("IdleCnt: %ld"), m_IdleCnt) );

if( m_check_id == ID_TC_1 || m_check_id == ID_TC_2)
{
ValidateMe( m_check_id);
}
m_check_id = 0;
#ifdef EV_DYN
if( ! Disconnect( wxEVT_IDLE, wxIdleEventHandler( MyFrame::OnIdle ) ) )
{
SetStatusText( wxT("Disconnect failed") );
}
#endif
event.Skip();
}

void MyFrame::ValidateMe(int id)
{
wxTextCtrl *tc = 0;

if (id == ID_TC_1) {
tc = m_tc1;
} else if (id == ID_TC_2) {
tc = m_tc2;
}

if (tc) {
double d;

if( ! tc->GetValue().ToDouble( &d) )
{
wxMessageBox( "Not a valid float!", "Error", wxOK|wxICON_ERROR);
tc->ChangeValue( "0");
tc->SetFocus(); // set focus back to wrong input field
tc->SetSelection(-1, -1); // select the whole string
}
}
}

Eric.

Loading...