首页 > 代码库 > 窗体皮肤实现 - 在标题栏上增加快速工具条(四)
窗体皮肤实现 - 在标题栏上增加快速工具条(四)
前面做的工作就是想在标题区域增加快速工具条。前续的基础工作完成,想要在标题区域增加特殊区域都非常方便。只要在绘制时控制自定义区域需要占用标题区域多少空间,然后直接在所占位置绘制。做这个事情前,稍微把代码规整了下。所以界面皮肤处理放到一个单元中。
主要处理步骤
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) then14 begin15 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 then21 FToolbar.FOffset := r.TopLeft;22 23 if PtInRect(r, p) then24 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 const16 SIZE_SPLITER = 10;17 SIZE_POPMENU = 10;18 SIZE_BUTTON = 20;19 var20 w, h: Integer;21 I: Integer;22 begin23 ///24 /// 占用宽度25 /// 如果考虑比较复杂的按钮样式和显示标题等功能,那么需要计算每个按钮实际占用宽度才能获得。26 27 w := SIZE_SPLITER * 2 + SIZE_POPMENU;28 for I := 0 to FCount - 1 do29 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 then10 begin11 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 then24 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 begin10 /// 获取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) then15 bHasImg := FImages.GetBitmap(FItems[Idx].ImageIndex, AImg);16 if not bHasImg and (FItems[Idx].Action is TCustomAction) then17 with TCustomAction(FItems[Idx].Action) do18 if (Images <> nil) and (ImageIndex >= 0) then19 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 var13 cIcon: TBitmap;14 r: TRect;15 I: Integer;16 iOpacity: byte;17 begin18 ///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 /// 绘制Button29 cIcon := TBitmap.Create;30 cIcon.PixelFormat := pf32bit;31 cIcon.alphaFormat := afIgnored;32 for I := 0 to FCount - 1 do33 begin34 r.Right := r.Left + FItems[i].Width;35 if FItems[I].Enabled then36 SkinData.DrawButtonBackground(DC, GetActionState(i), r, FItems[i].Fade);37 if LoadActionIcon(i, cIcon) then38 begin39 iOpacity := 255;40 /// 处理不可用状态,图标颜色变暗。41 /// 简易处理,增加绘制透明度。42 if not FItems[i].Enabled then43 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 else12 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 then13 begin14 for I := 0 to FCount - 1 do15 begin16 if p.X < iOff then17 Break;18 19 iIdx := i;20 inc(iOff, FItems[i].Width);21 end;22 23 if p.x > iOff then24 begin25 iIdx := -1;26 inc(iOff, RES_CAPTIONTOOLBAR.w);27 if p.x > iOff then28 iIdx := FCount; // FCount 为系统菜单按钮29 end;30 end;31 32 Result := iIdx;33 end;
还有些细节方面的处理,如鼠标离开这个区域时的处理。这样整个工具区的基本处理完成,整个工具条区域的处理还是相对比较简单。
Action状态处理
Action处理主要是考虑,当外部改变Action状态。如:无效,不可见的一些事件处理。标准的处理方法是在关联Action是创建一个ActionLink实现联动,由于TskForm没有从TControl继承,没法使用此方法进行处理。在TBasicAction改变状态时会触发一个OnChange的保护(protected)事件,可以直接把事件挂接上去,就能简单处理状态。
保护方法的访问:创建一个访问类,进行引用。
1 type2 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) then10 begin11 ///12 /// 外部状态改变响应13 ///14 if FItems[idx].Action.InheritsFrom(TContainedAction) then15 begin16 FItems[idx].Enabled := TContainedAction(Sender).Enabled;17 bResize := FItems[idx].Visible <> TContainedAction(Sender).Visible;18 if bResize then19 begin20 FItems[idx].Visible := not FItems[idx].Visible;21 Update22 end23 else24 Invalidate;25 end;26 27 /// 执行原有事件28 if Assigned(FItems[idx].SaveEvent) then29 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 then11 iOpacity := 100;12 13 SkinData.DrawIcon(DC, r, cIcon, iOpacity);14 end;15 ... ...16 17 // 获取Action底色的显示状态18 // 按下状态、滑过状态、默认状态19 function GetActionState(Idx: Integer): TSkinIndicator;20 begin21 Result := siInactive;22 if (Idx = FPressedIndex) and (FHotIndex = FPressedIndex) then23 Result := siPressed24 else if Idx = FHotIndex then25 Result := siHover;26 end;27
在窗体上加入测试Action
1 procedure TForm11.FormCreate(Sender: TObject);2 begin3 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 = http://www.mamicode.com/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 of1001 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 var1015 rSrc: TResArea;1016 x, y: integer;1017 begin1018 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 var1029 iXOff: Integer;1030 iYOff: Integer;1031 begin1032 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 begin1040 inherited;1041 FHotIndex := -1;1042 FPressedIndex := -1;1043 end;1044 1045 procedure TcpToolbar.Add(Action: TBasicAction; AImageIndex: Integer);1046 begin1047 if FCount >= Length(FItems) then1048 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 const1067 SIZE_SPLITER = 10;1068 SIZE_POPMENU = 10;1069 SIZE_BUTTON = 20;1070 var1071 w, h: Integer;1072 I: Integer;1073 begin1074 ///1075 /// 占用宽度1076 /// 如果考虑比较复杂的按钮样式和显示标题等功能,那么需要计算每个按钮实际占用宽度才能获得。1077 1078 w := SIZE_SPLITER * 2 + SIZE_POPMENU;1079 for I := 0 to FCount - 1 do1080 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 begin1087 if (Index >= 0) and (Index < FCount) then1088 begin1089 /// 删除时需要恢复1090 TacAction(FItems[Index].Action).OnChange := FItems[Index].SaveEvent;1091 1092 if Index < (FCount - 1) then1093 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 var1102 idx: Integer;1103 bResize: Boolean;1104 begin1105 if Sender is TBasicAction then1106 begin1107 idx := IndexOf(TBasicAction(Sender));1108 if (idx >= 0) and (idx < FCount) then1109 begin1110 ///1111 /// 外部状态改变响应1112 ///1113 if FItems[idx].Action.InheritsFrom(TContainedAction) then1114 begin1115 FItems[idx].Enabled := TContainedAction(Sender).Enabled;1116 bResize := FItems[idx].Visible <> TContainedAction(Sender).Visible;1117 if bResize then1118 begin1119 FItems[idx].Visible := not FItems[idx].Visible;1120 Update1121 end1122 else1123 Invalidate;1124 end;1125 1126 /// 执行原有事件1127 if Assigned(FItems[idx].SaveEvent) then1128 FItems[idx].SaveEvent(Sender);1129 end;1130 end;1131 end;1132 1133 function TcpToolbar.HitTest(P: TPoint): integer;1134 var1135 iOff: Integer;1136 iIdx: integer;1137 I: Integer;1138 begin1139 ///1140 /// 检测鼠标位置1141 /// 鼠标位置的 FCount位 为工具条系统菜单位置。1142 iIdx := -1;1143 iOff := RES_CAPTIONTOOLBAR.w;1144 if p.x > iOff then1145 begin1146 for I := 0 to FCount - 1 do1147 begin1148 if p.X < iOff then1149 Break;1150 1151 iIdx := i;1152 inc(iOff, FItems[i].Width);1153 end;1154 1155 if p.x > iOff then1156 begin1157 iIdx := -1;1158 inc(iOff, RES_CAPTIONTOOLBAR.w);1159 if p.x > iOff then1160 iIdx := FCount; // FCount 为系统菜单按钮1161 end;1162 end;1163 1164 Result := iIdx;1165 end;1166 1167 procedure TcpToolbar.ExecAction(Index: Integer);1168 begin1169 ///1170 /// 执行命令1171 ///1172 if (Index >= 0) and (Index < FCount) then1173 FItems[Index].Action.Execute;1174 1175 // FCount位 为系统配置按钮1176 if Index = FCount then1177 PopConfigMenu;1178 end;1179 1180 procedure TcpToolbar.PopConfigMenu;1181 begin1182 end;1183 1184 procedure TcpToolbar.SetImages(const Value: TCustomImageList);1185 begin1186 FImages := Value;1187 Invalidate;1188 end;1189 1190 function TcpToolbar.IndexOf(Action: TBasicAction): Integer;1191 var1192 I: Integer;1193 begin1194 Result := -1;1195 for I := 0 to FCount - 1 do1196 if FItems[i].Action = Action then1197 begin1198 Result := i;1199 Break;1200 end;1201 end;1202 1203 procedure TcpToolbar.MouseDown(Button: TMouseButton; p: TPoint);1204 begin1205 if (mbLeft = Button) then1206 begin1207 FPressedIndex := HitTest(p);1208 //Invalidate;1209 end;1210 end;1211 1212 procedure TcpToolbar.MouseLeave;1213 begin1214 if FHotIndex >= 0 then1215 begin1216 FHotIndex := -1;1217 //Invalidate;1218 end;1219 end;1220 1221 procedure TcpToolbar.HitWindowTest(P: TPoint);1222 begin1223 FHotIndex := HitTest(P);1224 end;1225 1226 procedure TcpToolbar.MouseMove(p: TPoint);1227 var1228 iIdx: Integer;1229 begin1230 iIdx := HitTest(p);1231 if iIdx <> FHotIndex then1232 begin1233 FHotIndex := iIdx;1234 Invalidate;1235 end;1236 end;1237 1238 procedure TcpToolbar.MouseUp(Button: TMouseButton; p: TPoint);1239 var1240 iAction: Integer;1241 begin1242 if (mbLeft = Button) and (FPressedIndex >= 0) and (FHotIndex = FPressedIndex) then1243 begin1244 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 var1254 bHasImg: Boolean;1255 begin1256 /// 获取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) then1261 bHasImg := FImages.GetBitmap(FItems[Idx].ImageIndex, AImg);1262 if not bHasImg and (FItems[Idx].Action is TCustomAction) then1263 with TCustomAction(FItems[Idx].Action) do1264 if (Images <> nil) and (ImageIndex >= 0) then1265 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 begin1273 Result := siInactive;1274 if (Idx = FPressedIndex) and (FHotIndex = FPressedIndex) then1275 Result := siPressed1276 else if Idx = FHotIndex then1277 Result := siHover;1278 end;1279 1280 var1281 cIcon: TBitmap;1282 r: TRect;1283 I: Integer;1284 iOpacity: byte;1285 begin1286 ///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 /// 绘制Button1297 cIcon := TBitmap.Create;1298 cIcon.PixelFormat := pf32bit;1299 cIcon.alphaFormat := afIgnored;1300 for I := 0 to FCount - 1 do1301 begin1302 r.Right := r.Left + FItems[i].Width;1303 if FItems[I].Enabled then1304 SkinData.DrawButtonBackground(DC, GetActionState(i), r, FItems[i].Fade);1305 if LoadActionIcon(i, cIcon) then1306 begin1307 iOpacity := 255;1308 /// 处理不可用状态,图标颜色变暗。1309 /// 简易处理,增加绘制透明度。1310 if not FItems[i].Enabled then1311 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 begin1331 FOwner := AOwner;1332 FVisible := True;1333 FBorder := CalcSize;1334 FOffset.X := -1;1335 end;1336 1337 function TFormCaptionPlugin.ScreenToClient(x, y: Integer): TPoint;1338 var1339 P: TPoint;1340 begin1341 /// 调整位置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 begin1353 Result := True;1354 1355 case Message.Msg of1356 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 else1362 Result := False;1363 end;1364 end;1365 1366 procedure TFormCaptionPlugin.HitWindowTest(P: TPoint);1367 begin1368 end;1369 1370 procedure TFormCaptionPlugin.Invalidate;1371 begin1372 FOwner.InvalidateNC;1373 end;1374 1375 procedure TFormCaptionPlugin.MouseDown(Button: TMouseButton; p: TPoint);1376 begin1377 end;1378 1379 procedure TFormCaptionPlugin.MouseLeave;1380 begin1381 end;1382 1383 procedure TFormCaptionPlugin.MouseMove(p: TPoint);1384 begin1385 end;1386 1387 procedure TFormCaptionPlugin.MouseUp(Button: TMouseButton; p: TPoint);1388 begin1389 end;1390 1391 procedure TFormCaptionPlugin.Update;1392 begin1393 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 begin100 if CheckBox1.Checked then101 FTest.Toolbar.Images := nil102 else103 FTest.Toolbar.Images := ImageList2;104 end;105 106 procedure TForm11.SpeedButton1Click(Sender: TObject);107 begin108 Caption := format(‘test %d‘, [1]);109 end;110 111 procedure TForm11.WndProc(var message: TMessage);112 begin113 if not FTest.DoHandleMessage(Message) then114 inherited;115 end;116 117 end.
相关API
MoveWindowOrg ---- 设置绘制原点
CreateRectRgnIndirect ---- 创建区域
SelectClipRgn ---- 剪切绘制区域
相关功能实现:
其实这个功能在Win7下已经有此接口可以实现(很久以前用过具体名字忘记了,没写日志的后果-_-),系统自带的画图就是使用此接口实现的。但有个问题就是XP下木有此功能。感兴趣的可以Google一下。
开发环境
XE3
Win7
完整源代码
https://github.com/cmacro/simple/tree/master/TestCaptionToolbar
窗体皮肤实现 - 在标题栏上增加快速工具条(四)