BorlandTalk.com Forum Index BorlandTalk.com
Borland discussion newsgroups
 
Archives   FAQFAQ   SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

How to find out inside TEdit::OnExit which control is about

 
Post new topic   Reply to topic    BorlandTalk.com Forum Index -> C++ Builder (VCL Components Usage)
View previous topic :: View next topic  
Author Message
M_R
Guest





PostPosted: Tue May 24, 2005 8:46 am    Post subject: How to find out inside TEdit::OnExit which control is about Reply with quote



Having a form with some TEdit-fields and an OK and CANCEL-Button, I
use TEdit's OnExit to check if the value the user entered was correct
and if it is not I set the focus directly to the appropriate TEdit.

This works fine but now I want to skip this test, if the user clicks
on the CANCEL-Button: there is no need to validate an edit-field, if
the result is not needed anyway.

So: can I find out (from within TEdit's OnExit-function) which control
is about to get the focus (or how else can I implement the needed
functionality)?

~~~~

In this context it would be useful to handle the case when the
window-close icon [x] has been clicked on separetely? How can I handle
this case?


Thanks a lot,

Michael
Back to top
JD
Guest





PostPosted: Tue May 24, 2005 10:04 am    Post subject: Re: How to find out inside TEdit::OnExit which control is ab Reply with quote




M_R <> wrote:
Quote:

[...] TEdit's OnExit to check [...] and if it is not I set
the focus directly to the appropriate TEdit.

This approach is problematic - to change focus when windows is
setting focus. It can lead to all sorts of problems.


Quote:
This works fine but now I want to skip this test, if the user clicks
on the CANCEL-Button: there is no need to validate an edit-field, if
the result is not needed anyway.

So: can I find out (from within TEdit's OnExit-function) which control
is about to get the focus

[...] or how else can I implement the needed functionality)?

I *always* validate the input at the time when the user clicks
my execute button - not as the user inputs it. It eliminates
alot of code and simplifies the code overall.

If however, you decide to keep your current design, I would
suggest that you use a single OnExit event assigned to all of
the TEdit's and in this event, post a custom message to the
form that included the TEdit as a parameter. You would also need a bool flag that you set when the Cancel Button is
clicked. For example:

//--- in the header -------------------------------------------
protected: // User declarations
virtual void __fastcall WndProc( TMessage &Message );
private: // User declarations
#define UWM_EDIT_EXIT (WM_USER + 100)
bool UserCanceled;


//--- in the unit ---------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
UserCanceled = false;
}
//-------------------------------------------------------------
void __fastcall TForm1::CancleButtonClick( TObject *Sender )
{
UserCanceled = true;
}
//-------------------------------------------------------------
void __fastcall TForm1::EditExit( TObject *Sender )
{
::PostMessage( Handle, UWM_EDIT_EXIT, WPARAM( Sender ), 0 );
}
//-------------------------------------------------------------
void __fastcall TForm1::WndProc( TMessage &Message )
{
if( Message.Msg == UWM_EDIT_EXIT )
{
TEdit *pEdit = static_cast<TEdit*>( Message.WParam );

switch( pEdit->Tag )
{
case 1: // validate Edit1
break;
}
or
if( pEdit == Edit1 )
{
// validate Edit1
}
}
TForm::WndProc( Message );
}
//-------------------------------------------------------------

~ JD


Back to top
M_R
Guest





PostPosted: Tue May 24, 2005 1:53 pm    Post subject: Re: How to find out inside TEdit::OnExit which control is ab Reply with quote




Thanks a lot for the extensive answer.

Yes, perhaps a validation at the end would be easier, but wouldn't it
be nice, if the user gets immediately an 'answer', if he entered
something wrong!?

So I tried your proposal...

Unfortuntely it does not work as intended:

Problem is that

void __fastcall TForm1::CancleButtonClick( TObject *Sender );

is NOT called AT ALL, when inside WndProc the TEdit-field pEdit gets
the focus (with SetFocus()), which is exactly than the case when the
validation failed. It is, when the validation was correct, i.e.
SetFocus() was not called.

