一种独辟蹊径的滚动条自绘方法

  Windows界面控件的内置滚动条处于非客户区,要对其进行自绘非常麻烦。仅仅处理WM_NCPAINT消息是不够的,系统有很多情况下并不经由WM_NCPAINT绘制滚动条,比如鼠标在滚动条区域点击、滚轮滚动时。如果自行处理这些消息,复杂度大大增加,还可能有未知的漏网之鱼。处理滑块拖动,必须调用SetCapture,但是SetCapture后窗口收到的鼠标消息将是WM_MOUSEMOVE、WM_MOUSEUP而非WM_NCMOUSEMOVE、WM_NCMOUSEUP,因此还要增加对WM_MOUSEMOVE和WM_MOUSEUP的处理。 总之,要做好滚动条的自绘,涉及到很多窗口消息,而且不少消息不能调用DefWindowProc让系统参与。设计一个基类封装这些处理逻辑,ListView、ListBox、TreeView从这个基类继承,可以把复杂度降到最低。在Delphi的VCL类库中,最适合作为这个基类的是TWinControl。很不幸,TWinControl并没有考虑到开发者的这个需求,没有封装并提供相应接口。如果继承TListView、TListBox、TTreeView等所有你需要自绘滚动条的控件,就需要编写很多重复的代码,既丑陋,又不好维护。本文将提供一种优雅便捷的滚动条自绘方法,无需替换窗口消息处理例程,也无需钩子。

  先描述一下我的方法。创建一个容器控件,作为实际控件的父控件,容器和子控件都除去边框,子控件的滚动条就被隐藏了,而我们在容器的客户区绘制滚动条。容器控件的尺寸变化时,自动调整子控件尺寸以填满容器除滚动条以外的空间。由于将滚动条绘制在容器的客户区,消息处理非常便利。子控件的滚动条变化时,通知容器重绘。容器上滑块滚动时,给子控件发送WM_VSCROLL、WM_HSCROLL等相应的消息。以下是实际代码:

unit FSScrollControls;

interface

uses
  SysUtils, Classes, Consts, Windows, Graphics, Controls, Messages, StdCtrls, ExtCtrls;

type
  TFsCustomScrollBar = class(TComponent)
  private
    FVScrollWidth: Integer;
    FHScrollHeight: Integer;
    FVArrowHeight: Integer;
    FHArrowWidth: Integer;
    FMinThumbLength: Integer;
    procedure SetHScrollHeight(const Value: Integer);
    procedure SetVScrollWidth(const Value: Integer);
    procedure SetHArrowWidth(const Value: Integer);
    procedure SetVArrowHeight(const Value: Integer);
    procedure SetMinThumbLength(const Value: Integer);
  protected
    procedure Changed;
  public
    constructor Create(AOwner: TComponent); override;
    procedure CalcVScroll(const rc: TRect; const si: TScrollInfo; var rcTopArrow, rcBottomArrow, rcThumb: TRect);
    procedure CalcHScroll(const rc: TRect; const si: TScrollInfo; var rcLeftArrow, rcRightArrow, rcThumb: TRect);
    function CalcVPos(const rc: TRect; const si: TScrollInfo; Y: Integer): Integer;
    function CalcHPos(const rc: TRect; const si: TScrollInfo; Y: Integer): Integer;
    procedure DrawVScroll(dc: HDC; const rc, rcTopArrow, rcBottomArrow, rcThumb: TRect); virtual; abstract;
    procedure DrawHScroll(dc: HDC; const rc, rcLeftArrow, rcRightArrow, rcThumb: TRect); virtual; abstract;
    procedure DrawIntersect(dc: HDC; const rc: TRect); virtual; abstract;
    property HScrollHeight: Integer read FHScrollHeight write SetHScrollHeight;
    property VScrollWidth: Integer read FVScrollWidth write SetVScrollWidth;
    property VArrowHeight: Integer read FVArrowHeight write SetVArrowHeight;
    property HHArrowWidth: Integer read FHArrowWidth write SetHArrowWidth;
    property MinThumbLength: Integer read FMinThumbLength write SetMinThumbLength;
  end;

  TFsFlatScrollBar = class(TFsCustomScrollBar)
  private
    procedure DrawUpArrow(dc: HDC; const rc: TRect; h, bw: Integer);
    procedure DrawDownArrow(dc: HDC; const rc: TRect; h, bw: Integer);
    procedure DrawLeftArrow(dc: HDC; const rc: TRect; h, bw: Integer);
    procedure DrawRightArrow(dc: HDC; const rc: TRect; h, bw: Integer);
  public
    procedure DrawVScroll(dc: HDC; const rc, rcTopArrow, rcBottomArrow, rcThumb: TRect); override;
    procedure DrawHScroll(dc: HDC; const rc, rcLeftArrow, rcRightArrow, rcThumb: TRect); override;
    procedure DrawIntersect(dc: HDC; const rc: TRect); override;
  end;

  TFsScrollInfo = record
    ShowVScroll: Boolean;
    ShowHScroll: Boolean;
    VScroll: TRect;
    TopArrow: TRect;
    BottomArrow: TRect;
    VThumb: TRect;
    HScroll: TRect;
    LeftArrow: TRect;
    RightArrow: TRect;
    HThumb: TRect;
    Intersect: TRect;
  end;

  TScrollHitTest = (shtNoWhere, shtBorder, shtLeftArrow, shtRightArrow, shtHorzThumb, shtPageLeft, shtPageRight,
    shtTopArrow, shtBottomArrow, shtVertThumb, shtPageUp, shtPageDown);

  TFsScrollContainer = class(TCustomControl)
  private
    FTimer: TTimer;
    FCaptureRegion: TScrollHitTest;
    FCapturePoint: TPoint;
    FScrollBarDrawer: TFsCustomScrollBar;
    FRealControl: TControl;
    procedure SetScrollBarDrawer(const Value: TFsCustomScrollBar);
    function GetRealScrollBar: TFsCustomScrollBar;
    function NeedScrollBar(out HScroll: Boolean): Boolean;
    procedure GetScrollInfo(var fsi: TFsScrollInfo; var vsi, hsi: TScrollInfo);
    procedure OnTimer(Sender: TObject);
    function ControlMessage(msg: DWORD; wparam, lparam: Integer): Integer;
  protected
    FMouseInControl: Boolean;
    procedure WMSize(var msgr: TWMSize); message WM_SIZE;
    procedure CMMouseEnter(var msgr: TMessage); message CM_MOUSEENTER;
    procedure CMMouseLeave(var msgr: TMessage); message CM_MOUSELEAVE;
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
    procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
    procedure CreateParams(var Params: TCreateParams); override;
    procedure Paint; override;

    function GetControlScrollInfo(var si: TScrollInfo; isVert: Boolean): Boolean; virtual;
    function CreateRealControl: TControl; virtual; abstract;
    procedure DragThumb(BarFlag, ScrollCode, nTrackPos: Integer); virtual;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure AdjustInnerControlBounds;
    function HitTest(X, Y: Integer): TScrollHitTest; 
    property Canvas;
    property RealControl: TControl read FRealControl;
  published
    property Align;
    property Anchors;
    property ScrollBarDrawer: TFsCustomScrollBar read FScrollBarDrawer write SetScrollBarDrawer;
  end;

