首页 > 代码库 > 窗体皮肤实现 - 实现简单Toolbar(六)

窗体皮肤实现 - 实现简单Toolbar(六)

自定义皮肤很方便,基础开发的工作也是很大的。不过还好一般产品真正需要开发的并不是很多。现在比较漂亮的界面产品都会有个大大的工具条。

Toolbar工具条实现皮肤的方法还是可以使用Form的处理方案。每当重复写相同东西的时候,有时会感觉无聊。所以想简单实现个轻量级的,依葫芦画瓢进行减肥。

 

完成后大致的效果

这个简易Toolbar只实现了Button样式,没有分割线没有下拉多选之类的样式。

”这么弱的东西有毛用?“

其实这个工具条主要目的是用于附着在其他控件上使用,比如某些控件的标题区域位置。当然如果想要搞的强大,那么代码量肯定会膨胀。

 

控件实现内容:

  1、加入Hint提示

  2、加入了简易动画效果,鼠标进入和离开会有个渐变效果。

 

实现方案

  1、基类选用

  2、Action的关联

  3、绘制按钮

  4、鼠标响应

  5、美化(淡入淡出简易动画)

  OK~完成

 

一、基类选择

  在基类选择上稍微纠结了下。Delphi大家都知道做一个显示控件一般有2种情况,一种是图形控件(VC里叫静态控件),还种种有焦点可交互的。

  如果我想做个Toolbar并不需要焦点,也不需要处理键盘输入,TGraphicControl 是比较理想的继承类。不过最终还是使用了TWinControl,主要一点是TWinControl有个句柄方便处理。当然TGraphicControl也是可以申请句柄的。这个问题就不纠结,确定使用TWinControl。

二、关联Action

  说是关联其实就是Toolbar有多少个Button,需要保存这些Button的信息。在标题工具栏(四)中已经有简易实现。个人喜欢用Record来记录东西,简单方便不要管创建和释放。

1   TmtToolItem = record2     Action: TBasicAction;  3     Enabled: boolean;4     Visible: boolean;5     ImageIndex: Word;         // 考虑到标题功能图标和实际工具栏功能使用不同图标情况,分开图标索引6     Width: Word;              // 实际占用宽度,考虑后续加不同的按钮样式使用7     Fade: Word;               // 褪色量 0 - 2558     SaveEvent: TNotifyEvent;  // 原始的Action OnChange事件9   end;

这是一个Button的信息,记录了些基本的信息(这个和原来一样)。如果愿意可以加个样式类型(Style),来绘制更多的Button样式。

1   TmtCustomToolbar = class(TWinControl)2   private3     FItems: array of TmtToolItem;4     FCount: Integer;5     ... ...

FItems 和 FCount 用来记录Button的数组容器。直接使用SetLength动态设置数组的长度,简易不用创建直接使用。有了容器,Action就需要个入口来传入。

处理三件事情:

  1、检测容器容量,不够增加

  2、清空第Count位的Record值(清零)。这步其实对Record比较重要,如果记录中增加参数值时...给你来个随机数那就比较郁闷了。

  3、填充记录

  4、重算尺寸并重新绘制

 1 procedure TmtCustomToolbar.Add(Action: TBasicAction; AImageIndex: Integer); 2 begin 3   if FCount >= Length(FItems) then 4     SetLength(FItems, FCount + 5); 5  6   // 保存Action信息 7   ZeroMemory(@FItems[FCount], SizeOf(TmtToolItem)); 8   FItems[FCount].Action := Action; 9   FItems[FCount].Enabled := true;10   FItems[FCount].Visible := true;11   FItems[FCount].ImageIndex := AImageIndex;12   FItems[FCount].Width := 20;13   FItems[FCount].Fade := 0;14   FItems[FCount].SaveEvent := TacAction(Action).OnChange;15   TacAction(Action).OnChange := DoOnActionChange;16 17   // 初始化状态18   with FItems[FCount] do19     if Action.InheritsFrom(TContainedAction) then20     begin21       Enabled := TContainedAction(Action).Enabled;22       Visible := TContainedAction(Action).Visible;23     end;24 25   inc(FCount);26 27   // 更新显示尺寸28   UpdateSize;    29 end;
保存Action信息

 

