lcl: fix OnEnter/OnExit handling:

- don't assume DoEnter, DoExit are the same as WM_SETFOCUS, WM_KILLFOCUS - Enter must happen for all controls from the current focused control to the new focused control on the form, Exit must happen from the focused control to the first parent of the new focused control. If another focusing event happens during this look we need to stop the loop and return False state for SetFocusedControl method (this is VCL compatible behavior)
  - handle enter, exit code in SetFocusedControl because this is the only place where form handles focus for child controls
  - send CM_ENTER, CM_EXIT messages instead of direct call of DoEnter, DoExit methods
  - reimplement SaveFocusState, RestoreFocusState - save restore the last global focused control
  (fixes issue #0014041)

git-svn-id: trunk@25255 -
This commit is contained in:
paul 2010-05-08 16:18:40 +00:00
parent 9528a09d43
commit 0254875222
4 changed files with 72 additions and 55 deletions

View File

@ -1725,7 +1725,9 @@ type
procedure CMEnabledChanged(var Message: TLMessage); message CM_ENABLEDCHANGED;
procedure CMShowingChanged(var Message: TLMessage); message CM_SHOWINGCHANGED; // called by TWinControl.UpdateShowing
procedure CMShowHintChanged(var Message: TLMessage); message CM_SHOWHINTCHANGED;
procedure CMVisibleChanged(var TheMessage: TLMessage); message CM_VISIBLECHANGED;
procedure CMVisibleChanged(var Message: TLMessage); message CM_VISIBLECHANGED;
procedure CMEnter(var Message: TLMessage); message CM_ENTER;
procedure CMExit(var Message: TLMessage); message CM_EXIT;
procedure WMEraseBkgnd(var Message: TLMEraseBkgnd); message LM_ERASEBKGND;
procedure WMNotify(var Message: TLMNotify); message LM_NOTIFY;
procedure WMSetFocus(var Message: TLMSetFocus); message LM_SETFOCUS;

View File

@ -422,6 +422,7 @@ type
FKeyPreview: Boolean;
FMenu: TMainMenu;
FModalResult: TModalResult;
FLastFocusedControl: TWinControl;
FOldBorderStyle: TFormBorderStyle;
FOnActivate: TNotifyEvent;
FOnClose: TCloseEvent;
@ -1635,9 +1636,9 @@ uses
WSForms; // Widgetset uses circle is allowed
var
FocusCount: Integer=0;
HandlingException: boolean=False;
HaltingProgram: boolean=False;
HandlingException: Boolean = False;
HaltingProgram: Boolean = False;
LastFocusedControl: TWinControl = nil;
procedure Register;
begin
@ -1684,28 +1685,16 @@ begin
Application.DoBeforeFinalization;
end;
//------------------------------------------------------------------------------
// The focus state is just the focus count for now. To save having to allocate
// anything, I just map the Integer to the TFocusState.
function SaveFocusState: TFocusState;
begin
Result := TFocusState(PtrInt(FocusCount));
Result := LastFocusedControl;
end;
procedure RestoreFocusState(FocusState: TFocusState);
begin
FocusCount := integer(PtrUInt(FocusState));
LastFocusedControl := TWinControl(FocusState);
end;
{function SendFocusMessage(Window: HWnd; Msg: Word): Boolean;
var
Count: Integer;
begin
Count := FocusCount;
SendMessage(Window, Msg, 0, 0);
Result := (FocusCount = Count);
end;}
//------------------------------------------------------------------------------
function KeysToShiftState(Keys: PtrUInt): TShiftState;
begin

View File

@ -1846,6 +1846,7 @@ begin
Include(FFormState,fsFirstShow);
//DebugLn('[TCustomForm.CreateNew] Class=',Classname);
BeginFormUpdate;
FLastFocusedControl := Self;
FBorderIcons := [biSystemMenu, biMinimize, biMaximize];
FDefaultMonitor := dmActiveForm;
FPopupMode := pmNone;
@ -2130,10 +2131,20 @@ end;
Switch focus.
------------------------------------------------------------------------------}
function TCustomForm.SetFocusedControl(Control: TWinControl): Boolean;
function NextChildControl(CurParent, Target: TWinControl): TWinControl; inline;
begin
while Target.Parent <> CurParent do
Target := Target.Parent;
Result := Target;
end;
var
ParentForm: TCustomForm;
CurControl: TWinControl;
LastState: TFocusState;
begin
LastFocusedControl := Control;
Result := False;
if (Control <> nil) and (csDestroying in Control.ComponentState) then Exit;
if (csDestroying in ComponentState) or (csDestroyingHandle in ControlState) then
@ -2180,17 +2191,42 @@ begin
if (Control <> nil) and (not (csFocusing in Control.ControlState)) then
begin
Control.ControlState := Control.ControlState + [csFocusing];
Screen.FFocusedForm := Self;
// prevent looping
// update ActiveControls of all parent forms
CurControl := Control.Parent;
while CurControl <> nil do
begin
if CurControl is TCustomForm then
TCustomForm(CurControl).FActiveControl := Control;
CurControl := CurControl.Parent;
try
Screen.FFocusedForm := Self;
// update ActiveControls of all parent forms
CurControl := Control.Parent;
while CurControl <> nil do
begin
if CurControl is TCustomForm then
TCustomForm(CurControl).FActiveControl := Control;
CurControl := CurControl.Parent;
end;
// send cm_exit, cm_enter messages
// cm_exit must be sent to all controls from lastfocusedcontrol to the first parent which contains control
// cm_enter must be sent from the control we stoped up to control
// if during this loop something happens with focus (another control or form has aquired it) we need to stop it
while not FLastFocusedControl.ContainsControl(Control) do
begin
LastState := SaveFocusState;
SendMessage(FLastFocusedControl.Handle, CM_EXIT, 0, 0);
if SaveFocusState <> LastState then
Exit;
FLastFocusedControl := FLastFocusedControl.Parent;
end;
while FLastFocusedControl <> Control do
begin
FLastFocusedControl := NextChildControl(FLastFocusedControl, Control);
LastState := SaveFocusState;
SendMessage(FLastFocusedControl.Handle, CM_ENTER, 0, 0);
if SaveFocusState <> LastState then
Exit;
end;
finally
Control.ControlState := Control.ControlState - [csFocusing];
end;
Control.ControlState := Control.ControlState - [csFocusing];
Result := True;
end;
end;