function GetDefaultScrollBar: TFsCustomScrollBar;

implementation


type
  TControlHack = class(TControl)

  end;

var
  DefaultScrollBar: TFsCustomScrollBar;

function GetDefaultScrollBar: TFsCustomScrollBar;
begin
  if not Assigned(DefaultScrollBar) then
    DefaultScrollBar := TFsFlatScrollBar.Create(nil);

  Result := DefaultScrollBar;
end;

procedure GradientFillRect(dc: THandle; const rect: TRect; TopLeftColor, BottomRightColor: TColor; IsVertical: Boolean);
var
  vertexs: array [0..1] of TRIVERTEX;
  color1, color2, mode: DWORD;
  gr: TGradientRect;
begin
  color1 := ColorToRGB(TopLeftColor);
  vertexs[0].x := rect.Left;
  vertexs[0].y := rect.Top;
  vertexs[0].Red := GetRValue(color1) * $ff00 div 255;
  vertexs[0].Green := GetGValue(color1) * $ff00 div 255;
  vertexs[0].Blue := GetBValue(color1) * $ff00 div 255;
  vertexs[0].Alpha := 0;

  color2 := ColorToRGB(BottomRightColor);
  vertexs[1].x := rect.Right;
  vertexs[1].y := rect.Bottom;
  vertexs[1].Red := GetRValue(color2) * $ff00 div 255;
  vertexs[1].Green := GetGValue(color2) * $ff00 div 255;
  vertexs[1].Blue := GetBValue(color2)* $ff00 div 255;
  vertexs[1].Alpha := 0;

  gr.UpperLeft := 0;
  gr.LowerRight := 1;

  if IsVertical then mode := GRADIENT_FILL_RECT_V
  else mode := GRADIENT_FILL_RECT_H;
  
  Windows.GradientFill(dc, @vertexs, 2, @gr, 1, mode);
end;

procedure GradientFillTriangle(dc: THandle; x1, y1, x2, y2, x3, y3: Integer; color1, color2, color3: TColor);
var
  vertexs: array [0..2] of TRIVERTEX;
  _color1, _color2, _color3: DWORD;
  gt: TGradientTriangle;
begin
  _color1 := ColorToRGB(color1);
  vertexs[0].x := x1;
  vertexs[0].y := y1;
  vertexs[0].Red := GetRValue(_color1) * $ff00 div 255;
  vertexs[0].Green := GetGValue(_color1) * $ff00 div 255;
  vertexs[0].Blue := GetBValue(_color1) * $ff00 div 255;
  vertexs[0].Alpha := 0;

  _color2 := ColorToRGB(color2);
  vertexs[1].x := x2;
  vertexs[1].y := y2;
  vertexs[1].Red := GetRValue(_color2) * $ff00 div 255;
  vertexs[1].Green := GetGValue(_color2) * $ff00 div 255;
  vertexs[1].Blue := GetBValue(_color2) * $ff00 div 255;
  vertexs[1].Alpha := 0;

  _color3 := ColorToRGB(color3);
  vertexs[2].x := x3;
  vertexs[2].y := y3;
  vertexs[2].Red := GetRValue(_color3) * $ff00 div 255;
  vertexs[2].Green := GetGValue(_color3) * $ff00 div 255;
  vertexs[2].Blue := GetBValue(_color3) * $ff00 div 255;
  vertexs[2].Alpha := 0;

  gt.Vertex1 := 0;
  gt.Vertex2 := 1;
  gt.Vertex3 := 2;

  Windows.GradientFill(dc, @vertexs, 3, @gt, 1, GRADIENT_FILL_TRIANGLE);
end;

{ TFsCustomScrollBar }

const
  SCROLL_MIN_THUMB_LENGTH = 20;

function TFsCustomScrollBar.CalcHPos(const rc: TRect; const si: TScrollInfo; Y: Integer): Integer;
var
  ThumbWidth, ScrollWidth: Integer;
begin
  if (si.nMax >= si.nMin) and (si.nPage <= si.nMax - si.nMin) then
  begin
    ScrollWidth := rc.Right - rc.Left - FHArrowWidth shl 1;

    ThumbWidth := si.nPage * ScrollWidth div (si.nMax - si.nMin + 1);

    if ThumbWidth < Self.MinThumbLength then ThumbWidth := Self.MinThumbLength;

    if Y <= rc.Left + FHArrowWidth then Result := si.nMin
    else if Y > rc.Right - FHArrowWidth - ThumbWidth then Result := si.nMax - si.nPage
    else begin
      Y := Y - rc.Left - FHArrowWidth;
      Result := (si.nMax - si.nMin + 1 - si.nPage) * Y div (ScrollWidth - ThumbWidth) + si.nMin - 1;
    end;
  end
  else Result := si.nMin - 1;
end;

procedure TFsCustomScrollBar.CalcHScroll(const rc: TRect; const si: TScrollInfo; var rcLeftArrow, rcRightArrow,
  rcThumb: TRect);
var
  ThumbWidth, ThumbPos, ScrollWidth: Integer;
begin
  rcLeftArrow.Left := rc.Left;
  rcLeftArrow.Right := rc.Left + FHArrowWidth;
  rcLeftArrow.Top := rc.Top;
  rcLeftArrow.Bottom := rc.Bottom;

  rcRightArrow.Left := rc.Right - FHArrowWidth;
  rcRightArrow.Right := rc.Right;
  rcRightArrow.Top := rc.Top;
  rcRightArrow.Bottom := rc.Bottom;

  if (si.nMax >= si.nMin) and (si.nPage <= si.nMax - si.nMin) then
  begin
    ScrollWidth := rc.Right - rc.Left - FHArrowWidth shl 1;

    rcThumb.Top := rc.Top;
    rcThumb.Bottom := rc.Bottom;

    ThumbWidth := si.nPage * ScrollWidth div (si.nMax - si.nMin + 1);

    if ThumbWidth < Self.MinThumbLength then ThumbWidth := Self.MinThumbLength;

    ThumbPos := (si.nPos - si.nMin + 1) * (ScrollWidth - ThumbWidth) div (si.nMax - si.nMin + 1 - si.nPage);

    rcThumb.Left := rc.Left + FHArrowWidth + ThumbPos;
    rcThumb.Right := rcThumb.Left + ThumbWidth;
  end
  else begin
    rcThumb.Left := 0;
    rcThumb.Right := -1;
    rcThumb.Top := 0;
    rcThumb.Bottom := -1;
  end;
end;

function TFsCustomScrollBar.CalcVPos(const rc: TRect; const si: TScrollInfo; Y: Integer): Integer;
var
  ThumbHeight, ScrollHeight: Integer;