void __fastcall TForm1::WndProc( TMessage &Message )
{
if( Message.Msg == UWM_EDIT_EXIT )
{
TEdit *pEdit = static_cast<TEdit*>( Message.WParam );
if(!CONDITION(pEdit->Text)) {
Beep();
pEdit->SetFocus();
}
}
TForm::WndProc( Message ); // ???
}

Unfortunately inside WndProc I can not find out, if the 'CancleButton'
gets the focus. In this case SetFocus() would not be needed.

P.S.

I'm not so sure about the last command-line. Is this also correct if
the caller-form has a different name, e.g. TFormMyPrg? (The compiler
accepts it)

~~~~~~~~

Is there no order the messages are evaluated? If the
Set-Focus-To-Cancel-Button-Message would be evaluated before our
user-message we wouldn't call SetFocus() at all...

Thanks a lot,

Michael
Back to top
JD
Guest





PostPosted: Tue May 24, 2005 10:11 pm    Post subject: Re: How to find out inside TEdit::OnExit which control is ab Reply with quote


M_R <> wrote:
Quote:

[...] but wouldn't it be nice, if the user gets immediately
an 'answer', if he entered something wrong!?

Your design only tells them when they're done entering a field
if they've enter invalid data. Immediate feedback would require
validating every keystroke and accounting for pasting which is
best handled with an OnChange event.

Then you'd need to suppliment the OnChange event with something
because the user could enter incomplete data (for example: 4
digits of a 5 digit zip code) and then move to a new field.

Another point that I didn't make before is that your design
also forces the user to organize the data the way that you
want them to. They will expect to be able to freely move from
field to field and you won't let them.

Quote:
[...] Problem is that [...] CancleButtonClick [...] is NOT
called AT ALL, [...] when the validation failed.

I suspect that it does indeed get called and that the real
problem is that the flag isn't getting set until *after* the
UWM_EDIT_EXIT message is processed (and your code doesn't show
that you're even checking for the flag before you validate).

Quote:
TForm::WndProc( Message ); // ???
[...]
I'm not so sure about the last command-line.

The sample overrides the form's default WndProc method (note
that subclassing a control's WndProc method, while very similar
is not the same). If you don't pass the message onto the base
class default WndProc, the only messages that the form will
process is are the ones that you process.

Quote:
Is this also correct if the caller-form has a different
name, e.g. TFormMyPrg? (The compiler accepts it)

No. The compiler accepts it because it's syntacically correct.
However, I would expect that to cause problems. The truth is I
don't know what would happen because it's wrong. You need to
call the base class handler and the base class is a TForm.

Quote:
Is there no order the messages are evaluated?

Messages are processed in a predictable way. When the message
is just added to the que (PostMessage), it's first come. first
serve when the form has time to process messages or when you
call Application::ProcessMessages.

If you use SendMessage, that message is processed immediately
while the caller waits for SendMessage to return.

Quote:
If the Set-Focus-To-Cancel-Button-Message would be evaluated
before our user-message we wouldn't call SetFocus() at all...

You can actually change that by not validating when you
process the UWM_EDIT_EXIT message. If you post a second
custom message, that should get added to the que behind
the button click:

#define UWM_EDIT_VALIDATE (WM_USER + 101)

void __fastcall TForm1::WndProc( TMessage &Message )
{
if( Message.Msg == UWM_EDIT_EXIT )
{
::PostMessage( Handle, UWM_EDIT_VALIDATE, Message.WParam, 0 );
}
else if( Message.Msg == UWM_EDIT_VALIDATE )
{
if( !UserClickedCancel )
{
TEdit *pEdit = static_cast<TEdit*>( Message.WParam );
if( !CONDITION(pEdit->Text) )
{
Beep();
pEdit->SetFocus();
}
}
}
TForm::WndProc( Message );
}

Note that you might need to call Application::ProcessMessages
in the CancelButton OnClick right after you set the flag. If
you get an Exception or an Access Violation, you'll know.

~ JD


Back to top
M_R
Guest





PostPosted: Wed May 25, 2005 6:09 am    Post subject: Re: How to find out inside TEdit::OnExit which control is ab Reply with quote