三、绘制按钮

  绘制肯定是要完全控制,画布画笔都必须牢牢的攥在手里。美与丑就的靠自己有多少艺术细胞。本人是只有艺术脓包,至于你信不信,反正我是信了。

处理两个消息:WM_Paint 和 WM_ERASEBKGND。不让父类(TWinControl)做多余的事情。

WM_ERASEBKGND 处理背景擦除,这个不必处理。直接告诉消息,不处理此消息。

1 procedure TmtCustomToolbar.WMEraseBkgnd(var message: TWMEraseBkgnd);2 begin3   Message.Result := 1;  // 已经处理完成了,不用再处理4 end;

WM_Paint消息为减少闪烁,使用Buffer进行绘制。

 1 procedure TmtCustomToolbar.WMPaint(var message: TWMPaint); 2 var 3   DC, hPaintDC: HDC; 4   cBuffer: TBitmap; 5   PS: TPaintStruct; 6   R: TRect; 7   w, h: Integer; 8 begin 9   ///10   /// 绘制客户区域11   ///12   R := GetClientRect;13   w := R.Width;14   h := R.Height;15 16   DC := Message.DC;17   hPaintDC := DC;18   if DC = 0 then19     hPaintDC := BeginPaint(Handle, PS);20 21   // 创建个画布,在这个上面绘制。22   cBuffer := TBitmap.Create;  23   try24     cBuffer.SetSize(w, h);25     PaintBackground(cBuffer.Canvas.Handle);26     PaintWindow(cBuffer.Canvas.Handle);27     // 绘制完成的图形,直接拷贝到界面。这就是传说中的双缓冲技术木?28     BitBlt(hPaintDC, 0, 0, w, h, cBuffer.Canvas.Handle, 0, 0, SRCCOPY);29   finally30     cBuffer.free;31   end;32 33   if DC = 0 then34     EndPaint(Handle, PS);35 end;

最有就是绘制界面上的Action。只要循环绘制完所有按钮就OK了

处理过程:

   1、是否要绘制,隐藏跳过

   2、根据鼠标事件状态绘制按钮底纹。(按钮在Hot状态还是鼠标按下状态)

   3、获得Action的图标,在2的基础上绘制。

   OK~完成,偏移位置继续画下个。

获取按钮的状态绘制,默认状态,按下状态和鼠标滑入的状态。

1   function GetActionState(Idx: Integer): TSkinIndicator;2   begin3     Result := siInactive;   4     if (Idx = FPressedIndex) then5       Result := siPressed6     else if (Idx = FHotIndex) and (FPressedIndex = -1) then7       Result := siHover;8   end;

具体绘制色块型的是非常简单,根据不同类型获取状态颜色。

 1   function GetColor(s: TSkinIndicator): Cardinal; inline; 2   begin 3     case s of 4       siHover         : Result := SKINCOLOR_BTNHOT; 5       siPressed       : Result := SKINCOLOR_BTNPRESSED; 6       siSelected      : Result := SKINCOLOR_BTNPRESSED; 7       siHoverSelected : Result := SKINCOLOR_BTNHOT; 8     else                Result := SKINCOLOR_BTNHOT; 9     end;10   end;

然后就是直接填充颜色。

  procedure DrawStyle(DC: HDC; const R: TRect; AColor: Cardinal); inline;  var    hB: HBRUSH;  begin    hB := CreateSolidBrush(AColor);    FillRect(DC, R, hB);    DeleteObject(hB);  end;
 1 class procedure TTreeViewSkin.DrawButtonState(DC: HDC; AState: TSkinIndicator; const R: TRect; const AOpacity: Byte); 2  3   function GetColor(s: TSkinIndicator): Cardinal; inline; 4   begin 5     case s of 6       siHover         : Result := SKINCOLOR_BTNHOT; 7       siPressed       : Result := SKINCOLOR_BTNPRESSED; 8       siSelected      : Result := SKINCOLOR_BTNPRESSED; 9       siHoverSelected : Result := SKINCOLOR_BTNHOT;10     else                Result := SKINCOLOR_BTNHOT;11     end;12   end;13 14   procedure DrawStyle(DC: HDC; const R: TRect; AColor: Cardinal); inline;15   var16     hB: HBRUSH;17   begin18     hB := CreateSolidBrush(AColor);19     FillRect(DC, R, hB);20     DeleteObject(hB);21   end;22 23 var24   cBmp: TBitmap;25 begin26   if AOpacity = 255 then27       DrawStyle(DC, R, GetColor(AState))28   else if AOpacity > 0 then29   begin30     cBmp := TBitmap.Create;31     cBmp.SetSize(r.Width, r.Height);32     DrawStyle(cBmp.Canvas.Handle, Rect(0, 0, r.Width, r.Height), GetColor(AState));33     DrawTransparentBitmap(cBmp, 0, 0, DC, r.Left, r.Top, r.Width, r.Height, AOpacity);34     cBmp.Free;35   end;36 end;