begin
  if (si.nMax >= si.nMin) and (si.nPage <= si.nMax - si.nMin) then
  begin
    ScrollHeight := rc.Bottom - rc.Top - FVArrowHeight shl 1;

    ThumbHeight := si.nPage * ScrollHeight div (si.nMax - si.nMin + 1);

    if ThumbHeight < Self.MinThumbLength then ThumbHeight := Self.MinThumbLength;

    if Y <= rc.Top + FVArrowHeight then Result := si.nMin
    else if Y > rc.Bottom - FVArrowHeight - ThumbHeight then Result := si.nMax - si.nPage
    else begin
      Y := Y - rc.Top - FVArrowHeight;
      Result := (si.nMax - si.nMin + 1 - si.nPage) * Y div (ScrollHeight - ThumbHeight) + si.nMin - 1;
    end;
  end
  else Result := si.nMin - 1;
end;

procedure TFsCustomScrollBar.CalcVScroll(const rc: TRect; const si: TScrollInfo;
  var rcTopArrow, rcBottomArrow, rcThumb: TRect);
var
  ThumbHeight, ThumbPos, ScrollHeight: Integer;
begin
  rcTopArrow.Left := rc.Left;
  rcTopArrow.Right := rc.Right;
  rcTopArrow.Top := rc.Top;
  rcTopArrow.Bottom := rcTopArrow.Top + FVArrowHeight;

  rcBottomArrow.Left := rc.Left;
  rcBottomArrow.Right := rc.Right;
  rcBottomArrow.Top := rc.Bottom - FVArrowHeight;
  rcBottomArrow.Bottom := rc.Bottom;

  if (si.nMax >= si.nMin) and (si.nPage <= si.nMax - si.nMin) then
  begin
    ScrollHeight := rc.Bottom - rc.Top - FVArrowHeight shl 1;
    
    rcThumb.Left := rc.Left;
    rcThumb.Right := rc.Right;

    ThumbHeight := si.nPage * ScrollHeight div (si.nMax - si.nMin + 1);

    if ThumbHeight < Self.MinThumbLength then ThumbHeight := Self.MinThumbLength;
    
    ThumbPos := (si.nPos - si.nMin + 1) * (ScrollHeight - ThumbHeight) div (si.nMax - si.nMin + 1 - si.nPage);

    rcThumb.Top := rc.Top + FVArrowHeight + ThumbPos;
    rcThumb.Bottom := rcThumb.Top + ThumbHeight;
  end
  else begin
    rcThumb.Left := 0;
    rcThumb.Right := -1;
    rcThumb.Top := 0;
    rcThumb.Bottom := -1;
  end;
end;

procedure TFsCustomScrollBar.Changed;
begin
  
end;

constructor TFsCustomScrollBar.Create(AOwner: TComponent);
begin
  inherited;
  FVScrollWidth := GetSystemMetrics(SM_CXVSCROLL);
  FHScrollHeight := GetSystemMetrics(SM_CYHSCROLL);
  FVArrowHeight := GetSystemMetrics(SM_CYVSCROLL);
  FHArrowWidth := GetSystemMetrics(SM_CXHSCROLL);
  FMinThumbLength := SCROLL_MIN_THUMB_LENGTH;
end;

procedure TFsCustomScrollBar.SetHArrowWidth(const Value: Integer);
begin
  if (FHArrowWidth <> Value) and (Value > 0) then
  begin
    FHArrowWidth := Value;
    Changed;
  end;
end;

procedure TFsCustomScrollBar.SetHScrollHeight(const Value: Integer);
begin
  if (FHScrollHeight <> Value) and (Value > 0) then
  begin
    FHScrollHeight := Value;
    Changed;
  end;
end;

procedure TFsCustomScrollBar.SetMinThumbLength(const Value: Integer);
begin
  if (FMinThumbLength <> Value) and (Value >= SCROLL_MIN_THUMB_LENGTH) then
  begin
    FMinThumbLength := Value;
    Changed;
  end;
end;

procedure TFsCustomScrollBar.SetVArrowHeight(const Value: Integer);
begin
  if (FVArrowHeight <> Value) and (Value > 0) then
  begin
    FVArrowHeight := Value;
    Changed;
  end;
end;

procedure TFsCustomScrollBar.SetVScrollWidth(const Value: Integer);
begin
  if (FVScrollWidth <> Value) and (Value > 0) then
  begin
    FVScrollWidth := Value;
    Changed;
  end;
end;

{ TFsFlatScrollBar }

procedure TFsFlatScrollBar.DrawHScroll(dc: HDC; const rc, rcLeftArrow, rcRightArrow, rcThumb: TRect);
var
  brush: HBRUSH;
begin
  Windows.FillRect(dc, rc, GetStockObject(LTGRAY_BRUSH));

  GradientFillRect(dc, Rect(rcLeftArrow.Left + 1, rcLeftArrow.Top + 1,
    rcLeftArrow.Right - 1, rcLeftArrow.Bottom - 1),
    RGB(255, 255, 255), RGB(229, 229, 229), False);

  Self.DrawLeftArrow(dc, rcLeftArrow,
    (rcLeftArrow.Right - rcLeftArrow.Left) div 2,
    (rcLeftArrow.Bottom - rcLeftArrow.Top) div 2);

  GradientFillRect(dc, Rect(rcRightArrow.Left + 1, rcRightArrow.Top + 1,
    rcRightArrow.Right - 1, rcRightArrow.Bottom - 1),
    RGB(255, 255, 255), RGB(229, 229, 229), False);

  Self.DrawRightArrow(dc, rcRightArrow,
    (rcRightArrow.Right - rcRightArrow.Left) div 2,
    (rcRightArrow.Bottom - rcRightArrow.Top) div 2);

  if rcThumb.Left < rcThumb.Right then
  begin
    brush := CreateSolidBrush(RGB(136, 136, 136));

    FillRect(dc, Rect(rcThumb.Left, rcThumb.Top + 1, rcThumb.Right - 1, rcThumb.Top + 2), GetStockObject(WHITE_BRUSH));
    FillRect(dc, Rect(rcThumb.Left, rcThumb.Top + 2, rcThumb.Left + 1, rcThumb.Bottom - 1), GetStockObject(WHITE_BRUSH));
    FillRect(dc, Rect(rcThumb.Right - 1, rcThumb.Top + 1, rcThumb.Right, rcThumb.Bottom - 1), brush);
    FillRect(dc, Rect(rcThumb.Left + 1, rcThumb.Bottom - 2, rcThumb.Right - 1, rcThumb.Bottom - 1), brush);

    DeleteObject(brush);

    GradientFillRect(dc, Rect(rcThumb.Left + 1, rcThumb.Top + 2, rcThumb.Right - 1, rcThumb.Bottom - 2),
      RGB(254, 254, 254), RGB(229, 229, 229), True);
  end;
end;

procedure TFsFlatScrollBar.DrawVScroll(dc: HDC; const rc, rcTopArrow, rcBottomArrow, rcThumb: TRect);
var
  brush: HBRUSH;
