Board index » delphi » Page Control, inter-tab focus changes

Page Control, inter-tab focus changes

I'm still learning Delphi and have another question;

I want to create a form, with a TPageControl, and 3 TTabsheets.   The form
has 3 TDBEdits, and then each tab has 3 TBEdits too.  When looking at
existing data, the user can select the Tabsheet they want, but when the user
is inserting new data I want the focus to move from the last TDBEdit of one
TabSheet to the first TDBEdit of the next TabSheet (and visa-versa).

Is this easy to do?  An example would be really appreciated.

TIA

 

Re:Page Control, inter-tab focus changes


Quote
> I want to create a form, with a TPageControl, and 3 TTabsheets.   The form
> has 3 TDBEdits, and then each tab has 3 TBEdits too.  When looking at
> existing data, the user can select the Tabsheet they want, but when the user
> is inserting new data I want the focus to move from the last TDBEdit of one
> TabSheet to the first TDBEdit of the next TabSheet (and visa-versa).

> Is this easy to do?  An example would be really appreciated.

I would not rate it as "easy" but it isn't rocket science either, if you know
that the form handles navigation via keyboard in a handler for the
CM_DIALOGKEY message <g>.

making Tab and Shift-Tab work across the tabsheets of a pagecontrol:

Unit Unit1;

Interface

Uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ComCtrls;

Type
  TForm1 = Class(TForm)
    PageControl1: TPageControl;
    TabSheet1: TTabSheet;
    Edit1: TEdit;
    Edit2: TEdit;
    Edit3: TEdit;
    TabSheet2: TTabSheet;
    Edit4: TEdit;
    Edit5: TEdit;
    Edit6: TEdit;
    TabSheet3: TTabSheet;
    Edit7: TEdit;
    Edit8: TEdit;
    Edit9: TEdit;
  Private
    { Private declarations }
    Procedure CMDialogKey(Var Msg: TWMKey); message CM_DIALOGKEY;

  Public
    { Public declarations }
  End;

Var
  Form1: TForm1;

Implementation

{$R *.DFM}

{ TForm1 }

Type
  TCracker = Class(TWinControl);
  TPCCracker = Class(TPageControl);

Procedure TForm1.CMDialogKey(Var Msg: TWMKey);
Var
  control, nextControl: TWinControl;
  tab: TTabSheet;
  moveforward: Boolean;
Begin
  If (msg.CharCode <> VK_TAB) Then Begin
    inherited;
    Exit;
  End; { if }

  // shift-tab moves backwards in tab order
  moveforward := GetKeyState( VK_SHIFT ) >= 0;

  msg.result := 0;
  control := ActiveControl;
  If Assigned( control ) and (Control.Parent Is TTabSheet) Then Begin
    nextControl :=
      TCracker(control.Parent).FindNextControl(
                                 control, moveforward, True, True );
    // FindNextControl wraps around if it hits the start or end of
    // the control list!
    If (nextControl = Nil) or
       ((nextcontrol.TabOrder < control.TabOrder) and moveForward) or
       ((nextcontrol.TabOrder > control.TabOrder) and not moveForward)
    Then Begin
      // no next control on this tabsheet in move direction,
      // swallow the key and move to next tabsheet.
      // Wrap around to first if on last.
      msg.result := 1;
      tab := TTabSheet( control.parent );
      With TPCCracker(tab.Pagecontrol) Do Begin
        // fire OnChanging event
        If CanChange Then Begin
          // change to new page
          If moveforward Then Begin
            If Succ(tab.PageIndex) = PageCount Then
              ActivePage := Pages[0]
            Else
              ActivePage := Pages[ Succ(tab.Pageindex) ];
          End { if }
          Else Begin
            // moving backwards
            If tab.PageIndex = 0 Then
              ActivePage := Pages[Pagecount-1]
            Else
              ActivePage := Pages[ Pred(tab.Pageindex) ];
            // move to last control on this page, we are on the first
            TCracker(ActivePage).SelectNext( ActiveControl, false, true );
          End; { else }
          // fire OnChange event
          Change;
        End; { if }
      End; { with }
    End; { if }
  End; { if  }

  If msg.result = 0 Then
    inherited;
End; { TForm1.CMDialogKey }

End.

Peter Below (TeamB)  100113.1...@compuserve.com)
No e-mail responses, please, unless explicitly requested!

Re:Page Control, inter-tab focus changes


Can it be done with a component you can place on a form?  I cant seem to see
the vk_tab message when I try.

Peter Below (TeamB) <100113.1...@compuXXserve.com> wrote in message ...

Quote
>I would not rate it as "easy" but it isn't rocket science either, if you
know
>that the form handles navigation via keyboard in a handler for the
>CM_DIALOGKEY message <g>.

>making Tab and Shift-Tab work across the tabsheets of a pagecontrol:

Re:Page Control, inter-tab focus changes


Quote
In article <7olhn8$6...@forums.borland.com>, Pat Riley wrote:
> Can it be done with a component you can place on a form?  I cant seem to see
> the vk_tab message when I try.

