Search code examples
listviewdelphiwinapidelphi-11-alexandria

Range check error when setting TListView Header Font Style


In a 32-bit VCL Application in Windows 10 in Delphi 11 Alexandria, I have a TListView, where I set the ListView's Header Font Style to BOLD in the FormCreate handler:

private
  FhHeaderFont: THandle;

procedure TformMain.FormCreate(Sender: TObject);
begin  
  ...
  SetColumnHeaderFontBold;
end;

procedure TformMain.SetColumnHeaderFontBold;
const
  LVM_GETHEADER = Winapi.CommCtrl.LVM_FIRST + 31;
var
  LF: Winapi.Windows.TLogFont;
  hHeader, hCurrFont, hOldFont: THandle;
begin
  hHeader := Winapi.Windows.SendMessage(lvMRUProjects.Handle, LVM_GETHEADER, 0, 0);
  hCurrFont := Winapi.Windows.SendMessage(hHeader, WM_GETFONT, 0, 0); // ERangeError

  if GetObject(hCurrFont, SizeOf(LF), Addr(LF)) > 0 then
  begin
    LF.lfWeight := Winapi.Windows.FW_BOLD;
    FhHeaderFont := Winapi.Windows.CreateFontIndirect(LF);
    hOldFont := Winapi.Windows.SelectObject(hHeader, FhHeaderFont);
    Winapi.Windows.SendMessage(hHeader, winapi.Messages.WM_SETFONT, FhHeaderFont, 1);
  end;
end;

Now, I RANDOMLY get an ERangeError in this line when starting the program:

hCurrFont := Winapi.Windows.SendMessage(hHeader, WM_GETFONT, 0, 0); // ERangeError

This is the Eurekalog call-stack:

enter image description here

This is the error message:

Range check error at (004EDAED){MyApp.exe} [008EDAED] MainForm.TformMain.SetColumnHeaderFontBold (Line 616, "MainForm.pas") + $13.

What is causing this error and how can I avoid it?


Solution

  • There are two problems here:

    1. You have declared various variables with the wrong type.
    2. You sometimes need to use type casts when working with Windows messages.

    For the first issue, each variable that you declare as THandle is declared incorrectly. THandle is used for kernel handles, it is the Delphi type corresponding to HANDLE in the Windows C headers. None of the handles in your code is a kernel handle, they are all user mode objects.

    FhHeaderFont, hCurrFont and hOldFont should all be declared as HFONT. hHeader should be declared as HWND.

    The issue that is causing your range check error is the second one in my list. If you consider what SendMessage has to do, it is used for any Windows message. And these messages work with a huge range of different data types. This means that there will sometimes be mismatches between the generic integer types that SendMessage accepts and returns, and the types of the variables used for parameters and return values. These mismatches should be dealt with by type casting.

    The cause of the range check error is that SendMessage returns LRESULT which is a signed integer, pointer sized. But THandle is an unsigned integer, pointer sized. Even when you correct this by using HFONT, then the same error can arise because HFONT is also an unsigned integer, pointer sized.

    Deal with this by type-casting the return value:

    hCurrFont := HFONT(Winapi.Windows.SendMessage(hHeader, WM_GETFONT, 0, 0));
    

    Likewise you should do the same for the other return value assignment:

    hHeader := HWND(...);
    

    Strictly speaking, it would be best to do the same when passing FhHeaderFont as wParam in the final call to SendMessage. I think I would write that as:

    Winapi.Windows.SendMessage(hHeader, winapi.Messages.WM_SETFONT, WPARAM(FhHeaderFont), 1);
    

    One final note is that LVM_GETHEADER is defined in Winapi.CommCtrl and should not be re-defined here.