绘制按钮底纹的完整过程

 

获得图标就不多说啦。直接根据Action的信息获得。

 1 function TmtCustomToolbar.LoadActionIcon(Idx: Integer; AImg: TBitmap): boolean; 2  3   function LoadIcon(AImgs: TCustomImageList; AIndex: Integer): boolean; 4   begin 5     Result := False; 6     if Assigned(AImgs) and (AIndex >= 0) and (AIndex < AImgs.Count) then 7       Result := AImgs.GetBitmap(AIndex, AImg); 8   end; 9 10 var11   bHasImg: boolean;12   ImgIdx: Integer;13 14 begin15   /// 获取Action的图标16   ImgIdx := -1;17   AImg.Canvas.Brush.Color := clBlack;18   AImg.Canvas.FillRect(Rect(0, 0, AImg.Width, AImg.Height));19   bHasImg := LoadIcon(FImages, FItems[Idx].ImageIndex);20   if not bHasImg and (FItems[Idx].Action is TCustomAction) then21   begin22     ImgIdx := TCustomAction(FItems[Idx].Action).ImageIndex;23     bHasImg := LoadIcon(TCustomAction(FItems[Idx].Action).Images, ImgIdx);24   end;25   if not bHasImg then26     bHasImg := LoadIcon(FImages, ImgIdx);27 28   Result := bHasImg;29 end;
获取Action的图标

这里主要注意的是,图标是有透明层。需要使用绘制透明函数AlphaBlend处理。

 1 class procedure TTreeViewSkin.DrawIcon(DC: HDC; R: TRect; ASrc: TBitmap; const 2     Opacity: Byte = 255); 3 var 4   iXOff: Integer; 5   iYOff: Integer; 6 begin 7   /// 8   ///  绘制图标 9   ///    绘制图标是会作居中处理10   iXOff := r.Left + (R.Right - R.Left - ASrc.Width) div 2;11   iYOff := r.Top + (r.Bottom - r.Top - ASrc.Height) div 2;12   DrawTransparentBitmap(ASrc, 0, 0, DC, iXOff, iYOff, ASrc.Width, ASrc.Height, Opacity);13 end;
 1 procedure DrawTransparentBitmap(Source: TBitmap; sx, sy: Integer; Destination: HDC; 2   const dX, dY: Integer;  w, h: Integer; const Opacity: Byte); overload; 3 var 4   BlendFunc: TBlendFunction; 5 begin 6   BlendFunc.BlendOp := AC_SRC_OVER; 7   BlendFunc.BlendFlags := 0; 8   BlendFunc.SourceConstantAlpha := Opacity; 9 10   if Source.PixelFormat = pf32bit then11     BlendFunc.AlphaFormat := AC_SRC_ALPHA12   else13     BlendFunc.AlphaFormat := 0;14 15   AlphaBlend(Destination, dX, dY, w, h, Source.Canvas.Handle, sx, sy, w, h, BlendFunc);16 end;
函数:DrawTransparentBitmap

 

四、鼠标事件响应

  鼠标的响应,处理移动、按下、弹起。其他就不需要了。在鼠标移动时检测所在的按钮,按下是一样确定按下的是那个Button,弹开时执行Button的Action事件。不同状态的切换,需要告诉界面进行重新绘制。

在鼠标移动时,除了检测所在按钮外。FHotIndex记录当前光标所在的按钮索引。如果没有按下的状态,需要告诉系统我要显示提示(Hint)。

 1 procedure TmtCustomToolbar.WMMouseMove(var message: TWMMouseMove); 2 var 3   iSave: Integer; 4 begin 5   iSave := FHotIndex; 6   HotIndex := HitTest(message.XPos, message.YPos); 7   // 在没有按下按钮时触发Hint显示 8   if (iSave <> FHotIndex) and (FHotIndex >= 0) and  (FPressedIndex = -1) then 9     Application.ActivateHint(message.Pos);  10 end;