begin
  Windows.FillRect(dc, rc, GetStockObject(LTGRAY_BRUSH));

  GradientFillRect(dc, Rect(rcTopArrow.Left + 1, rcTopArrow.Top + 1,
    rcTopArrow.Right - 1, rcTopArrow.Bottom - 1),
    RGB(255, 255, 255), RGB(229, 229, 229), False);

  Self.DrawUpArrow(dc, rcTopArrow, (rcTopArrow.Bottom - rcTopArrow.Top) div 2, (rcTopArrow.Right - rcTopArrow.Left) div 2);

  GradientFillRect(dc, Rect(rcBottomArrow.Left + 1, rcBottomArrow.Top + 1,
    rcBottomArrow.Right - 1, rcBottomArrow.Bottom - 1),
    RGB(255, 255, 255), RGB(229, 229, 229), False);

  Self.DrawDownArrow(dc, rcBottomArrow, (rcBottomArrow.Bottom - rcBottomArrow.Top) div 2, (rcBottomArrow.Right - rcBottomArrow.Left) div 2);

  if rcThumb.Left < rcThumb.Right then
  begin
    brush := CreateSolidBrush(RGB(136, 136, 136));

    FillRect(dc, Rect(rcThumb.Left + 1, rcThumb.Top, rcThumb.Right - 2, rcThumb.Top + 1), GetStockObject(WHITE_BRUSH));
    FillRect(dc, Rect(rcThumb.Left + 1, rcThumb.Top + 1, rcThumb.Left + 2, rcThumb.Bottom), GetStockObject(WHITE_BRUSH));
    FillRect(dc, Rect(rcThumb.Right - 2, rcThumb.Top, rcThumb.Right - 1, rcThumb.Bottom), brush);
    FillRect(dc, Rect(rcThumb.Left + 2, rcThumb.Bottom - 1, rcThumb.Right - 1, rcThumb.Bottom), brush);

    DeleteObject(brush);

    GradientFillRect(dc, Rect(rcThumb.Left + 2, rcThumb.Top + 1, rcThumb.Right - 2, rcThumb.Bottom - 1),
      RGB(254, 254, 254), RGB(229, 229, 229), False);
  end;
end;

procedure TFsFlatScrollBar.DrawDownArrow(dc: HDC; const rc: TRect; h, bw: Integer);
var
  pts: array [0..2] of TPoint;
begin
  pts[0].X := rc.Left + (rc.Right - rc.Left - bw) div 2 ;
  pts[0].Y := rc.Top + (rc.Bottom - rc.Top - h) div 2;

  pts[1].X := pts[0].X + bw;
  pts[1].Y := pts[0].Y;

  pts[2].X := rc.Left + (rc.Right - rc.Left) div 2;
  pts[2].Y := pts[0].Y + h;

  GradientFillTriangle(dc, pts[0].X, pts[0].Y, pts[1].X, pts[1].Y, pts[2].X, pts[2].Y,
    RGB(180, 180, 180), RGB(180, 180, 180), RGB(192, 192, 192));
end;

procedure TFsFlatScrollBar.DrawIntersect(dc: HDC; const rc: TRect);
begin
  FillRect(dc, rc, GetStockObject(LTGRAY_BRUSH));
end;

procedure TFsFlatScrollBar.DrawLeftArrow(dc: HDC; const rc: TRect; h, bw: Integer);
var
  pts: array [0..2] of TPoint;
begin
  pts[0].X := rc.Left + (rc.Right - rc.Left - h) div 2;
  pts[0].Y := rc.Top + (rc.Bottom - rc.Top) div 2;

  pts[1].X := pts[0].X + h;
  pts[1].Y := rc.Top + (rc.Bottom - rc.Top - bw) div 2;

  pts[2].X := pts[1].X;
  pts[2].Y := pts[1].Y + bw;

  GradientFillTriangle(dc, pts[0].X, pts[0].Y, pts[1].X, pts[1].Y, pts[2].X, pts[2].Y,
    RGB(192, 192, 192), RGB(180, 180, 180), RGB(180, 180, 180));
end;

procedure TFsFlatScrollBar.DrawRightArrow(dc: HDC; const rc: TRect; h, bw: Integer);
var
  pts: array [0..2] of TPoint;
begin
  pts[1].X := rc.Left + (rc.Right - rc.Left - h) div 2;
  pts[1].Y := rc.Top + (rc.Bottom - rc.Top - bw) div 2;

  pts[2].X := pts[1].X;
  pts[2].Y := pts[1].Y + bw;

  pts[0].X := pts[1].X + h;
  pts[0].Y := rc.Top + (rc.Bottom - rc.Top) div 2;

  GradientFillTriangle(dc, pts[0].X, pts[0].Y, pts[1].X, pts[1].Y, pts[2].X, pts[2].Y,
    RGB(192, 192, 192), RGB(180, 180, 180), RGB(180, 180, 180));
end;

procedure TFsFlatScrollBar.DrawUpArrow(dc: HDC; const rc: TRect; h, bw: Integer);
var
  pts: array [0..2] of TPoint;
begin
  pts[0].X := rc.Left + (rc.Right - rc.Left) div 2;
  pts[0].Y := rc.Top + (rc.Bottom - rc.Top - h) div 2;

  pts[1].X := rc.Left + (rc.Right - rc.Left - bw) div 2;
  pts[1].Y := pts[0].Y + h;

  pts[2].X := pts[1].X + bw;
  pts[2].Y := pts[1].Y;

  GradientFillTriangle(dc, pts[0].X, pts[0].Y, pts[1].X, pts[1].Y, pts[2].X, pts[2].Y,
    RGB(192, 192, 192), RGB(180, 180, 180), RGB(180, 180, 180));
end;

{ TFsScrollContainer }

procedure TFsScrollContainer.AdjustInnerControlBounds;
var
  L, R, T, B: Integer;
  sb: TFsCustomScrollBar;
  VScroll, HScroll: Boolean;
begin
  if Assigned(FRealControl) then
  begin
  L := 2;
    R := Self.ClientWidth - 2;
    T := 2;
    B := Self.ClientHeight - 2;

    VScroll := Self.NeedScrollBar(HScroll);

    sb := Self.GetRealScrollBar;

    if VScroll then Dec(R, sb.VScrollWidth);

    if HScroll then Dec(B, sb.HScrollHeight);

    FRealControl.SetBounds(L, T, R - L, B - T);
  end;
end;

procedure TFsScrollContainer.CMMouseEnter(var msgr: TMessage);
begin
  inherited;
  FMouseInControl := True;
  Self.Invalidate;
end;

procedure TFsScrollContainer.CMMouseLeave(var msgr: TMessage);
begin
  inherited;
  FMouseInControl := False;
  Self.Invalidate;
end;

function TFsScrollContainer.ControlMessage(msg: DWORD; wparam, lparam: Integer): Integer;
begin
  if Assigned(FRealControl) then
    Result := FRealControl.Perform(msg, wparam, lparam)
  else Result := -1;
