type
(*
 *  Die Komponente muss die aktuelle Position des Scroll-Bereichs und die
 *  maximalen Angaben selbst speichern. Zudem kann über die "Page"-
 *  Eigenschaften (hier zu "Pag" verkürzt) angegeben werden, wie viele
 *  Spalten bzw. Zeilen im Fenster sichtbar sind.
 *)
  TEditor = class(TCustomControl)
  private
    FScrollBars: TScrollStyle;
    FScrollMaxX: Integer;
    FScrollMaxY: Integer;
    FScrollPagX: Integer;
    FScrollPagY: Integer;
    FScrollPosX: Integer;
    FScrollPosY: Integer;
  protected
    procedure WMMouseWheel(var Message: TWMMouseWheel); message WM_MOUSEWHEEL;
    procedure WMHScroll(var Msg: TWMHScroll); message WM_HSCROLL;
    procedure WMVScroll(var Msg: TWMVScroll); message WM_VSCROLL;
    procedure WMSize(var Message: TWMSize); message WM_SIZE;
  protected
    procedure CreateParams(var Params: TCreateParams); override;
    procedure DoScrollBy(DeltaX, DeltaY: Integer); virtual;
    procedure DoScrollTo(PosX, PosY: Integer); virtual;
    procedure UpdateScrollBars; virtual;
  end;

(*
 *  Wenn das Rädchen an der Maus gedreht wurde, wird in der Regel
 *  um drei Einheiten gescrollt.
 *)
procedure TEditor.WMMouseWheel(var Message: TWMMouseWheel);
var
  SL: Integer;
begin
  inherited;
  Windows.SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, @SL, 0);
  if (SL <= 1) then SL := 3;
  DoScrollBy(0, (Message.WheelDelta * SL div -120));
end;

(*
 *  Die beiden Ereignisse für HSCROLL und VSCROLL unterscheiden sich
 *  nicht wesentlich. Es wird geprüft, was der Benutzer mit der ScrollBar
 *  gemacht hat und dann entsprechend darauf reagiert. Das eigentliche
 *  Scrollen findet dann in "DoScrollBy" und "DoScrollTo" statt.
 *)
procedure TEditor.WMHScroll(var Msg: TWMHScroll);
var
  S: TScrollInfo;
begin
  inherited;
  case Msg.ScrollCode of
    SB_LINELEFT: DoScrollBy(-1, 0);
    SB_LINERIGHT: DoScrollBy(1, 0);
    SB_PAGELEFT: DoScrollBy(-FScrollPagX, 0);
    SB_PAGERIGHT: DoScrollBy(FScrollPagX, 0);
    SB_THUMBTRACK,
    SB_THUMBPOSITION:
                begin
                    S.cbSize := SizeOf(S);
                    S.fMask := SIF_TRACKPOS;
                    GetScrollInfo(WindowHandle, SB_HORZ, S);
                    DoScrollTo(S.nTrackPos, FScrollPosY);
                end;
    SB_TOP: DoScrollTo(0, 0);
    SB_BOTTOM: DoScrollTo(FScrollMaxX, FScrollMaxY);
  else
      exit;
  end;
  Msg.Result := 0;
end;

(*
 *  Immer wenn sich die "Fenstergrösse" ändert, müssen die ScrollBars angepasst
 *  werden. Dieses Ereignis wird auch schon aufgerufen, wenn der Editor neu
 *  erstellt wird. "UpdateScrollBars" muss also nicht in "Create" aufgerufen
 *  werden!
 *)
procedure TEditor.WMSize(var Message: TWMSize);
begin
  inherited;
  UpdateScrollBars;
end;

(*
 *  Setzt man die "STYLE"-Eigenschaft für das Fenster auf WS_HSCROLL bzw.
 *  WS_VSCROLL, so wird das Fenster automatisch mit eigenen ScrollBars
 *  ausgestattet.
 *)
procedure TEditor.CreateParams(var Params: TCreateParams);
begin
  inherited CreateParams(Params);
  case FScrollBars of
    ssHorizontal: Params.Style := Params.Style or WS_HSCROLL;
    ssVertical: Params.Style := Params.Style or WS_VSCROLL;
    ssBoth: Params.Style := Params.Style or WS_HSCROLL or WS_VSCROLL;
  end;
  Params.WindowClass.hCursor := LoadCursor(0, IDC_IBEAM);
end;

(*
 *  Hier findet das eigentliche Scollen statt. Am besten prüft man noch, ob
 *  die Werte für "FScrollPos" in einem gültigen Breich liegen.
 *)
procedure TEditor.DoScrollBy(DeltaX, DeltaY: Integer);
begin
  DoScrollTo(FScrollPosX + DeltaX, FScrollPosY + DeltaY);
end;

procedure TEditor.DoScrollTo(PosX, PosY: Integer);
begin
  if (PosX <> FScrollPosX) or
     (PosY <> FScrollPosY) then
    begin
        FScrollPosX := PosX;
        FScrollPosY := PosY;
        Refresh;
        UpdateScrollBars;
    end;
end;

(*
 *  Hier werden die Informationen für die aktuelle Position und die Grösse
 *  des Dokumentes an die ScrollBars weitergeleitet. Das ganze muss man für
 *  beide ScrollBars separat machen.
 *)
procedure TEditor.UpdateScrollBars;
var
  S: TScrollInfo;
begin
  if (HandleAllocated) and (FScrollBars in [ssHorizontal, ssBoth]) then
    begin
        S.cbSize := SizeOf(S);
        S.fMask := SIF_POS or SIF_RANGE or SIF_PAGE or SIF_DISABLENOSCROLL;
        S.nPos := FScrollPosX;
        S.nMin := 0;
        S.nMax := FScrollMaxX;
        if (FScrollPagX > 0) then
          inc(S.nMax, FScrollPagX - 1);
        S.nPage := FScrollPagX;
        SetScrollInfo(WindowHandle, SB_HORZ, S, true);
    end;
  // Analog für SB_VERT...
end;

end.
