窗体皮肤实现 - 在标题栏上增加快速工具条(四)
前面做的工作就是想在标题区域增加快速工具条。前续的基础工作完成,想要在标题区域增加特殊区域都非常方便。只要在绘制时控制自定义区域需要占用标题区域多少空间,然后直接在所占位置绘制。做这个事情前,稍微把代码规整了下。所以界面皮肤处理放到一个单元中。
主要处理步骤
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;
绘制工具条
有了尺寸和Action就可以直接进行绘制。鼠标滑过和按下状态的处理方法和系统按钮区域的方法一致。
1 procedure TcpToolbar.Paint(DC: HDC); 2 3 function GetActionState(Idx: Integer): TSkinIndicator; 4 begin 5 Result := siInactive; 6 if (Idx = FPressedIndex) and (FHotIndex = FPressedIndex) then 7 Result := siPressed 8 else if Idx = FHotIndex then 9 Result := siHover; 10 end; 11 12 var 13 cIcon: TBitmap; 14 r: TRect; 15 I: Integer; 16 iOpacity: byte; 17 begin 18 /// 19 /// 工具条绘制 20 /// 21 22 /// 分割线 23 r := Border; 24 r.Right := r.Left + RES_CAPTIONTOOLBAR.w; 25 SkinData.DrawElement(DC, steSplitter, r); 26 OffsetRect(r, r.Right - r.Left, 0); 27 28 /// 绘制Button 29 cIcon := TBitmap.Create; 30 cIcon.PixelFormat := pf32bit; 31 cIcon.alphaFormat := afIgnored; 32 for I := 0 to FCount - 1 do 33 begin 34 r.Right := r.Left + FItems[i].Width; 35 if FItems[I].Enabled then 36 SkinData.DrawButtonBackground(DC, GetActionState(i), r, FItems[i].Fade); 37 if LoadActionIcon(i, cIcon) then 38 begin 39 iOpacity := 255; 40 /// 处理不可用状态,图标颜色变暗。 41 /// 简易处理,增加绘制透明度。 42 if not FItems[i].Enabled then 43 iOpacity := 100; 44 45 SkinData.DrawIcon(DC, r, cIcon, iOpacity); 46 end; 47 OffsetRect(r, r.Right - r.Left, 0); 48 end; 49 cIcon.free; 50 51 /// 分割条 52 r.Right := r.Left + RES_CAPTIONTOOLBAR.w; 53 SkinData.DrawElement(DC, steSplitter, r); 54 OffsetRect(r, r.Right - r.Left, 0); 55 56 /// 绘制下拉菜单按钮 57 r.Right := r.Left + RES_CAPTIONTOOLBAR.w; 58 SkinData.DrawElement(DC, stePopdown, r); 59 end;
相应鼠标事件
对于一个工具条,需要相应的事件有三个鼠标滑过、按下和弹起。滑过是出现Hot效果,按下时处理Button被压下的效果,弹起时执行实际的Action事件。简单处理处理的这三种效果,如果考虑动画效果。那么需要创建一个时钟,设置个背景褪色量(其实是个Alpha透明通道值),然后根据褪色量在时钟消息中进行绘制。时钟最好设置在主皮肤类(TskForm)上,不必为每个区域创建一个句柄,这样可以减少系统资源(句柄)的占用。
统一消息入口,如果处理了此消息就返回True。这样可以让外部知道是否此消息被处理,以便外部作进一步的响应处理。
1 function TFormCaptionPlugin.HandleMessage(var Message: TMessage): Boolean; 2 begin 3 Result := True; 4 5 case Message.Msg of 6 WM_NCMOUSEMOVE : MouseMove(ScreenToClient(TWMNCMouseMove(Message).XCursor, TWMNCMouseMove(Message).YCursor)); 7 WM_NCLBUTTONDOWN : MouseDown(mbLeft, ScreenToClient(TWMNCLButtonDown(Message).XCursor, TWMNCLButtonDown(Message).YCursor)); 8 WM_NCHITTEST : HitWindowTest(ScreenToClient(TWMNCHitTest(Message).XPos, TWMNCHitTest(Message).YPos)); 9 WM_NCLBUTTONUP : MouseUp(mbLeft, ScreenToClient(TWMNCLButtonUp(Message).XCursor, TWMNCLButtonUp(Message).YCursor)); 10 11 else 12 Result := False; 13 end; 14 end;
这里一个比较关键的是,鼠标在这个区域内的实际位置。一般窗体都会有Handle,所以能直接通过API转换鼠标位置。
区域需要依靠主窗口的位置才能获得。每次窗口在处理尺寸时,区域的偏移位置是可以获得的。像标题工具条这种左靠齐,其实这个偏移位置算好后就肯定是不会变的。
1 // 偏移量 2 // = 有效标题区域 - 系统图标位置 - 区域间隙 3 r.Left := rCaptionRect.Left + 2 + GetSystemMetrics(SM_CXSMICON) + SPALCE_CAPTIONAREA; 4 r.Top := rCaptionRect.Top + (rCaptionRect.Height - FToolbar.Border.Height) div 2;
1 function TFormCaptionPlugin.ScreenToClient(x, y: Integer): TPoint; 2 var 3 P: TPoint; 4 begin 5 /// 调整位置 6 /// 以 FOffset 为中心位置 7 P := FOwner.NormalizePoint(Point(x, Y)); 8 p.X := p.X - FOffset.X; 9 p.Y := p.y - FOffset.Y; 10 11 Result := p; 12 end;
上面鼠标的消息最终通过HitTest获取,实际鼠标所在按钮位置。这个处理方法和外部的TskForm处理方法一致,检测位置设置状态参数然后再重绘。
如:鼠标滑过时的消息处理。
1 procedure TcpToolbar.MouseMove(p: TPoint); 2 var 3 iIdx: Integer; 4 begin 5 /// 鼠标滑入时设置HotIndex值 6 iIdx := HitTest(p); 7 if iIdx <> FHotIndex then 8 begin 9 FHotIndex := iIdx; 10 Invalidate; 11 end; 12 end;
1 function TcpToolbar.HitTest(P: TPoint): integer; 2 var 3 iOff: Integer; 4 iIdx: integer; 5 I: Integer; 6 begin 7 /// 8 /// 检测鼠标位置 9 /// 鼠标位置的 FCount位 为工具条系统菜单位置。 10 iIdx := -1; 11 iOff := RES_CAPTIONTOOLBAR.w; 12 if p.x > iOff then 13 begin 14 for I := 0 to FCount - 1 do 15 begin 16 if p.X < iOff then 17 Break; 18 19 iIdx := i; 20 inc(iOff, FItems[i].Width); 21 end; 22 23 if p.x > iOff then 24 begin 25 iIdx := -1; 26 inc(iOff, RES_CAPTIONTOOLBAR.w); 27 if p.x > iOff then 28 iIdx := FCount; // FCount 为系统菜单按钮 29 end; 30 end; 31 32 Result := iIdx; 33 end;
还有些细节方面的处理,如鼠标离开这个区域时的处理。这样整个工具区的基本处理完成,整个工具条区域的处理还是相对比较简单。
Action状态处理
Action处理主要是考虑,当外部改变Action状态。如:无效,不可见的一些事件处理。标准的处理方法是在关联Action是创建一个ActionLink实现联动,由于TskForm没有从TControl继承,没法使用此方法进行处理。在TBasicAction改变状态时会触发一个OnChange的保护(protected)事件,可以直接把事件挂接上去,就能简单处理状态。
保护方法的访问:创建一个访问类,进行引用。
1 type 2 TacWinControl = class(TWinControl); 3 TacAction = class(TBasicAction);
1 ZeroMemory(@FItems[FCount], SizeOf(TcpToolButton)); 2 FItems[FCount].Action := Action; 3 FItems[FCount].Enabled := true; // <--- 这里应该获取Actoin的当前状态,这里简略处理。 4 FItems[FCount].Visible := True; // <--- 同上,注:现有代码中并未处理此状态 5 FItems[FCount].ImageIndex := AImageIndex; 6 FItems[FCount].Width := 20; 7 FItems[FCount].Fade := 255; 8 FItems[FCount].SaveEvent := TacAction(Action).OnChange; // 保存原事件 9 TacAction(Action).OnChange := DoOnActionChange; // 挂接事件
注意:不要把原事件丢了,需要保存。防止外部有挂接的情况下出现外部无法。
根据状态的不同,直接修改记录的Enabled 和 Visible 这两个状态。绘制时可以直接使用。
1 procedure TcpToolbar.DoOnActionChange(Sender: TObject); 2 var 3 idx: Integer; 4 bResize: Boolean; 5 begin 6 if Sender is TBasicAction then 7 begin 8 idx := IndexOf(TBasicAction(Sender)); 9 if (idx >= 0) and (idx < FCount) then 10 begin 11 /// 12 /// 外部状态改变响应 13 /// 14 if FItems[idx].Action.InheritsFrom(TContainedAction) then 15 begin 16 FItems[idx].Enabled := TContainedAction(Sender).Enabled; 17 bResize := FItems[idx].Visible <> TContainedAction(Sender).Visible; 18 if bResize then 19 begin 20 FItems[idx].Visible := not FItems[idx].Visible; 21 Update 22 end 23 else 24 Invalidate; 25 end; 26 27 /// 执行原有事件 28 if Assigned(FItems[idx].SaveEvent) then 29 FItems[idx].SaveEvent(Sender); 30 end; 31 end; 32 end;
在绘制时就可以通过记录中的状态和鼠标位置状态进行判断,来绘制出所需要的效果
1 ... ... 2 // 如果按钮有效,那么进行按钮底色绘制。 3 if FItems[I].Enabled then 4 SkinData.DrawButtonBackground(DC, GetActionState(i), r, FItems[i].Fade); 5 if LoadActionIcon(i, cIcon) then 6 begin 7 /// 处理不可用状态,图标颜色变暗。 8 /// 简易处理,增加绘制透明度。 9 iOpacity := 255; 10 if not FItems[i].Enabled then 11 iOpacity := 100; 12 13 SkinData.DrawIcon(DC, r, cIcon, iOpacity); 14 end; 15 ... ... 16 17 // 获取Action底色的显示状态 18 // 按下状态、滑过状态、默认状态 19 function GetActionState(Idx: Integer): TSkinIndicator; 20 begin 21 Result := siInactive; 22 if (Idx = FPressedIndex) and (FHotIndex = FPressedIndex) then 23 Result := siPressed 24 else if Idx = FHotIndex then 25 Result := siHover; 26 end; 27
在窗体上加入测试Action
1 procedure TForm11.FormCreate(Sender: TObject); 2 begin 3 FTest.Toolbar.Images := ImageList2; 4 FTest.Toolbar.Add(Action1, 0); 5 FTest.Toolbar.Add(Action2, 1); 6 FTest.Toolbar.Add(Action3, 2); 7 end;
完成~~
最终效果,就是上面的GIF效果。想做的更好,那么就需要在细节上考虑。细节是最花时间的地方。
单元代码
1 unit uFormSkins; 2 3 interface 4 5 uses 6 Classes, windows, Controls, Graphics, Forms, messages, pngimage, Types, ImgList, Actions, ActnList; 7 8 const 9 WM_NCUAHDRAWCAPTION = $00AE; 10 11 CKM_ADD = WM_USER + 1; // 增加标题区域位置 12 13 HTCUSTOM = 100; //HTHELP + 1; /// 自定义区域ID 14 HTCAPTIONTOOLBAR = HTCUSTOM + 1; /// 标题工具区域 15 16 type 17 TskForm = class; 18 19 TFormButtonKind = (fbkMin, fbkMax, fbkRestore, fbkClose, fbkHelp); 20 TSkinIndicator = (siInactive, siHover, siPressed, siSelected, siHoverSelected); 21 22 TFormCaptionPlugin = class 23 private 24 FOffset: TPoint; // 实际标题区域所在的偏移位置 25 FBorder: TRect; 26 FOwner: TskForm; 27 FVisible: Boolean; 28 29 protected 30 procedure Paint(DC: HDC); virtual; abstract; 31 function CalcSize: TRect; virtual; abstract; 32 function ScreenToClient(x, y: Integer): TPoint; 33 34 function HandleMessage(var Message: TMessage): Boolean; virtual; 35 36 procedure HitWindowTest(P: TPoint); virtual; 37 procedure MouseMove(p: TPoint); virtual; 38 procedure MouseDown(Button: TMouseButton; p: TPoint); virtual; 39 procedure MouseUp(Button: TMouseButton; p: TPoint); virtual; 40 procedure MouseLeave; virtual; 41 42 procedure Invalidate; 43 procedure Update; 44 public 45 constructor Create(AOwner: TskForm); virtual; 46 47 property Border: TRect read FBorder; 48 property Visible: Boolean read FVisible; 49 end; 50 51 TcpToolButton = record 52 Action: TBasicAction; 53 Enabled: boolean; 54 Visible: Boolean; 55 ImageIndex: Integer; // 考虑到标题功能图标和实际工具栏功能使用不同图标情况,分开图标索引 56 Width: Word; // 实际占用宽度,考虑后续加不同的按钮样式使用 57 Fade: Word; // 褪色量 0 - 255 58 SaveEvent: TNotifyEvent; // 原始的Action OnChange事件 59 end; 60 61 TcpToolbar = class(TFormCaptionPlugin) 62 private 63 FItems: array of TcpToolButton; 64 FCount: Integer; 65 FHotIndex: Integer; 66 67 // 考虑标题栏比较特殊,背景使用的是纯属情况。图标需要做的更符合纯属需求。 68 FImages: TCustomImageList; 69 FPressedIndex: Integer; 70 71 procedure ExecAction(Index: Integer); 72 procedure PopConfigMenu; 73 function HitTest(P: TPoint): integer; 74 function LoadActionIcon(Idx: Integer; AImg: TBitmap):Boolean; 75 procedure SetImages(const Value: TCustomImageList); 76 procedure DoOnActionChange(Sender: TObject); 77 protected 78 // 绘制按钮样式 79 procedure Paint(DC: HDC); override; 80 // 计算实际占用尺寸 81 function CalcSize: TRect; override; 82 83 procedure HitWindowTest(P: TPoint); override; 84 procedure MouseMove(p: TPoint); override; 85 procedure MouseDown(Button: TMouseButton; p: TPoint); override; 86 procedure MouseUp(Button: TMouseButton; p: TPoint); override; 87 procedure MouseLeave; override; 88 89 public 90 constructor Create(AOwner: TskForm); override; 91 92 procedure Add(Action: TBasicAction; AImageIndex: Integer = -1); 93 procedure Delete(Index: Integer); 94 function IndexOf(Action: TBasicAction): Integer; 95 96 property Images: TCustomImageList read FImages write SetImages; 97 end; 98 99 100 TskForm = class 101 private 102 FCallDefaultProc: Boolean; 103 FChangeSizeCalled: Boolean; 104 FControl: TWinControl; 105 FHandled: Boolean; 106 107 FRegion: HRGN; 108 FLeft: integer; 109 FTop: integer; 110 FWidth: integer; 111 FHeight: integer; 112 113 /// 窗体图标 114 FIcon: TIcon; 115 FIconHandle: HICON; 116 117 // 鼠标位置状态,只处理监控的位置,其他有交由系统处理 118 FPressedHit: Integer; // 实际按下的位置 119 FHotHit: integer; // 记录上次的测试位置 120 121 FToolbar: TcpToolbar; 122 123 function GetHandle: HWND; inline; 124 function GetForm: TCustomForm; inline; 125 function GetFrameSize: TRect; 126 function GetCaptionRect(AMaxed: Boolean): TRect; inline; 127 function GetCaption: string; 128 function GetIcon: TIcon; 129 function GetIconFast: TIcon; 130 131 procedure ChangeSize; 132 function NormalizePoint(P: TPoint): TPoint; 133 function HitTest(P: TPoint):integer; 134 procedure Maximize; 135 procedure Minimize; 136 137 // 第一组 实现绘制基础 138 procedure WMNCPaint(var message: TWMNCPaint); message WM_NCPAINT; 139 procedure WMNCActivate(var message: TMessage); message WM_NCACTIVATE; 140 procedure WMNCLButtonDown(var message: TWMNCLButtonDown); message WM_NCLBUTTONDOWN; 141 procedure WMNCUAHDrawCaption(var message: TMessage); message WM_NCUAHDRAWCAPTION; 142 143 // 第二组 控制窗体样式 144 procedure WMNCCalcSize(var message: TWMNCCalcSize); message WM_NCCALCSIZE; 145 procedure WMWindowPosChanging(var message: TWMWindowPosChanging); message WM_WINDOWPOSCHANGING; 146 147 // 第三组 绘制背景和内部控件 148 procedure WMEraseBkgnd(var message: TWMEraseBkgnd); message WM_ERASEBKGND; 149 procedure WMPaint(var message: TWMPaint); message WM_PAINT; 150 151 // 第四组 控制按钮状态 152 procedure WMNCHitTest(var Message: TWMNCHitTest); message WM_NCHITTEST; 153 procedure WMNCLButtonUp(var Message: TWMNCLButtonUp); message WM_NCLBUTTONUP; 154 procedure WMNCMouseMove(var Message: TWMNCMouseMove); message WM_NCMOUSEMOVE; 155 procedure WMSetText(var Message: TMessage); message WM_SETTEXT; 156 157 158 procedure WndProc(var message: TMessage); 159 160 procedure CallDefaultProc(var message: TMessage); 161 protected 162 property Handle: HWND read GetHandle; 163 procedure InvalidateNC; 164 procedure PaintNC(DC: HDC); 165 procedure PaintBackground(DC: HDC); 166 procedure Paint(DC: HDC); 167 168 public 169 constructor Create(AOwner: TWinControl); 170 destructor Destroy; override; 171 172 function DoHandleMessage(var message: TMessage): Boolean; 173 174 property Toolbar: TcpToolbar read FToolbar; 175 property Handled: Boolean read FHandled write FHandled; 176 property Control: TWinControl read FControl; 177 property Form: TCustomForm read GetForm; 178 end; 179 180 181 implementation 182 183 const 184 SPALCE_CAPTIONAREA = 3; 185 186 {$R MySkin.RES} 187 188 type 189 TacWinControl = class(TWinControl); 190 TacAction = class(TBasicAction); 191 192 Res = class 193 class procedure LoadGraphic(const AName: string; AGraphic: TGraphic); 194 class procedure LoadBitmap(const AName: string; AGraphic: TBitmap); 195 end; 196 197 TResArea = record 198 x: Integer; 199 y: Integer; 200 w: Integer; 201 h: Integer; 202 end; 203 204 TSkinToolbarElement = (steSplitter, stePopdown); 205 206 SkinData = class 207 private 208 class var 209 FData: TBitmap; 210 211 public 212 class constructor Create; 213 class destructor Destroy; 214 215 class procedure DrawButtonBackground(DC: HDC; AState: TSkinIndicator; const R: TRect; const Opacity: Byte = 255); static; 216 class procedure DrawButton(DC: HDC; AKind: TFormButtonKind; AState: TSkinIndicator; const R: TRect); static; 217 class procedure DrawElement(DC: HDC; AItem: TSkinToolbarElement; const R: TRect); 218 class procedure DrawIcon(DC: HDC; R: TRect; ASrc: TBitmap; const Opacity: Byte = 255); 219 end; 220 221 const 222 SKINCOLOR_BAKCGROUND = $00BF7B18; // 背景色 223 SKINCOLOR_BTNHOT = $00F2D5C2; // Hot 激活状态 224 SKINCOLOR_BTNPRESSED = $00E3BDA3; // 按下状态 225 SIZE_SYSBTN: TSize = (cx: 29; cy: 18); 226 SIZE_FRAME: TRect = (Left: 4; Top: 29; Right: 5; Bottom: 5); // 窗体边框的尺寸 227 SPACE_AREA = 3; // 功能区域之间间隔 228 SIZE_RESICON = 16; // 资源中图标默认尺寸 229 SIZE_HEIGHTTOOLBAR = 16; 230 231 RES_CAPTIONTOOLBAR: TResArea = (x: 0; y: 16; w: 9; h: 16); 232 233 234 function BuildRect(L, T, W, H: Integer): TRect; inline; 235 begin 236 Result := Rect(L, T, L + W, T + H); 237 end; 238 239 procedure DrawTransparentBitmap(Source: TBitmap; sx, sy: Integer; Destination: HDC; 240 const dX, dY: Integer; w, h: Integer; const Opacity: Byte = 255); overload; 241 var 242 BlendFunc: TBlendFunction; 243 begin 244 BlendFunc.BlendOp := AC_SRC_OVER; 245 BlendFunc.BlendFlags := 0; 246 BlendFunc.SourceConstantAlpha := Opacity; 247 248 if Source.PixelFormat = pf32bit then 249 BlendFunc.AlphaFormat := AC_SRC_ALPHA 250 else 251 BlendFunc.AlphaFormat := 0; 252 253 AlphaBlend(Destination, dX, dY, w, h, Source.Canvas.Handle, sx, sy, w, h, BlendFunc); 254 end; 255 256 257 procedure TskForm.CallDefaultProc(var message: TMessage); 258 begin 259 if FCallDefaultProc then 260 FControl.WindowProc(message) 261 else 262 begin 263 FCallDefaultProc := True; 264 FControl.WindowProc(message); 265 FCallDefaultProc := False; 266 end; 267 end; 268 269 procedure TskForm.ChangeSize; 270 var 271 hTmp: HRGN; 272 begin 273 /// 设置窗体外框样式 274 FChangeSizeCalled := True; 275 try 276 hTmp := FRegion; 277 try 278 /// 创建矩形外框,3的倒角 279 FRegion := CreateRoundRectRgn(0, 0, FWidth, FHeight, 3, 3); 280 SetWindowRgn(Handle, FRegion, True); 281 finally 282 if hTmp <> 0 then 283 DeleteObject(hTmp); 284 end; 285 finally 286 FChangeSizeCalled := False; 287 end; 288 end; 289 290 function TskForm.NormalizePoint(P: TPoint): TPoint; 291 var 292 rWindowPos, rClientPos: TPoint; 293 begin 294 rWindowPos := Point(FLeft, FTop); 295 rClientPos := Point(0, 0); 296 ClientToScreen(Handle, rClientPos); 297 Result := P; 298 ScreenToClient(Handle, Result); 299 Inc(Result.X, rClientPos.X - rWindowPos.X); 300 Inc(Result.Y, rClientPos.Y - rWindowPos.Y); 301 end; 302 303 function TskForm.HitTest(P: TPoint):integer; 304 var 305 bMaxed: Boolean; 306 r: TRect; 307 rCaptionRect: TRect; 308 rFrame: TRect; 309 begin 310 Result := HTNOWHERE; 311 312 /// 313 /// 检测位置 314 /// 315 rFrame := GetFrameSize; 316 if p.Y > rFrame.Top then 317 Exit; 318 319 /// 320 /// 只关心窗体按钮区域 321 /// 322 bMaxed := IsZoomed(Handle); 323 rCaptionRect := GetCaptionRect(bMaxed); 324 if PtInRect(rCaptionRect, p) then 325 begin 326 r.Right := rCaptionRect.Right - 1; 327 r.Top := 0; 328 if bMaxed then 329 r.Top := rCaptionRect.Top; 330 r.Top := r.Top + (rFrame.Top - r.Top - SIZE_SYSBTN.cy) div 2; 331 r.Left := r.Right - SIZE_SYSBTN.cx; 332 r.Bottom := r.Top + SIZE_SYSBTN.cy; 333 334 /// 335 /// 实际绘制的按钮就三个,其他没处理 336 /// 337 if (P.Y >= r.Top) and (p.Y <= r.Bottom) and (p.X <= r.Right) then 338 begin 339 if (P.X >= r.Left) then 340 Result := HTCLOSE 341 else if p.X >= (r.Left - SIZE_SYSBTN.cx) then 342 Result := HTMAXBUTTON 343 else if p.X >= (r.Left - SIZE_SYSBTN.cx * 2) then 344 Result := HTMINBUTTON; 345 end; 346 347 /// 348 /// 标题工具区域 349 /// 需要前面扣除窗体图标区域 350 if (Result = HTNOWHERE) and (FToolbar.Visible) then 351 begin 352 r.Left := rCaptionRect.Left + 2 + GetSystemMetrics(SM_CXSMICON) + SPALCE_CAPTIONAREA; 353 R.Top := rCaptionRect.Top + (rCaptionRect.Height - FToolbar.Border.Height) div 2; 354 R.Right := R.Left + FToolbar.Border.Width; 355 R.Bottom := R.Top + FToolbar.Border.Height; 356 357 if FToolbar.FOffset.X = -1 then 358 FToolbar.FOffset := r.TopLeft; 359 360 if PtInRect(r, p) then 361 Result := HTCAPTIONTOOLBAR; 362 end; 363 end; 364 end; 365 366 constructor TskForm.Create(AOwner: TWinControl); 367 begin 368 FControl := AOwner; 369 FRegion := 0; 370 FChangeSizeCalled := False; 371 FCallDefaultProc := False; 372 373 FWidth := FControl.Width; 374 FHeight := FControl.Height; 375 FIcon := nil; 376 FIconHandle := 0; 377 378 FToolbar := TcpToolbar.Create(Self); 379 end; 380 381 destructor TskForm.Destroy; 382 begin 383 FToolbar.Free; 384 385 FIconHandle := 0; 386 if FIcon <> nil then FIcon.Free; 387 if FRegion <> 0 then DeleteObject(FRegion); 388 inherited; 389 end; 390 391 function TskForm.DoHandleMessage(var message: TMessage): Boolean; 392 begin 393 Result := False; 394 if not FCallDefaultProc then 395 begin 396 FHandled := False; 397 WndProc(message); 398 Result := Handled; 399 end; 400 end; 401 402 function TskForm.GetFrameSize: TRect; 403 begin 404 Result := SIZE_FRAME; 405 end; 406 407 function TskForm.GetCaptionRect(AMaxed: Boolean): TRect; 408 var 409 rFrame: TRect; 410 begin 411 rFrame := GetFrameSize; 412 // 最大化状态简易处理 413 if AMaxed then 414 Result := Rect(8, 8, FWidth - 9 , rFrame.Top) 415 else 416 Result := Rect(rFrame.Left, 3, FWidth - rFrame.right, rFrame.Top); 417 end; 418 419 function TskForm.GetCaption: string; 420 var 421 Buffer: array [0..255] of Char; 422 iLen: integer; 423 begin 424 if Handle <> 0 then 425 begin 426 iLen := GetWindowText(Handle, Buffer, Length(Buffer)); 427 SetString(Result, Buffer, iLen); 428 end 429 else 430 Result := ''; 431 end; 432 433 function TskForm.GetForm: TCustomForm; 434 begin 435 Result := TCustomForm(Control); 436 end; 437 438 function TskForm.GetHandle: HWND; 439 begin 440 if FControl.HandleAllocated then 441 Result := FControl.Handle 442 else 443 Result := 0; 444 end; 445 446 function TskForm.GetIcon: TIcon; 447 var 448 IconX, IconY: integer; 449 TmpHandle: THandle; 450 Info: TWndClassEx; 451 Buffer: array [0 .. 255] of Char; 452 begin 453 /// 454 /// 获取当前form的图标 455 /// 这个图标和App的图标是不同的 456 /// 457 TmpHandle := THandle(SendMessage(Handle, WM_GETICON, ICON_SMALL, 0)); 458 if TmpHandle = 0 then 459 TmpHandle := THandle(SendMessage(Handle, WM_GETICON, ICON_BIG, 0)); 460 461 if TmpHandle = 0 then 462 begin 463 { Get instance } 464 GetClassName(Handle, @Buffer, SizeOf(Buffer)); 465 FillChar(Info, SizeOf(Info), 0); 466 Info.cbSize := SizeOf(Info); 467 468 if GetClassInfoEx(GetWindowLong(Handle, GWL_HINSTANCE), @Buffer, Info) then 469 begin 470 TmpHandle := Info.hIconSm; 471 if TmpHandle = 0 then 472 TmpHandle := Info.HICON; 473 end 474 end; 475 476 if FIcon = nil then 477 FIcon := TIcon.Create; 478 479 if TmpHandle <> 0 then 480 begin 481 IconX := GetSystemMetrics(SM_CXSMICON); 482 if IconX = 0 then 483 IconX := GetSystemMetrics(SM_CXSIZE); 484 IconY := GetSystemMetrics(SM_CYSMICON); 485 if IconY = 0 then 486 IconY := GetSystemMetrics(SM_CYSIZE); 487 FIcon.Handle := CopyImage(TmpHandle, IMAGE_ICON, IconX, IconY, 0); 488 FIconHandle := TmpHandle; 489 end; 490 491 Result := FIcon; 492 end; 493 494 function TskForm.GetIconFast: TIcon; 495 begin 496 if (FIcon = nil) or (FIconHandle = 0) then 497 Result := GetIcon 498 else 499 Result := FIcon; 500 end; 501 502 procedure TskForm.InvalidateNC; 503 begin 504 if FControl.HandleAllocated then 505 SendMessage(Handle, WM_NCPAINT, 1, 0); 506 end; 507 508 procedure TskForm.Maximize; 509 begin 510 if Handle <> 0 then 511 begin 512 FPressedHit := 0; 513 FHotHit := 0; 514 if IsZoomed(Handle) then 515 SendMessage(Handle, WM_SYSCOMMAND, SC_RESTORE, 0) 516 else 517 SendMessage(Handle, WM_SYSCOMMAND, SC_MAXIMIZE, 0); 518 end; 519 end; 520 521 procedure TskForm.Minimize; 522 begin 523 if Handle <> 0 then 524 begin 525 FPressedHit := 0; 526 FHotHit := 0; 527 if IsIconic(Handle) then 528 SendMessage(Handle, WM_SYSCOMMAND, SC_RESTORE, 0) 529 else 530 SendMessage(Handle, WM_SYSCOMMAND, SC_MINIMIZE, 0); 531 end; 532 end; 533 534 procedure TskForm.PaintNC(DC: HDC); 535 const 536 HITVALUES: array [TFormButtonKind] of integer = (HTMINBUTTON, HTMAXBUTTON, HTMAXBUTTON, HTCLOSE, HTHELP); 537 538 function GetBtnState(AKind: TFormButtonKind): TSkinIndicator; 539 begin 540 if (FPressedHit = FHotHit) and (FPressedHit = HITVALUES[AKind]) then 541 Result := siPressed 542 else if FHotHit = HITVALUES[AKind] then 543 Result := siHover 544 else 545 Result := siInactive; 546 end; 547 548 var 549 bClipRegion: boolean; 550 hB: HBRUSH; 551 rFrame: TRect; 552 rButton: TRect; 553 SaveIndex: integer; 554 bMaxed: Boolean; 555 ClipRegion: HRGN; 556 CurrentIdx: Integer; 557 rCaptionRect : TRect; 558 sData: string; 559 Flag: Cardinal; 560 iLeftOff: Integer; 561 iTopOff: Integer; 562 SaveColor: cardinal; 563 begin 564 SaveIndex := SaveDC(DC); 565 try 566 bMaxed := IsZoomed(Handle); 567 568 // 扣除客户区域 569 rFrame := GetFrameSize; 570 ExcludeClipRect(DC, rFrame.Left, rFrame.Top, FWidth - rFrame.Right, FHeight - rFrame.Bottom); 571 572 /// 573 /// 标题区域 574 /// 575 rCaptionRect := GetCaptionRect(bMaxed); 576 577 // 填充整个窗体背景 578 hB := CreateSolidBrush(SKINCOLOR_BAKCGROUND); 579 FillRect(DC, Rect(0, 0, FWidth, FHeight), hB); 580 DeleteObject(hB); 581 582 /// 583 /// 绘制窗体图标 584 rButton := BuildRect(rCaptionRect.Left + 2, rCaptionRect.Top, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON)); 585 rButton.Top := rButton.Top + (rFrame.Top - rButton.Bottom) div 2; 586 DrawIconEx(DC, rButton.Left, rButton.Top, GetIconFast.Handle, 0, 0, 0, 0, DI_NORMAL); 587 rCaptionRect.Left := rButton.Right + SPALCE_CAPTIONAREA; // 588 589 /// 590 /// 绘制窗体按钮区域 591 rButton.Right := rCaptionRect.Right - 1; 592 rButton.Top := 0; 593 if bMaxed then 594 rButton.Top := rCaptionRect.Top; 595 rButton.Top := rButton.Top + (rFrame.Top - rButton.Top - SIZE_SYSBTN.cy) div 2; 596 rButton.Left := rButton.Right - SIZE_SYSBTN.cx; 597 rButton.Bottom := rButton.Top + SIZE_SYSBTN.cy; 598 SkinData.DrawButton(Dc, fbkClose, GetBtnState(fbkClose), rButton); 599 600 OffsetRect(rButton, - SIZE_SYSBTN.cx, 0); 601 if bMaxed then 602 SkinData.DrawButton(Dc, fbkRestore, GetBtnState(fbkRestore), rButton) 603 else 604 SkinData.DrawButton(Dc, fbkMax, GetBtnState(fbkMax), rButton); 605 606 OffsetRect(rButton, - SIZE_SYSBTN.cx, 0); 607 SkinData.DrawButton(Dc, fbkMin, GetBtnState(fbkMin), rButton); 608 rCaptionRect.Right := rButton.Left - SPALCE_CAPTIONAREA; // 后部空出 609 610 /// 611 /// 绘制工具条 612 if FToolbar.Visible and (rCaptionRect.Right > rCaptionRect.Left) then 613 begin 614 /// 防止出现绘制出多余区域,当区域不够时需要进行剪切。 615 /// 如: 窗体缩小时 616 CurrentIdx := 0; 617 bClipRegion := rCaptionRect.Width < FToolbar.Border.Width; 618 if bClipRegion then 619 begin 620 ClipRegion := CreateRectRgnIndirect(rCaptionRect); 621 CurrentIdx := SelectClipRgn(DC, ClipRegion); 622 DeleteObject(ClipRegion); 623 end; 624 625 iLeftOff := rCaptionRect.Left; 626 iTopOff := rCaptionRect.Top + (rCaptionRect.Height - FToolbar.Border.Height) div 2; 627 MoveWindowOrg(DC, iLeftOff, iTopOff); 628 FToolbar.Paint(DC); 629 MoveWindowOrg(DC, -iLeftOff, -iTopOff); 630 631 if bClipRegion then 632 SelectClipRgn(DC, CurrentIdx); 633 634 /// 扣除工具条区域 635 rCaptionRect.Left := rCaptionRect.Left + FToolbar.Border.Width + SPALCE_CAPTIONAREA; 636 end; 637 638 /// 639 /// 绘制Caption 640 if rCaptionRect.Right > rCaptionRect.Left then 641 begin 642 sData := GetCaption; 643 SetBkMode(DC, TRANSPARENT); 644 SaveColor := SetTextColor(DC, $00FFFFFF); 645 646 Flag := DT_LEFT or DT_VCENTER or DT_SINGLELINE or DT_NOPREFIX; 647 DrawTextEx(DC, PChar(sData), Length(sData), rCaptionRect, Flag, nil); 648 SetTextColor(DC, SaveColor); 649 end; 650 finally 651 RestoreDC(DC, SaveIndex); 652 end; 653 end; 654 655 procedure TskForm.PaintBackground(DC: HDC); 656 var 657 hB: HBRUSH; 658 R: TRect; 659 begin 660 GetClientRect(Handle, R); 661 hB := CreateSolidBrush($00F0F0F0); 662 FillRect(DC, R, hB); 663 DeleteObject(hB); 664 end; 665 666 procedure TskForm.Paint(DC: HDC); 667 begin 668 // PaintBackground(DC); 669 // TODO -cMM: TskForm.Paint default body inserted 670 end; 671 672 procedure TskForm.WMEraseBkgnd(var message: TWMEraseBkgnd); 673 var 674 DC: HDC; 675 SaveIndex: integer; 676 begin 677 DC := Message.DC; 678 if DC <> 0 then 679 begin 680 SaveIndex := SaveDC(DC); 681 PaintBackground(DC); 682 RestoreDC(DC, SaveIndex); 683 end; 684 685 Handled := True; 686 Message.Result := 1; 687 end; 688 689 procedure TskForm.WMNCActivate(var message: TMessage); 690 begin 691 // FFormActive := Message.WParam > 0; 692 Message.Result := 1; 693 InvalidateNC; 694 Handled := True; 695 end; 696 697 procedure TskForm.WMNCCalcSize(var message: TWMNCCalcSize); 698 var 699 R: TRect; 700 begin 701 // 改变边框尺寸 702 R := GetFrameSize; 703 with TWMNCCalcSize(Message).CalcSize_Params^.rgrc[0] do 704 begin 705 Inc(Left, R.Left); 706 Inc(Top, R.Top); 707 Dec(Right, R.Right); 708 Dec(Bottom, R.Bottom); 709 end; 710 Message.Result := 0; 711 Handled := True; 712 end; 713 714 procedure TskForm.WMNCHitTest(var Message: TWMNCHitTest); 715 var 716 P: TPoint; 717 iHit: integer; 718 begin 719 // 需要把位置转换到实际窗口位置 720 P := NormalizePoint(Point(Message.XPos, Message.YPos)); 721 722 // 获取 位置 723 iHit := HitTest(p); 724 if FHotHit > HTNOWHERE then 725 begin 726 Message.Result := iHit; 727 Handled := True; 728 end; 729 730 if iHit <> FHotHit then 731 begin 732 if FHotHit = HTCAPTIONTOOLBAR then 733 FToolbar.MouseLeave; 734 735 FHotHit := iHit; 736 InvalidateNC; 737 end; 738 739 end; 740 741 procedure TskForm.WMWindowPosChanging(var message: TWMWindowPosChanging); 742 var 743 bChanged: Boolean; 744 begin 745 CallDefaultProc(TMessage(Message)); 746 747 Handled := True; 748 bChanged := False; 749 750 /// 防止嵌套 751 if FChangeSizeCalled then 752 Exit; 753 754 if (Message.WindowPos^.flags and SWP_NOSIZE = 0) or (Message.WindowPos^.flags and SWP_NOMOVE = 0) then 755 begin 756 if (Message.WindowPos^.flags and SWP_NOMOVE = 0) then 757 begin 758 FLeft := Message.WindowPos^.x; 759 FTop := Message.WindowPos^.y; 760 end; 761 if (Message.WindowPos^.flags and SWP_NOSIZE = 0) then 762 begin 763 bChanged := ((Message.WindowPos^.cx <> FWidth) or (Message.WindowPos^.cy <> FHeight)) and 764 (Message.WindowPos^.flags and SWP_NOSIZE = 0); 765 FWidth := Message.WindowPos^.cx; 766 FHeight := Message.WindowPos^.cy; 767 end; 768 end; 769 770 if (Message.WindowPos^.flags and SWP_FRAMECHANGED <> 0) then 771 bChanged := True; 772 773 if bChanged then 774 begin 775 ChangeSize; 776 InvalidateNC; 777 end; 778 end; 779 780 procedure TskForm.WMNCLButtonDown(var message: TWMNCLButtonDown); 781 var 782 iHit: integer; 783 begin 784 iHit := HTNOWHERE; 785 if (Message.HitTest = HTCLOSE) or (Message.HitTest = HTMAXBUTTON) or (Message.HitTest = HTMINBUTTON) or 786 (Message.HitTest = HTHELP) or (Message.HitTest > HTCUSTOM) then 787 iHit := Message.HitTest; 788 789 790 /// 只处理系统按钮和自定义区域 791 if iHit <> HTNOWHERE then 792 begin 793 if iHit <> FPressedHit then 794 begin 795 FPressedHit := iHit; 796 if FPressedHit = HTCAPTIONTOOLBAR then 797 FToolbar.HandleMessage(TMessage(message)); 798 InvalidateNC; 799 end; 800 801 Message.Result := 0; 802 Message.Msg := WM_NULL; 803 Handled := True; 804 end; 805 end; 806 807 procedure TskForm.WMNCLButtonUp(var Message: TWMNCLButtonUp); 808 var 809 iWasHit: Integer; 810 begin 811 iWasHit := FPressedHit; 812 if iWasHit <> HTNOWHERE then 813 begin 814 FPressedHit := HTNOWHERE; 815 //InvalidateNC; 816 817 if iWasHit = FHotHit then 818 begin 819 case Message.HitTest of 820 HTCLOSE : SendMessage(Handle, WM_SYSCOMMAND, SC_CLOSE, 0); 821 HTMAXBUTTON : Maximize; 822 HTMINBUTTON : Minimize; 823 HTHELP : SendMessage(Handle, WM_SYSCOMMAND, SC_CONTEXTHELP, 0); 824 825 HTCAPTIONTOOLBAR : FToolbar.HandleMessage(TMessage(Message)); 826 end; 827 828 Message.Result := 0; 829 Message.Msg := WM_NULL; 830 Handled := True; 831 end; 832 end; 833 end; 834 835 procedure TskForm.WMNCMouseMove(var Message: TWMNCMouseMove); 836 begin 837 if Message.HitTest = HTCAPTIONTOOLBAR then 838 begin 839 FToolbar.HandleMessage(TMessage(Message)); 840 Handled := True; 841 end 842 else 843 begin 844 if (FPressedHit <> HTNOWHERE) and (FPressedHit <> Message.HitTest) then 845 FPressedHit := HTNOWHERE; 846 end; 847 end; 848 849 procedure TskForm.WMSetText(var Message: TMessage); 850 begin 851 CallDefaultProc(Message); 852 InvalidateNC; 853 Handled := true; 854 end; 855 856 procedure TskForm.WMNCPaint(var message: TWMNCPaint); 857 var 858 DC: HDC; 859 begin 860 DC := GetWindowDC(Control.Handle); 861 PaintNC(DC); 862 ReleaseDC(Handle, DC); 863 Handled := True; 864 end; 865 866 procedure TskForm.WMNCUAHDrawCaption(var message: TMessage); 867 begin 868 /// 这个消息会在winxp下产生,是内部Bug处理,直接丢弃此消息 869 Handled := True; 870 end; 871 872 procedure TskForm.WMPaint(var message: TWMPaint); 873 var 874 DC, hPaintDC: HDC; 875 cBuffer: TBitmap; 876 PS: TPaintStruct; 877 begin 878 /// 879 /// 绘制客户区域 880 /// 881 DC := Message.DC; 882 883 hPaintDC := DC; 884 if DC = 0 then 885 hPaintDC := BeginPaint(Handle, PS); 886 887 if DC = 0 then 888 begin 889 /// 缓冲模式绘制,减少闪烁 890 cBuffer := TBitmap.Create; 891 try 892 cBuffer.SetSize(FWidth, FHeight); 893 PaintBackground(cBuffer.Canvas.Handle); 894 Paint(cBuffer.Canvas.Handle); 895 /// 通知子控件进行绘制 896 /// 主要是些图形控件的重绘制(如TShape),否则停靠在Form上的图像控件无法正常显示 897 if Control is TWinControl then 898 TacWinControl(Control).PaintControls(cBuffer.Canvas.Handle, nil); 899 BitBlt(hPaintDC, 0, 0, FWidth, FHeight, cBuffer.Canvas.Handle, 0, 0, SRCCOPY); 900 finally 901 cBuffer.Free; 902 end; 903 end 904 else 905 begin 906 Paint(hPaintDC); 907 // 通知子控件重绘 908 if Control is TWinControl then 909 TacWinControl(Control).PaintControls(hPaintDC, nil); 910 end; 911 912 if DC = 0 then 913 EndPaint(Handle, PS); 914 915 Handled := True; 916 end; 917 918 procedure TskForm.WndProc(var message: TMessage); 919 begin 920 FHandled := False; 921 Dispatch(message); 922 end; 923 924 class procedure Res.LoadBitmap(const AName: string; AGraphic: TBitmap); 925 var 926 cPic: TPngImage; 927 cBmp: TBitmap; 928 begin 929 cBmp := AGraphic; 930 cPic := TPngImage.Create; 931 try 932 cBmp.PixelFormat := pf32bit; 933 cBmp.alphaFormat := afIgnored; 934 try 935 LoadGraphic(AName, cPic); 936 cBmp.SetSize(cPic.Width, cPic.Height); 937 cBmp.Canvas.Brush.Color := clBlack; 938 cBmp.Canvas.FillRect(Rect(0, 0, cBmp.Width, cBmp.Height)); 939 cBmp.Canvas.Draw(0, 0, cPic); 940 except 941 // 不处理空图片 942 end; 943 finally 944 cPic.Free; 945 end; 946 end; 947 948 class procedure Res.LoadGraphic(const AName: string; AGraphic: TGraphic); 949 var 950 cStream: TResourceStream; 951 h: THandle; 952 begin 953 /// 954 /// 加载图片资源 955 h := HInstance; 956 cStream := TResourceStream.Create(h, AName, RT_RCDATA); 957 try 958 AGraphic.LoadFromStream(cStream); 959 finally 960 cStream.Free; 961 end; 962 end; 963 964 class constructor SkinData.Create; 965 begin 966 // 加载资源 967 FData := TBitmap.Create; 968 Res.LoadBitmap('MySkin', FData); 969 end; 970 971 class destructor SkinData.Destroy; 972 begin 973 FData.Free; 974 end; 975 976 class procedure SkinData.DrawButton(DC: HDC; AKind: TFormButtonKind; AState: 977 TSkinIndicator; const R: TRect); 978 var 979 rSrcOff: TPoint; 980 x, y: integer; 981 begin 982 /// 绘制背景 983 DrawButtonBackground(DC, AState, R); 984 985 /// 绘制图标 986 rSrcOff := Point(SIZE_RESICON * ord(AKind), 0); 987 x := R.Left + (R.Right - R.Left - SIZE_RESICON) div 2; 988 y := R.Top + (R.Bottom - R.Top - SIZE_RESICON) div 2; 989 DrawTransparentBitmap(FData, rSrcOff.X, rSrcOff.Y, DC, x, y, SIZE_RESICON, SIZE_RESICON); 990 end; 991 992 class procedure SkinData.DrawButtonBackground(DC: HDC; AState: TSkinIndicator; const R: TRect; const Opacity: Byte = 255); 993 var 994 hB: HBRUSH; 995 iColor: Cardinal; 996 begin 997 if AState <> siInactive then 998 begin 999 /// 绘制背景 1000 case AState of 1001 siHover : iColor := SKINCOLOR_BTNHOT; 1002 siPressed : iColor := SKINCOLOR_BTNPRESSED; 1003 siSelected : iColor := SKINCOLOR_BTNPRESSED; 1004 siHoverSelected : iColor := SKINCOLOR_BTNHOT; 1005 else iColor := SKINCOLOR_BAKCGROUND; 1006 end; 1007 hB := CreateSolidBrush(iColor); 1008 FillRect(DC, R, hB); 1009 DeleteObject(hB); 1010 end; 1011 end; 1012 1013 class procedure SkinData.DrawElement(DC: HDC; AItem: TSkinToolbarElement; const R: TRect); 1014 var 1015 rSrc: TResArea; 1016 x, y: integer; 1017 begin 1018 rSrc := RES_CAPTIONTOOLBAR; 1019 rSrc.x := rSrc.x + rSrc.w * (ord(AItem) - ord(Low(TSkinToolbarElement))); 1020 1021 /// 绘制图标 1022 x := R.Left + (R.Right - R.Left - rSrc.w) div 2; 1023 y := R.Top + (R.Bottom - R.Top - rSrc.h) div 2; 1024 DrawTransparentBitmap(FData, rSrc.x, rSrc.y, DC, x, y, rSrc.w, rSrc.h); 1025 end; 1026 1027 class procedure SkinData.DrawIcon(DC: HDC; R: TRect; ASrc: TBitmap; const Opacity: Byte = 255); 1028 var 1029 iXOff: Integer; 1030 iYOff: Integer; 1031 begin 1032 iXOff := r.Left + (R.Right - R.Left - ASrc.Width) div 2; 1033 iYOff := r.Top + (r.Bottom - r.Top - ASrc.Height) div 2; 1034 DrawTransparentBitmap(ASrc, 0, 0, DC, iXOff, iYOff, ASrc.Width, ASrc.Height, Opacity); 1035 end; 1036 1037 { TcpToolbar } 1038 constructor TcpToolbar.Create(AOwner: TskForm); 1039 begin 1040 inherited; 1041 FHotIndex := -1; 1042 FPressedIndex := -1; 1043 end; 1044 1045 procedure TcpToolbar.Add(Action: TBasicAction; AImageIndex: Integer); 1046 begin 1047 if FCount >= Length(FItems) then 1048 SetLength(FItems, FCount + 5); 1049 1050 ZeroMemory(@FItems[FCount], SizeOf(TcpToolButton)); 1051 FItems[FCount].Action := Action; 1052 FItems[FCount].Enabled := true; 1053 FItems[FCount].Visible := True; 1054 FItems[FCount].ImageIndex := AImageIndex; 1055 FItems[FCount].Width := 20; 1056 FItems[FCount].Fade := 255; 1057 FItems[FCount].SaveEvent := TacAction(Action).OnChange; 1058 TacAction(Action).OnChange := DoOnActionChange; 1059 1060 inc(FCount); 1061 1062 Update; 1063 end; 1064 1065 function TcpToolbar.CalcSize: TRect; 1066 const 1067 SIZE_SPLITER = 10; 1068 SIZE_POPMENU = 10; 1069 SIZE_BUTTON = 20; 1070 var 1071 w, h: Integer; 1072 I: Integer; 1073 begin 1074 /// 1075 /// 占用宽度 1076 /// 如果考虑比较复杂的按钮样式和显示标题等功能,那么需要计算每个按钮实际占用宽度才能获得。 1077 1078 w := SIZE_SPLITER * 2 + SIZE_POPMENU; 1079 for I := 0 to FCount - 1 do 1080 w := w + FItems[i].Width; 1081 h := SIZE_BUTTON; 1082 Result := Rect(0, 0, w, h); 1083 end; 1084 1085 procedure TcpToolbar.Delete(Index: Integer); 1086 begin 1087 if (Index >= 0) and (Index < FCount) then 1088 begin 1089 /// 删除时需要恢复 1090 TacAction(FItems[Index].Action).OnChange := FItems[Index].SaveEvent; 1091 1092 if Index < (FCount - 1) then 1093 Move(FItems[Index+1], FItems[Index], sizeof(TcpToolButton) * (FCount - Index - 1)); 1094 dec(FCount); 1095 1096 Update; 1097 end; 1098 end; 1099 1100 procedure TcpToolbar.DoOnActionChange(Sender: TObject); 1101 var 1102 idx: Integer; 1103 bResize: Boolean; 1104 begin 1105 if Sender is TBasicAction then 1106 begin 1107 idx := IndexOf(TBasicAction(Sender)); 1108 if (idx >= 0) and (idx < FCount) then 1109 begin 1110 /// 1111 /// 外部状态改变响应 1112 /// 1113 if FItems[idx].Action.InheritsFrom(TContainedAction) then 1114 begin 1115 FItems[idx].Enabled := TContainedAction(Sender).Enabled; 1116 bResize := FItems[idx].Visible <> TContainedAction(Sender).Visible; 1117 if bResize then 1118 begin 1119 FItems[idx].Visible := not FItems[idx].Visible; 1120 Update 1121 end 1122 else 1123 Invalidate; 1124 end; 1125 1126 /// 执行原有事件 1127 if Assigned(FItems[idx].SaveEvent) then 1128 FItems[idx].SaveEvent(Sender); 1129 end; 1130 end; 1131 end; 1132 1133 function TcpToolbar.HitTest(P: TPoint): integer; 1134 var 1135 iOff: Integer; 1136 iIdx: integer; 1137 I: Integer; 1138 begin 1139 /// 1140 /// 检测鼠标位置 1141 /// 鼠标位置的 FCount位 为工具条系统菜单位置。 1142 iIdx := -1; 1143 iOff := RES_CAPTIONTOOLBAR.w; 1144 if p.x > iOff then 1145 begin 1146 for I := 0 to FCount - 1 do 1147 begin 1148 if p.X < iOff then 1149 Break; 1150 1151 iIdx := i; 1152 inc(iOff, FItems[i].Width); 1153 end; 1154 1155 if p.x > iOff then 1156 begin 1157 iIdx := -1; 1158 inc(iOff, RES_CAPTIONTOOLBAR.w); 1159 if p.x > iOff then 1160 iIdx := FCount; // FCount 为系统菜单按钮 1161 end; 1162 end; 1163 1164 Result := iIdx; 1165 end; 1166 1167 procedure TcpToolbar.ExecAction(Index: Integer); 1168 begin 1169 /// 1170 /// 执行命令 1171 /// 1172 if (Index >= 0) and (Index < FCount) then 1173 FItems[Index].Action.Execute; 1174 1175 // FCount位 为系统配置按钮 1176 if Index = FCount then 1177 PopConfigMenu; 1178 end; 1179 1180 procedure TcpToolbar.PopConfigMenu; 1181 begin 1182 end; 1183 1184 procedure TcpToolbar.SetImages(const Value: TCustomImageList); 1185 begin 1186 FImages := Value; 1187 Invalidate; 1188 end; 1189 1190 function TcpToolbar.IndexOf(Action: TBasicAction): Integer; 1191 var 1192 I: Integer; 1193 begin 1194 Result := -1; 1195 for I := 0 to FCount - 1 do 1196 if FItems[i].Action = Action then 1197 begin 1198 Result := i; 1199 Break; 1200 end; 1201 end; 1202 1203 procedure TcpToolbar.MouseDown(Button: TMouseButton; p: TPoint); 1204 begin 1205 if (mbLeft = Button) then 1206 begin 1207 FPressedIndex := HitTest(p); 1208 //Invalidate; 1209 end; 1210 end; 1211 1212 procedure TcpToolbar.MouseLeave; 1213 begin 1214 if FHotIndex >= 0 then 1215 begin 1216 FHotIndex := -1; 1217 //Invalidate; 1218 end; 1219 end; 1220 1221 procedure TcpToolbar.HitWindowTest(P: TPoint); 1222 begin 1223 FHotIndex := HitTest(P); 1224 end; 1225 1226 procedure TcpToolbar.MouseMove(p: TPoint); 1227 var 1228 iIdx: Integer; 1229 begin 1230 iIdx := HitTest(p); 1231 if iIdx <> FHotIndex then 1232 begin 1233 FHotIndex := iIdx; 1234 Invalidate; 1235 end; 1236 end; 1237 1238 procedure TcpToolbar.MouseUp(Button: TMouseButton; p: TPoint); 1239 var 1240 iAction: Integer; 1241 begin 1242 if (mbLeft = Button) and (FPressedIndex >= 0) and (FHotIndex = FPressedIndex) then 1243 begin 1244 iAction := FPressedIndex; 1245 FPressedIndex := -1; 1246 Invalidate; 1247 1248 ExecAction(iAction); 1249 end; 1250 end; 1251 1252 function TcpToolbar.LoadActionIcon(Idx: Integer; AImg: TBitmap):Boolean; 1253 var 1254 bHasImg: Boolean; 1255 begin 1256 /// 获取Action的图标 1257 AImg.Canvas.Brush.Color := clBlack; 1258 AImg.Canvas.FillRect(Rect(0,0, AImg.Width, AImg.Height)); 1259 bHasImg := False; 1260 if (FImages <> nil) and (FItems[Idx].ImageIndex >= 0) then 1261 bHasImg := FImages.GetBitmap(FItems[Idx].ImageIndex, AImg); 1262 if not bHasImg and (FItems[Idx].Action is TCustomAction) then 1263 with TCustomAction(FItems[Idx].Action) do 1264 if (Images <> nil) and (ImageIndex >= 0) then 1265 bHasImg := Images.GetBitmap(ImageIndex, AImg); 1266 Result := bHasImg; 1267 end; 1268 1269 procedure TcpToolbar.Paint(DC: HDC); 1270 1271 function GetActionState(Idx: Integer): TSkinIndicator; 1272 begin 1273 Result := siInactive; 1274 if (Idx = FPressedIndex) and (FHotIndex = FPressedIndex) then 1275 Result := siPressed 1276 else if Idx = FHotIndex then 1277 Result := siHover; 1278 end; 1279 1280 var 1281 cIcon: TBitmap; 1282 r: TRect; 1283 I: Integer; 1284 iOpacity: byte; 1285 begin 1286 /// 1287 /// 工具条绘制 1288 /// 1289 1290 /// 分割线 1291 r := Border; 1292 r.Right := r.Left + RES_CAPTIONTOOLBAR.w; 1293 SkinData.DrawElement(DC, steSplitter, r); 1294 OffsetRect(r, r.Right - r.Left, 0); 1295 1296 /// 绘制Button 1297 cIcon := TBitmap.Create; 1298 cIcon.PixelFormat := pf32bit; 1299 cIcon.alphaFormat := afIgnored; 1300 for I := 0 to FCount - 1 do 1301 begin 1302 r.Right := r.Left + FItems[i].Width; 1303 if FItems[I].Enabled then 1304 SkinData.DrawButtonBackground(DC, GetActionState(i), r, FItems[i].Fade); 1305 if LoadActionIcon(i, cIcon) then 1306 begin 1307 iOpacity := 255; 1308 /// 处理不可用状态,图标颜色变暗。 1309 /// 简易处理,增加绘制透明度。 1310 if not FItems[i].Enabled then 1311 iOpacity := 100; 1312 1313 SkinData.DrawIcon(DC, r, cIcon, iOpacity); 1314 end; 1315 OffsetRect(r, r.Right - r.Left, 0); 1316 end; 1317 cIcon.free; 1318 1319 /// 分割条 1320 r.Right := r.Left + RES_CAPTIONTOOLBAR.w; 1321 SkinData.DrawElement(DC, steSplitter, r); 1322 OffsetRect(r, r.Right - r.Left, 0); 1323 1324 /// 绘制下拉菜单 1325 r.Right := r.Left + RES_CAPTIONTOOLBAR.w; 1326 SkinData.DrawElement(DC, stePopdown, r); 1327 end; 1328 1329 constructor TFormCaptionPlugin.Create(AOwner: TskForm); 1330 begin 1331 FOwner := AOwner; 1332 FVisible := True; 1333 FBorder := CalcSize; 1334 FOffset.X := -1; 1335 end; 1336 1337 function TFormCaptionPlugin.ScreenToClient(x, y: Integer): TPoint; 1338 var 1339 P: TPoint; 1340 begin 1341 /// 调整位置 1342 /// 以 FOffset 为中心位置 1343 P := FOwner.NormalizePoint(Point(x, Y)); 1344 p.X := p.X - FOffset.X; 1345 p.Y := p.y - FOffset.Y; 1346 1347 Result := p; 1348 end; 1349 1350 1351 function TFormCaptionPlugin.HandleMessage(var Message: TMessage): Boolean; 1352 begin 1353 Result := True; 1354 1355 case Message.Msg of 1356 WM_NCMOUSEMOVE : MouseMove(ScreenToClient(TWMNCMouseMove(Message).XCursor, TWMNCMouseMove(Message).YCursor)); 1357 WM_NCLBUTTONDOWN : MouseDown(mbLeft, ScreenToClient(TWMNCLButtonDown(Message).XCursor, TWMNCLButtonDown(Message).YCursor)); 1358 WM_NCHITTEST : HitWindowTest(ScreenToClient(TWMNCHitTest(Message).XPos, TWMNCHitTest(Message).YPos)); 1359 WM_NCLBUTTONUP : MouseUp(mbLeft, ScreenToClient(TWMNCLButtonUp(Message).XCursor, TWMNCLButtonUp(Message).YCursor)); 1360 1361 else 1362 Result := False; 1363 end; 1364 end; 1365 1366 procedure TFormCaptionPlugin.HitWindowTest(P: TPoint); 1367 begin 1368 end; 1369 1370 procedure TFormCaptionPlugin.Invalidate; 1371 begin 1372 FOwner.InvalidateNC; 1373 end; 1374 1375 procedure TFormCaptionPlugin.MouseDown(Button: TMouseButton; p: TPoint); 1376 begin 1377 end; 1378 1379 procedure TFormCaptionPlugin.MouseLeave; 1380 begin 1381 end; 1382 1383 procedure TFormCaptionPlugin.MouseMove(p: TPoint); 1384 begin 1385 end; 1386 1387 procedure TFormCaptionPlugin.MouseUp(Button: TMouseButton; p: TPoint); 1388 begin 1389 end; 1390 1391 procedure TFormCaptionPlugin.Update; 1392 begin 1393 FBorder := CalcSize; 1394 Invalidate; 1395 end; 1396 1397 end.
1 unit ufrmCaptionToolbar; 2 3 interface 4 5 uses 6 Messages, SysUtils, Variants, Types, Controls, Forms, Dialogs, StdCtrls, 7 ExtCtrls, ComCtrls, Windows, Classes, Graphics, Actions, ActnList, ToolWin, 8 Vcl.ImgList, Vcl.Buttons, 9 10 uFormSkins; 11 12 type 13 TForm11 = class(TForm) 14 Button1: TButton; 15 Shape1: TShape; 16 Edit1: TEdit; 17 Edit2: TEdit; 18 Edit3: TEdit; 19 Edit4: TEdit; 20 ToolBar1: TToolBar; 21 ToolButton1: TToolButton; 22 ToolButton2: TToolButton; 23 ToolButton3: TToolButton; 24 ActionList1: TActionList; 25 Action1: TAction; 26 Action2: TAction; 27 Action3: TAction; 28 ImageList1: TImageList; 29 ImageList2: TImageList; 30 CheckBox1: TCheckBox; 31 procedure FormCreate(Sender: TObject); 32 procedure Action1Execute(Sender: TObject); 33 procedure Action2Execute(Sender: TObject); 34 procedure Action3Execute(Sender: TObject); 35 procedure CheckBox1Click(Sender: TObject); 36 procedure SpeedButton1Click(Sender: TObject); 37 private 38 FTest: TskForm; 39 protected 40 41 procedure WndProc(var message: TMessage); override; 42 public 43 constructor Create(AOwner: TComponent); override; 44 destructor Destroy; override; 45 end; 46 47 var 48 Form11: TForm11; 49 50 implementation 51 52 53 {$R *.dfm} 54 55 56 57 { TForm11 } 58 59 constructor TForm11.Create(AOwner: TComponent); 60 begin 61 FTest := TskForm.Create(Self); 62 inherited; 63 end; 64 65 procedure TForm11.FormCreate(Sender: TObject); 66 begin 67 FTest.Toolbar.Images := ImageList2; 68 FTest.Toolbar.Add(Action1, 0); 69 FTest.Toolbar.Add(Action2, 1); 70 FTest.Toolbar.Add(Action3, 2); 71 end; 72 73 destructor TForm11.Destroy; 74 begin 75 inherited; 76 FreeAndNil(FTest); 77 end; 78 79 procedure TForm11.Action1Execute(Sender: TObject); 80 begin 81 Tag := Tag + 1; 82 Caption := format('test %d', [Tag]); 83 end; 84 85 procedure TForm11.Action2Execute(Sender: TObject); 86 begin 87 if Shape1.Shape <> High(TShapeType) then 88 Shape1.Shape := Succ(Shape1.Shape) 89 else 90 Shape1.Shape := low(TShapeType); 91 end; 92 93 procedure TForm11.Action3Execute(Sender: TObject); 94 begin 95 Action1.Enabled := not Action1.Enabled; 96 end; 97 98 procedure TForm11.CheckBox1Click(Sender: TObject); 99 begin 100 if CheckBox1.Checked then 101 FTest.Toolbar.Images := nil 102 else 103 FTest.Toolbar.Images := ImageList2; 104 end; 105 106 procedure TForm11.SpeedButton1Click(Sender: TObject); 107 begin 108 Caption := format('test %d', [1]); 109 end; 110 111 procedure TForm11.WndProc(var message: TMessage); 112 begin 113 if not FTest.DoHandleMessage(Message) then 114 inherited; 115 end; 116 117 end.
相关API
MoveWindowOrg ---- 设置绘制原点
CreateRectRgnIndirect ---- 创建区域
SelectClipRgn ---- 剪切绘制区域
相关功能实现:
其实这个功能在Win7下已经有此接口可以实现(很久以前用过具体名字忘记了,没写日志的后果-_-),系统自带的画图就是使用此接口实现的。但有个问题就是XP下木有此功能。感兴趣的可以Google一下。
开发环境
XE3
Win7
完整源代码
https://github.com/cmacro/simple/tree/master/TestCaptionToolbar