end;

constructor TFsScrollContainer.Create(AOwner: TComponent);
begin
  inherited;

  ControlStyle := [csAcceptsControls, csCaptureMouse, csClickEvents,
    csSetCaption, csOpaque, csDoubleClicks, csReplicatable, csPannable];

  Width := 185;
  Height := 41;

  FRealControl := Self.CreateRealControl;
  FRealControl.Parent := Self;

  FTimer := TTimer.Create(Self);
  FTimer.Enabled := False;
  FTimer.Interval := 200;
  FTimer.OnTimer := Self.OnTimer;
end;

procedure TFsScrollContainer.CreateParams(var Params: TCreateParams);
begin
  inherited;

  Params.Style := Params.Style and not WS_BORDER;
  Params.WindowClass.style := Params.WindowClass.style or CS_HREDRAW or CS_VREDRAW;
end;

destructor TFsScrollContainer.Destroy;
begin

  inherited;
end;

procedure TFsScrollContainer.DragThumb(BarFlag, ScrollCode, nTrackPos: Integer);
begin
  if BarFlag = SB_VERT then
    Self.ControlMessage(WM_VSCROLL, MakeLong(ScrollCode, nTrackPos), 0)
  else
    Self.ControlMessage(WM_HSCROLL, MakeLong(ScrollCode, nTrackPos), 0);
end;

function TFsScrollContainer.GetControlScrollInfo(var si: TScrollInfo; isVert: Boolean): Boolean;
const
  BARS: array [Boolean] of DWORD = (SB_HORZ, SB_VERT);
begin
  if Assigned(FRealControl) and (FRealControl is TWinControl) and (TWinControl(FRealControl).HandleAllocated) then
    Result := Windows.GetScrollInfo(TWinControl(FRealControl).Handle, BARS[isVert], si) and (si.nPage <> 0)
  else Result := False;
end;

function TFsScrollContainer.GetRealScrollBar: TFsCustomScrollBar;
begin
  if Assigned(FScrollBarDrawer) then Result := FScrollBarDrawer
  else Result := GetDefaultScrollBar;
end;

procedure TFsScrollContainer.GetScrollInfo(var fsi: TFsScrollInfo; var vsi, hsi: TScrollInfo);
var
  sb: TFsCustomScrollBar;
begin
  sb := GetRealScrollBar;

  fsi.ShowVScroll := False;
  fsi.ShowHScroll := False;

  vsi.cbSize := SizeOf(vsi);
  vsi.fMask := SIF_RANGE or SIF_POS or SIF_PAGE;

  if GetControlScrollInfo(vsi, True) and (vsi.nMax - vsi.nMin + 1 > vsi.nPage) then
    fsi.ShowVScroll := True;

  hsi.cbSize := SizeOf(hsi);
  hsi.fMask := SIF_RANGE or SIF_POS or SIF_PAGE;

  if GetControlScrollInfo(hsi, False) and (hsi.nMax - hsi.nMin + 1 > hsi.nPage) then
    fsi.ShowHScroll := True;

  if fsi.ShowVScroll then
  begin
    fsi.VScroll.Right := Self.Width - 2;
    fsi.VScroll.Left := fsi.VScroll.Right - sb.VScrollWidth;
    fsi.VScroll.Top := 2;

    if fsi.ShowHScroll then fsi.VScroll.Bottom := Self.Height - 2 - sb.HScrollHeight
    else fsi.VScroll.Bottom := Self.Height - 2;

    sb.CalcVScroll(fsi.VScroll, vsi, fsi.TopArrow, fsi.BottomArrow, fsi.VThumb);
  end;

  if fsi.ShowHScroll then
  begin
    fsi.HScroll.Bottom := Self.Height - 2;
    fsi.HScroll.Top := fsi.HScroll.Bottom - sb.HScrollHeight;
    fsi.HScroll.Left := 2;

    if fsi.ShowHScroll then fsi.HScroll.Right := Self.Width - 2 - sb.VScrollWidth
    else fsi.HScroll.Right := Self.Width - 2;

    sb.CalcHScroll(fsi.HScroll, hsi, fsi.LeftArrow, fsi.RightArrow, fsi.HThumb);
  end;

  if fsi.ShowVScroll and fsi.ShowHScroll then
  begin
    fsi.Intersect.Left := Self.Width - 2 - sb.VScrollWidth;
    fsi.Intersect.Right := Self.Width - 2;
    fsi.Intersect.Top := Self.Height - 2 - sb.HScrollHeight;
    fsi.Intersect.Bottom := Self.Height - 2;
  end;
end;

function TFsScrollContainer.HitTest(X, Y: Integer): TScrollHitTest;
var
  fsi: TFsScrollInfo;
  vsi, hsi: TScrollInfo;
  pt: TPoint;
begin
  pt.X := X;
  pt.Y := Y;

  Self.GetScrollInfo(fsi, vsi, hsi);

  Result := shtNoWhere;

  if fsi.ShowVScroll then
  begin
    if PtInRect(fsi.TopArrow, pt) then Result := shtTopArrow
    else if PtInRect(fsi.BottomArrow, pt) then Result := shtBottomArrow
    else if PtInRect(fsi.VThumb, pt) then Result := shtVertThumb
    else if PtInRect(fsi.VScroll, pt) then
    begin
      if pt.Y < fsi.VThumb.Top then Result := shtPageUp
      else Result := shtPageDown;
    end;
  end;

  if fsi.ShowHScroll and (Result = shtNoWhere) then
  begin
    if PtInRect(fsi.LeftArrow, pt) then Result := shtLeftArrow
    else if PtInRect(fsi.RightArrow, pt) then Result := shtRightArrow
    else if PtInRect(fsi.HThumb, pt) then Result := shtHorzThumb
    else if PtInRect(fsi.HScroll, pt) then
    begin
      if pt.X < fsi.HThumb.Left then Result := shtPageLeft
      else Result := shtPageRight;
    end;
  end;
end;

procedure TFsScrollContainer.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
  sht: TScrollHitTest;
  DoCapture: Boolean;
  fsi: TFsScrollInfo;
  vsi, hsi: TScrollInfo;
begin
  DoCapture := True;
  sht := Self.HitTest(X, Y);

  case sht of
    shtNoWhere, shtBorder: DoCapture := False;
    shtLeftArrow: Self.ControlMessage(WM_HSCROLL, SB_LINELEFT, 0);
    shtRightArrow: Self.ControlMessage(WM_HSCROLL, SB_LINERIGHT, 0);
    shtHorzThumb: ;
    shtPageLeft: Self.ControlMessage(WM_HSCROLL, SB_PAGELEFT, 0);
    shtPageRight: Self.ControlMessage(WM_HSCROLL, SB_PAGERIGHT, 0);
    shtTopArrow: Self.ControlMessage(WM_VSCROLL, SB_LINEUP, 0);
    shtBottomArrow: Self.ControlMessage(WM_VSCROLL, SB_LINEDOWN, 0);
    shtVertThumb: ;
    shtPageUp: Self.ControlMessage(WM_VSCROLL, SB_PAGEUP, 0);
    shtPageDown: Self.ControlMessage(WM_VSCROLL, SB_PAGEDOWN, 0);
  end;

  if DoCapture then
  begin
    FCaptureRegion := sht;
    SetCapture(Self.Handle);

    if FCaptureRegion in [shtVertThumb, shtHorzThumb] then
    begin
      Self.GetScrollInfo(fsi, vsi, hsi);
      FCapturePoint.X := X - fsi.HThumb.Left;
      FCapturePoint.Y := Y - fsi.VThumb.Top;
    end
    else FTimer.Enabled := True;
  end;

  inherited;
