{----------------------------------------------------------------------------- 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/MPL-1.1.html Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the License for the specific language governing rights and limitations under the License. The Original Code is: JvCaptionButton.PAS, released on 2002-05-26. The Initial Developer of the Original Code is Peter Thörnqvist [peter3 at sourceforge dot net] Portions created by Peter Thörnqvist are Copyright (C) 2002 Peter Thörnqvist. This unit is a merging of the original TJvCaptionButton, TJvaCaptionButton. Merging done 2003-06-12 by Remko Bonte [remkobonte at myrealbox dot com] All Rights Reserved. Contributor(s): Andrei Prygounkov , author of TJvaCaptionButton. Remko Bonte [remkobonte at myrealbox dot com], theme support, actions Olivier Sannier [obones att altern dott org], caption hints. You may retrieve the latest version of this file at the Project JEDI's JVCL home page, located at http://jvcl.sourceforge.net Modified 2003-06-13 (p3): - Fixed MouseUp X,Y inconsistentcy (did not report the same values as MouseDown) - Added MouseMove handler - Added ShowHint, ParentShowHint - Fixed drawing of disabled MinimizeToTray icon as well as incorrect Font.Color in text drawing - Added Assign - Tested on W2k - Demo (examples\CaptionBtn) updated and extended Known Issues: * Msimg32.dll code should be moved to seperate import unit. Code is partly copied from JwaWinGDI.pas. * Button can disappear at design-time when switching themes. * With more buttons, button can appear hot while mouse is over another caption button. * Still some flicker while resizing due to wrong FButtonRect, see comment at HandleNCPaintBefore. * Buttons on small caption (BorderStyle in [bsSizeToolWin, bsToolWin]) looks ugly. -----------------------------------------------------------------------------} // $Id: JvCaptionButton.pas 11240 2007-03-28 21:21:47Z remkobonte $ unit JvCaptionButton; {$I jvcl.inc} {$I windowsonly.inc} interface uses {$IFDEF UNITVERSIONING} JclUnitVersioning, {$ENDIF UNITVERSIONING} Windows, Messages, Classes, Graphics, Controls, Forms, {$IFDEF HAS_UNIT_TYPES} Types, {$ENDIF HAS_UNIT_TYPES} ActnList, ImgList, JvComponentBase, JvTypes, JvWin32; type TJvStandardButton = (tsbNone, tsbClose, tsbHelp, tsbMax, tsbMin, tsbRestore, tsbMinimizeToTray); // a la e-Mule TJvCaptionButtonLayout = (cbImageLeft, cbImageRight); TJvRedrawKind = (rkDirect, rkIndirect, rkTotalCaptionBar); TJvCaptionButton = class; TJvCaptionButtonActionLink = class(TActionLink) protected FClient: TJvCaptionButton; procedure AssignClient(AClient: TObject); override; function IsCaptionLinked: Boolean; override; function IsEnabledLinked: Boolean; override; function IsHintLinked: Boolean; override; function IsImageIndexLinked: Boolean; override; function IsVisibleLinked: Boolean; override; function IsOnExecuteLinked: Boolean; override; procedure SetCaption(const Value: string); override; procedure SetEnabled(Value: Boolean); override; procedure SetHint(const Value: string); override; procedure SetImageIndex(Value: Integer); override; procedure SetVisible(Value: Boolean); override; procedure SetOnExecute(Value: TNotifyEvent); override; end; TJvCaptionButtonActionLinkClass = class of TJvCaptionButtonActionLink; TJvCaptionButton = class(TJvComponent) private { Properties } FAlignment: TAlignment; FHeight: Integer; FLeft: Integer; FTop: Integer; FWidth: Integer; FCaption: string; FDown: Boolean; FEnabled: Boolean; FFont: TFont; FHint: string; FImageIndex: TImageIndex; FImages: TCustomImageList; FLayout: TJvCaptionButtonLayout; FMargin: Integer; FPosition: Integer; FSpacing: Integer; FStandard: TJvStandardButton; FToggle: Boolean; FVisible: Boolean; FOnClick: TNotifyEvent; FOnMouseUp: TMouseEvent; FOnMouseDown: TMouseEvent; FOnMouseMove: TMouseMoveEvent; FDefaultButtonLeft: Integer; FDefaultButtonTop: Integer; FDefaultButtonWidth: Integer; FDefaultButtonHeight: Integer; FActionLink: TJvCaptionButtonActionLink; FBuffer: TBitmap; FButtonRect: TRect; FCaptionHeight: Integer; FClickRect: TRect; // Clickable area is a bit bigger than the button FHasCaption: Boolean; // True, if the form has a caption FHasSmallCaption: Boolean; // True, if the form has BorderStyle bsToolWindow, bsSizeToolWin FImageChangeLink: TChangeLink; FMouseButtonDown: Boolean; FMouseInControl: Boolean; FNeedRecalculate: Boolean; FRgnChanged: Boolean; FSaveRgn: HRGN; FShowHint: Boolean; FParentShowHint: Boolean; {tool tip specific} FToolTipHandle: THandle; {tool tip specific end} {$IFDEF JVCLThemesEnabled} FCaptionActive: Boolean; FForceDrawSimple: Boolean; function GetIsThemed: Boolean; procedure SetForceDrawSimple(const Value: Boolean); {$ENDIF JVCLThemesEnabled} function GetAction: TBasicAction; function GetIsImageVisible: Boolean; function GetParentForm: TCustomForm; function GetParentFormHandle: THandle; function IsCaptionStored: Boolean; function IsEnabledStored: Boolean; function IsHintStored: Boolean; function IsImageIndexStored: Boolean; procedure SetAction(const Value: TBasicAction); procedure SetAlignment(Value: TAlignment); procedure SetCaption(Value: string); procedure SetDown(const Value: Boolean); procedure SetEnabled(const Value: Boolean); procedure SetFont(Value: TFont); procedure SetHeight(Value: Integer); procedure SetImageIndex(const Value: TImageIndex); procedure SetImages(const Value: TCustomImageList); procedure SetLayout(const Value: TJvCaptionButtonLayout); procedure SetLeft(Value: Integer); procedure SetMargin(const Value: Integer); procedure SetMouseInControl(const Value: Boolean); procedure SetPosition(const Value: Integer); procedure SetSpacing(const Value: Integer); procedure SetStandard(Value: TJvStandardButton); procedure SetToggle(const Value: Boolean); procedure SetTop(Value: Integer); procedure SetVisible(const Value: Boolean); procedure SetWidth(Value: Integer); {tool tip handling} procedure CreateToolTip(Wnd: THandle); procedure DestroyToolTip; procedure HideToolTip; procedure ForwardToToolTip(Msg: TMessage); procedure Hook; procedure UnHook; function WndProcAfter(var Msg: TMessage): Boolean; function WndProcBefore(var Msg: TMessage): Boolean; procedure DrawButton(DC: HDC); {$IFDEF JVCLThemesEnabled} procedure DrawButtonBackground(ACanvas: TCanvas); {$ENDIF JVCLThemesEnabled} procedure DrawStandardButton(ACanvas: TCanvas); procedure DrawNonStandardButton(ACanvas: TCanvas); procedure DrawButtonImage(ACanvas: TCanvas; ImageBounds: TRect); procedure DrawButtonText(ACanvas: TCanvas; TextBounds: TRect); procedure Redraw(const AKind: TJvRedrawKind); procedure CalcDefaultButtonRect(Wnd: THandle); {Paint related messages} procedure HandleNCActivate(var Msg: TWMNCActivate); procedure HandleNCPaintAfter(Wnd: THandle; var Msg: TWMNCPaint); procedure HandleNCPaintBefore(Wnd: THandle; var Msg: TWMNCPaint); {Mouse down-related messages} function HandleButtonDown(var Msg: TWMNCHitMessage): Boolean; function HandleButtonUp(var Msg: TWMNCHitMessage): Boolean; function HandleHitTest(var Msg: TWMNCHitTest): Boolean; function HandleMouseMove(var Msg: TWMNCHitMessage): Boolean; procedure HandleNCMouseMove(var Msg: TWMNCHitMessage); {Other} function HandleNotify(var Msg: TWMNotify): Boolean; procedure ImageListChange(Sender: TObject); procedure DoActionChange(Sender: TObject); function MouseOnButton(X, Y: Integer; const TranslateToScreenCoord: Boolean): Boolean; procedure SetParentShowHint(const Value: Boolean); procedure SetShowHint(const Value: Boolean); protected procedure ActionChange(Sender: TObject; CheckDefaults: Boolean); dynamic; procedure CalcButtonParts(ACanvas: TCanvas; ButtonRect: TRect; var RectText, RectImage: TRect); function GetActionLinkClass: TJvCaptionButtonActionLinkClass; dynamic; procedure Loaded; override; procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); virtual; procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); virtual; procedure MouseMove(Shift: TShiftState; X, Y: Integer); virtual; procedure Notification(AComponent: TComponent; Operation: TOperation); override; procedure UpdateButtonRect(Wnd: THandle); property ActionLink: TJvCaptionButtonActionLink read FActionLink write FActionLink; property IsImageVisible: Boolean read GetIsImageVisible; {$IFDEF JVCLThemesEnabled} // The value of IsThemed stays the same until a WM_THEMECHANGED is received. property IsThemed: Boolean read GetIsThemed; {$ENDIF JVCLThemesEnabled} property MouseInControl: Boolean read FMouseInControl; property ParentFormHandle: THandle read GetParentFormHandle; property ParentForm: TCustomForm read GetParentForm; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; procedure Assign(Source: TPersistent); override; procedure InitiateAction; virtual; procedure ResetButton; procedure Click; dynamic; property DefaultButtonWidth: Integer read FDefaultButtonWidth; published property Action: TBasicAction read GetAction write SetAction; property Alignment: TAlignment read FAlignment write SetAlignment default taLeftJustify; property ButtonHeight: Integer read FHeight write SetHeight default 0; property ButtonLeft: Integer read FLeft write SetLeft default 0; property ButtonTop: Integer read FTop write SetTop default 0; property ButtonWidth: Integer read FWidth write SetWidth default 0; property Caption: string read FCaption write SetCaption stored IsCaptionStored; property Down: Boolean read FDown write SetDown default False; {$IFDEF JVCLThemesEnabled} property ForceDrawSimple: Boolean read FForceDrawSimple write SetForceDrawSimple default False; {$ENDIF JVCLThemesEnabled} property ShowHint: Boolean read FShowHint write SetShowHint default False; property ParentShowHint: Boolean read FParentShowHint write SetParentShowHint default True; property Enabled: Boolean read FEnabled write SetEnabled stored IsEnabledStored default True; property Font: TFont read FFont write SetFont; property Hint: string read FHint write FHint stored IsHintStored; property ImageIndex: TImageIndex read FImageIndex write SetImageIndex stored IsImageIndexStored default -1; property Images: TCustomImageList read FImages write SetImages; property Layout: TJvCaptionButtonLayout read FLayout write SetLayout default cbImageLeft; property Margin: Integer read FMargin write SetMargin default -1; property Position: Integer read FPosition write SetPosition default 0; property Spacing: Integer read FSpacing write SetSpacing default 4; property Standard: TJvStandardButton read FStandard write SetStandard default tsbNone; property Toggle: Boolean read FToggle write SetToggle default False; property Visible: Boolean read FVisible write SetVisible default True; property OnClick: TNotifyEvent read FOnClick write FOnClick; property OnMouseUp: TMouseEvent read FOnMouseUp write FOnMouseUp; property OnMouseDown: TMouseEvent read FOnMouseDown write FOnMouseDown; property OnMouseMove: TMouseMoveEvent read FOnMouseMove write FOnMouseMove; end; function TransparentBlt(hdcDest: HDC; nXOriginDest, nYOriginDest, nWidthDest, hHeightDest: Integer; hdcSrc: HDC; nXOriginSrc, nYOriginSrc, nWidthSrc, nHeightSrc: Integer; crTransparent: UINT): BOOL; stdcall; function AlphaBlend(hdcDest: HDC; nXOriginDest, nYOriginDest, nWidthDest, nHeightDest: Integer; hdcSrc: HDC; nXOriginSrc, nYOriginSrc, nWidthSrc, nHeightSrc: Integer; BlendFunction: BLENDFUNCTION): BOOL; stdcall; {$IFDEF UNITVERSIONING} const UnitVersioning: TUnitVersionInfo = ( RCSfile: '$URL: https://jvcl.svn.sourceforge.net/svnroot/jvcl/tags/JVCL3_32/run/JvCaptionButton.pas $'; Revision: '$Revision: 11240 $'; Date: '$Date: 2007-03-28 23:21:47 +0200 (mer., 28 mars 2007) $'; LogPath: 'JVCL\run' ); {$ENDIF UNITVERSIONING} implementation uses CommCtrl, Buttons, SysUtils, JvThemes, {$IFDEF JVCLThemesEnabled} UxTheme, {$IFNDEF COMPILER7_UP} TmSchema, {$ENDIF !COMPILER7_UP} JvJVCLUtils, {$ENDIF JVCLThemesEnabled} JvDsgnIntf, JvConsts, JvJCLUtils, JvResources, JvWndProcHook; const { Msimg32.dll is included in Windows 98 and later } Msimg32DLLName = 'Msimg32.dll'; TransparentBltName = 'TransparentBlt'; AlphaBlendName = 'AlphaBlend'; htCaptionButton = HTSIZELAST + 1; Alignments: array [TAlignment] of Word = (DT_LEFT, DT_RIGHT, DT_CENTER); var GMsimg32Handle: THandle = 0; GTriedLoadMsimg32Dll: Boolean = False; _AlphaBlend: Pointer; _TransparentBlt: Pointer; {$IFDEF JVCLThemesEnabled} type { (rb) I couldn't get the alpha channel to work with the normal TBitmap so introduced TAlphaBitmap. TBitmapAdapter hides the implementation details of the TBitmap/TAlphaBitmap } TAlphaBitmap = class(TObject) private FHandle: HDC; FBitmapInfo: TBitmapInfo; FDIBHandle: HBitmap; FOldBitmap: HBitmap; FBitsMem: Pointer; FBitCount: Byte; FHasAlphaChannel: Boolean; function GetWidth: Integer; function GetHeight: Integer; protected procedure CreateHandle(AWidth, AHeight: Integer); function CreateDIB(ADC: HDC; AWidth, AHeight: Integer): HBitmap; procedure Duplicate(Src: HBitmap); procedure FreeHandle; procedure InitAlpha; public destructor Destroy; override; procedure Assign(Source: TPersistent); procedure LoadFromResourceID(Instance: THandle; ResID: Integer); procedure LoadFromResourceName(Instance: THandle; const ResName: string); property Handle: HDC read FHandle; property Width: Integer read GetWidth; property Height: Integer read GetHeight; property Data: Pointer read FBitsMem; property BitCount: Byte read FBitCount; property HasAlphaChannel: Boolean read FHasAlphaChannel; end; TBitmapAdapter = class(TObject) private FBitmap: TObject; FMargins: TMargins; FTransparentColor: TColorRef; function GetHeight: Integer; function GetWidth: Integer; function GetIsValid: Boolean; public constructor Create; virtual; destructor Destroy; override; procedure Clear; procedure Assign(Source: TPersistent); procedure LoadFromResourceName(Instance: THandle; const ResName: string); procedure LoadFromResourceID(Instance: THandle; ResID: Integer); function Draw(ACanvas: TCanvas; const Rect: TRect; AMargins: PMargins): Boolean; function DrawFixed(ACanvas: TCanvas; const X, Y: Integer): Boolean; function DrawFixedPart(ACanvas: TCanvas; const DestRect: TRect; const SrcX, SrcY: Integer): Boolean; function DrawPart(ACanvas: TCanvas; const SrcRect, DestRect: TRect; AMargins: PMargins): Boolean; property Margins: TMargins read FMargins write FMargins; property Width: Integer read GetWidth; property Height: Integer read GetHeight; property IsValid: Boolean read GetIsValid; property TransparentColor: TColorRef read FTransparentColor write FTransparentColor; end; TGlobalXPData = class(TObject) private FCaptionButtonHeight: Integer; FCaptionButtonCount: Integer; FCaptionButtons: TBitmapAdapter; FIsThemed: Boolean; FBitmapValid: Boolean; FClientCount: Integer; public constructor Create; virtual; destructor Destroy; override; procedure AddClient; procedure RemoveClient; procedure Update; procedure DrawSimple(ACanvas: TCanvas; State: Integer; const DrawRect: TRect); function Draw(ACanvas: TCanvas; State: Integer; const DrawRect: TRect): Boolean; property IsThemed: Boolean read FIsThemed; end; var GGlobalXPData: TGlobalXPData; //=== Local procedures ======================================================= function GlobalXPData: TGlobalXPData; begin if not Assigned(GGlobalXPData) then GGlobalXPData := TGlobalXPData.Create; Result := GGlobalXPData; end; function TranslateBitmapFileName(const S: string): string; var I: Integer; begin Result := S; for I := 1 to Length(S) do case S[I] of 'A'..'Z', '0'..'9': {do nothing}; 'a'..'z': Result[I] := UpCase(S[I]); else Result[I] := '_'; end; end; procedure DupBits(Src, Dst: HBitmap; Size: TPoint); var MemDC: HDC; DesktopDC: HDC; OldBitmap: HBitmap; begin OldBitmap := 0; DesktopDC := GetDC(GetDesktopWindow); MemDC := CreateCompatibleDC(DesktopDC); try OldBitmap := SelectObject(MemDC, Src); BitBlt(Dst, 0, 0, Size.X, Size.Y, MemDC, 0, 0, SRCCOPY); finally SelectObject(MemDC, OldBitmap); ReleaseDC(GetDesktopWindow, DesktopDC); DeleteDC(MemDC); end; end; function GetHasAlphaChannel(Data: PChar; Count: Integer): Boolean; begin Result := False; while Count > 0 do begin Result := PRGBQuad(Data).rgbReserved <> 0; if Result then Exit; Inc(Data, 4); Dec(Count); end; end; procedure PreMultiplyAlphaChannel(Data: PChar; Count: Integer); begin while Count > 0 do begin with PRGBQuad(Data)^ do begin rgbBlue := (rgbBlue * rgbReserved + 128) div 255; rgbGreen := (rgbGreen * rgbReserved + 128) div 255; rgbRed := (rgbRed * rgbReserved + 128) div 255; end; Inc(Data, 4); Dec(Count); end; end; {$ENDIF JVCLThemesEnabled} procedure UnloadMsimg32Dll; begin _TransparentBlt := nil; _AlphaBlend := nil; if GMsimg32Handle > 0 then FreeLibrary(GMsimg32Handle); GMsimg32Handle := 0; end; procedure LoadMsimg32Dll; begin GTriedLoadMsimg32Dll := True; GMsimg32Handle := Windows.LoadLibrary(Msimg32DLLName); if GMsimg32Handle > 0 then begin _TransparentBlt := GetProcAddress(GMsimg32Handle, TransparentBltName); _AlphaBlend := GetProcAddress(GMsimg32Handle, AlphaBlendName); end; end; {$IFDEF JVCLThemesEnabled} function TransparentBltStretch(DestDC: HDC; const DestRect: TRect; SourceDC: HDC; const SourceRect: TRect; const SizingMargins: TMargins; const TransparentColor: TColor): Boolean; var ESourceWidth, ESourceHeight: Integer; EDestWidth, EDestHeight: Integer; LastOriginSource: TPoint; LastOriginDest: TPoint; begin { Source Dest |--------------| |--------------------| | A | B | C | | A | B | C | |-- |------|---| |-- |------------|---| | | | | | | | | | D | E | F | | | | | | | | | => | D | E | F | |---|------|---| | | | | | G | H | I | | | | | |--------------| |--------------------| | G | H | I | |-- |------------|---| } ESourceWidth := SourceRect.Right - SourceRect.Left - SizingMargins.cxLeftWidth - SizingMargins.cxRightWidth; ESourceHeight := SourceRect.Bottom - SourceRect.Top - SizingMargins.cyTopHeight - SizingMargins.cyBottomHeight; EDestWidth := DestRect.Right - DestRect.Left - SizingMargins.cxLeftWidth - SizingMargins.cxRightWidth; EDestHeight := DestRect.Bottom - DestRect.Top - SizingMargins.cyTopHeight - SizingMargins.cyBottomHeight; GetWindowOrgEx(SourceDC, LastOriginSource); SetWindowOrgEx(SourceDC, LastOriginSource.X - SourceRect.Left, LastOriginSource.Y - SourceRect.Top, nil); GetWindowOrgEx(DestDC, LastOriginDest); SetWindowOrgEx(DestDC, LastOriginDest.X - DestRect.Left, LastOriginDest.Y - DestRect.Top, nil); Result := { A } TransparentBlt( DestDC, 0, 0, SizingMargins.cxLeftWidth, SizingMargins.cyTopHeight, SourceDC, 0, 0, SizingMargins.cxLeftWidth, SizingMargins.cyTopHeight, TransparentColor ) and { B } TransparentBlt( DestDC, SizingMargins.cxLeftWidth, 0, EDestWidth, SizingMargins.cyTopHeight, SourceDC, SizingMargins.cxLeftWidth, 0, ESourceWidth, SizingMargins.cyTopHeight, TransparentColor ) and { C } TransparentBlt( DestDC, EDestWidth + SizingMargins.cxLeftWidth, 0, SizingMargins.cxRightWidth, SizingMargins.cyTopHeight, SourceDC, ESourceWidth + SizingMargins.cxLeftWidth, 0, SizingMargins.cxRightWidth, SizingMargins.cyTopHeight, TransparentColor ) and { D } TransparentBlt( DestDC, 0, SizingMargins.cyTopHeight, SizingMargins.cxLeftWidth, EDestHeight, SourceDC, 0, SizingMargins.cyTopHeight, SizingMargins.cxLeftWidth, ESourceHeight, TransparentColor ) and { E } TransparentBlt( DestDC, SizingMargins.cxLeftWidth, SizingMargins.cyTopHeight, EDestWidth, EDestHeight, SourceDC, SizingMargins.cxLeftWidth, SizingMargins.cyTopHeight, ESourceWidth, ESourceHeight, TransparentColor ) and { F } TransparentBlt( DestDC, EDestWidth + SizingMargins.cxLeftWidth, SizingMargins.cyTopHeight, SizingMargins.cxRightWidth, EDestHeight, SourceDC, ESourceWidth + SizingMargins.cxLeftWidth, SizingMargins.cyTopHeight, SizingMargins.cxRightWidth, ESourceHeight, TransparentColor ) and { G } TransparentBlt( DestDC, 0, EDestHeight + SizingMargins.cyTopHeight, SizingMargins.cxLeftWidth, SizingMargins.cyBottomHeight, SourceDC, 0, ESourceHeight + SizingMargins.cyTopHeight, SizingMargins.cxLeftWidth, SizingMargins.cyBottomHeight, TransparentColor ) and { H } TransparentBlt( DestDC, SizingMargins.cxLeftWidth, EDestHeight + SizingMargins.cyTopHeight, EDestWidth, SizingMargins.cyBottomHeight, SourceDC, SizingMargins.cxLeftWidth, ESourceHeight + SizingMargins.cyTopHeight, ESourceWidth, SizingMargins.cyBottomHeight, TransparentColor ) and { I } TransparentBlt( DestDC, EDestWidth + SizingMargins.cxLeftWidth, EDestHeight + SizingMargins.cyTopHeight, SizingMargins.cxRightWidth, SizingMargins.cyBottomHeight, SourceDC, ESourceWidth + SizingMargins.cxLeftWidth, ESourceHeight + SizingMargins.cyTopHeight, SizingMargins.cxRightWidth, SizingMargins.cyBottomHeight, TransparentColor ); SetWindowOrgEx(SourceDC, LastOriginSource.X, LastOriginSource.Y, nil); SetWindowOrgEx(DestDC, LastOriginDest.X, LastOriginDest.Y, nil); end; function GetXPCaptionButtonBitmap(ABitmap: TBitmapAdapter; out BitmapCount: Integer): Boolean; var Handle: THandle; ThemeFileNameW, BitmapFileNameW: array [0..MAX_PATH] of WideChar; OldError: Longint; Details: TThemedElementDetails; Margins: TMargins; begin ThemeFileNameW[MAX_PATH] := #0; BitmapFileNameW[MAX_PATH] := #0; Result := UxTheme.GetCurrentThemeName(ThemeFileNameW, MAX_PATH, nil, 0, nil, 0) = S_OK; if not Result then Exit; Details := ThemeServices.GetElementDetails(twMinButtonNormal); with Details do Result := GetThemeFilename(ThemeServices.Theme[Element], Part, State, TMT_IMAGEFILE, BitmapFileNameW, MAX_PATH) = S_OK; if not Result then Exit; with Details do Result := GetThemeInt(ThemeServices.Theme[Element], Part, State, TMT_IMAGECOUNT, BitmapCount) = S_OK; if not Result then Exit; Result := BitmapCount > 0; if not Result then Exit; with Details do if GetThemeMargins(ThemeServices.Theme[Element], 0, Part, State, TMT_SIZINGMARGINS, nil, Margins) <> S_OK then FillChar(Margins, SizeOf(Margins), 0); ABitmap.Margins := Margins; OldError := SetErrorMode(SEM_NOOPENFILEERRORBOX or SEM_FAILCRITICALERRORS); try Handle := LoadLibraryW(ThemeFileNameW); if Handle > 0 then try ABitmap.Assign(nil); // fixes GDI resource leak ABitmap.LoadFromResourceName(Handle, TranslateBitmapFileName(BitmapFileNameW)); { (rb) can't determine actual transparent color? } ABitmap.TransparentColor := clFuchsia; Result := (ABitmap.Width > 0) and (ABitmap.Height > 0); finally FreeLibrary(Handle); end; finally SetErrorMode(OldError); end; end; {$ENDIF JVCLThemesEnabled} //=== Global procedures ====================================================== function AlphaBlend; begin if not GTriedLoadMsimg32Dll then LoadMsimg32Dll; Result := Assigned(_AlphaBlend); if Result then asm mov esp, ebp pop ebp jmp [_AlphaBlend] end; end; function TransparentBlt; begin if not GTriedLoadMsimg32Dll then LoadMsimg32Dll; Result := Assigned(_TransparentBlt); if Result then asm mov esp, ebp pop ebp jmp [_TransparentBlt] end; end; {$IFDEF JVCLThemesEnabled} //=== { TAlphaBitmap } ======================================================= destructor TAlphaBitmap.Destroy; begin FreeHandle; inherited Destroy; end; procedure TAlphaBitmap.Assign(Source: TPersistent); begin // What to do here when Source is not nil??? if not Assigned(Source) then FreeHandle else ; end; function TAlphaBitmap.CreateDIB(ADC: HDC; AWidth, AHeight: Integer): HBitmap; begin with FBitmapInfo.bmiHeader do begin biSize := SizeOf(FBitmapInfo.bmiHeader); biWidth := AWidth; biHeight := AHeight; biPlanes := 1; biBitCount := 32; biCompression := BI_RGB; biSizeImage := AWidth * AHeight * 4; end; // Create the DIB Result := CreateDIBSection(ADC, FBitmapInfo, DIB_RGB_COLORS, FBitsMem, 0, 0); end; procedure TAlphaBitmap.CreateHandle(AWidth, AHeight: Integer); var H: HBitmap; begin FreeHandle; H := CreateScreenCompatibleDC; FDIBHandle := CreateDIB(H, AWidth, AHeight); if FDIBHandle <> 0 then FOldBitmap := SelectObject(H, FDIBHandle) else FOldBitmap := 0; FHandle := H; end; procedure TAlphaBitmap.Duplicate(Src: HBitmap); var Bitmap: Windows.TBitmap; begin GetObject(Src, SizeOf(Bitmap), @Bitmap); CreateHandle(Bitmap.bmWidth, Bitmap.bmHeight); DupBits(Src, FHandle, Point(Bitmap.bmWidth, Bitmap.bmHeight)); end; procedure TAlphaBitmap.FreeHandle; begin if FHandle <> 0 then begin if FDIBHandle <> 0 then begin if FOldBitmap <> 0 then SelectObject(FHandle, FOldBitmap); DeleteObject(FDIBHandle); end; DeleteDC(FHandle); end; end; function TAlphaBitmap.GetHeight: Integer; begin Result := FBitmapInfo.bmiHeader.biHeight; end; function TAlphaBitmap.GetWidth: Integer; begin Result := FBitmapInfo.bmiHeader.biWidth; end; procedure TAlphaBitmap.InitAlpha; var Count: Integer; begin Count := Width * Height; if BitCount < 32 then FHasAlphaChannel := False else begin FHasAlphaChannel := GetHasAlphaChannel(Data, Count); if HasAlphaChannel then PreMultiplyAlphaChannel(Data, Count); end; end; procedure TAlphaBitmap.LoadFromResourceID(Instance: THandle; ResID: Integer); var Stream: TCustomMemoryStream; BitmapInfoHeader: TBitmapInfoHeader; BitmapHandle: HBitmap; begin Stream := TResourceStream.CreateFromID(Instance, ResID, RT_BITMAP); try Stream.Read(BitmapInfoHeader, SizeOf(TBitmapInfoHeader)); FBitCount := BitmapInfoHeader.biBitCount; finally Stream.Free; end; if FBitCount = 32 then begin BitmapHandle := LoadImage(Instance, PChar(ResID), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION); if BitmapHandle = 0 then Exit; Duplicate(BitmapHandle); DeleteObject(BitmapHandle); InitAlpha; end; end; procedure TAlphaBitmap.LoadFromResourceName(Instance: THandle; const ResName: string); var Stream: TCustomMemoryStream; BitmapInfoHeader: TBitmapInfoHeader; BitmapHandle: HBitmap; begin Stream := TResourceStream.Create(Instance, ResName, RT_BITMAP); try Stream.Read(BitmapInfoHeader, SizeOf(TBitmapInfoHeader)); FBitCount := BitmapInfoHeader.biBitCount; finally Stream.Free; end; if FBitCount = 32 then begin BitmapHandle := LoadImage(Instance, PChar(ResName), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION); if BitmapHandle = 0 then Exit; Duplicate(BitmapHandle); DeleteObject(BitmapHandle); InitAlpha; end; end; //=== { TBitmapAdapter } ===================================================== constructor TBitmapAdapter.Create; begin inherited Create; FTransparentColor := clFuchsia; end; destructor TBitmapAdapter.Destroy; begin FBitmap.Free; inherited Destroy; end; procedure TBitmapAdapter.Assign(Source: TPersistent); begin if FBitmap is TBitmap then (FBitmap as TBitmap).Assign(Source) else if FBitmap is TAlphaBitmap then (FBitmap as TAlphaBitmap).Assign(Source); end; procedure TBitmapAdapter.Clear; begin FreeAndNil(FBitmap); end; function TBitmapAdapter.Draw(ACanvas: TCanvas; const Rect: TRect; AMargins: PMargins): Boolean; begin if (Rect.Right - Rect.Left = Width) and (Rect.Bottom - Rect.Top = Height) then Result := DrawFixedPart(ACanvas, Rect, 0, 0) else begin if AMargins = nil then AMargins := @FMargins; if FBitmap is TAlphaBitmap then with TAlphaBitmap(FBitmap) do Result := TransparentBltStretch(ACanvas.Handle, Rect, Handle, Bounds(0, 0, Width, Height), AMargins^, FTransparentColor) else if FBitmap is TBitmap then with TBitmap(FBitmap) do Result := TransparentBltStretch(ACanvas.Handle, Rect, Canvas.Handle, Bounds(0, 0, Width, Height), AMargins^, FTransparentColor) else Result := False; end; end; function TBitmapAdapter.DrawFixed(ACanvas: TCanvas; const X, Y: Integer): Boolean; begin Result := DrawFixedPart(ACanvas, Bounds(X, Y, Width, Height), 0, 0); end; function TBitmapAdapter.DrawFixedPart(ACanvas: TCanvas; const DestRect: TRect; const SrcX, SrcY: Integer): Boolean; var BlendFunction: TBlendFunction; W, H: Integer; begin W := DestRect.Right - DestRect.Left; H := DestRect.Bottom - DestRect.Top; if FBitmap is TAlphaBitmap then begin with TAlphaBitmap(FBitmap) do begin BlendFunction.BlendOp := AC_SRC_OVER; BlendFunction.BlendFlags := 0; BlendFunction.SourceConstantAlpha := $FF; BlendFunction.AlphaFormat := AC_SRC_ALPHA; Result := AlphaBlend(ACanvas.Handle, DestRect.Left, DestRect.Top, W, H, Handle, SrcX, SrcY, W, H, BlendFunction); end; end else if FBitmap is TBitmap then with TBitmap(FBitmap) do Result := TransparentBlt(ACanvas.Handle, DestRect.Left, DestRect.Top, W, H, Canvas.Handle, SrcX, SrcY, W, H, Self.TransparentColor) else Result := False; end; function TBitmapAdapter.DrawPart(ACanvas: TCanvas; const SrcRect, DestRect: TRect; AMargins: PMargins): Boolean; begin // Same width/height? if (SrcRect.Right - SrcRect.Left = DestRect.Right - DestRect.Left) and (SrcRect.Bottom - SrcRect.Top = DestRect.Bottom - DestRect.Top) then Result := DrawFixedPart(ACanvas, DestRect, SrcRect.Left, SrcRect.Top) else begin if AMargins = nil then AMargins := @FMargins; if FBitmap is TAlphaBitmap then with TAlphaBitmap(FBitmap) do Result := TransparentBltStretch(ACanvas.Handle, DestRect, Handle, SrcRect, AMargins^, Self.TransparentColor) else if FBitmap is TBitmap then with TBitmap(FBitmap) do Result := TransparentBltStretch(ACanvas.Handle, DestRect, Canvas.Handle, SrcRect, AMargins^, Self.TransparentColor) else Result := False; end; end; function TBitmapAdapter.GetHeight: Integer; begin if FBitmap is TAlphaBitmap then Result := TAlphaBitmap(FBitmap).Height else if FBitmap is TBitmap then Result := TBitmap(FBitmap).Height else Result := 0; end; function TBitmapAdapter.GetIsValid: Boolean; begin Result := Assigned(FBitmap); end; function TBitmapAdapter.GetWidth: Integer; begin if FBitmap is TAlphaBitmap then Result := TAlphaBitmap(FBitmap).Width else if FBitmap is TBitmap then Result := TBitmap(FBitmap).Width else Result := 0; end; procedure TBitmapAdapter.LoadFromResourceID(Instance: THandle; ResID: Integer); var AlphaBitmap: TAlphaBitmap; begin Clear; AlphaBitmap := TAlphaBitmap.Create; try AlphaBitmap.LoadFromResourceID(Instance, ResID); if AlphaBitmap.BitCount < 32 then begin FBitmap := TBitmap.Create; TBitmap(FBitmap).LoadFromResourceID(Instance, ResID); end else begin FBitmap := AlphaBitmap; AlphaBitmap := nil; end; finally AlphaBitmap.Free; end; end; procedure TBitmapAdapter.LoadFromResourceName(Instance: THandle; const ResName: string); var AlphaBitmap: TAlphaBitmap; begin Clear; AlphaBitmap := TAlphaBitmap.Create; try AlphaBitmap.LoadFromResourceName(Instance, ResName); if AlphaBitmap.BitCount < 32 then begin FBitmap := TBitmap.Create; TBitmap(FBitmap).LoadFromResourceName(Instance, ResName); end else begin FBitmap := AlphaBitmap; AlphaBitmap := nil; end; finally AlphaBitmap.Free; end; end; //=== { TGlobalXPData } ====================================================== constructor TGlobalXPData.Create; begin inherited Create; Update; end; destructor TGlobalXPData.Destroy; begin FCaptionButtons.Free; inherited Destroy; end; procedure TGlobalXPData.AddClient; begin Inc(FClientCount); end; function TGlobalXPData.Draw(ACanvas: TCanvas; State: Integer; const DrawRect: TRect): Boolean; var SrcRect: TRect; begin Result := FBitmapValid; if not Result then Exit; { State is 1-based } if (State >= FCaptionButtonCount) and (State > 4) then State := ((State - 1) mod 4) + 1; if State > FCaptionButtonCount then State := FCaptionButtonCount; SrcRect := Bounds(0, FCaptionButtonHeight * (State - 1), FCaptionButtons.Width, FCaptionButtonHeight); Result := FCaptionButtons.DrawPart(ACanvas, SrcRect, DrawRect, nil); end; procedure TGlobalXPData.DrawSimple(ACanvas: TCanvas; State: Integer; const DrawRect: TRect); const // Normal, Hot, Pushed, Disabled, cCaptionButton: array [0..3] of TThemedWindow = (twMinButtonNormal, twMinButtonHot, twMinButtonPushed, twMinButtonDisabled); cNormalButton: array [0..3] of TThemedButton = (tbPushButtonNormal, tbPushButtonHot, tbPushButtonPressed, tbPushButtonDisabled); var Details: TThemedElementDetails; DrawRgn: HRGN; begin { Draw the button in 2 pieces, draw the edge of a caption button, and the inner of a normal button, because drawing a normal button looks ugly } // State = 1..8 -> State = 0..3 State := (State - 1) mod 4; { 1a. Draw the outer bit as a caption button } Details := ThemeServices.GetElementDetails(cCaptionButton[State]); ThemeServices.DrawElement(ACanvas.Handle, Details, DrawRect); { 1b. Draw the inner bit as a normal button } with DrawRect do DrawRgn := CreateRectRgn(Left + 1, Top + 1, Right - 1, Bottom - 1); try Details := ThemeServices.GetElementDetails(cNormalButton[State]); SelectClipRgn(ACanvas.Handle, DrawRgn); ThemeServices.DrawElement(ACanvas.Handle, Details, DrawRect); SelectClipRgn(ACanvas.Handle, 0); finally DeleteObject(DrawRgn); end; end; procedure TGlobalXPData.RemoveClient; begin Dec(FClientCount); if FClientCount = 0 then begin if Self = GGlobalXPData then GGlobalXPData := nil; Self.Free; end; end; procedure TGlobalXPData.Update; begin FIsThemed := ThemeServices.ThemesAvailable and IsThemeActive and IsAppThemed; if not FIsThemed then Exit; if FCaptionButtons = nil then FCaptionButtons := TBitmapAdapter.Create; FBitmapValid := GetXPCaptionButtonBitmap(FCaptionButtons, FCaptionButtonCount); if FBitmapValid then FCaptionButtonHeight := FCaptionButtons.Height div FCaptionButtonCount else FreeAndNil(FCaptionButtons); end; {$ENDIF JVCLThemesEnabled} //=== { TJvCaptionButton } =================================================== constructor TJvCaptionButton.Create(AOwner: TComponent); begin if not (AOwner is TCustomForm) then raise EJVCLException.CreateRes(@RsEOwnerMustBeTCustomForm); inherited Create(AOwner); { Defaults } FAlignment := taLeftJustify; FHeight := 0; FLeft := 0; FTop := 0; FWidth := 0; FEnabled := True; FImageIndex := -1; FLayout := cbImageLeft; FMargin := -1; FPosition := 0; FSpacing := 4; FStandard := tsbNone; FToggle := False; FVisible := True; FNeedRecalculate := True; FCaption := ''; FDown := False; FToolTipHandle := 0; FFont := TFont.Create; FBuffer := TBitmap.Create; FImageChangeLink := TChangeLink.Create; FImageChangeLink.OnChange := ImageListChange; FParentShowHint := True; {$IFDEF JVCLThemesEnabled} GlobalXPData.AddClient; {$ENDIF JVCLThemesEnabled} Hook; end; destructor TJvCaptionButton.Destroy; begin DestroyToolTip; UnHook; Redraw(rkTotalCaptionBar); FFont.Free; FBuffer.Free; FreeAndNil(FActionLink); FreeAndNil(FImageChangeLink); {$IFDEF JVCLThemesEnabled} GlobalXPData.RemoveClient; {$ENDIF JVCLThemesEnabled} inherited Destroy; end; procedure TJvCaptionButton.ActionChange(Sender: TObject; CheckDefaults: Boolean); begin if Sender is TCustomAction then with TCustomAction(Sender) do begin if not CheckDefaults or not Assigned(Self.Images) then Self.Images := ActionList.Images; if not CheckDefaults or (Self.Caption = '') then Self.Caption := Caption; if not CheckDefaults or Self.Enabled then Self.Enabled := Enabled; if not CheckDefaults or (Self.Hint = '') then Self.Hint := Hint; if not CheckDefaults or (Self.ImageIndex = -1) then Self.ImageIndex := ImageIndex; if not CheckDefaults or Self.Visible then Self.Visible := Visible; if not CheckDefaults or not Assigned(Self.OnClick) then Self.OnClick := OnExecute; end; end; procedure TJvCaptionButton.Assign(Source: TPersistent); begin if Source is TJvCaptionButton then begin Alignment := TJvCaptionButton(Source).Alignment; ButtonHeight := TJvCaptionButton(Source).ButtonHeight; ButtonLeft := TJvCaptionButton(Source).ButtonLeft; ButtonTop := TJvCaptionButton(Source).ButtonTop; ButtonWidth := TJvCaptionButton(Source).ButtonWidth; Caption := TJvCaptionButton(Source).Caption; ShowHint := TJvCaptionButton(Source).ShowHint; ParentShowHint := TJvCaptionButton(Source).ParentShowHint; Enabled := TJvCaptionButton(Source).Enabled; Font := TJvCaptionButton(Source).Font; Hint := TJvCaptionButton(Source).Hint; ImageIndex := TJvCaptionButton(Source).ImageIndex; Images := TJvCaptionButton(Source).Images; Layout := TJvCaptionButton(Source).Layout; Margin := TJvCaptionButton(Source).Margin; Position := TJvCaptionButton(Source).Position; Spacing := TJvCaptionButton(Source).Spacing; Standard := TJvCaptionButton(Source).Standard; // set toggle before down Toggle := TJvCaptionButton(Source).Toggle; Down := TJvCaptionButton(Source).Down; Visible := TJvCaptionButton(Source).Visible; end else inherited Assign(Source); end; procedure TJvCaptionButton.CalcButtonParts(ACanvas: TCanvas; ButtonRect: TRect; var RectText, RectImage: TRect); // copied from TJvCustomImageButton const CDefaultMargin = 4; var BlockWidth, ButtonWidth, ButtonHeight, BlockMargin, InternalSpacing: Integer; LMargin: Integer; OldFont: TFont; begin SetRect(RectText, 0, 0, 0, 0); OldFont := ACanvas.Font; ACanvas.Font := Font; DrawText(ACanvas, Caption, -1, RectText, DT_CALCRECT or Alignments[FAlignment]); ACanvas.Font := OldFont; if IsImageVisible then begin with Images do SetRect(RectImage, 0, 0, Width - 1, Height - 1); InternalSpacing := Spacing; end else begin SetRect(RectImage, 0, 0, 0, 0); InternalSpacing := 0; end; BlockWidth := RectImage.Right + InternalSpacing + RectText.Right; ButtonWidth := ButtonRect.Right - ButtonRect.Left; if Margin = -1 then LMargin := CDefaultMargin else LMargin := Margin; case Alignment of taLeftJustify: BlockMargin := LMargin; taRightJustify: BlockMargin := ButtonWidth - BlockWidth - LMargin - 1; else {taCenter} BlockMargin := (ButtonWidth - BlockWidth) div 2 end; case Layout of cbImageLeft: begin OffsetRect(RectImage, BlockMargin, 0); OffsetRect(RectText, RectImage.Right + InternalSpacing, 0); end; cbImageRight: begin OffsetRect(RectText, BlockMargin, 0); OffsetRect(RectImage, RectText.Right + InternalSpacing, 0); end; end; ButtonHeight := ButtonRect.Bottom - ButtonRect.Top; OffsetRect(RectImage, ButtonRect.Left, (ButtonHeight - RectImage.Bottom) div 2 + ButtonRect.Top); OffsetRect(RectText, ButtonRect.Left, (ButtonHeight - RectText.Bottom) div 2 + ButtonRect.Top); end; procedure TJvCaptionButton.CalcDefaultButtonRect(Wnd: THandle); const CSpaceBetweenButtons = 2; var Style: DWORD; ExStyle: DWORD; FrameSize: TSize; begin if Wnd = 0 then Exit; { 0. Init some local vars } FNeedRecalculate := False; Style := GetWindowLong(Wnd, GWL_STYLE); FHasCaption := Style and WS_CAPTION = WS_CAPTION; if not FHasCaption then Exit; ExStyle := GetWindowLong(Wnd, GWL_EXSTYLE); FHasSmallCaption := ExStyle and WS_EX_TOOLWINDOW = WS_EX_TOOLWINDOW; {$IFDEF JVCLThemesEnabled} FCaptionActive := (GetActiveWindow = Wnd) and IsForegroundTask; {$ENDIF JVCLThemesEnabled} if Style and WS_THICKFRAME = WS_THICKFRAME then begin FrameSize.cx := GetSystemMetrics(SM_CXSIZEFRAME); FrameSize.cy := GetSystemMetrics(SM_CYSIZEFRAME); end else begin FrameSize.cx := GetSystemMetrics(SM_CXFIXEDFRAME); FrameSize.cy := GetSystemMetrics(SM_CYFIXEDFRAME); end; { 1. Calc FDefaultButtonTop } FDefaultButtonTop := FrameSize.cy + 2; { 2. Calc FDefaultButtonHeight } if FHasSmallCaption then FCaptionHeight := GetSystemMetrics(SM_CYSMCAPTION) else FCaptionHeight := GetSystemMetrics(SM_CYCAPTION); FDefaultButtonHeight := FCaptionHeight - 5; { 3. Calc FDefaultButtonWidth } {$IFDEF JVCLThemesEnabled} if IsThemed then FDefaultButtonWidth := FDefaultButtonHeight else {$ENDIF JVCLThemesEnabled} if FHasSmallCaption then FDefaultButtonWidth := GetSystemMetrics(SM_CXSMSIZE) - CSpaceBetweenButtons else FDefaultButtonWidth := GetSystemMetrics(SM_CXSIZE) - CSpaceBetweenButtons; { 4. Calc FDefaultButtonLeft } FDefaultButtonLeft := FrameSize.cx; Inc(FDefaultButtonLeft, FDefaultButtonWidth + CSpaceBetweenButtons); if Style and WS_SYSMENU = WS_SYSMENU then begin { 4a. Avoid close button } Inc(FDefaultButtonLeft, FDefaultButtonWidth + CSpaceBetweenButtons); if not FHasSmallCaption then begin if (Style and WS_MAXIMIZEBOX = WS_MAXIMIZEBOX) or (Style and WS_MINIMIZEBOX = WS_MINIMIZEBOX) then begin {$IFDEF JVCLThemesEnabled} if IsThemed then { 4b. If it have Max or Min button, both are visible. When themed the CONTEXTHELP button is then never visible } Inc(FDefaultButtonLeft, 2 * (FDefaultButtonWidth + CSpaceBetweenButtons)) else {$ENDIF JVCLThemesEnabled} begin { 4b. If it have Max or Min button, both are visible. } Inc(FDefaultButtonLeft, 2 * FDefaultButtonWidth + CSpaceBetweenButtons); { 4c. If it have CONTEXTHELP button, avoid it. } if ((Style and WS_MAXIMIZEBOX = 0) or (Style and WS_MINIMIZEBOX = 0)) and (ExStyle and WS_EX_CONTEXTHELP = WS_EX_CONTEXTHELP) then Inc(FDefaultButtonLeft, FDefaultButtonWidth + 2 * CSpaceBetweenButtons); end; end else { 4c. If it have CONTEXTHELP button, avoid it. } if ExStyle and WS_EX_CONTEXTHELP = WS_EX_CONTEXTHELP then Inc(FDefaultButtonLeft, FDefaultButtonWidth + CSpaceBetweenButtons); end; end; end; procedure TJvCaptionButton.Click; begin if csDesigning in ComponentState then DesignerSelectComponent(Self) else if Enabled then begin { Call OnClick if assigned and not equal to associated action's OnExecute. If associated action's OnExecute assigned then call it, otherwise, call OnClick. } if Assigned(FOnClick) and (Action <> nil) and (@FOnClick <> @Action.OnExecute) then FOnClick(Self) else if {not (csDesigning in ComponentState) and} Assigned(ActionLink) then FActionLink.Execute{$IFDEF COMPILER6_UP}(Self){$ENDIF} else if Assigned(FOnClick) then FOnClick(Self); end; end; procedure TJvCaptionButton.CreateToolTip(Wnd: THandle); var ToolInfo: TToolInfo; begin if csDesigning in ComponentState then Exit; DestroyToolTip; if Wnd = 0 then Exit; FToolTipHandle := CreateWindowEx(0, TOOLTIPS_CLASS, nil, TTS_ALWAYSTIP, Integer(CW_USEDEFAULT), Integer(CW_USEDEFAULT), Integer(CW_USEDEFAULT), Integer(CW_USEDEFAULT), ParentForm.Handle, // Thus automatically destroyed if ParentForm handle is destroyed. 0, HInstance, nil); if FToolTipHandle = 0 then Exit; // initialize tooltip info ToolInfo.cbSize := SizeOf(TToolInfo); ToolInfo.uFlags := TTF_IDISHWND; { Thus ignores rect param } ToolInfo.hwnd := Wnd; ToolInfo.uId := Wnd; ToolInfo.lpszText := LPSTR_TEXTCALLBACK; // register button with tooltip SendMessage(FToolTipHandle, TTM_ADDTOOL, 0, Integer(@ToolInfo)); end; procedure TJvCaptionButton.DestroyToolTip; begin if FToolTipHandle <> 0 then begin DestroyWindow(FToolTipHandle); FToolTipHandle := 0; end; end; procedure TJvCaptionButton.DoActionChange(Sender: TObject); begin if Sender = Action then ActionChange(Sender, False); end; procedure TJvCaptionButton.DrawButton(DC: HDC); var Canvas: TControlCanvas; begin if not Visible or not FHasCaption or (csDestroying in ComponentState) then Exit; Canvas := TControlCanvas.Create; try Canvas.Handle := DC; UpdateButtonRect(ParentFormHandle); with FButtonRect do begin FBuffer.Width := Right - Left; FBuffer.Height := Bottom - Top; end; {$IFDEF JVCLThemesEnabled} DrawButtonBackground(FBuffer.Canvas); {$ENDIF JVCLThemesEnabled} { We do a buffered drawing, otherwise you get flickering on XP, and you have to hassle with the clipping rects. } if FStandard <> tsbNone then DrawStandardButton(FBuffer.Canvas) else DrawNonStandardButton(FBuffer.Canvas); Canvas.Draw(FButtonRect.Left, FButtonRect.Top, FBuffer); finally Canvas.Handle := 0; Canvas.Free; end; end; {$IFDEF JVCLThemesEnabled} procedure TJvCaptionButton.DrawButtonBackground(ACanvas: TCanvas); const CCaption: array [Boolean, Boolean] of TThemedWindow = ( (twCaptionInactive, twCaptionActive), (twSmallCaptionInactive, twSmallCaptionActive) ); var Details: TThemedElementDetails; ClipRect: TRect; CaptionRect: TRect; begin if not IsThemed or (csDestroying in ComponentState) then Exit; { We basically draw the background to display 4 pixels - the corners of the rect - correct . Don't know a better way to do this. } { Determine the rect of the caption } GetWindowRect(ParentFormHandle, CaptionRect); OffsetRect(CaptionRect, -CaptionRect.Left, -CaptionRect.Top); CaptionRect.Bottom := FCaptionHeight + 4; { Offset it so the place where the button is, is at (0, 0) } OffsetRect(CaptionRect, -FButtonRect.Left, -FButtonRect.Top); with FButtonRect do ClipRect := Rect(0, 0, Right - Left, Bottom - Top); Details := ThemeServices.GetElementDetails(CCaption[FHasSmallCaption, FCaptionActive]); ThemeServices.DrawElement(ACanvas.Handle, Details, CaptionRect, @ClipRect); end; {$ENDIF JVCLThemesEnabled} procedure TJvCaptionButton.DrawButtonImage(ACanvas: TCanvas; ImageBounds: TRect); begin if csDestroying in ComponentState then Exit; with ImageBounds do if IsImageVisible then Images.Draw(ACanvas, Left, Top, ImageIndex, Enabled); end; procedure TJvCaptionButton.DrawButtonText(ACanvas: TCanvas; TextBounds: TRect); var Flags: DWORD; OldFont: TFont; begin Flags := DT_VCENTER or Alignments[FAlignment]; with ACanvas do begin Brush.Style := bsClear; if not Enabled then begin OffsetRect(TextBounds, 1, 1); OldFont := Font; Font := Self.Font; Font.Color := clBtnHighlight; DrawText(ACanvas, Caption, Length(Caption), TextBounds, Flags); OffsetRect(TextBounds, -1, -1); Font.Color := clBtnShadow; DrawText(ACanvas, Caption, Length(Caption), TextBounds, Flags); Font := OldFont; end else begin OldFont := Font; Font := Self.Font; DrawText(ACanvas, Caption, Length(Caption), TextBounds, Flags); Font := OldFont; end; end; end; procedure TJvCaptionButton.DrawNonStandardButton(ACanvas: TCanvas); {$IFDEF JVCLThemesEnabled} const cState_Normal = 1; cState_Hot = 2; cState_Pushed = 3; cState_Disabled = 4; {$ENDIF JVCLThemesEnabled} var DrawRect: TRect; RectText, RectImage: TRect; {$IFDEF JVCLThemesEnabled} State: Integer; DrawRgn: HRGN; {$ENDIF JVCLThemesEnabled} begin if csDestroying in ComponentState then Exit; with FButtonRect do DrawRect := Rect(0, 0, Right - Left, Bottom - Top); {$IFDEF JVCLThemesEnabled} // Satisfy the compiler DrawRgn := 0; { 1. Draw the button } if IsThemed then begin if not Enabled then State := 4 else if FDown then State := 3 else if FMouseInControl then State := 2 else State := 1; { Special state for buttons drawn on a not active caption } if not FCaptionActive then Inc(State, 4); if ForceDrawSimple or not GlobalXPData.Draw(ACanvas, State, DrawRect) then GlobalXPData.DrawSimple(ACanvas, State, DrawRect) end else {$ENDIF JVCLThemesEnabled} DrawButtonFace(ACanvas, DrawRect, 1, bsAutoDetect, False, FDown, False); { 2. Draw the text & picture } {$IFDEF JVCLThemesEnabled} if IsThemed then begin { 2a. If themed, only draw in the inner bit of the button using a clip region } with DrawRect do DrawRgn := CreateRectRgn(Left + 2, Top + 2, Right - 2, Bottom - 2); SelectClipRgn(ACanvas.Handle, DrawRgn); end; {$ENDIF JVCLThemesEnabled} if FDown then begin {$IFDEF JVCLThemesEnabled} if IsThemed then OffsetRect(DrawRect, 1, 0) else {$ENDIF JVCLThemesEnabled} OffsetRect(DrawRect, 1, 1); end; { 2b. Calc position and Draw the picture & text } CalcButtonParts(ACanvas, DrawRect, RectText, RectImage); DrawButtonText(ACanvas, RectText); DrawButtonImage(ACanvas, RectImage); { 2c. Clean up } {$IFDEF JVCLThemesEnabled} if IsThemed then begin SelectClipRgn(ACanvas.Handle, 0); DeleteObject(DrawRgn); end; {$ENDIF JVCLThemesEnabled} end; procedure TJvCaptionButton.DrawStandardButton(ACanvas: TCanvas); const {$IFDEF JVCLThemesEnabled} CElements: array [TJvStandardButton] of TThemedWindow = (twWindowDontCare, twCloseButtonNormal, twHelpButtonNormal, twMaxButtonNormal, twMinButtonNormal, twRestoreButtonNormal, twMinButtonNormal); {$ENDIF JVCLThemesEnabled} CDrawFlags: array [TJvStandardButton] of Word = (0, DFCS_CAPTIONCLOSE, DFCS_CAPTIONHELP, DFCS_CAPTIONMAX, DFCS_CAPTIONMIN, DFCS_CAPTIONRESTORE, 0); CDown: array [Boolean] of Word = (0, DFCS_PUSHED); CEnabled: array [Boolean] of Word = (DFCS_INACTIVE, 0); var DrawRect: TRect; {$IFDEF JVCLThemesEnabled} Details: TThemedElementDetails; CaptionButton: TThemedWindow; {$ENDIF JVCLThemesEnabled} begin if csDestroying in ComponentState then Exit; with FButtonRect do DrawRect := Rect(0, 0, Right - Left, Bottom - Top); {$IFDEF JVCLThemesEnabled} if IsThemed then begin CaptionButton := CElements[FStandard]; { Note : There is only a small close button (??) } if FHasSmallCaption and (FStandard = tsbClose) then CaptionButton := twSmallCloseButtonNormal; if not Enabled then Inc(CaptionButton, 3) else if FDown then { If Down and inactive, draw inactive border } Inc(CaptionButton, 2) else if FMouseInControl then Inc(CaptionButton); Details := ThemeServices.GetElementDetails(CaptionButton); { Special state for buttons drawn on a not active caption } if not FCaptionActive and (Details.State = 1) then Details.State := 5; ThemeServices.DrawElement(ACanvas.Handle, Details, DrawRect) end else {$ENDIF JVCLThemesEnabled} if Standard = tsbMinimizeToTray then begin DrawButtonFace(ACanvas, DrawRect, 1, bsAutoDetect, False, FDown, False); if Enabled then begin ACanvas.Brush.Color := clWindowText; with DrawRect do ACanvas.FillRect(Rect(Right - 7, Bottom - 5, Right - 4, Bottom - 3)); end else begin ACanvas.Brush.Color := clBtnHighlight; with DrawRect do ACanvas.FillRect(Rect(Right - 6, Bottom - 4, Right - 3, Bottom - 2)); ACanvas.Brush.Color := clBtnShadow; with DrawRect do ACanvas.FillRect(Rect(Right - 7, Bottom - 5, Right - 4, Bottom - 3)); end; end else DrawFrameControl(ACanvas.Handle, DrawRect, DFC_CAPTION, {DFCS_ADJUSTRECT or} CDrawFlags[Standard] or CDown[Down] or CEnabled[Enabled]); end; procedure TJvCaptionButton.ForwardToToolTip(Msg: TMessage); var ForwardMsg: TMsg; begin if FToolTipHandle = 0 then Exit; // forward to tool tip ForwardMsg.lParam := Msg.LParam; ForwardMsg.wParam := Msg.WParam; ForwardMsg.message := Msg.Msg; ForwardMsg.hwnd := ParentFormHandle; SendMessage(FToolTipHandle, TTM_RELAYEVENT, 0, Integer(@ForwardMsg)); end; function TJvCaptionButton.GetAction: TBasicAction; begin if FActionLink <> nil then Result := FActionLink.Action else Result := nil; end; function TJvCaptionButton.GetActionLinkClass: TJvCaptionButtonActionLinkClass; begin Result := TJvCaptionButtonActionLink; end; function TJvCaptionButton.GetIsImageVisible: Boolean; begin Result := Assigned(Images) and (ImageIndex > -1) and (ImageIndex < Images.Count); end; {$IFDEF JVCLThemesEnabled} function TJvCaptionButton.GetIsThemed: Boolean; begin Result := GlobalXPData.IsThemed; end; {$ENDIF JVCLThemesEnabled} function TJvCaptionButton.GetParentForm: TCustomForm; begin if Owner is TControl then Result := Forms.GetParentForm(TControl(Owner)) else Result := nil; end; function TJvCaptionButton.GetParentFormHandle: THandle; var P: TCustomForm; begin P := GetParentForm; if Assigned(P) and P.HandleAllocated then Result := P.Handle else Result := 0; end; function TJvCaptionButton.HandleButtonDown(var Msg: TWMNCHitMessage): Boolean; begin Result := Visible and Enabled and (Msg.HitTest = htCaptionButton) and MouseOnButton(Msg.XCursor, Msg.YCursor, False); if Result then begin FMouseButtonDown := True; if Toggle then FDown := not FDown else FDown := True; with TWMMouse(Msg) do MouseDown(mbLeft, KeysToShiftState(Keys), XPos, YPos); {if not Toggle then} SetCapture(ParentFormHandle); Redraw(rkIndirect); { Note: If Toggle = False -> click event is fired in HandleButtonUp } if Toggle then Click; end else if FDown and not Toggle then begin FMouseButtonDown := False; FDown := False; Redraw(rkIndirect); end; end; function TJvCaptionButton.HandleButtonUp(var Msg: TWMNCHitMessage): Boolean; var DoClick: Boolean; P: TPoint; begin Result := False; if not FMouseButtonDown then Exit; Result := FDown and MouseOnButton(Msg.XCursor, Msg.YCursor, Msg.Msg = WM_LBUTTONUP); { Note: If Toggle = True -> click event is fired in HandleButtonDown } DoClick := Result and not Toggle; FMouseButtonDown := False; ReleaseCapture; if not Toggle then begin FDown := False; Redraw(rkIndirect); end; if DoClick then Click; //(p3) we need to convert MouseUp message because they are in client coordinates (MouseDown are already in screen coords, so no need to change) with TWMMouse(Msg) do begin P := Point(XPos, YPos); Assert(ParentForm <> nil, ''); P := ParentForm.ClientToScreen(P); MouseUp(mbLeft, KeysToShiftState(Keys), P.X, P.Y); end; end; function TJvCaptionButton.HandleHitTest(var Msg: TWMNCHitTest): Boolean; var CurPos: TPoint; begin Result := Visible and MouseOnButton(Msg.XPos, Msg.YPos, False); if Result then Msg.Result := htCaptionButton; if not Result and Visible and MouseInControl then begin // We can get weird hittest values (probably from the hint window) so // double check that the mouse is not on the button. // Actually we wrongfully assumed that Msg represents the current mouse // position so we have to double check. GetCursorPos(CurPos); if not MouseOnButton(CurPos.X, CurPos.Y, False) then begin SetMouseInControl(False); Redraw(rkIndirect); end; end; //Result := False; end; function TJvCaptionButton.HandleMouseMove(var Msg: TWMNCHitMessage): Boolean; var DoRedraw: Boolean; MouseWasInControl: Boolean; begin Result := FMouseButtonDown; if Result then begin MouseWasInControl := FMouseInControl; SetMouseInControl(MouseOnButton(Msg.XCursor, Msg.YCursor, Msg.Msg = WM_MOUSEMOVE)); DoRedraw := (FMouseInControl <> MouseWasInControl) or // User presses mouse button, but left the caption button (FDown and not Toggle and not FMouseInControl) or // User presses mouse button, and enters the caption button (not FDown and not Toggle and FMouseInControl); FDown := (FDown and Toggle) or (FMouseButtonDown and not Toggle and FMouseInControl); if DoRedraw then Redraw(rkIndirect); end; // (p3) don't handle mouse move here: it is triggered even if the mouse is outside the button // with TWmMouseMove(Msg) do // MouseMove(KeysToShiftState(Keys), XPos, YPos); end; procedure TJvCaptionButton.HandleNCActivate(var Msg: TWMNCActivate); begin {$IFDEF JVCLThemesEnabled} FCaptionActive := Msg.Active; {$ENDIF JVCLThemesEnabled} SetMouseInControl(MouseInControl and Msg.Active); Redraw(rkDirect); end; procedure TJvCaptionButton.HandleNCMouseMove(var Msg: TWMNCHitMessage); var IsOnButton: Boolean; begin IsOnButton := MouseOnButton(Msg.XCursor, Msg.YCursor, False); if Visible then begin if (IsOnButton <> FMouseInControl) then begin SetMouseInControl(not FMouseInControl); if not Down then Redraw(rkIndirect); end; // (p3) only handle mouse move if we are inside the button or it will be triggered for the entire NC area if IsOnButton then with TWMMouseMove(Msg) do MouseMove(KeysToShiftState(Keys), XPos, YPos); end; end; procedure TJvCaptionButton.HandleNCPaintAfter(Wnd: THandle; var Msg: TWMNCPaint); begin if FRgnChanged then begin DeleteObject(Msg.RGN); Msg.RGN := FSaveRgn; FRgnChanged := False; end; Redraw(rkDirect); end; procedure TJvCaptionButton.HandleNCPaintBefore(Wnd: THandle; var Msg: TWMNCPaint); var WindowRect: TRect; DrawRgn: HRGN; LButtonRect: TRect; begin { Note: There is one problem with this reduce flickering method: This function is executed before windows handles the WM_NCPAINT and HandleNCPaintAfter is executed after windows handles WM_NCPAINT. When you resize a form, the value returned by API GetWindowRect is adjusted when windows handles the WM_NCPAINT. Thus return value of GetWindowRect in HandleNCPaintBefore differs from return value of GetWindowRect in HandleNCPaintAfter. -> Thus value of FButtonRect in HandleNCPaintBefore differs from return value of FButtonRect in HandleNCPaintAfter. (Diff is typically 1 pixel) This causes a light flickering at the edge of the button when you resize the form. To see it, put Sleep(1000) or so, before and after the DrawButton call in HandleNCPaintAfter and resize the screen horizontally } if Wnd = 0 then Exit; FSaveRgn := Msg.RGN; FRgnChanged := False; { Calculate button rect in screen coordinates, put it in LButtonRect } UpdateButtonRect(Wnd); LButtonRect := FButtonRect; GetWindowRect(Wnd, WindowRect); OffsetRect(LButtonRect, WindowRect.Left, WindowRect.Top); { Check if button rect is in the to be updated region.. } if RectInRegion(FSaveRgn, LButtonRect) then begin { ..If so remove the button rectangle from the region (otherwise the caption background would be drawn over the button, which causes flicker) } with LButtonRect do DrawRgn := CreateRectRgn(Left, Top, Right, Bottom); try Msg.RGN := CreateRectRgn(0, 0, 1, 1); FRgnChanged := True; CombineRgn(Msg.RGN, FSaveRgn, DrawRgn, RGN_DIFF); finally DeleteObject(DrawRgn); end; end; end; function TJvCaptionButton.HandleNotify(var Msg: TWMNotify): Boolean; var CurPos: TPoint; LButtonRect, WindowRect: TRect; begin // if we receive a TTN_GETDISPINFO notification // and it is from the tooltip Result := (Msg.NMHdr.code = TTN_NEEDTEXT) and (Msg.NMHdr.hwndFrom = FToolTipHandle); if Result and (ShowHint or (ParentShowHint and ParentForm.ShowHint)) then begin // get cursor position GetCursorPos(CurPos); GetWindowRect(ParentFormHandle, WindowRect); LButtonRect := FButtonRect; OffsetRect(LButtonRect, WindowRect.Left, WindowRect.Top); // if the mouse is in the area of the button if PtInRect(LButtonRect, CurPos) then if Msg.NMHdr.code = TTN_NEEDTEXTA then begin with PNMTTDispInfoA(Msg.NMHdr)^ do begin // then we return the hint lpszText := PChar(FHint); hinst := 0; uFlags := TTF_IDISHWND; hdr.idFrom := ParentFormHandle; end; end else with PNMTTDispInfoW(Msg.NMHdr)^ do begin // then we return the hint lpszText := PWideChar(WideString(FHint)); hinst := 0; uFlags := TTF_IDISHWND; hdr.idFrom := ParentFormHandle; end else //else we hide the tooltip HideToolTip; end; end; procedure TJvCaptionButton.HideToolTip; begin if FToolTipHandle <> 0 then SendMessage(FToolTipHandle, TTM_POP, 0, 0); end; procedure TJvCaptionButton.Hook; var P: TCustomForm; begin //if not Visible or not FHasCaption then // Exit; P := ParentForm; if Assigned(P) then begin RegisterWndProcHook(P, WndProcAfter, hoAfterMsg); RegisterWndProcHook(P, WndProcBefore, hoBeforeMsg); if P.HandleAllocated then CreateToolTip(P.Handle); end; end; procedure TJvCaptionButton.ImageListChange(Sender: TObject); begin if Sender = Images then Redraw(rkIndirect); end; procedure TJvCaptionButton.InitiateAction; begin if FActionLink <> nil then FActionLink.Update; end; function TJvCaptionButton.IsCaptionStored: Boolean; begin Result := (ActionLink = nil) or not FActionLink.IsCaptionLinked; end; function TJvCaptionButton.IsEnabledStored: Boolean; begin Result := (ActionLink = nil) or not FActionLink.IsEnabledLinked; end; function TJvCaptionButton.IsHintStored: Boolean; begin Result := (ActionLink = nil) or not FActionLink.IsHintLinked; end; function TJvCaptionButton.IsImageIndexStored: Boolean; begin Result := (ActionLink = nil) or not FActionLink.IsImageIndexLinked; end; procedure TJvCaptionButton.Loaded; begin inherited Loaded; CreateToolTip(ParentFormHandle); Redraw(rkTotalCaptionBar); end; procedure TJvCaptionButton.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin if Assigned(FOnMouseDown) then FOnMouseDown(Self, Button, Shift, X, Y); end; procedure TJvCaptionButton.MouseMove(Shift: TShiftState; X, Y: Integer); begin if Assigned(FOnMouseMove) then FOnMouseMove(Self, Shift, X, Y); end; function TJvCaptionButton.MouseOnButton(X, Y: Integer; const TranslateToScreenCoord: Boolean): Boolean; var WindowRect: TRect; Wnd: THandle; P: TPoint; begin Wnd := ParentFormHandle; Result := Wnd <> 0; if not Result then Exit; P := Point(X, Y); if TranslateToScreenCoord then Windows.ClientToScreen(Wnd, P); GetWindowRect(Wnd, WindowRect); Result := PtInRect(FClickRect, Point(P.X - WindowRect.Left, P.Y - WindowRect.Top)); end; procedure TJvCaptionButton.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin if Assigned(FOnMouseUp) then FOnMouseUp(Self, Button, Shift, X, Y); end; procedure TJvCaptionButton.Notification(AComponent: TComponent; Operation: TOperation); begin inherited Notification(AComponent, Operation); if (AComponent = Images) and (Operation = opRemove) then Images := nil; end; procedure TJvCaptionButton.Redraw(const AKind: TJvRedrawKind); var Wnd: THandle; DC: HDC; begin if csLoading in ComponentState then Exit; Wnd := ParentFormHandle; if Wnd = 0 then Exit; case AKind of rkDirect: begin DC := GetWindowDC(Wnd); try DrawButton(DC); finally ReleaseDC(Wnd, DC); end; end; rkIndirect: begin UpdateButtonRect(Wnd); RedrawWindow(Wnd, @FButtonRect, 0, RDW_FRAME or RDW_INVALIDATE or RDW_ERASE); end; rkTotalCaptionBar: begin UpdateButtonRect(Wnd); RedrawWindow(Wnd, PRect(0), 0, RDW_FRAME or RDW_NOINTERNALPAINT or RDW_INVALIDATE); end; end; end; procedure TJvCaptionButton.ResetButton; begin UnHook; Hook; Redraw(rkTotalCaptionBar); end; procedure TJvCaptionButton.SetAction(const Value: TBasicAction); begin if Value = nil then begin FActionLink.Free; FActionLink := nil; end else begin if FActionLink = nil then FActionLink := GetActionLinkClass.Create(Self); FActionLink.Action := Value; FActionLink.OnChange := DoActionChange; ActionChange(Value, csLoading in Value.ComponentState); Value.FreeNotification(Self); end; end; procedure TJvCaptionButton.SetAlignment(Value: TAlignment); begin if FAlignment <> Value then begin FAlignment := Value; if Standard = tsbNone then Redraw(rkIndirect); end; end; procedure TJvCaptionButton.SetCaption(Value: string); begin if FCaption <> Value then begin FCaption := Value; if Standard = tsbNone then Redraw(rkIndirect); end; end; procedure TJvCaptionButton.SetDown(const Value: Boolean); begin if (FDown <> Value) and Toggle then begin FDown := Value; Redraw(rkIndirect); end; end; procedure TJvCaptionButton.SetEnabled(const Value: Boolean); begin if FEnabled <> Value then begin FEnabled := Value; Redraw(rkIndirect); end; end; procedure TJvCaptionButton.SetFont(Value: TFont); begin if FFont <> Value then begin FFont.Assign(Value); if Standard = tsbNone then Redraw(rkIndirect); end; end; {$IFDEF JVCLThemesEnabled} procedure TJvCaptionButton.SetForceDrawSimple(const Value: Boolean); begin if FForceDrawSimple <> Value then begin FForceDrawSimple := Value; if IsThemed then Redraw(rkDirect); end; end; {$ENDIF JVCLThemesEnabled} procedure TJvCaptionButton.SetHeight(Value: Integer); begin if (FHeight <> Value) and (Value >= 0) then begin FHeight := Value; Redraw(rkTotalCaptionBar); end; end; procedure TJvCaptionButton.SetImageIndex(const Value: TImageIndex); begin if Value <> FImageIndex then begin FImageIndex := Value; if Standard = tsbNone then Redraw(rkIndirect); end; end; procedure TJvCaptionButton.SetImages(const Value: TCustomImageList); begin if FImages <> nil then FImages.UnRegisterChanges(FImageChangeLink); FImages := Value; if FImages <> nil then begin FImages.RegisterChanges(FImageChangeLink); FImages.FreeNotification(Self); end; if Standard = tsbNone then Redraw(rkIndirect); end; procedure TJvCaptionButton.SetLayout(const Value: TJvCaptionButtonLayout); begin if FLayout <> Value then begin FLayout := Value; if (csDesigning in ComponentState) and (FAlignment <> taCenter) then case FLayout of cbImageLeft: FAlignment := taLeftJustify; cbImageRight: FAlignment := taRightJustify; end; if (Standard = tsbNone) and IsImageVisible then Redraw(rkIndirect); end; end; procedure TJvCaptionButton.SetLeft(Value: Integer); begin if FLeft <> Value then begin FLeft := Value; Redraw(rkTotalCaptionBar); end; end; procedure TJvCaptionButton.SetMargin(const Value: Integer); begin if (FMargin <> Value) and (Value >= -1) then begin FMargin := Value; if Standard = tsbNone then Redraw(rkIndirect); end; end; procedure TJvCaptionButton.SetMouseInControl(const Value: Boolean); begin if FMouseInControl <> Value then begin if not Value then HideToolTip; FMouseInControl := Value; end; end; procedure TJvCaptionButton.SetParentShowHint(const Value: Boolean); begin if FParentShowHint <> Value then begin FParentShowHint := Value; if FParentShowHint then FShowHint := ParentForm.ShowHint; end; end; procedure TJvCaptionButton.SetPosition(const Value: Integer); begin if FPosition <> Value then begin FPosition := Value; Redraw(rkTotalCaptionBar); end; end; procedure TJvCaptionButton.SetShowHint(const Value: Boolean); begin if FShowHint <> Value then begin FShowHint := Value; FParentShowHint := False; end; end; procedure TJvCaptionButton.SetSpacing(const Value: Integer); begin if FSpacing <> Value then begin FSpacing := Value; if Standard = tsbNone then Redraw(rkIndirect); end; end; procedure TJvCaptionButton.SetStandard(Value: TJvStandardButton); {$IFDEF JVCLThemesEnabled} var ButtonSizeChanged: Boolean; {$ENDIF JVCLThemesEnabled} begin if FStandard <> Value then begin {$IFDEF JVCLThemesEnabled} ButtonSizeChanged := IsThemed and ((Value = tsbMinimizeToTray) or (FStandard = tsbMinimizeToTray)); {$ENDIF JVCLThemesEnabled} FStandard := Value; {$IFDEF JVCLThemesEnabled} if ButtonSizeChanged then UpdateButtonRect(ParentFormHandle); if ButtonSizeChanged and (FStandard = tsbMinimizeToTray) then Redraw(rkTotalCaptionBar) else {$ENDIF JVCLThemesEnabled} Redraw(rkIndirect); end; end; procedure TJvCaptionButton.SetToggle(const Value: Boolean); begin if FToggle <> Value then begin FToggle := Value; if FDown and not FToggle and not (FMouseInControl and FMouseButtonDown) then begin FDown := False; Redraw(rkIndirect); end; end; end; procedure TJvCaptionButton.SetTop(Value: Integer); begin if FTop <> Value then begin FTop := Value; Redraw(rkTotalCaptionBar); end; end; procedure TJvCaptionButton.SetVisible(const Value: Boolean); begin if Value <> FVisible then begin FVisible := Value; { Caption, RedrawButton doesn't draw the caption itself } Redraw(rkTotalCaptionBar); end; end; procedure TJvCaptionButton.SetWidth(Value: Integer); begin if (FWidth <> Value) and (Value >= 0) then begin FWidth := Value; Redraw(rkTotalCaptionBar); end; end; procedure TJvCaptionButton.UnHook; var P: TCustomForm; begin P := ParentForm; if Assigned(P) then begin DestroyToolTip; UnRegisterWndProcHook(P, WndProcAfter, hoAfterMsg); UnRegisterWndProcHook(P, WndProcBefore, hoBeforeMsg); end; end; procedure TJvCaptionButton.UpdateButtonRect(Wnd: THandle); var WindowRect: TRect; LButtonWidth: Integer; LButtonHeight: Integer; begin if Wnd = 0 then Exit; if FNeedRecalculate then CalcDefaultButtonRect(Wnd); GetWindowRect(Wnd, WindowRect); if ButtonWidth <> 0 then LButtonWidth := ButtonWidth else LButtonWidth := FDefaultButtonWidth; if ButtonHeight <> 0 then LButtonHeight := ButtonHeight else LButtonHeight := FDefaultButtonHeight; FButtonRect := Bounds( WindowRect.Right - WindowRect.Left - FDefaultButtonLeft + ButtonLeft + FDefaultButtonWidth - LButtonWidth, FDefaultButtonTop + ButtonTop, LButtonWidth, LButtonHeight); OffsetRect(FButtonRect, -FPosition * (FDefaultButtonWidth + 2), 0); { Special } {$IFDEF JVCLThemesEnabled} if (FStandard = tsbMinimizeToTray) and IsThemed then Inc(FButtonRect.Top, 2); {$ENDIF JVCLThemesEnabled} { Click rect is a bit bigger } with FButtonRect do FClickRect := Rect(Left - 2, Top - 2, Right + 1, Bottom + 2); end; function TJvCaptionButton.WndProcAfter(var Msg: TMessage): Boolean; begin { let others listen in too } Result := False; case Msg.Msg of {$IFDEF JVCLThemesEnabled} WM_THEMECHANGED, {$ENDIF JVCLThemesEnabled} CM_SYSCOLORCHANGE: begin FNeedRecalculate := True; {$IFDEF JVCLThemesEnabled} { force theme data refresh, needed when * Switching from 'windows classic' style to 'windows XP' style ( delphi 7 bug) } ThemeServices.UpdateThemes; GlobalXPData.Update; {$ENDIF JVCLThemesEnabled} end; CM_SYSFONTCHANGED: begin FNeedRecalculate := True; {$IFDEF JVCLThemesEnabled} { force theme data refresh, needed when * Non-themed application and switching system font size } if not ThemeServices.ThemesEnabled then ThemeServices.UpdateThemes; {$ENDIF JVCLThemesEnabled} end; WM_SETTEXT: { Caption text may overwrite the button, so redraw } Redraw(rkIndirect); WM_NCPAINT: HandleNCPaintAfter(ParentFormHandle, TWMNCPaint(Msg)); WM_NCACTIVATE: HandleNCActivate(TWMNCActivate(Msg)); CM_RECREATEWND: begin CreateToolTip(ParentFormHandle); FNeedRecalculate := True; //CalcDefaultButtonRect(ParentFormHandle); Redraw(rkTotalCaptionBar); end; WM_LBUTTONDOWN, WM_NCLBUTTONUP, WM_LBUTTONUP, WM_NCMOUSEMOVE, WM_NCRBUTTONUP, WM_RBUTTONUP, WM_NCRBUTTONDOWN, WM_RBUTTONDOWN, WM_NCLBUTTONDOWN: ForwardToToolTip(Msg); end; end; function TJvCaptionButton.WndProcBefore(var Msg: TMessage): Boolean; begin case Msg.Msg of CM_SHOWHINTCHANGED: begin if ParentShowHint then FShowHint := ParentForm.ShowHint; Result := False; end; CM_MOUSELEAVE, CM_MOUSEENTER: begin if FMouseInControl then begin SetMouseInControl(False); Redraw(rkIndirect); end; Result := False; end; WM_NCLBUTTONDOWN: //, WM_LBUTTONDOWN: begin Result := HandleButtonDown(TWMNCHitMessage(Msg)); if Result then ForwardToToolTip(Msg); end; WM_NCLBUTTONUP, WM_LBUTTONUP: begin Result := HandleButtonUp(TWMNCHitMessage(Msg)); if Result then ForwardToToolTip(Msg); end; WM_MOUSEMOVE: begin Result := HandleMouseMove(TWMNCHitMessage(Msg)); if Result then ForwardToToolTip(Msg); end; WM_NCMOUSEMOVE: begin Result := False; HandleNCMouseMove(TWMNCHitMessage(Msg)); end; WM_NCHITTEST: Result := HandleHitTest(TWMNCHitTest(Msg)); WM_NCPAINT: begin Result := False; HandleNCPaintBefore(ParentFormHandle, TWMNCPaint(Msg)); end; WM_DESTROY: begin Result := False; {DestroyToolTip;} // FToolTipHandle is automatically destroyed when ParentForm handle is destroyed. FToolTipHandle := 0; end; WM_NOTIFY: Result := HandleNotify(TWMNotify(Msg)); else Result := False; end; end; //=== { TJvCaptionButtonActionLink } ========================================= procedure TJvCaptionButtonActionLink.AssignClient(AClient: TObject); begin FClient := AClient as TJvCaptionButton; end; function TJvCaptionButtonActionLink.IsCaptionLinked: Boolean; begin Result := inherited IsCaptionLinked and AnsiSameText(FClient.Caption, (Action as TCustomAction).Caption); end; function TJvCaptionButtonActionLink.IsEnabledLinked: Boolean; begin Result := inherited IsEnabledLinked and (FClient.Enabled = (Action as TCustomAction).Enabled); end; function TJvCaptionButtonActionLink.IsHintLinked: Boolean; begin Result := inherited IsHintLinked and (FClient.Hint = (Action as TCustomAction).Hint); end; function TJvCaptionButtonActionLink.IsImageIndexLinked: Boolean; begin Result := inherited IsImageIndexLinked and (FClient.ImageIndex = (Action as TCustomAction).ImageIndex); end; function TJvCaptionButtonActionLink.IsOnExecuteLinked: Boolean; begin Result := inherited IsOnExecuteLinked and (@FClient.OnClick = @Action.OnExecute); end; function TJvCaptionButtonActionLink.IsVisibleLinked: Boolean; begin Result := inherited IsVisibleLinked and (FClient.Visible = (Action as TCustomAction).Visible); end; procedure TJvCaptionButtonActionLink.SetCaption(const Value: string); begin if IsCaptionLinked then FClient.Caption := Value; end; procedure TJvCaptionButtonActionLink.SetEnabled(Value: Boolean); begin if IsEnabledLinked then FClient.Enabled := Value; end; procedure TJvCaptionButtonActionLink.SetHint(const Value: string); begin if IsHintLinked then FClient.Hint := Value; end; procedure TJvCaptionButtonActionLink.SetImageIndex(Value: Integer); begin if IsImageIndexLinked then FClient.ImageIndex := Value; end; procedure TJvCaptionButtonActionLink.SetOnExecute(Value: TNotifyEvent); begin if IsOnExecuteLinked then FClient.OnClick := Value; end; procedure TJvCaptionButtonActionLink.SetVisible(Value: Boolean); begin if IsVisibleLinked then FClient.Visible := Value; end; initialization {$IFDEF UNITVERSIONING} RegisterUnitVersion(HInstance, UnitVersioning); {$ENDIF UNITVERSIONING} finalization UnloadMsimg32Dll; {$IFDEF UNITVERSIONING} UnregisterUnitVersion(HInstance); {$ENDIF UNITVERSIONING} end.