delphivclsetfocus

Avoid that SetFocus raises an Exception


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:

  1. 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;

  2. I include the unit to every unit which uses ".SetFocus" (I will use the global code search)

  3. 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)


Solution

  • 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.