unit EasyScrollFrame; // Version 1.7.0 // // The contents of this file are subject to the Mozilla Public License // Version 1.1 (the "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/ // // Alternatively, you may redistribute this library, use and/or modify it under the terms of the // GNU Lesser General Public License as published by the Free Software Foundation; // either version 2.1 of the License, or (at your option) any later version. // You may obtain a copy of the LGPL at http://www.gnu.org/copyleft/. // // Software distributed under the License is distributed on an "AS IS" basis, // WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the // specific language governing rights and limitations under the License. // // The initial developer of this code is Jim Kueneman // // Special thanks to the following in no particular order for their help/support/code // Danijel Malik, Robert Lee, Werner Lehmann, Alexey Torgashin, Milan Vandrovec //---------------------------------------------------------------------------- interface {$I Compilers.inc} {$I Options.inc} {$I ..\Include\Addins.inc} uses Windows, Messages, SysUtils, Classes, Graphics, {$IFDEF COMPILER_7_UP} Themes, UxTheme, {$ELSE} {$IFDEF USETHEMES} TmSchema, UxTheme, {$ENDIF} {$ENDIF} Controls, MPCommonObjects, MPCommonUtilities; const TIMER_AUTOSCROLLDELAY = 1003; TIMER_AUTOSCROLL = 1004; type TEasyScrollButtonState = ( sbsHovering, // The mouse is hovering over the button sbsDown, // The button is down sbsAutoClick, // The button is down for a predetermined time and is autoscrolling at some time interval sbsAutoScrollTimerRunning // Timer is running for AutoClick ); TEasyScrollButtonStates = set of TEasyScrollButtonState; TEasyScrollButtonDir = ( sbdLeft, // Arrow points Left sbdUp, // Arrow points Up sbdDown, // Arrow points Down sbdRight // Arrow points right ); TEasyTimerType = ( ettAutoScrollDelay, ettAutoScroll ); TCustomEasyScrollButton = class(TCommonCanvasControl) private FArrowSize: Integer; FAutoScroll: Boolean; FAutoScrollTime: Integer; FAutoScrollTimeOut: Integer; FDirection: TEasyScrollButtonDir; FFlat: Boolean; FOnClick: TNotifyEvent; FState: TEasyScrollButtonStates; FTimerID: THandle; FTimerStub: Pointer; procedure SetDirection(const Value: TEasyScrollButtonDir); procedure SetFlat(const Value: Boolean); protected procedure DoClick; virtual; procedure DoPaintRect(ACanvas: TCanvas; WindowClipRect: TRect; SelectedOnly: Boolean); virtual; procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X: Integer; Y: Integer); override; procedure MouseMove(Shift: TShiftState; X: Integer; Y: Integer); override; procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X: Integer; Y: Integer); override; procedure SetEnabled(Value: Boolean); override; procedure StartAutoScrollTimer(TimerType: TEasyTimerType); procedure StopAutoScrollTimer(FreeStub: Boolean); procedure TimerProc(Window: HWnd; uMsg: UINT; idEvent: UINT; dwTime: DWORD); stdcall; procedure WMPaint(var Msg: TWMPaint); message WM_PAINT; property ArrowSize: Integer read FArrowSize write FArrowSize default 12; property AutoScroll: Boolean read FAutoScroll write FAutoScroll default False; property AutoScrollDelay: Integer read FAutoScrollTimeOut write FAutoScrollTimeOut default 1000; property AutoScrollTime: Integer read FAutoScrollTime write FAutoScrollTime default 100; property Direction: TEasyScrollButtonDir read FDirection write SetDirection default sbdLeft; property Flat: Boolean read FFlat write SetFlat default False; property OnClick: TNotifyEvent read FOnClick write FOnClick; property State: TEasyScrollButtonStates read FState write FState; property TimerID: THandle read FTimerID write FTimerID; property TimerStub: Pointer read FTimerStub write FTimerStub; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; published end; TEasyScrollButton = class(TCustomEasyScrollButton) public property State; published property Action; property ActionLink; property Align; property Anchors; property ArrowSize; property AutoScroll; property AutoScrollDelay; property AutoScrollTime; property AutoSize; property BorderWidth; property Constraints; property Direction; property DragCursor; property DragKind; property DragMode; property Enabled; property Flat; property OnCanResize; property OnClick; property OnConstrainedResize; {$IFDEF COMPILER_5_UP} property OnContextPopup; {$ENDIF} property OnDblClick; property OnDockDrop; property OnDockOver; property OnDragDrop; property OnDragOver; property OnEndDock; property OnEndDrag; property OnEnter; property OnExit; property OnKeyDown; property OnKeyPress; property OnKeyUp; property OnMouseDown; property OnMouseMove; property OnMouseUp; property OnMouseWheel; property OnMouseWheelDown; property OnMouseWheelUp; property OnResize; property OnStartDock; property OnStartDrag; property OnUnDock; property ParentShowHint; property PopupMenu; property ShowHint; property TabOrder; property TabStop; property Themed; property Visible; end; implementation { TEasyScrollButton } constructor TCustomEasyScrollButton.Create(AOwner: TComponent); begin inherited Create(AOwner); AutoScrollDelay := 1000; AutoScrollTime := 100; Width := GetSystemMetrics(SM_CXHSCROLL); Height := GetSystemMetrics(SM_CYHSCROLL); ArrowSize := 12 end; destructor TCustomEasyScrollButton.Destroy; begin inherited Destroy; end; procedure TCustomEasyScrollButton.DoClick; begin if Assigned(OnClick) then OnClick(Self) end; procedure TCustomEasyScrollButton.DoPaintRect(ACanvas: TCanvas; WindowClipRect: TRect; SelectedOnly: Boolean); procedure DrawWithoutThemes; var uState: Cardinal; begin uState := 0; if (sbsHovering in State) and not (sbsDown in State) then begin case Direction of sbdLeft: uState := DFCS_SCROLLLEFT; sbdUp: uState := DFCS_SCROLLUP ; sbdDown: uState := DFCS_SCROLLDOWN; sbdRight: uState := DFCS_SCROLLRIGHT; end end else if (sbsDown in State) and (sbsHovering in State) then begin case Direction of sbdLeft: uState := DFCS_SCROLLLEFT or DFCS_PUSHED; sbdUp: uState := DFCS_SCROLLUP or DFCS_PUSHED; sbdDown: uState := DFCS_SCROLLDOWN or DFCS_PUSHED; sbdRight: uState := DFCS_SCROLLRIGHT or DFCS_PUSHED; end end else begin case Direction of sbdLeft: uState := DFCS_SCROLLLEFT; sbdUp: uState := DFCS_SCROLLUP; sbdDown: uState := DFCS_SCROLLDOWN; sbdRight: uState := DFCS_SCROLLRIGHT; end end; if Flat then uState := uState or DFCS_FLAT; if not Enabled then uState := uState or DFCS_INACTIVE; DrawFrameControl(ACanvas.Handle, ClientRect, DFC_SCROLL, uState); end; {$IFDEF USETHEMES} var Part, uState: Longword; {$ENDIF} begin {$IFDEF USETHEMES} if DrawWithThemes then begin uState := 0; Part := SBP_ARROWBTN; if Enabled then begin if (sbsHovering in State) and not (sbsDown in State) then begin case Direction of sbdLeft: uState := ABS_LEFTHOT; sbdUp: uState := ABS_UPHOT; sbdDown: uState := ABS_DOWNHOT; sbdRight: uState := ABS_RIGHTHOT; end end else if (sbsDown in State) and (sbsHovering in State) then begin case Direction of sbdLeft: uState := ABS_LEFTPRESSED; sbdUp: uState := ABS_UPPRESSED; sbdDown: uState := ABS_DOWNPRESSED; sbdRight: uState := ABS_RIGHTPRESSED; end end else begin case Direction of sbdLeft: uState := ABS_LEFTNORMAL; sbdUp: uState := ABS_UPNORMAL; sbdDown: uState := ABS_DOWNNORMAL; sbdRight: uState := ABS_RIGHTNORMAL; end end; end else begin case Direction of sbdLeft: uState := ABS_LEFTDISABLED; sbdUp: uState := ABS_UPDISABLED; sbdDown: uState := ABS_DOWNDISABLED; sbdRight: uState := ABS_RIGHTDISABLED; end end; DrawThemeBackground(Themes.ScrollbarTheme, ACanvas.Handle, Part, uState, ClientRect, @WindowClipRect); end else DrawWithOutThemes; {$ELSE} DrawWithoutThemes; {$ENDIF} end; procedure TCustomEasyScrollButton.MouseDown(Button: TMouseButton; Shift: TShiftState; X: Integer; Y: Integer); begin inherited MouseDown(Button, Shift, X, Y); if not (sbsDown in State) then begin Include(FState, sbsDown); StartAutoScrollTimer(ettAutoScrollDelay); Invalidate end end; procedure TCustomEasyScrollButton.MouseMove(Shift: TShiftState; X: Integer; Y: Integer); begin inherited MouseMove(Shift, X, Y); if PtInRect(ClientRect, Point(X, Y)) then begin if not (sbsHovering in State) then begin Include(FState, sbsHovering); Invalidate end; Mouse.Capture := Handle end else begin if sbsHovering in State then begin Exclude(FState, sbsHovering); StopAutoScrollTimer(True); Invalidate end; if [sbsDown, sbsHovering] * State = [] then begin if Mouse.Capture = Handle then Mouse.Capture := 0 end end end; procedure TCustomEasyScrollButton.MouseUp(Button: TMouseButton; Shift: TShiftState; X: Integer; Y: Integer); begin inherited MouseUp(Button, Shift, X, Y); if PtInRect(ClientRect, Point(X, Y)) and (sbsDown in State) then DoClick; if sbsDown in State then begin StopAutoScrollTimer(True); Exclude(FState, sbsDown); Invalidate end end; procedure TCustomEasyScrollButton.SetDirection(const Value: TEasyScrollButtonDir); begin if FDirection <> Value then begin FDirection := Value; Invalidate end end; procedure TCustomEasyScrollButton.SetEnabled(Value: Boolean); begin inherited; Invalidate end; procedure TCustomEasyScrollButton.SetFlat(const Value: Boolean); begin if FFlat <> Value then begin FFlat := Value; Invalidate end end; procedure TCustomEasyScrollButton.StartAutoScrollTimer(TimerType: TEasyTimerType); begin if AutoScroll then begin if not Assigned(TimerStub) then TimerStub := CreateStub(Self, @TCustomEasyScrollButton.TimerProc); if TimerType = ettAutoScrollDelay then TimerID := SetTimer(Handle, TIMER_AUTOSCROLLDELAY, AutoScrollDelay, TimerStub) else TimerID := SetTimer(Handle, TIMER_AUTOSCROLL, AutoScrollTime, TimerStub); Include(FState, sbsAutoScrollTimerRunning); end end; procedure TCustomEasyScrollButton.StopAutoScrollTimer(FreeStub: Boolean); begin if sbsAutoScrollTimerRunning in State then begin if KillTimer(Handle, TimerID) then begin TimerID := 0; Exclude(FState, sbsAutoScrollTimerRunning); if FreeStub then begin DisposeStub(FTimerStub); TimerStub := nil; end end else Exception.Create('Can not Destroy Scroll Button Timer'); end end; procedure TCustomEasyScrollButton.TimerProc(Window: HWnd; uMsg: UINT; idEvent: UINT; dwTime: DWORD); begin case idEvent of TIMER_AUTOSCROLLDELAY: begin StopAutoScrollTimer(False); StartAutoScrollTimer(ettAutoScroll); end; TIMER_AUTOSCROLL: begin DoClick end; end end; procedure TCustomEasyScrollButton.WMPaint(var Msg: TWMPaint); var PaintInfo: TPaintStruct; begin if UpdateCount = 0 then begin BeginPaint(Handle, PaintInfo); try if not IsRectEmpty(PaintInfo.rcPaint) then begin // Paint the rectangle that is needed Canvas.Handle := Msg.DC; DoPaintRect(Canvas, PaintInfo.rcPaint, False); end finally // Release the Handle from the canvas so that EndPaint may dispose of the DC as it sees fit EndPaint(Handle, PaintInfo); end end end; end.