"JD" <nospam (AT) nospam (DOT) com> schrieb:

Thanks again for the extensive answer...

Quote:
[...] but wouldn't it be nice, if the user gets immediately
an 'answer', if he entered something wrong!?

Your design only tells them when they're done entering a field
if they've enter invalid data. Immediate feedback would require
validating every keystroke and accounting for pasting which is
best handled with an OnChange event.

Yes, you are right: I didn't mean such an immediate answer. I meant
immediately after he has finished editing the entry (instead of
checking it after he as finished editing all entries).(Else if a user
enters '-' as start of a valid integer-number the app would complain,
because '-' itself is no valid integer number...)

Quote:
[...] Problem is that [...] CancleButtonClick [...] is NOT
called AT ALL, [...] when the validation failed.

I suspect that it does indeed get called and that the real
problem is that the flag isn't getting set until *after* the
UWM_EDIT_EXIT message is processed (and your code doesn't show
that you're even checking for the flag before you validate).

A breakpoint set inside debugger does not react in this case. If
SetFocus() is cut out it does...

Quote:
If the Set-Focus-To-Cancel-Button-Message would be evaluated
before our user-message we wouldn't call SetFocus() at all...

You can actually change that by not validating when you
process the UWM_EDIT_EXIT message. If you post a second
custom message, that should get added to the que behind
the button click:

#define UWM_EDIT_VALIDATE (WM_USER + 101)

void __fastcall TForm1::WndProc( TMessage &Message )
{
....
}

Note that you might need to call Application::ProcessMessages
in the CancelButton OnClick right after you set the flag. If
you get an Exception or an Access Violation, you'll know.


I tried it out, but the behaviour is the same: a breakpoint set inside
debugger does not react in case of SetFocus(). If SetFocus() is cut
out it does...

I suppose the scheduler-instance of BCB which posts the
Set-Focus-To-Cancel-Button-Message doesn't do it when the focus is no
more on the Cancel-Button-Control. Is this possible?

~~~~~~~~
Are there other messages which are sent from this scheduler before the
OnExit-message. If e.g. the mouse-click-message at the form has been
clicked, we could check for ouself, if the click was on the
cancel-button and could react before the OnExit-Message...

Thanks again,

Michael


Back to top
JD
Guest





PostPosted: Wed May 25, 2005 10:37 am    Post subject: Re: How to find out inside TEdit::OnExit which control is ab Reply with quote


M_R <> wrote:
Quote:

[...] I meant immediately after he has finished editing the
entry [...] Else if a user enters '-' as start of a valid
integer-number the app would complain, because '-' itself is
no valid integer number

Not if your code handled it correctly.

Quote:
A breakpoint set inside debugger [...]

Much to my surprise, I found the same - that the Button's
OnClick event is not fired.

Quote:
[...] I tried it out, but the behaviour is the same

I found another problem as well. Try the code as is and
instead of clicking the button, leave one edit and enter
another where both edits are invalid. You'll enter into an
endless loop.

Quote:
I suppose the scheduler-instance of BCB which posts the
Set-Focus-To-Cancel-Button-Message [...]

There is no such animal. Windows is an event driven OS.
However, your question gave me an idea; use a TTimer which
did in fact allow the click to pass through.

This is a kludge to be sure but it was the only way that I
could get it to work and it has it's own problem. The TTimer
Interval needs to be as short as possible to elimate an
obviouse delay but if it's too short, the click isn't seen.
To compound it, the minimum delay needed will vary from system
to system depending on the cpu speed and the cpu load at the
moment of the click.

Now you need to deal with the endless loop problem (I told you
this approach was problematic). It would be easier if all you
had on the form were TEdit's but I had to assume that there
are other controls. Besides the code that follows, remove the
assigned OnExit event from the TEdits in the Object Inspector.

//--- in the header -------------------------------------------
protected: // User declarations
virtual void __fastcall WndProc( TMessage &Message );
private: // User declarations
#define UWM_EDIT_EXIT (WM_USER + 100)
bool UserClickedCancel;
void __fastcall ValidateEdit( UINT AEdit );
void __fastcall ActiveControlChanged(TObject *Sender);

public: // User declarations
__fastcall TForm1(TComponent* Owner);
__fastcall ~TForm1();

//--- in the unit ---------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
UserClickedCancel = false;
ActiveControl = Edit1;
Edit1->OnExit = EditExit;
}
//-------------------------------------------------------------
__fastcall TForm1::~TForm1()
{
Screen->OnActiveControlChange = NULL;
}
//-------------------------------------------------------------
void __fastcall TForm1::WndProc( TMessage &Message )
{
if( Message.Msg == UWM_EDIT_EXIT )
{
ValidateEdit( Message.WParam );
}
TForm::WndProc( Message );
}
//-------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
UserClickedCancel = true;
}
//-------------------------------------------------------------
void __fastcall TForm1::EditExit(TObject *Sender)
{
Timer1->Tag = reinterpret_cast<int>( Sender );
Timer1->Enabled = true;
}
//-------------------------------------------------------------
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
Timer1->Enabled = false;
if( !UserClickedCancel )
{
::PostMessage( Handle, UWM_EDIT_EXIT, WPARAM(Timer1->Tag), 0 );
}
}
//-------------------------------------------------------------
void __fastcall TForm1::ValidateEdit( UINT AEdit )
{
TEdit *pEdit = reinterpret_cast<TEdit*>( AEdit );

bool Result = add your validation here

if( !Result ) pEdit->SetFocus();
else
{
pEdit->OnExit = NULL;
pEdit = dynamic_cast<TEdit*>( ActiveControl );
if( pEdit )
{
// focus has changed to a TEdit
pEdit->OnExit = EditExit;
}
else
{
// we need to monitor a change of focus back to a TEdit
Screen->OnActiveControlChange = ActiveControlChanged;
}
}
}
//-------------------------------------------------------------
void __fastcall TForm1::ActiveControlChanged(TObject *Sender)
{
if( Screen->ActiveForm == this )
{
TEdit *pEdit = dynamic_cast<TEdit*>( ActiveControl );
if( pEdit )
{
pEdit->OnExit = EditExit;
Screen->OnActiveControlChange = NULL;
}
}
}
//-------------------------------------------------------------