Yes. You cannot get CM_DIALOGKEY on the control level, however, the message to
look for (in a derived TPagecontrol for example) would be CM_CHILDKEY. This
message is send to the control with focus for each key down *before* any other
processing (with the exception of shortcuts) has been done, so also before the
form sees it as CM_DIALOGKEY. The message is send from TWincontrol.CNKeyDown
with

      if Perform(CM_CHILDKEY, CharCode, Integer(Self)) <> 0 then Exit;

So it comes with a reference to the current control in lparam and the keys
virtual key code in wparam. The default handler in TWincontrol just echos the
message to its parent, so it will eventually arrive at the pagecontrol level.
To prevent further processing set msg.result := 1 in your handler for it and
do not call the inherited handler.

Note that there are some differences to CM_DIALOGKEY here: CM_CHILDKEY will
also be send if the active control is one that processes Tab itself, e.g. a
Grid with goTabs in Options, or a TMemo with WantTabs = true. You have to take
this into account and check if the control is one that deals with tabs itself
(send a WM_GETDLGCODE message to it and check the returned value for
DLGC_WANTTAB).

Peter Below (TeamB)  100113.1...@compuserve.com)
No e-mail responses, please, unless explicitly requested!

Re:Page Control, inter-tab focus changes


Thanks again Peter, It sounds easy enough.  I'll make a go out of making a
component from this.

Peter Below (TeamB) <100113.1...@compuXXserve.com> wrote in message ...

Quote
>In article <7olhn8$6...@forums.borland.com>, Pat Riley wrote:
>> Can it be done with a component you can place on a form?  I cant seem to
see
>> the vk_tab message when I try.

>Yes. You cannot get CM_DIALOGKEY on the control level, however, the message
to
>look for (in a derived TPagecontrol for example) would be CM_CHILDKEY. This
>message is send to the control with focus for each key down *before* any
other
>processing (with the exception of shortcuts) has been done, so also before
the
>form sees it as CM_DIALOGKEY. The message is send from

TWincontrol.CNKeyDown
Quote
>with

>      if Perform(CM_CHILDKEY, CharCode, Integer(Self)) <> 0 then Exit;

>So it comes with a reference to the current control in lparam and the keys
>virtual key code in wparam. The default handler in TWincontrol just echos
the
>message to its parent, so it will eventually arrive at the pagecontrol
level.
>To prevent further processing set msg.result := 1 in your handler for it
and
>do not call the inherited handler.

>Note that there are some differences to CM_DIALOGKEY here: CM_CHILDKEY will
>also be send if the active control is one that processes Tab itself, e.g. a
>Grid with goTabs in Options, or a TMemo with WantTabs = true. You have to
take
>this into account and check if the control is one that deals with tabs
itself
>(send a WM_GETDLGCODE message to it and check the returned value for
>DLGC_WANTTAB).

>Peter Below (TeamB)  100113.1...@compuserve.com)
>No e-mail responses, please, unless explicitly requested!

Re:Page Control, inter-tab focus changes


Peter, here is what I ended up with.  Thank you once again for sharing your
wisdom. Pat.

unit MyPageControl;  // a very small component

interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
stdCtrls, comctrls;

type
  TMyPageControl = class(TPageControl)
  private
    procedure CMChildKey(var Msg: TWMKey); message CM_ChildKey;
end;

procedure Register;

implementation

type
  TCracker = class(TWinControl);
  TPCCracker = Class(TPageControl);

procedure Register;
begin
  RegisterComponents('MyStuff', [TMyPageControl]);
end;

procedure TMyPageControl.CMChildKey(var msg:TWMKey);
var
  control, nextControl: TWinControl;
  tab: TTabSheet;
  moveforward:Boolean;
begin
  If (msg.CharCode <> VK_TAB) Then Begin
    inherited;
    Exit;
  End; { if }

  // shift-tab moves backwards in tab order
  moveforward := GetKeyState( VK_SHIFT ) >= 0;

  msg.result := 0;
  control := TCracker(msg.keydata);  // keydata is lparam, which is is
focused control

  If Assigned( control ) and (Control.parent Is TTabSheet) Then Begin
    nextControl := TCracker(control.parent).FindNextControl(control,
moveforward, True, True );
    // FindNextControl wraps around if it hits the start or end of
    // the control list!
    If (nextControl = Nil) or
       ((nextcontrol.TabOrder < control.TabOrder) and moveForward) or
       ((nextcontrol.TabOrder > control.TabOrder) and not moveForward)
    Then Begin
      // no next control on this tabsheet in move direction,
      // swallow the key and move to next tabsheet.
      // Wrap around to first if on last.
      msg.result := 1;
      tab := TTabSheet( control.parent );
      With TPCCracker(tab.Pagecontrol) Do Begin
        // fire OnChanging event
        If CanChange Then Begin
          // change to new page
          If moveforward Then Begin
            If Succ(tab.PageIndex) = PageCount Then begin
              msg.result := 0;  // leave the pagecontrol forwards
              inherited;
              exit;
            end
            //  ActivePage := Pages[0]
            Else
              ActivePage := Pages[ Succ(tab.Pageindex) ];
          End { if }
          Else Begin
            // moving backwards
            If tab.PageIndex = 0 Then begin
              msg.result :=0;  // leave the pagecontrol backwards
              inherited;
              exit;
            end
            // ActivePage := Pages[Pagecount-1]
            Else
              ActivePage := Pages[ Pred(tab.Pageindex) ];
            // move backwards to last control on this page, we are on the
