Long descriptions on Inno Setup components

This solution uses only Inno Setup proper (not the obsolete 3rd party build of the Inno Setup of suspicious origin).

The solution is partially based on my answer to Inno Setup: OnHover event.

Adjust the HoverComponentChanged procedure to your needs.

[Code]

var
  LastMouse: TPoint;
  CompLabel: TLabel;

function GetCursorPos(var lpPoint: TPoint): BOOL;
  external '[email protected] stdcall';
function SetTimer(
  hWnd: longword; nIDEvent, uElapse: LongWord; lpTimerFunc: LongWord): LongWord;
  external '[email protected] stdcall';
function ScreenToClient(hWnd: HWND; var lpPoint: TPoint): BOOL;
  external '[email protected] stdcall';
function ClientToScreen(hWnd: HWND; var lpPoint: TPoint): BOOL;
  external '[email protected] stdcall';
function ListBox_GetItemRect(
  const hWnd: HWND; const Msg: Integer; Index: LongInt; var Rect: TRect): LongInt;
  external '[email protected] stdcall';  

const
  LB_GETITEMRECT = $0198;
  LB_GETTOPINDEX = $018E;

function FindControl(Parent: TWinControl; P: TPoint): TControl;
var
  Control: TControl;
  WinControl: TWinControl;
  I: Integer;
  P2: TPoint;
begin
  for I := 0 to Parent.ControlCount - 1 do
  begin
    Control := Parent.Controls[I];
    if Control.Visible and
       (Control.Left <= P.X) and (P.X < Control.Left + Control.Width) and
       (Control.Top <= P.Y) and (P.Y < Control.Top + Control.Height) then
    begin
      if Control is TWinControl then
      begin
        P2 := P;
        ClientToScreen(Parent.Handle, P2);
        WinControl := TWinControl(Control);
        ScreenToClient(WinControl.Handle, P2);
        Result := FindControl(WinControl, P2);
        if Result <> nil then Exit;
      end;

      Result := Control;
      Exit;
    end;
  end;

  Result := nil;
end;

function PointInRect(const Rect: TRect; const Point: TPoint): Boolean;
begin
  Result :=
    (Point.X >= Rect.Left) and (Point.X <= Rect.Right) and
    (Point.Y >= Rect.Top) and (Point.Y <= Rect.Bottom);
end;

function ListBoxItemAtPos(ListBox: TCustomListBox; Pos: TPoint): Integer;
var
  Count: Integer;
  ItemRect: TRect;
begin
  Result := SendMessage(ListBox.Handle, LB_GETTOPINDEX, 0, 0);
  Count := ListBox.Items.Count;
  while Result < Count do
  begin
    ListBox_GetItemRect(ListBox.Handle, LB_GETITEMRECT, Result, ItemRect);
    if PointInRect(ItemRect, Pos) then Exit;
    Inc(Result);
  end;
  Result := -1;
end;

procedure HoverComponentChanged(Index: Integer);
var 
  Description: string;
begin
  case Index of
    0: Description := 'This is the description of Main Files';
    1: Description := 'This is the description of Additional Files';
    2: Description := 'This is the description of Help Files';
  else
    Description := 'Move your mouse over a component to see its description.';
  end;
  CompLabel.Caption := Description;
end;

procedure HoverTimerProc(
  H: LongWord; Msg: LongWord; IdEvent: LongWord; Time: LongWord);
var
  P: TPoint;
  Control: TControl; 
  Index: Integer;
begin
  GetCursorPos(P);
  if P <> LastMouse then { just optimization }
  begin
    LastMouse := P;
    ScreenToClient(WizardForm.Handle, P);

    if (P.X < 0) or (P.Y < 0) or
       (P.X > WizardForm.ClientWidth) or (P.Y > WizardForm.ClientHeight) then
    begin
      Control := nil;
    end
      else
    begin
      Control := FindControl(WizardForm, P);
    end;

    Index := -1;
    if (Control = WizardForm.ComponentsList) and
       (not WizardForm.TypesCombo.DroppedDown) then
    begin
      P := LastMouse;
      ScreenToClient(WizardForm.ComponentsList.Handle, P);
      Index := ListBoxItemAtPos(WizardForm.ComponentsList, P);
    end;

    HoverComponentChanged(Index);
  end;
end;

procedure InitializeWizard();
begin
  SetTimer(0, 0, 50, CreateCallback(@HoverTimerProc));

  CompLabel := TLabel.Create(WizardForm);
  CompLabel.Parent := WizardForm.SelectComponentsPage;
  CompLabel.Left := WizardForm.ComponentsList.Left;
  CompLabel.Width := WizardForm.ComponentsList.Width;
  CompLabel.Height := ScaleY(32);
  CompLabel.Top :=
    WizardForm.ComponentsList.Top + WizardForm.ComponentsList.Height -
    CompLabel.Height;
  CompLabel.AutoSize := False;
  CompLabel.WordWrap := True;

  WizardForm.ComponentsList.Height :=
    WizardForm.ComponentsList.Height - CompLabel.Height - ScaleY(8);
end;

enter image description here

For CreateCallback function, you need Inno Setup 6. If you are stuck with Inno Setup 5, you can use WrapCallback function from InnoTools InnoCallback library. Use the Unicode version of Inno Setup 5.

To make this work correctly in resized/resizable/modern wizard, you will need some adjustments. See Inno Setup – how to center an animated gif in resized wizard and Long components descriptions in enlarged Inno Setup wizard.

Leave a Comment