前面做的工作就是想在标题区域增加快速工具条。前续的基础工作完成,想要在标题区域增加特殊区域都非常方便。只要在绘制时控制自定义区域需要占用标题区域多少空间,然后直接在所占位置绘制。做这个事情前,稍微把代码规整了下。所以界面皮肤处理放到一个单元中。
主要处理步骤
1、划出一个新区域(整个工具条作为一个区域)
2、处理区域检测(HitTest)
3、如果是新区域,把相应消息传给这个区域处理。
4、响应鼠标点击,执行Action
通过上述步骤就能扩展出所想要的标题区快速工具条的。
标题按钮区域是作为一个整体处理,这样比较容易控制和扩展。只要当检测区域是标题工具区时,消息交由工具条实现。
1 HTCUSTOM = 100; //HTHELP + 1; /// 自定义区域ID 2 HTCAPTIONTOOLBAR = HTCUSTOM + 1; /// 标题工具区域ID 3 4 5 /// 6 /// 检测区域时增加自定义区域的检测 7 function TskForm.HitTest(P: TPoint):integer; 8 begin 9 ... ... (代码略) 10 /// 11 /// 标题工具区域 12 /// 需要前面扣除窗体图标区域 13 if (Result = HTNOWHERE) and (FToolbar.Visible) then 14 begin 15 r.Left := rCaptionRect.Left + 2 + GetSystemMetrics(SM_CXSMICON) + SPALCE_CAPTIONAREA; 16 R.Top := rCaptionRect.Top + (rCaptionRect.Height - FToolbar.Border.Height) div 2; 17 R.Right := R.Left + FToolbar.Border.Width; 18 R.Bottom := R.Top + FToolbar.Border.Height; 19 20 if FToolbar.FOffset.X = -1 then 21 FToolbar.FOffset := r.TopLeft; 22 23 if PtInRect(r, p) then 24 Result := HTCAPTIONTOOLBAR; 25 end; 26 end; 27 end;
这样做的好处就是,简化自定义皮肤TskForm内部的处理。模块化比较清晰,简化实现逻辑。
标题工具条实现过程
1、准备绘制的区域
2、确定绘制区域大小
3、实现绘制
4、响应消息
确定绘制区域大小
考虑到按钮是动态增加上去,需要根据实际标题区域的按钮数量来确定实际大小。所有的Action存放在记录中,这样每次只要循环Action数组就可以获得相应宽度。
区域的宽度包括:两条分割线 + 下拉配置菜单 + Button * Count
1 /// 用于保存Action的信息 2 TcpToolButton = record 3 Action: TBasicAction; 4 Enabled: boolean; 5 Visible: Boolean; 6 ImageIndex: Integer; // 考虑到标题功能图标和实际工具栏功能使用不同图标情况,分开图标索引 7 Width: Word; // 实际占用宽度,考虑后续加不同的按钮样式使用 8 Fade: Word; // 褪色量 0 - 255 9 SaveEvent: TNotifyEvent; // 原始的Action OnChange事件 10 end; 11 12 /// 13 /// 计算实际占用尺寸 14 function TcpToolbar.CalcSize: TRect; 15 const 16 SIZE_SPLITER = 10; 17 SIZE_POPMENU = 10; 18 SIZE_BUTTON = 20; 19 var 20 w, h: Integer; 21 I: Integer; 22 begin 23 /// 24 /// 占用宽度 25 /// 如果考虑比较复杂的按钮样式和显示标题等功能,那么需要计算每个按钮实际占用宽度才能获得。 26 27 w := SIZE_SPLITER * 2 + SIZE_POPMENU; 28 for I := 0 to FCount - 1 do 29 w := w + FItems[i].Width; 30 h := SIZE_BUTTON; 31 Result := Rect(0, 0, w, h); 32 end;
占用区域大小的问题解决,绘制问题主要考虑在什么位置绘制,怎么获得Action的图标和实际的状态。
以正常情况考虑绘制区域:从原点(0,0)开始绘制,这样比较符合一般的习惯。只要在绘制前对画布重新设置原点,就能实现。
1 /// 2 /// 绘制工具条 3 if FToolbar.Visible and (rCaptionRect.Right > rCaptionRect.Left) then 4 begin 5 /// 防止出现绘制出多余区域,当区域不够时需要进行剪切。 6 /// 如: 窗体缩小时 7 CurrentIdx := 0; 8 bClipRegion := rCaptionRect.Width < FToolbar.Border.Width; 9 if bClipRegion then 10 begin 11 ClipRegion := CreateRectRgnIndirect(rCaptionRect); 12 CurrentIdx := SelectClipRgn(DC, ClipRegion); 13 DeleteObject(ClipRegion); 14 end; 15 16 /// 设置原点偏移量 17 iLeftOff := rCaptionRect.Left; 18 iTopOff := rCaptionRect.Top + (rCaptionRect.Height - FToolbar.Border.Height) div 2; 19 MoveWindowOrg(DC, iLeftOff, iTopOff); 20 FToolbar.Paint(DC); 21 MoveWindowOrg(DC, -iLeftOff, -iTopOff); 22 23 if bClipRegion then 24 SelectClipRgn(DC, CurrentIdx); 25 26 /// 扣除工具条区域 27 rCaptionRect.Left := rCaptionRect.Left + FToolbar.Border.Width + SPALCE_CAPTIONAREA; 28 end;
获取Action的图标
直接从ImageList中获取。考虑标题区域是纯色,能让标题工具条显的更美观(个人审美),能让工具条支持2中不同的图标。画了一组纯白的图标用于标题区域的显示。
1 // 创建Bmp,支持透明 2 // cIcon := TBitmap.Create; 3 // cIcon.PixelFormat := pf32bit; // 支持透明 4 // cIcon.alphaFormat := afIgnored; 5 6 function TcpToolbar.LoadActionIcon(Idx: Integer; AImg: TBitmap):Boolean; 7 var 8 bHasImg: Boolean; 9 begin 10 /// 获取Action的图标 11 AImg.Canvas.Brush.Color := clBlack; 12 AImg.Canvas.FillRect(Rect(0,0, AImg.Width, AImg.Height)); 13 bHasImg := False; 14 if (FImages <> nil) and (FItems[Idx].ImageIndex >= 0) then 15 bHasImg := FImages.GetBitmap(FItems[Idx].ImageIndex, AImg); 16 if not bHasImg and (FItems[Idx].Action is TCustomAction) then 17 with TCustomAction(FItems[Idx].Action) do 18 if (Images <> nil) and (ImageIndex >= 0) then 19 bHasImg := Images.GetBitmap(ImageIndex, AImg); 20 Result := bHasImg; 21 end;