View File

@ -6330,10 +6330,6 @@ end;
procedure TWinControl.WMSetFocus(var Message: TLMSetFocus);
begin
//DebugLn('TWinControl.WMSetFocus A ',Name,':',ClassName);
Assert(False, Format('Trace: %s', [ClassName]));
if [csLoading,csDestroying,csDesigning]*ComponentState=[] then begin
DoEnter;
end;
end;
{------------------------------------------------------------------------------
@ -6351,23 +6347,9 @@ begin
Assert(False, Format('Trace: %s', [ClassName]));
if [csLoading,csDestroying,csDesigning]*ComponentState=[] then
begin
if Self is TCustomForm then
begin
if TCustomForm(Self).Active then
begin
EditingDone;
DoExit;
end;
end else
begin
ParentForm := GetParentForm(Self);
if Assigned(ParentForm) and
ParentForm.Active then
begin
EditingDone;
DoExit;
end;
end;
ParentForm := GetParentForm(Self);
if Assigned(ParentForm) and ParentForm.Active then
EditingDone;
end;
end;
@ -7815,17 +7797,25 @@ end;
Performs actions when visibility has changed
------------------------------------------------------------------------------}
procedure TWinControl.CMVisibleChanged(var TheMessage : TLMessage);
procedure TWinControl.CMVisibleChanged(var Message : TLMessage);
begin
if not FVisible and (Parent <> nil)
then RemoveFocus(False);
if not FVisible and (Parent <> nil) then
RemoveFocus(False);
if not (csDesigning in ComponentState)
or (csNoDesignVisible in ControlStyle)
then
if not (csDesigning in ComponentState) or (csNoDesignVisible in ControlStyle) then
UpdateControlState;
end;
procedure TWinControl.CMEnter(var Message: TLMessage);
begin
DoEnter;
end;
procedure TWinControl.CMExit(var Message: TLMessage);
begin
DoExit;
end;
procedure TWinControl.DoSendShowHideToInterface;
var
NewVisible: Boolean;