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 CMEnabledChanged(var Message: TLMessage); message CM_ENABLEDCHANGED;
procedure CMShowingChanged(var Message: TLMessage); message CM_SHOWINGCHANGED; // called by TWinControl.UpdateShowing procedure CMShowingChanged(var Message: TLMessage); message CM_SHOWINGCHANGED; // called by TWinControl.UpdateShowing
procedure CMShowHintChanged(var Message: TLMessage); message CM_SHOWHINTCHANGED; 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 WMEraseBkgnd(var Message: TLMEraseBkgnd); message LM_ERASEBKGND;
procedure WMNotify(var Message: TLMNotify); message LM_NOTIFY; procedure WMNotify(var Message: TLMNotify); message LM_NOTIFY;
procedure WMSetFocus(var Message: TLMSetFocus); message LM_SETFOCUS; procedure WMSetFocus(var Message: TLMSetFocus); message LM_SETFOCUS;

View File

@ -422,6 +422,7 @@ type
FKeyPreview: Boolean; FKeyPreview: Boolean;
FMenu: TMainMenu; FMenu: TMainMenu;
FModalResult: TModalResult; FModalResult: TModalResult;
FLastFocusedControl: TWinControl;
FOldBorderStyle: TFormBorderStyle; FOldBorderStyle: TFormBorderStyle;
FOnActivate: TNotifyEvent; FOnActivate: TNotifyEvent;
FOnClose: TCloseEvent; FOnClose: TCloseEvent;
@ -1635,9 +1636,9 @@ uses
WSForms; // Widgetset uses circle is allowed WSForms; // Widgetset uses circle is allowed
var var
FocusCount: Integer=0; HandlingException: Boolean = False;
HandlingException: boolean=False; HaltingProgram: Boolean = False;
HaltingProgram: boolean=False; LastFocusedControl: TWinControl = nil;
procedure Register; procedure Register;
begin begin
@ -1684,28 +1685,16 @@ begin
Application.DoBeforeFinalization; Application.DoBeforeFinalization;
end; 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; function SaveFocusState: TFocusState;
begin begin
Result := TFocusState(PtrInt(FocusCount)); Result := LastFocusedControl;
end; end;
procedure RestoreFocusState(FocusState: TFocusState); procedure RestoreFocusState(FocusState: TFocusState);
begin begin
FocusCount := integer(PtrUInt(FocusState)); LastFocusedControl := TWinControl(FocusState);
end; 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; function KeysToShiftState(Keys: PtrUInt): TShiftState;
begin begin

View File

@ -1846,6 +1846,7 @@ begin
Include(FFormState,fsFirstShow); Include(FFormState,fsFirstShow);
//DebugLn('[TCustomForm.CreateNew] Class=',Classname); //DebugLn('[TCustomForm.CreateNew] Class=',Classname);
BeginFormUpdate; BeginFormUpdate;
FLastFocusedControl := Self;
FBorderIcons := [biSystemMenu, biMinimize, biMaximize]; FBorderIcons := [biSystemMenu, biMinimize, biMaximize];
FDefaultMonitor := dmActiveForm; FDefaultMonitor := dmActiveForm;
FPopupMode := pmNone; FPopupMode := pmNone;
@ -2130,10 +2131,20 @@ end;
Switch focus. Switch focus.
------------------------------------------------------------------------------} ------------------------------------------------------------------------------}
function TCustomForm.SetFocusedControl(Control: TWinControl): Boolean; 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 var
ParentForm: TCustomForm; ParentForm: TCustomForm;
CurControl: TWinControl; CurControl: TWinControl;
LastState: TFocusState;
begin begin
LastFocusedControl := Control;
Result := False; Result := False;
if (Control <> nil) and (csDestroying in Control.ComponentState) then Exit; if (Control <> nil) and (csDestroying in Control.ComponentState) then Exit;
if (csDestroying in ComponentState) or (csDestroyingHandle in ControlState) then if (csDestroying in ComponentState) or (csDestroyingHandle in ControlState) then
@ -2180,17 +2191,42 @@ begin
if (Control <> nil) and (not (csFocusing in Control.ControlState)) then if (Control <> nil) and (not (csFocusing in Control.ControlState)) then
begin begin
Control.ControlState := Control.ControlState + [csFocusing]; Control.ControlState := Control.ControlState + [csFocusing];
Screen.FFocusedForm := Self; try
// prevent looping Screen.FFocusedForm := Self;
// update ActiveControls of all parent forms // update ActiveControls of all parent forms
CurControl := Control.Parent; CurControl := Control.Parent;
while CurControl <> nil do while CurControl <> nil do
begin begin
if CurControl is TCustomForm then if CurControl is TCustomForm then
TCustomForm(CurControl).FActiveControl := Control; TCustomForm(CurControl).FActiveControl := Control;
CurControl := CurControl.Parent; 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; end;
Control.ControlState := Control.ControlState - [csFocusing];
Result := True; Result := True;
end; end;
end; end;

View File

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