To see if any pixel of a child control ChildCtrl currently is visible in the parent TScrollBox control named ScrollBox, check
ScrollBox.ClientRect.IntersectsWith(ChildCtrl.BoundsRect)
This is just one definition of "not out of view", however. If you instead want to check if the entire control is visible, instead check
ScrollBox.ClientRect.Contains(ChildCtrl.BoundsRect)
To detect scrolling, you would love a published OnScroll property of TScrollBox, but unfortunately there is no such property. Instead, you must intercept the scroll messages yourself, as detailed in this Q&A.
Here is a complete example (just quick and dirty to show how it is done -- in a real app, you would refactor it):
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, StdCtrls;
type
TScrollBox = class(Vcl.Forms.TScrollBox)
procedure WMVScroll(var Message: TWMVScroll); message WM_VSCROLL;
end;
TForm1 = class(TForm)
ScrollBox1: TScrollBox;
lblTitle: TLabel;
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
FButtons: TArray<TButton>;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
const
N = 30;
var
i, y: Integer;
btn: TButton;
begin
// First, populate the scroll box with some sample buttons
SetLength(FButtons, N);
y := 10;
for i := 0 to N - 1 do
begin
btn := TButton.Create(ScrollBox1);
btn.Parent := ScrollBox1;
btn.Left := 10;
btn.Top := y;
btn.Caption := 'Button ' + (i + 1).ToString;
Inc(y, 3*btn.Height div 2);
FButtons[i] := btn;
end;
end;
{ TScrollBox }
procedure TScrollBox.WMVScroll(var Message: TWMVScroll);
var
i: Integer;
begin
inherited;
for i := 0 to High(Form1.FButtons) do
if Form1.ScrollBox1.ClientRect.Contains(Form1.FButtons[i].BoundsRect) then
begin
Form1.lblTitle.Caption := Form1.FButtons[i].Caption;
Break;
end;
end;
end.

Don't forget to set TScrollBox.VertScrollBar.Tracking to True!