end;

procedure TFsScrollContainer.MouseMove(Shift: TShiftState; X, Y: Integer);
var
  pt: TPoint;
  fsi: TFsScrollInfo;
  vsi, hsi: TScrollInfo;
  nPos: Integer;
begin
  case FCaptureRegion of
    shtLeftArrow, shtRightArrow, shtPageLeft, shtPageRight,
    shtTopArrow, shtBottomArrow, shtPageUp, shtPageDown:
      begin
        GetCursorPos(pt);
        Windows.ScreenToClient(Handle, pt);
        FTimer.Enabled := HitTest(pt.X, pt.Y) = FCaptureRegion;
      end;

    shtVertThumb:
      begin
        Self.GetScrollInfo(fsi, vsi, hsi);

        if fsi.ShowVScroll then
        begin
          nPos := GetRealScrollBar.CalcVPos(fsi.VScroll, vsi, Y - FCapturePoint.Y);

          if nPos >= hsi.nMin then
            Self.DragThumb(SB_VERT, SB_THUMBTRACK, nPos);
        end;
      end;

    shtHorzThumb:
      begin
        Self.GetScrollInfo(fsi, vsi, hsi);

        if fsi.ShowHScroll then
        begin
          nPos := GetRealScrollBar.CalcHPos(fsi.HScroll, hsi, X - FCapturePoint.X);

          if nPos >= hsi.nMin then
            Self.DragThumb(SB_HORZ, SB_THUMBTRACK, nPos);
        end;
      end;
  end;

  inherited;
end;

procedure TFsScrollContainer.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
  fsi: TFsScrollInfo;
  vsi, hsi: TScrollInfo;
  nPos: Integer;
begin
  case FCaptureRegion of
    shtVertThumb:
      begin
        Self.GetScrollInfo(fsi, vsi, hsi);

        if fsi.ShowVScroll then
        begin
          nPos := GetRealScrollBar.CalcVPos(fsi.VScroll, vsi, Y - FCapturePoint.Y);

          if nPos >= hsi.nMin then
            Self.DragThumb(SB_VERT, SB_THUMBPOSITION, nPos);
        end;
      end;

    shtHorzThumb:
      begin
        Self.GetScrollInfo(fsi, vsi, hsi);

        if fsi.ShowHScroll then
        begin
          nPos := GetRealScrollBar.CalcHPos(fsi.HScroll, hsi, X - FCapturePoint.X);

          if nPos >= hsi.nMin then
            Self.DragThumb(SB_HORZ, SB_THUMBPOSITION, nPos);
        end;
      end;
  end;

  ReleaseCapture;
  FCaptureRegion := shtNoWhere;
  FTimer.Enabled := False;
  
  inherited;
end;

function TFsScrollContainer.NeedScrollBar(out HScroll: Boolean): Boolean;
var
  si: TScrollInfo;
begin
  si.cbSize := SizeOf(si);
  si.fMask := SIF_RANGE or SIF_PAGE;

  HScroll := GetControlScrollInfo(si, False) and (si.nMax - si.nMin + 1 > si.nPage);

  si.cbSize := SizeOf(si);
  si.fMask := SIF_RANGE or SIF_PAGE;

  Result := GetControlScrollInfo(si, True) and (si.nMax - si.nMin + 1 > si.nPage);
end;

procedure TFsScrollContainer.OnTimer(Sender: TObject);
var
  pt: TPoint;
begin
  case FCaptureRegion of
    shtLeftArrow: Self.ControlMessage(WM_HSCROLL, SB_LINELEFT, 0);
    shtRightArrow: Self.ControlMessage(WM_HSCROLL, SB_LINERIGHT, 0);
    shtPageLeft:
      begin
        Self.ControlMessage(WM_HSCROLL, SB_PAGELEFT, 0);
        GetCursorPos(pt);
        Windows.ScreenToClient(Handle, pt);
        if HitTest(pt.X, pt.Y) <> shtPageLeft then FTimer.Enabled := False;
      end;
    shtPageRight:
      begin
        Self.ControlMessage(WM_HSCROLL, SB_PAGERIGHT, 0);
        GetCursorPos(pt);
        Windows.ScreenToClient(Handle, pt);
        if HitTest(pt.X, pt.Y) <> shtPageLeft then FTimer.Enabled := False;
      end;
    shtTopArrow: Self.ControlMessage(WM_VSCROLL, SB_LINEUP, 0);
    shtBottomArrow: Self.ControlMessage(WM_VSCROLL, SB_LINEDOWN, 0);
    shtPageUp:
      begin
        Self.ControlMessage(WM_VSCROLL, SB_PAGEUP, 0);
        GetCursorPos(pt);
        Windows.ScreenToClient(Handle, pt);
        if HitTest(pt.X, pt.Y) <> shtPageUp then FTimer.Enabled := False;
      end;
    shtPageDown:
      begin
        Self.ControlMessage(WM_VSCROLL, SB_PAGEDOWN, 0);
        GetCursorPos(pt);
        Windows.ScreenToClient(Handle, pt);
        if HitTest(pt.X, pt.Y) <> shtPageDown then FTimer.Enabled := False;
      end;
  end;
end;

procedure TFsScrollContainer.Paint;
var
  sb: TFsCustomScrollBar;
  r: TRect;
  fsi: TFsScrollInfo;
  vsi, hsi: TScrollInfo;
begin
  inherited;

  if FMouseInControl then Canvas.Brush.Color := RGB(123, 228, 255)
  else Canvas.Brush.Color := RGB(78, 160, 209);

  r.Left := 0;
  r.Top := 0;
  r.Right := Self.Width;
  r.Bottom := Self.Height;

  Canvas.FrameRect(r);

  if FMouseInControl then Canvas.Brush.Color := RGB(78, 160, 209)
  else Canvas.Brush.Color := TControlHack(FRealControl).Color;

  r.Left := 1;
  r.Top := 1;
  r.Right := Self.Width - 1;
  r.Bottom := Self.Height - 1;

  Canvas.FrameRect(r);

  sb := GetRealScrollBar;

  Self.GetScrollInfo(fsi, vsi, hsi);

  if fsi.ShowVScroll then
    sb.DrawVScroll(Canvas.Handle, fsi.VScroll, fsi.TopArrow, fsi.BottomArrow, fsi.VThumb);

  if fsi.ShowHScroll then
    sb.DrawHScroll(Canvas.Handle, fsi.HScroll, fsi.LeftArrow, fsi.RightArrow, fsi.HThumb);

  if fsi.ShowVScroll and fsi.ShowHScroll then
    sb.DrawIntersect(Canvas.Handle, fsi.Intersect);