If it doesn't work for you, increase the Timer Interval.

~ JD


Back to top
M_R
Guest





PostPosted: Wed May 25, 2005 1:16 pm    Post subject: Re: How to find out inside TEdit::OnExit which control is ab Reply with quote

Quote:
A breakpoint set inside debugger [...]

Much to my surprise, I found the same - that the Button's
OnClick event is not fired.

[...] I tried it out, but the behaviour is the same

I found another problem as well. Try the code as is and
instead of clicking the button, leave one edit and enter
another where both edits are invalid. You'll enter into an
endless loop.

I suppose the scheduler-instance of BCB which posts the
Set-Focus-To-Cancel-Button-Message [...]

There is no such animal. Windows is an event driven OS.
However, your question gave me an idea; use a TTimer which
did in fact allow the click to pass through.

This is a kludge to be sure but it was the only way that I
could get it to work and it has it's own problem. The TTimer
Interval needs to be as short as possible to elimate an
obviouse delay but if it's too short, the click isn't seen.
To compound it, the minimum delay needed will vary from system
to system depending on the cpu speed and the cpu load at the
moment of the click.

Now you need to deal with the endless loop problem (I told you
this approach was problematic). It would be easier if all you
had on the form were TEdit's but I had to assume that there
are other controls. Besides the code that follows, remove the
assigned OnExit event from the TEdits in the Object Inspector.

//--- in the header -------------------------------------------
protected: // User declarations
virtual void __fastcall WndProc( TMessage &Message );
private: // User declarations
#define UWM_EDIT_EXIT (WM_USER + 100)
bool UserClickedCancel;
void __fastcall ValidateEdit( UINT AEdit );
void __fastcall ActiveControlChanged(TObject *Sender);

public: // User declarations
__fastcall TForm1(TComponent* Owner);
__fastcall ~TForm1();

