避免 SetFocus 引发异常
Avoid that SetFocus raises an Exception
我正在处理一个巨大的遗留源代码,其中在许多地方调用了多个 SetFocus
,但有时,缺少控件是否可见或启用的检查。
由于时间有限和源代码量巨大,我决定忽略这些错误,因为重点(在我们的例子中)不是关键功能。引发的异常将导致完全失败,而缺少焦点只是一个光学问题。
我目前的计划如下:
我用这样的 class 助手创建了一个单元:
类型
TWinControlEx = class TWinControl 的助手
程序 SetFocusSafe;
结束;
程序TWinControlEx.SetFocusSafe;
开始
如果 CanFocus 则 SetFocus;
结束;
我把单位包含到每个使用“.SetFocus”的单位(我会使用全局代码搜索)
我将每个 .SetFocus 替换为 .SetFocusSafe
但是有一个问题:如果可能的话,我想避免同事不小心使用 .SetFocus ,或者忘记包含 classhelper 单元。
我还有哪些其他选择?
最好的情况是 technique/hack 让 SetFocus 不引发异常。 (无需重新编译 VCL)
只需修补 TWinControl.SetFocus
方法:
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.
或者
TWinControlEx = class helper for TWinControl
procedure SetFocus; reintroduce;
end;
与...
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;
我在下面的回答没有直接回答你的问题,但它仍然是相关的,因为你依赖 CanFocus。 CanFocus return 是个谎言。你不应该依赖它。文档也是错误的。更准确地说,即使控件不可聚焦,CanFocus 也可以 return True。在这种情况下将引发异常。
所以,改用这个:
function CanFocus(Control: TWinControl): Boolean;
begin
Result:= Control.CanFocus AND Control.Enabled AND Control.Visible;
if Result
AND NOT Control.InheritsFrom(TForm)
then
{ Recursive call:
This control might be hosted by a panel which could be also invisible/disabled.
So, we need to check all the parents down the road, until we encounter the parent Form.
Also see: GetParentForm }
Result:= CanFocus(Control.Parent); { Parent of a control could be nil, but in this case Control.CanFocus will deal with that.}
end;
procedure SetFocus(Control: TWinControl);
begin
if CanFocus(Control)
then Control.SetFocus;
end;
PS: Lazarus CanFocus 下运行正常
理由:
J 提供了一个很好的答案,但我不喜欢 class 助手,因为如果您有多个 class 助手用于同一个 class,将只使用一个。这个过程几乎是“通过骰子”:“使用”子句中的单位顺序决定了将应用哪个助手。我不喜欢编程语言中的这种随机性。
我正在处理一个巨大的遗留源代码,其中在许多地方调用了多个 SetFocus
,但有时,缺少控件是否可见或启用的检查。
由于时间有限和源代码量巨大,我决定忽略这些错误,因为重点(在我们的例子中)不是关键功能。引发的异常将导致完全失败,而缺少焦点只是一个光学问题。
我目前的计划如下:
我用这样的 class 助手创建了一个单元:
类型 TWinControlEx = class TWinControl 的助手 程序 SetFocusSafe; 结束;
程序TWinControlEx.SetFocusSafe; 开始 如果 CanFocus 则 SetFocus; 结束;
我把单位包含到每个使用“.SetFocus”的单位(我会使用全局代码搜索)
我将每个 .SetFocus 替换为 .SetFocusSafe
但是有一个问题:如果可能的话,我想避免同事不小心使用 .SetFocus ,或者忘记包含 classhelper 单元。
我还有哪些其他选择?
最好的情况是 technique/hack 让 SetFocus 不引发异常。 (无需重新编译 VCL)
只需修补 TWinControl.SetFocus
方法:
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.
或者
TWinControlEx = class helper for TWinControl
procedure SetFocus; reintroduce;
end;
与...
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;
我在下面的回答没有直接回答你的问题,但它仍然是相关的,因为你依赖 CanFocus。 CanFocus return 是个谎言。你不应该依赖它。文档也是错误的。更准确地说,即使控件不可聚焦,CanFocus 也可以 return True。在这种情况下将引发异常。
所以,改用这个:
function CanFocus(Control: TWinControl): Boolean;
begin
Result:= Control.CanFocus AND Control.Enabled AND Control.Visible;
if Result
AND NOT Control.InheritsFrom(TForm)
then
{ Recursive call:
This control might be hosted by a panel which could be also invisible/disabled.
So, we need to check all the parents down the road, until we encounter the parent Form.
Also see: GetParentForm }
Result:= CanFocus(Control.Parent); { Parent of a control could be nil, but in this case Control.CanFocus will deal with that.}
end;
procedure SetFocus(Control: TWinControl);
begin
if CanFocus(Control)
then Control.SetFocus;
end;
PS: Lazarus CanFocus 下运行正常
理由: J 提供了一个很好的答案,但我不喜欢 class 助手,因为如果您有多个 class 助手用于同一个 class,将只使用一个。这个过程几乎是“通过骰子”:“使用”子句中的单位顺序决定了将应用哪个助手。我不喜欢编程语言中的这种随机性。