end;

procedure TFsScrollContainer.SetScrollBarDrawer(const Value: TFsCustomScrollBar);
begin
  if Value <> FScrollBarDrawer then
  begin
    if Assigned(FScrollBarDrawer) then
      FScrollBarDrawer.RemoveFreeNotification(Self);

    FScrollBarDrawer := Value;

    if Assigned(FScrollBarDrawer) then
      FScrollBarDrawer.FreeNotification(Self);

    Self.AdjustInnerControlBounds;

    //Self.Invalidate;
  end;
end;

procedure TFsScrollContainer.WMSize(var msgr: TWMSize);
begin
  inherited;
  AdjustInnerControlBounds;
end;

initialization

finalization
  DefaultScrollBar.Free;

end.

 

继承TFsScrollContainer并重写(overide)CreateRealControl方法,就写好了相应的自绘滚动条控件了。以Memo为例:

  TFsMemo = class(TFsScrollContainer)
  private
    function GetScrollBars: TScrollStyle;
    procedure SetScrollBars(const Value: TScrollStyle);
    function GetLines: TStrings;
    procedure SetLines(const Value: TStrings);
    function GetMemo: TCustomMemo;
  protected
    function CreateRealControl: TControl; override;
  public
    property Memo: TCustomMemo read GetMemo;
  published
    property ScrollBars: TScrollStyle read GetScrollBars write SetScrollBars;
    property Lines: TStrings read GetLines write SetLines;
  end;

  TFsBorderlessMemo = class(TCustomMemo)
  protected
    procedure WndProc(var msgr: TMessage); override;
    procedure WMNCCalcSize(var msgr: TWMNCCalcSize); message WM_NCCALCSIZE;
    procedure WMNCPaint(var msgr: TWMNCPaint); message WM_NCPAINT;
  end;

{ TFsBorderlessMemo }

procedure TFsBorderlessMemo.WMNCCalcSize(var msgr: TWMNCCalcSize);
begin
  msgr.Result := 0;
end;

procedure TFsBorderlessMemo.WMNCPaint(var msgr: TWMNCPaint);
begin
  msgr.Result := 0;
end;

procedure TFsBorderlessMemo.WndProc(var msgr: TMessage);
var
  vsi1, hsi1, vsi2, hsi2: TScrollInfo;
  style: Integer;
  changed: Boolean;
begin
  if not HandleAllocated or (msgr.Msg = WM_CREATE) or (msgr.Msg = WM_NCCREATE)
    or (msgr.Msg = WM_DESTROY) or (msgr.Msg = WM_NCDESTROY) then
  begin
    inherited;
    Exit;
  end;

  changed := False;

  style := GetWindowLong(Self.Handle, GWL_STYLE);

  if style and WS_VSCROLL <> 0 then
  begin
    vsi1.cbSize := SizeOf(vsi1);
    vsi1.fMask := SIF_RANGE or SIF_PAGE or SIF_POS;
    GetScrollInfo(Self.Handle, SB_VERT, vsi1);
  end;

  if style and WS_HSCROLL <> 0 then
  begin
    hsi1.cbSize := SizeOf(hsi1);
    hsi1.fMask := SIF_RANGE or SIF_PAGE or SIF_POS;
    GetScrollInfo(Self.Handle, SB_HORZ, hsi1);
  end;

  inherited;

  if style and WS_VSCROLL <> 0 then
  begin
    vsi2.cbSize := SizeOf(vsi2);
    vsi2.fMask := SIF_RANGE or SIF_PAGE or SIF_POS;
    GetScrollInfo(Self.Handle, SB_VERT, vsi2);

    if (vsi1.nMin <> vsi2.nMin) or (vsi1.nMax <> vsi2.nMax)
      or (vsi1.nPage <> vsi2.nPage) or (vsi1.nPos <> vsi2.nPos) then
      changed := True;
  end;

  if style and WS_HSCROLL <> 0 then
  begin
    hsi2.cbSize := SizeOf(hsi2);
    hsi2.fMask := SIF_RANGE or SIF_PAGE or SIF_POS;
    GetScrollInfo(Self.Handle, SB_HORZ, hsi2);

    if (hsi1.nMin <> hsi2.nMin) or (hsi1.nMax <> hsi2.nMax)
      or (hsi1.nPage <> hsi2.nPage) or (hsi1.nPos <> hsi2.nPos) then
      changed := True;
  end;

  if changed and (Self.Parent is TFsScrollContainer) then
  begin
    TFsScrollContainer(Self.Parent).AdjustInnerControlBounds;
    Self.Parent.Invalidate;
  end;
end;


{ TFsMemo }

function TFsMemo.CreateRealControl: TControl;
begin
  Result := TFsBorderlessMemo.Create(Self);
  TFsBorderlessMemo(Result).ScrollBars := ssVertical;
end;

function TFsMemo.GetLines: TStrings;
begin
  Result := TFsBorderlessMemo(RealControl).Lines;
end;

function TFsMemo.GetMemo: TCustomMemo;
begin
  Result := TCustomMemo(RealControl);
end;

function TFsMemo.GetScrollBars: TScrollStyle;
begin
  Result := TFsBorderlessMemo(RealControl).ScrollBars;
end;

procedure TFsMemo.SetLines(const Value: TStrings);
begin
  TFsBorderlessMemo(RealControl).Lines := Value;
end;

procedure TFsMemo.SetScrollBars(const Value: TScrollStyle);
begin
  TFsBorderlessMemo(RealControl).ScrollBars := Value;
end;

 

如果读者依照TFsMemo写TFsListView,TFsTreeView,将会发现拖动滑块时没有任何反应,控件的内容并不滚动。这是因为这ListView和TreeView在处理SB_THUMBTRACK和SB_THUMBPOITION操作时,并没有提取里面的nTrackPos参数,而是调用GetScrollInfo获取,但是Windows没有提供任何API来设置nTrackPos。而且用SetScrollInfo、SetScrollPos也无法设置它们的滚动条滑块位置。因此我们必须想另外的方式来实现ListView和TreeView的滑块拖动。我的方案是用循环的SB_PAGEDOWN、SB_LINEDOWN、SB_PAGEUP、SB_LINEUP来达到一样的目的。为了避免循环次数太多导致界面一直慢慢滚动很久,我用LockWindowUpdate来禁止控件的界面重绘,等循环结束再解除。从TFsHackThumbDragScrollContainer派生出的TFsListView和TFsTreeView就不会有拖动滑块异常了:

  TFsHackThumbDragScrollContainer = class(TFsScrollContainer)
  protected
    procedure DragThumb(BarFlag, ScrollCode, nTrackPos: Integer); override;
  end;

