窗体皮肤实现 - 在标题栏上增加快速工具条(四)

前面做的工作就是想在标题区域增加快速工具条。前续的基础工作完成,想要在标题区域增加特殊区域都非常方便。只要在绘制时控制自定义区域需要占用标题区域多少空间,然后直接在所占位置绘制。做这个事情前,稍微把代码规整了下。所以界面皮肤处理放到一个单元中。

主要处理步骤

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的图标

绘制工具条

有了尺寸和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;

坐标所在按钮区域检测 HitTest

还有些细节方面的处理,如鼠标离开这个区域时的处理。这样整个工具区的基本处理完成,整个工具条区域的处理还是相对比较简单。

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.

uFormSkins.pas

  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.

ufrmCaptionToolbar.pas

相关API

MoveWindowOrg                ---- 设置绘制原点

CreateRectRgnIndirect        ---- 创建区域

SelectClipRgn                     ---- 剪切绘制区域

相关功能实现:

其实这个功能在Win7下已经有此接口可以实现(很久以前用过具体名字忘记了,没写日志的后果-_-),系统自带的画图就是使用此接口实现的。但有个问题就是XP下木有此功能。感兴趣的可以Google一下。

开发环境

XE3

Win7

完整源代码

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

时间: 2024-10-06 20:00:07

窗体皮肤实现 - 在标题栏上增加快速工具条(四)的相关文章

DELPHI在标题栏上增加按钮

Delphi代码 unit Unit1; interface uses SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, Dialogs, Buttons, DdeMan, StdCtrls; type TTitleBtnForm = class(TForm) Button1: TButton; procedure FormResize(Sender: TObject); private Tit

窗体皮肤实现 - 增加Toolbar的交互性

稍微改造一下,让交互性更好点.增加提示和动态效果. 控件实现内容: 1.加入Hint提示 2.加入了简易动画效果,鼠标进入和离开会有个渐变效果. 实现方案: 1.基类选用 2.Action的关联 3.绘制按钮 4.鼠标响应 5.美化(淡入淡出简易动画) OK-完成 一.基类选择 在基类选择上稍微纠结了下.Delphi大家都知道做一个显示控件一般有2种情况,一种是图形控件(VC里叫静态控件),还种种有焦点可交互的. 如果我想做个Toolbar并不需要焦点,也不需要处理键盘输入,TGraphicCo

Winform自定义窗体样式,实现标题栏可灵活自定义

最近在编写C/S结构应用程序时,感觉窗体的标题栏样式太死板了,标题文字不能更改大小.颜色.字体等,按钮不能隐藏等问题,在网上也查找了许多相关的资料,没有找到合适的解决方案,发现许多人也在寻求这个问题,最后我决定自己研究动手画一个标题栏出来,经过今天一天的研究与编写,终于完成全部功能,现公布一下我的设计思路. 一.去掉Form类自带的标题栏                          要去掉自带的标题栏有两种方法,第一方法是直接将FormBorderStyle设为 System.Window

C#窗体皮肤制作(二):创建窗体库项目以及最小化、最大化、关闭按钮的实现

很高兴有朋友关注这篇博客,同时也十分抱歉让关注的朋友久等了,隔上一篇博客也有3个月没有更新,主要是由于3月份辞职,4月份初离职到期离开了北京高德,来到了上海张江.目前新工作也处于熟悉当中,希望大家能体谅.刚好这周末有点时间,我就接着写写,这篇博客主要是针对初学者,希望给为他们能提供一种较易理解的窗体皮肤制作思路,记得自己当初学习C#编程的时候也是摸着石头过河. 闲话少说,我还是接着上篇博客继续写,上次说明了下如何收集图片资源,这次就以360安全卫士来做示例进行模仿,本来也想过模仿下qq,但是qq

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

自定义皮肤很方便,基础开发的工作也是很大的.不过还好一般产品真正需要开发的并不是很多.现在比较漂亮的界面产品都会有个大大的工具条. Toolbar工具条实现皮肤的方法还是可以使用Form的处理方案.每当重复写相同东西的时候,有时会感觉无聊.所以想简单实现个轻量级的,依葫芦画瓢进行减肥. 完成后大致的效果 这个简易Toolbar只实现了Button样式,没有分割线没有下拉多选之类的样式. ”这么弱的东西有毛用?“ 其实这个工具条主要目的是用于附着在其他控件上使用,比如某些控件的标题区域位置.当然如

从下往上增加的柱状图生成动画(适用于统计类应用)

我们在一些统计,理财应用中,经常能看到很多的柱状图,用来直观的标注信息,最近一个朋友刚好在做这方面的应用,跑来问我这个怎么实现,我笑他不就是简单的实现一个动画嘛,额,然后自己去做的时候才发现各种坑. 1.所有的UIView子类中,UILabel不能实现效果 2.创建View和对View实现的动画效果要放在一个方法中 3.增加的height和减少的top(顶部y坐标)必须成2倍关系 或者 增加的height和增加的bottom(底部y坐标)必须成2倍关系 @最后,直接上代码,大家可以去试验下,我也

在iOS上增加手势锁屏、解锁功能

在iOS上增加手势锁屏.解锁功能 在一些涉及个人隐私的场景下,尤其是当移动设备包含太多私密信息时,为用户的安全考虑是有必要的. 桌面版的QQ在很多年前就考虑到用户离开电脑后隐私泄露的危险,提供了“离开电脑自动锁定”或者“闲置锁定”等类似功能,具体我也忘了. 而在iPhone版的QQ上,也提供了手势锁的功能.如下图: 我在上一篇博文中简单提到如何根据手指移动画线条,而这里是进一步的版本,仍然只是粗糙原型: 具体的代码实现如下: [cpp]  //  //  ViewController.m  //

AIX上增加逻辑卷时报错误0516-787 extendlv: Maximum allocation for logical volume

AIX上增加逻辑卷时报错误0516-787 extendlv: Maximum allocation for logical volume jdelv02 is 512. 在往aix使用chfs -a size=xx /fs 命令增加逻辑卷的时候,有时候增加到一定大小的时候,会报出0516-787 extendlv: Maximum allocation for logical volume jdelv02 is 512.这样的一个错误原因主要是因为在默认情况下的逻辑卷允许的最大逻辑分区数为51

在标题栏上显示目录完整路径

  (http://mac.linsheng.me/archives/515.html) 当我们使用Finder浏览文件的时候,当前目录的名字会显示在标题栏顶端的中央.不过美中不足的是,在显示目录名称的时候,并没有显示出文件夹的完整路径,这样我们就很难知道当前目录的准确位置.解决这个问题的一个方案是使用路径栏,而另外一个小窍门就是让标题栏上直接显示出目录的完整路径. 方法非常简单,打开“终端”,输入下面的命令就可以了: 第一步:defaults write com.apple.finder _F