//--- in the unit ---------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
UserClickedCancel = false;
ActiveControl = Edit1;
Edit1->OnExit = EditExit;
}
//-------------------------------------------------------------
__fastcall TForm1::~TForm1()
{
Screen->OnActiveControlChange = NULL;
}
//-------------------------------------------------------------
void __fastcall TForm1::WndProc( TMessage &Message )
{
if( Message.Msg == UWM_EDIT_EXIT )
{
ValidateEdit( Message.WParam );
}
TForm::WndProc( Message );
}
//-------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
UserClickedCancel = true;
}
//-------------------------------------------------------------
void __fastcall TForm1::EditExit(TObject *Sender)
{
Timer1->Tag = reinterpret_cast<int>( Sender );
Timer1->Enabled = true;
}
//-------------------------------------------------------------
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
Timer1->Enabled = false;
if( !UserClickedCancel )
{
::PostMessage( Handle, UWM_EDIT_EXIT, WPARAM(Timer1->Tag), 0 );
}
}
//-------------------------------------------------------------
void __fastcall TForm1::ValidateEdit( UINT AEdit )
{
TEdit *pEdit = reinterpret_cast<TEdit*>( AEdit );

bool Result = add your validation here

if( !Result ) pEdit->SetFocus();
else
{
pEdit->OnExit = NULL;
pEdit = dynamic_cast<TEdit*>( ActiveControl );
if( pEdit )
{
// focus has changed to a TEdit
pEdit->OnExit = EditExit;
}
else
{
// we need to monitor a change of focus back to a TEdit
Screen->OnActiveControlChange = ActiveControlChanged;
}
}
}
//-------------------------------------------------------------
void __fastcall TForm1::ActiveControlChanged(TObject *Sender)
{
if( Screen->ActiveForm == this )
{
TEdit *pEdit = dynamic_cast<TEdit*>( ActiveControl );
if( pEdit )
{
pEdit->OnExit = EditExit;
Screen->OnActiveControlChange = NULL;
}
}
}
//-------------------------------------------------------------

If it doesn't work for you, increase the Timer Interval.

~ JD


Thanks again for the very extensive answer...

Even if I (as you) misbehave with this additional TTimer (which looks
a little artificial), I decided to use it.

Another possibility would be to store globally the last edited Control
control and interfer each the OnEntry/OnClick-Event, check if it is
valid and if not set the Focus to it. But that look artificial too...

I modified your code a little, since the WndProc, UserClickedCancel
is no more necessary:

The EditExit() verifies, if the edit-field-entry is valid, if not it
sets stores a pointer to the Edit in the Timer's Tag and enables the
Timer. In the OnTimer-Event the Tag's Edit-Field is focussed. If in
the meantime the Cancel-Button has been clicked on, the Timers is
disabled from there.

Quote:
A breakpoint set inside debugger [...]

Much to my surprise, I found the same - that the Button's
OnClick event is not fired.

[...] I tried it out, but the behaviour is the same

I suppose the scheduler-instance of BCB which posts the
Set-Focus-To-Cancel-Button-Message [...]

There is no such animal. Windows is an event driven OS.

Nevertheless I would be interested who does or (as in our case, when
SetFocus() was used) not post the Cancel-Button-On-Click-message.

(I imagine(d) the evaluation of the events coming to a VCL-component
like TEdit this way, that a BCB/VCL procedure gets the information
there is an On-TEdit-Click-Event. Now it looks at the OnClick-Pointer.
If it is NULL there is no external function to call, else the
OnClick-function is called. Before checking the OnClick-Pointer being
NULL (or not) it might check, if the clicked on control has the focus
(as expected). In case it has not the focus it would not check (and
call) the OnClick-Function-pointer. Might that be thus or similar? If
not, what's the explanation why the
Cancel-Button-On-Click-Event-Function was not called at all?)

Thanks again,

Michael


Back to top
Display posts from previous:   
Post new topic   Reply to topic    BorlandTalk.com Forum Index -> C++ Builder (VCL Components Usage) All times are GMT
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2006 phpBB Group
SEO toolkit © 2004-2006 webmedic.