按下时检测,按下的那个按钮。FPressedIndex记录按下的按钮索引(就是数组索引)。

1 procedure TmtCustomToolbar.MouseDown(Button: TMouseButton; Shift: TShiftState; x, y: Integer);2 begin3   if mbLeft = Button then4   begin5     FPressedIndex := HitTest(x, y);6     Invalidate;7   end;8 end;
MouseDown 函数

弹起时处理按钮事件。这里稍微需要处理一下,就是按下鼠标后不松开移动鼠标到其他地方~~ 结果~~。一般系统的处理方式是不执行那个先前被按下的按钮事件。

所以在弹起时也要检测一下。原先按下的和现在的按钮是否一致,不一致就不处理Action。

 1 procedure TmtCustomToolbar.MouseUp(Button: TMouseButton; Shift: TShiftState; x, y: Integer); 2 var 3   iPressed: Integer; 4 begin 5   if FPressedIndex >= 0 then 6   begin 7     iPressed := HitTest(x, y); 8     if iPressed = FPressedIndex then 9       ExecAction(iPressed);10   end;11   FPressedIndex := -1;12   Invalidate;13 end;
MouseUp 函数

 

五、美化,加入简易动画效果。

  为了能看起来不是很生硬,在进入按钮和离开时增加点动画效果。当然这个还是比较菜的效果。如果想很炫那就的现象一下,如何才能很炫。然后用你手里攥着的画笔涂鸦把!

  动画效果主要加入一个90毫秒的一个定时器,90毫秒刷一次界面~。这样就能感觉有点像动画的效果,要更加精细的话可以再短些。

 1 CONST 2   TIMID_FADE = 1; // Action褪色 3  4 procedure TmtCustomToolbar.SetHotIndex(const Value: Integer); 5 begin 6   if FHotIndex <> Value then 7   begin 8     FHotIndex := Value; 9     Invalidate;10     // 鼠标的位置变了,启动定时器11     //   有Handle 就不用再独立创建一个Timer,可以启动很多个用ID区分。12     if not(csDestroying in ComponentState) and HandleAllocated then13       SetTimer(Handle, TIMID_FADE, 90, nil);14   end;15 end;

到点刷新界面

1 procedure TmtCustomToolbar.WMTimer(var message: TWMTimer);2 begin3   // 是褪色定时器,那么刷新界面4   if message.TimerID = TIMID_FADE then5     UpdateFade;6 end;

褪色值其实就是一个0~255的一个透明Alpha通道值,每次绘制底色时根据这个阀值来绘制透明背景Button底纹。所有都为透明时,关闭动画时钟。

 1 procedure TmtCustomToolbar.UpdateFade; 2 var 3   I: Integer; 4   bHas: boolean; 5 begin 6   bHas := False; 7   for I := 0 to FCount - 1 do 8     if FItems[I].Visible and FItems[I].Enabled then 9     begin10       // 设置褪色值11       //   鼠标:当前Button,那么趋向不透明(25512       //        不再当前位置,趋向透明(013       if FHotIndex = I then14         FItems[I].Fade := GetShowAlpha(FItems[I].Fade)15       else if FItems[I].Fade > 0 then16         FItems[I].Fade := GetFadeAlpha(FItems[I].Fade);17       bHas := bHas or (FItems[I].Fade > 0);18     end;19   Invalidate;20   if not bHas and HandleAllocated then21     KillTimer(Handle, TIMID_FADE);22 end;
 1   function GetShowAlpha(v: byte): byte; inline; 2   begin 3     if v = 0 then           Result := 180 4     else if v <= 180 then   Result := 220 5     else                    Result := 255; 6   end; 7  8   function GetFadeAlpha(v: byte): byte; inline; 9   begin10     if v >= 255 then        Result := 23011     else if v >= 230 then   Result := 18012     else if v >= 180 then   Result := 10013     else if v >= 100 then   Result := 5014     else if v >= 50 then    Result := 1015     else                    Result := 0;16   end;
函数: GetShowAlpha 和 GetFadeAlpha

 

完成啦~

 

