Reputation: 3879
I am working at a huge, legacy source code where several SetFocus
is called at many places, but sometimes, the check if the control is visible or enabled is missing.
Due to limited time, and the huge amount of source code, I decided that I want to ignore these errors, since the focus is (in our case) not a critical feature. A raised Exception will result in a complete failure, while a missing focus is just an optical issue.
My current plan is following:
I create an unit with a class helper like this:
type TWinControlEx = class helper for TWinControl procedure SetFocusSafe; end;
procedure TWinControlEx.SetFocusSafe; begin if CanFocus then SetFocus; end;
I include the unit to every unit which uses ".SetFocus" (I will use the global code search)
I replace every .SetFocus with .SetFocusSafe
There is a problem though: If possible, I want to avoid that coworkers accidently use .SetFocus , or forget to include the classhelper unit.
Which other options do I have?
The best case would be if there is a technique/hack to make SetFocus not raising an exception. (Without recompiling the VCL)
Upvotes: 7
Views: 2328
Reputation: 21202
My answer below does not answer DIRECTLY your question, but it is still relevant because you rely on CanFocus. CanFocus returns a lie. You should not rely on it. The documentation is also wrong. More exactly, CanFocus can return True even if the control is not focusable. In this case, an exception will be raised. PS: Under Lazarus CanFocus works properly.
So, you will have to write a new CanFocus that tests not only the state of the current control but ALL its parents. You will end up with a recursive call. Only then you can write a new SetFocus that really works:
procedure SetFocus(Control: TWinControl);
begin
if CanFocusFixed(Control)
then Control.SetFocus;
end;
Full code and explanation here in this article from my website: Setfocus-is-broken-in-Delphi There is also a tool to batch replace the broken SetFocus with my safe alternative.
Justification:
J provided a nice answer, but PERSONALLY I don't like class helpers because if you have more than one class helper for the same class, then only one will be used - which one is determined (almost randomly) by the order of the units in the "uses" clause. Accidentally change the order of the units in the "uses" and you accidentally change the behavior of your program! I don't like this amount of randomness in a programming language. Until Emba comes with a stable solution, I won't use Class Helpers.
Upvotes: 4
Reputation: 31443
Alternatively
TWinControlEx = class helper for TWinControl
procedure SetFocus; reintroduce;
end;
with...
procedure TWinControlEx.SetFocus;
var
Parent: TCustomForm;
begin
if not CanFocus then Exit;
Parent := GetParentForm(Self);
if Parent <> nil then
Parent.FocusControl(Self)
else if ParentWindow <> 0 then
Winapi.Windows.SetFocus(Handle)
else
ValidParentForm(Self);
end;
Upvotes: 4
Reputation: 21748
Just patch the TWinControl.SetFocus
method:
unit SetFocusFix;
interface
implementation
uses
Controls,
Forms,
SysUtils,
Windows;
type
TWinControlHack = class(TWinControl)
public
procedure SetFocus; override;
end;
procedure TWinControlHack.SetFocus;
var
Parent: TCustomForm;
begin
if not CanFocus then Exit;
Parent := GetParentForm(Self);
if Parent <> nil then
Parent.FocusControl(Self)
else if ParentWindow <> 0 then
Windows.SetFocus(Handle)
else
ValidParentForm(Self);
end;
procedure RedirectFunction(OrgProc, NewProc: Pointer);
type
TJmpBuffer = packed record
Jmp: Byte;
Offset: Integer;
end;
var
n: UINT_PTR;
JmpBuffer: TJmpBuffer;
begin
JmpBuffer.Jmp := $E9;
JmpBuffer.Offset := PByte(NewProc) - (PByte(OrgProc) + 5);
if not WriteProcessMemory(GetCurrentProcess, OrgProc, @JmpBuffer, SizeOf(JmpBuffer), n) then
RaiseLastOSError;
end;
initialization
RedirectFunction(@TWinControl.SetFocus, @TWinControlHack.SetFocus);
end.
Upvotes: 7