first
            TCracker(ActivePage).SelectNext( self, false, true );
          End; { else }
          // fire OnChange event
          Change;
        End; { if }
      End; { with }
    End; { if }
  End; { if  }

  If msg.result = 0 Then
    inherited;
End;

end.

Re:Page Control, inter-tab focus changes


Ok Peter.  Now for a challange...<g>

I want TMyPageControl to know when it gets focused if the user used a Tab
key or a Shift-Tab key.  This will allow me to know if I should display the
first tab or the last tab, accordingly.

Can you steer me once again in the direction of a solution?

TIA

Re:Page Control, inter-tab focus changes


Quote
> Ok Peter.  Now for a challange...<g>

> I want TMyPageControl to know when it gets focused if the user used a Tab
> key or a Shift-Tab key.  This will allow me to know if I should display the
> first tab or the last tab, accordingly.

I take it you want to know from which direction the user navigated to your
pagecontrol from outside. This is a wee bit more difficult, i cannot think of
a solution that would work without the pagecontrol monitoring the message
flow of its parent form (assuming that you want to encapsulate the
functionality into your component again).

So what you need to do is to subclass the pagecontrols parent form, via its
WindowProc property. This immediately raises a problem if you have more than
one of your pagecontrols on the form, i'm afraid. Unpleasant stuff would hit
the fan if you created pagecontrol1 and 2 (which subclass the form in this
sequence) and then delete pagecontrol1. The only way to get this under
control is to create a kind of "subclass manager" object, of which only one
instance is guaranteed to exist on one form. That would subclass the form and
pass the trapped messages to any "client" registered with it. The
pagecontrols would register themselves when created and unregister when
destroyed. The first pagecontrol would create the manager object, it would
destroy itself when it no longer has any clients or perhaps only reverse the
subclassing and live for the forms lifetime.

With that mechanism in place the pagecontrol(s) could monitor CM_DIALOGKEY on
the form level and react to VK_TAB, call the forms FindNextControl to see
where the focus would end up if ActiveControl is not already on one of the
pagecontrols tabsheets. Quite a bit of work for a nice-to-have feature <g>.

Peter Below (TeamB)  100113.1...@compuserve.com)
No e-mail responses, please, unless explicitly requested!

Re:Page Control, inter-tab focus changes


I'm certain that I broke some rule somewhere... but this was my solution to
the Leaving and Returning to TMyPageControl;

It's an illusion! But it works... I modified TMyPageControl with;

procedure TMyPageControl.CMChildKey(var msg:TWMKey);
<snip>
          // change to new page
          If moveforward Then Begin
            If Succ(tab.PageIndex) = PageCount Then begin
              if circulartabs then begin   //  Circle last tab to first tab
Peter's way
                ActivePage := Pages[0];
              end else begin                   // Leave the Page Control
Pats way;
                msg.result := 0;  // leave the pagecontrol forwards
                inherited;  // go ahead and do that tab
                exit;  // ... and on to the next control
              end;
            end  { trying to tab beyond last tabsheet }
<snip>

I did a similar thing in the BackPage... section ('im trying to spare the
newsgroup by being brief <g>)

Then I added;

procedure TMyPageControl.CMEnter(var Message: TCMEnter);
var
  moveforward : boolean;
begin
  moveforward := GetKeyState( VK_SHIFT ) >= 0;
  if not moveforward then begin
    ActivePage := Pages[ pagecount-1 ];
    TCracker(ActivePage).SelectNext( self, false, true );
  end;
  inherited;
end;

procedure TMyPageControl.CMExit(var Message: TCMExit);
begin
  ActivePage := Pages[ 0 ];
  inherited;
end;

This last CMExit thing was the {*word*156}e to swallow.  It was a "design
decision" that puts the page control in a predictable configuration, but
still allows the user to select a different tab when focus is outside of the
pagecontrol.

Everything is working as best as I can hope with my limited windows
experience.  Thank you once again for your help.

With a smile <g> and warm regards,

Pat.

Re:Page Control, inter-tab focus changes


Quote
In article <7paoh6$qj...@forums.borland.com>, Pat Riley wrote:
> Everything is working as best as I can hope with my limited windows
> experience

Actually not a bad solution at all <g>.

Peter Below (TeamB)  100113.1...@compuserve.com)
No e-mail responses, please, unless explicitly requested!

Other Threads