完整单元代码

  1 unit uMTToolbars;  2   3 interface  4   5 uses  6   Classes, Windows, Messages, Controls, Actions, ImgList, Graphics, ActnList, Forms, Menus, SysUtils;  7   8 type  9   TmtToolItem = record 10     Action: TBasicAction; 11     Enabled: boolean; 12     Visible: boolean; 13     ImageIndex: Integer;      // 考虑到标题功能图标和实际工具栏功能使用不同图标情况,分开图标索引 14     Width: Word;              // 实际占用宽度,考虑后续加不同的按钮样式使用 15     Fade: Word;               // 褪色量 0 - 255 16     SaveEvent: TNotifyEvent;  // 原始的Action OnChange事件 17   end; 18  19   TmtCustomToolbar = class(TWinControl) 20   private 21     FAutoWidth: Boolean; 22     FItems: array of TmtToolItem; 23     FCount: Integer; 24     FImages: TCustomImageList; 25  26     FHotIndex: Integer; 27     FPressedIndex: Integer; 28  29     function HitTest(x, y: Integer): Integer; 30     procedure ExecAction(Index: Integer); 31  32     procedure DoOnActionChange(Sender: TObject); 33     function  LoadActionIcon(Idx: Integer; AImg: TBitmap): boolean; 34     procedure SetAutoWidth(const Value: Boolean); 35     procedure SetHotIndex(const Value: Integer); 36     procedure UpdateFade; 37  38     procedure WMEraseBkgnd(var message: TWMEraseBkgnd); message WM_ERASEBKGND; 39     procedure WMPaint(var message: TWMPaint); message WM_Paint; 40     procedure WMMouseLeave(var message: TMessage); message WM_MOUSELEAVE; 41     procedure WMMouseMove(var message: TWMMouseMove); message WM_MOUSEMOVE; 42     procedure WMTimer(var message: TWMTimer); message WM_TIMER; 43     procedure CMHintShow(var Message: TCMHintShow); message CM_HINTSHOW; 44     function GetActualWidth: Integer; 45   protected 46     // 计算实际占用尺寸 47     function CalcSize: TRect; 48     procedure UpdateSize; 49  50     procedure MouseMove(Shift: TShiftState; x: Integer; y: Integer); override; 51     procedure MouseDown(Button: TMouseButton; Shift: TShiftState; x: Integer; y: Integer); override; 52     procedure MouseUp(Button: TMouseButton; Shift: TShiftState; x: Integer; y: Integer); override; 53     procedure PaintBackground(DC: HDC); 54     procedure PaintWindow(DC: HDC); override; 55  56   public 57     procedure Add(Action: TBasicAction; AImageIndex: Integer = -1); 58     function IndexOf(Action: TBasicAction): Integer; 59  60     constructor Create(AOwner: TComponent); override; 61     destructor Destroy; override; 62  63     property AutoWidth: Boolean read FAutoWidth write SetAutoWidth; 64     property HotIndex: Integer read FHotIndex write SetHotIndex; 65     property Images: TCustomImageList read FImages write FImages; 66     property ActualWidth: Integer read GetActualWidth; 67  68   end; 69  70   TmtToolbar = class(TmtCustomToolbar) 71   published 72     property Color; 73   end; 74  75  76 implementation 77  78 uses 79   uUISkins; 80  81 CONST 82   TIMID_FADE = 1; // Action褪色 83  84 type 85   TacAction = class(TBasicAction); 86  87 procedure TmtCustomToolbar.Add(Action: TBasicAction; AImageIndex: Integer); 88 begin 89   if FCount >= Length(FItems) then 90     SetLength(FItems, FCount + 5); 91  92   ZeroMemory(@FItems[FCount], SizeOf(TmtToolItem)); 93   FItems[FCount].Action := Action; 94   FItems[FCount].Enabled := true; 95   FItems[FCount].Visible := true; 96   FItems[FCount].ImageIndex := AImageIndex; 97   FItems[FCount].Width := 20; 98   FItems[FCount].Fade := 0; 99   FItems[FCount].SaveEvent := TacAction(Action).OnChange;100   TacAction(Action).OnChange := DoOnActionChange;101 102   // 初始化状态103   with FItems[FCount] do104     if Action.InheritsFrom(TContainedAction) then105     begin106       Enabled := TContainedAction(Action).Enabled;107       Visible := TContainedAction(Action).Visible;108     end;109 110   inc(FCount);111 112   UpdateSize;    113 end;114 115 function TmtCustomToolbar.CalcSize: TRect;116 const117   SIZE_SPLITER = 10;118   SIZE_POPMENU = 10;119   SIZE_BUTTON = 20;120 var121   w, h: Integer;122   I: Integer;123 begin124   ///125   /// 占用宽度126   /// 如果考虑比较复杂的按钮样式和显示标题等功能,那么需要计算每个按钮实际占用宽度才能获得。127 128   // w := SIZE_SPLITER * 2 + SIZE_POPMENU;129   w := 0;130   for I := 0 to FCount - 1 do131     if FItems[i].Visible then132       w := w + FItems[I].Width;133   h := SIZE_BUTTON;134   Result := Rect(0, 0, w, h);135 end;136 137 procedure TmtCustomToolbar.CMHintShow(var Message: TCMHintShow);138 var139   Idx: Integer;140   sHint: string;141   sTitle, sRemark, sShortCut: string;142 begin143   sTitle := ‘‘;144   sRemark := ‘‘;145   sShortCut := ‘‘;146   Idx := FHotIndex;147   if (Idx >= FCount) or (not FItems[idx].Visible) then148     Idx := -1;149 150   // get hint data151   if Idx >= 0 then152   begin153     if FItems[Idx].Action.InheritsFrom(TContainedAction) then154       with TContainedAction(FItems[Idx].Action) do155       begin156         sTitle := Caption;157         sRemark := Hint;158         if ShortCut <> scNone then159           sShortCut := ShortCutToText(TCustomAction(Action).ShortCut);160       end;161   end;162 163   /// format hint string164   if sTitle <> ‘‘  then165   begin166     if sShortCut = ‘‘ then167       sHint := sTitle168     else169       sHint := Format(%s(%s), [sTitle, sShortCut]);170 171     if (sRemark <> ‘‘) and not SameText(sRemark, sTitle) then172       sHint := Format(%s#13#10  %s, [sHint, sRemark]);173   end174   else175     sHint := sRemark;176 177   Message.HintInfo.HintStr := sHint;178   if sHint = ‘‘ then179     Message.Result := 1;180 end;181 182 constructor TmtCustomToolbar.Create(AOwner: TComponent);183 begin184   inherited;185   inherited Height := 20;186   inherited Width := 20 * 3;187   FHotIndex := -1;188   FPressedIndex := -1;189   FAutoWidth := true;190 end;191 192 destructor TmtCustomToolbar.Destroy;193 begin194   if HandleAllocated  then195     KillTimer(Handle, TIMID_FADE);196 197   inherited;198 end;199 200 procedure TmtCustomToolbar.DoOnActionChange(Sender: TObject);201 var202   Idx: Integer;203   bResize: boolean;204 begin205   if Sender is TBasicAction then206   begin207     Idx := IndexOf(TBasicAction(Sender));208     if (Idx >= 0) and (Idx < FCount) then209     begin210       ///211       /// 外部状态改变响应212       ///213       if FItems[Idx].Action.InheritsFrom(TContainedAction) then214       begin215         FItems[Idx].Enabled := TContainedAction(Sender).Enabled;216         bResize := FItems[Idx].Visible <> TContainedAction(Sender).Visible;217         if bResize then218         begin219           FItems[Idx].Visible := not FItems[Idx].Visible;220           UpdateSize;221         end222         else if FItems[Idx].Visible then223           Invalidate;224       end;225 226       /// 执行原有事件227       if Assigned(FItems[Idx].SaveEvent) then228         FItems[Idx].SaveEvent(Sender);229     end;230   end;231 end;232 233 procedure TmtCustomToolbar.ExecAction(Index: Integer);234 begin235   ///236   /// 执行命令237   ///238   if (Index >= 0) and (Index < FCount) then239     FItems[Index].Action.Execute;240 end;241 242 function TmtCustomToolbar.GetActualWidth: Integer;243 var244   R: TRect;245 begin246   R := CalcSize;247   Result := r.Width;248 end;249 250 function TmtCustomToolbar.HitTest(x, y: Integer): Integer;251 var252   I: Integer;253   Idx: Integer;254   iOffx: Integer;255 begin256   Idx := -1;257   iOffx := 0;258   if PtInRect(ClientRect, Point(x, y)) then259     for I := 0 to FCount - 1 do260     begin261       if not FItems[I].Visible then262         Continue;263 264       iOffx := iOffx + FItems[I].Width;265       if (iOffx > x) then266       begin267         Idx := I;268         Break;269       end;270     end;271 272   // 去除无效的按钮273   if (Idx >= 0) and (not FItems[Idx].Visible or not FItems[Idx].Enabled) then274     Idx := -1;275 276   Result := Idx;277 end;278 279 function TmtCustomToolbar.IndexOf(Action: TBasicAction): Integer;280 var281   I: Integer;282 begin283   Result := -1;284   for I := 0 to FCount - 1 do285     if FItems[I].Action = Action then286     begin287       Result := I;288       Break;289     end;290 end;291 292 function TmtCustomToolbar.LoadActionIcon(Idx: Integer; AImg: TBitmap): boolean;293 294   function LoadIcon(AImgs: TCustomImageList; AIndex: Integer): boolean;295   begin296     Result := False;297     if Assigned(AImgs) and (AIndex >= 0) and (AIndex < AImgs.Count) then298       Result := AImgs.GetBitmap(AIndex, AImg);299   end;300 301 var302   bHasImg: boolean;303   ImgIdx: Integer;304 305 begin306   /// 获取Action的图标307   ImgIdx := -1;308   AImg.Canvas.Brush.Color := clBlack;309   AImg.Canvas.FillRect(Rect(0, 0, AImg.Width, AImg.Height));310   bHasImg := LoadIcon(FImages, FItems[Idx].ImageIndex);311   if not bHasImg and (FItems[Idx].Action is TCustomAction) then312   begin313     ImgIdx := TCustomAction(FItems[Idx].Action).ImageIndex;314     bHasImg := LoadIcon(TCustomAction(FItems[Idx].Action).Images, ImgIdx);315   end;316   if not bHasImg then317     bHasImg := LoadIcon(FImages, ImgIdx);318 319   Result := bHasImg;320 end;321 322 procedure TmtCustomToolbar.MouseDown(Button: TMouseButton; Shift: TShiftState; x, y: Integer);323 begin324   if mbLeft = Button then325   begin326     FPressedIndex := HitTest(x, y);327     Invalidate;328   end;329 end;330 331 procedure TmtCustomToolbar.MouseMove(Shift: TShiftState; x, y: Integer);332 begin333 end;334 335 procedure TmtCustomToolbar.MouseUp(Button: TMouseButton; Shift: TShiftState; x, y: Integer);336 var337   iPressed: Integer;338 begin339   if FPressedIndex >= 0 then340   begin341     iPressed := HitTest(x, y);342     if iPressed = FPressedIndex then343       ExecAction(iPressed);344   end;345   FPressedIndex := -1;346   Invalidate;347 end;348 349 procedure TmtCustomToolbar.PaintBackground(DC: HDC);350 var351   hB: HBRUSH;352   R: TRect;353 begin354   R := GetClientRect;355   hB := CreateSolidBrush(ColorToRGB(Color));356   FillRect(DC, R, hB);357   DeleteObject(hB);358 end;359 360 procedure TmtCustomToolbar.PaintWindow(DC: HDC);361   function GetActionState(Idx: Integer): TSkinIndicator;362   begin363     Result := siInactive;364     if (Idx = FPressedIndex) then365       Result := siPressed366     else if (Idx = FHotIndex) and (FPressedIndex = -1) then367       Result := siHover;368   end;369 370 var371   cIcon: TBitmap;372   R: TRect;373   I: Integer;374   iOpacity: byte;375 begin376   R := Rect(0, 0, 0, ClientHeight);377 378   /// 绘制Button379   cIcon := TBitmap.Create;380   cIcon.PixelFormat := pf32bit;381   cIcon.alphaFormat := afIgnored;382   for I := 0 to FCount - 1 do383   begin384     if not FItems[i].Visible then385       Continue;386 387     R.Right := R.Left + FItems[I].Width;388     if FItems[I].Enabled then389       mtUISkin.DrawButtonState(DC, GetActionState(I), R, FItems[I].Fade);390     if LoadActionIcon(I, cIcon) then391     begin392       iOpacity := 255;393       /// 处理不可用状态,图标颜色变暗。394       /// 简易处理,增加绘制透明度。395       if not FItems[I].Enabled then396         iOpacity := 100;397 398       mtUISkin.DrawIcon(DC, R, cIcon, iOpacity);399     end;400     OffsetRect(R, R.Right - R.Left, 0);401   end;402   cIcon.free;403 end;404 405 procedure TmtCustomToolbar.SetAutoWidth(const Value: Boolean);406 begin407   if FAutoWidth <> Value then408   begin409     FAutoWidth := Value;410     UpdateSize;411   end;412 end;413 414 procedure TmtCustomToolbar.SetHotIndex(const Value: Integer);415 begin416   if FHotIndex <> Value then417   begin418     FHotIndex := Value;419     Invalidate;420     421     if not(csDestroying in ComponentState) and HandleAllocated then422       SetTimer(Handle, TIMID_FADE, 90, nil);423   end;424 end;425 426 procedure TmtCustomToolbar.UpdateFade;427 428   function GetShowAlpha(v: byte): byte; inline;429   begin430     if v = 0 then           Result := 180431     else if v <= 180 then   Result := 220432     else                    Result := 255;433   end;434 435   function GetFadeAlpha(v: byte): byte; inline;436   begin437     if v >= 255 then        Result := 230438     else if v >= 230 then   Result := 180439     else if v >= 180 then   Result := 100440     else if v >= 100 then   Result := 50441     else if v >= 50 then    Result := 10442     else                    Result := 0;443   end;444 445 var446   I: Integer;447   bHas: boolean;448 begin449   bHas := False;450   for I := 0 to FCount - 1 do451     if FItems[I].Visible and FItems[I].Enabled then452     begin453       if FHotIndex = I then454         FItems[I].Fade := GetShowAlpha(FItems[I].Fade)455       else if FItems[I].Fade > 0 then456         FItems[I].Fade := GetFadeAlpha(FItems[I].Fade);457       bHas := bHas or (FItems[I].Fade > 0);458     end;459   Invalidate;460   if not bHas and HandleAllocated then461     KillTimer(Handle, TIMID_FADE);462 end;463 464 procedure TmtCustomToolbar.UpdateSize;465 var466   R: TRect;467 begin468   if FAutoWidth then469   begin470     R := CalcSize;471     SetBounds(Left, Top, R.Width, Height);472   end473   else474     Invalidate;475 end;476 477 procedure TmtCustomToolbar.WMEraseBkgnd(var message: TWMEraseBkgnd);478 begin479   Message.Result := 1;480 end;481 482 procedure TmtCustomToolbar.WMMouseLeave(var message: TMessage);483 begin484   HotIndex := -1;485 end;486 487 procedure TmtCustomToolbar.WMMouseMove(var message: TWMMouseMove);488 var489   iSave: Integer;490 begin491   iSave := FHotIndex;492   HotIndex := HitTest(message.XPos, message.YPos);493   if (iSave <> FHotIndex) and (FHotIndex >= 0) and  (FPressedIndex = -1) then494     Application.ActivateHint(message.Pos);495 end;496 497 procedure TmtCustomToolbar.WMPaint(var message: TWMPaint);498 var499   DC, hPaintDC: HDC;500   cBuffer: TBitmap;501   PS: TPaintStruct;502   R: TRect;503   w, h: Integer;504 begin505   ///506   /// 绘制客户区域507   ///508   R := GetClientRect;509   w := R.Width;510   h := R.Height;511 512   DC := Message.DC;513   hPaintDC := DC;514   if DC = 0 then515     hPaintDC := BeginPaint(Handle, PS);516 517   cBuffer := TBitmap.Create;518   try519     cBuffer.SetSize(w, h);520     PaintBackground(cBuffer.Canvas.Handle);521     PaintWindow(cBuffer.Canvas.Handle);522     BitBlt(hPaintDC, 0, 0, w, h, cBuffer.Canvas.Handle, 0, 0, SRCCOPY);523   finally524     cBuffer.free;525   end;526 527   if DC = 0 then528     EndPaint(Handle, PS);529 end;530 531 procedure TmtCustomToolbar.WMTimer(var message: TWMTimer);532 begin533   if message.TimerID = TIMID_FADE then534     UpdateFade;535 end;536 537 end.
unit uMTToolbars;

 

完整工程

    https://github.com/cmacro/simple/tree/master/AnimateToolbar

 

开发环境:

  Delphi XE3

  Win7

 

窗体皮肤实现 - 实现简单Toolbar(六)