{ TFsHackThumbDragScrollContainer }

procedure TFsHackThumbDragScrollContainer.DragThumb(BarFlag, ScrollCode, nTrackPos: Integer);
var
  si: TScrollInfo;
  offset: Integer;
  msg: DWORD;
begin
  if BarFlag = SB_VERT then msg := WM_VSCROLL
  else msg := WM_HSCROLL;

  si.cbSize := SizeOf(si);
  si.fMask := SIF_PAGE or SIF_POS;

  if not Windows.GetScrollInfo(TWinControl(RealControl).Handle, BarFlag, si) then
  begin
    inherited;
    Exit;
  end;

  if si.nPos <> nTrackPos then
  begin
    //OutputDebugString(PChar('before scroll: ' + IntToStr(si.nPos) + ', ' + IntToStr(nTrackPos)));

    Windows.LockWindowUpdate(Handle);

    try
      if si.nPos < nTrackPos then
      begin
        offset := nTrackPos - si.nPos;

        while offset > 0 do
        begin
          if offset >= si.nPage then
          begin
            RealControl.Perform(msg, SB_PAGEDOWN, 0);
            Dec(offset, si.nPage);
          end
          else begin
            RealControl.Perform(msg, SB_LINEDOWN, 0);
            Dec(offset);
          end;
        end;
      end
      else begin
        offset := si.nPos - nTrackPos;

        while offset > 0 do
        begin
          if offset >= si.nPage then
          begin
            RealControl.Perform(msg, SB_PAGEUP, 0);
            Dec(offset, si.nPage);
          end
          else begin
            RealControl.Perform(msg, SB_LINEUP, 0);
            Dec(offset);
          end;
        end;
      end;
    finally
      Windows.LockWindowUpdate(0);
      Self.Invalidate;
    end;

    //OutputDebugString(PChar('after scroll: ' + IntToStr(GetScrollPos(TWinControl(RealControl).Handle, BarFlag))));
  end;
end;

  TFsBorderlessListView = class(TCustomListView)
  protected
    procedure WndProc(var msgr: TMessage); override;
    procedure WMNCCalcSize(var msgr: TWMNCCalcSize); message WM_NCCALCSIZE;
    procedure WMNCPaint(var msgr: TWMNCPaint); message WM_NCPAINT;
  end;

{ TFsBorderlessListView }

procedure TFsBorderlessListView.WMNCCalcSize(var msgr: TWMNCCalcSize);
begin
  msgr.Result := 0;
end;

procedure TFsBorderlessListView.WMNCPaint(var msgr: TWMNCPaint);
begin
  msgr.Result := 0;
end;

procedure TFsBorderlessListView.WndProc(var msgr: TMessage);
var
  vsi1, hsi1, vsi2, hsi2: TScrollInfo;
  style: Integer;
  changed: Boolean;
begin
  if not HandleAllocated or (msgr.Msg = WM_CREATE) or (msgr.Msg = WM_NCCREATE)
    or (msgr.Msg = WM_DESTROY) or (msgr.Msg = WM_NCDESTROY) then
  begin
    inherited;
    Exit;
  end;

  changed := False;

  style := GetWindowLong(Self.Handle, GWL_STYLE);

  if style and WS_VSCROLL <> 0 then
  begin
    vsi1.cbSize := SizeOf(vsi1);
    vsi1.fMask := SIF_RANGE or SIF_PAGE or SIF_POS;
    GetScrollInfo(Self.Handle, SB_VERT, vsi1);
  end;

  if style and WS_HSCROLL <> 0 then
  begin
    hsi1.cbSize := SizeOf(hsi1);
    hsi1.fMask := SIF_RANGE or SIF_PAGE or SIF_POS;
    GetScrollInfo(Self.Handle, SB_HORZ, hsi1);
  end;

  inherited;

  if style and WS_VSCROLL <> 0 then
  begin
    vsi2.cbSize := SizeOf(vsi2);
    vsi2.fMask := SIF_RANGE or SIF_PAGE or SIF_POS;
    GetScrollInfo(Self.Handle, SB_VERT, vsi2);

    if (vsi1.nMin <> vsi2.nMin) or (vsi1.nMax <> vsi2.nMax)
      or (vsi1.nPage <> vsi2.nPage) or (vsi1.nPos <> vsi2.nPos) then
      changed := True;
  end;

  if style and WS_HSCROLL <> 0 then
  begin
    hsi2.cbSize := SizeOf(hsi2);
    hsi2.fMask := SIF_RANGE or SIF_PAGE or SIF_POS;
    GetScrollInfo(Self.Handle, SB_HORZ, hsi2);

    if (hsi1.nMin <> hsi2.nMin) or (hsi1.nMax <> hsi2.nMax)
      or (hsi1.nPage <> hsi2.nPage) or (hsi1.nPos <> hsi2.nPos) then
      changed := True;
  end;

  if changed and (Self.Parent is TFsScrollContainer) then
  begin
    TFsScrollContainer(Self.Parent).AdjustInnerControlBounds;
    Self.Parent.Invalidate;
  end;
end;

  TFsListView = class(TFsHackThumbDragScrollContainer)
  private
    function GetItems: TListItems;
    procedure SetItems(const Value: TListItems);
    function GetColumns: TListColumns;
    procedure SetColumns(const Value: TListColumns);
    function GetViewStyle: TViewStyle;
    procedure SetViewStyle(const Value: TViewStyle);
    function GetListView: TCustomListView;
  protected
    function CreateRealControl: TControl; override;
  public
    property ListView: TCustomListView read GetListView;
  published
    property Columns: TListColumns read GetColumns write SetColumns;
    property Items: TListItems read GetItems write SetItems;
    property ViewStyle: TViewStyle read GetViewStyle write SetViewStyle default vsIcon;
  end;


{ TFsListView }

function TFsListView.CreateRealControl: TControl;
begin
  Result := TFsBorderlessListView.Create(Self);
end;

function TFsListView.GetColumns: TListColumns;
begin
  Result := TFsBorderlessListView(RealControl).Columns;
end;

function TFsListView.GetItems: TListItems;
begin
  Result := TFsBorderlessListView(RealControl).Items;
end;

function TFsListView.GetListView: TCustomListView;
begin
  Result := TCustomListView(RealControl);
end;

function TFsListView.GetViewStyle: TViewStyle;
begin
  Result := TFsBorderlessListView(RealControl).ViewStyle;
end;

procedure TFsListView.SetColumns(const Value: TListColumns);
begin
  TFsBorderlessListView(RealControl).Columns := Value;
end;

procedure TFsListView.SetItems(const Value: TListItems);
begin
  TFsBorderlessListView(RealControl).Items := Value;
end;

procedure TFsListView.SetViewStyle(const Value: TViewStyle);
begin
  TFsBorderlessListView(RealControl).ViewStyle := Value;
end;

 

转载于:https://www.cnblogs.com/coderush/p/3616125.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值