稍微改造一下,让交互性更好点。增加提示和动态效果。

控件实现内容:

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

实现方案:

  • 1、基类选用
  • 2、Action的关联
  • 3、绘制按钮
  • 4、鼠标响应
  • 5、美化(淡入淡出简易动画) OK~完成

一、基类选择

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

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

二、关联Action

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

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

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

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

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

处理三件事情:
1、检测容器容量,不够增加
2、清空第Count位的Record值(清零)。这步其实对Record比较重要,如果记录中增加参数值时...给你来个随机数那就比较郁闷了。
3、填充记录
4、重算尺寸并重新绘制

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

三、绘制按钮

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

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

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

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

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

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

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

处理过程:
1、是否要绘制,隐藏跳过
2、根据鼠标事件状态绘制按钮底纹。(按钮在Hot状态还是鼠标按下状态)
3、获得Action的图标,在2的基础上绘制。

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

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

  1. function GetActionState(Idx: Integer): TSkinIndicator;
  2. begin
  3. Result := siInactive;
  4. if (Idx = FPressedIndex) then
  5. Result := siPressed
  6. else if (Idx = FHotIndex) and (FPressedIndex = -1) then
  7. Result := siHover;
  8. end;

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

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

然后就是直接填充颜色

  1. procedure DrawStyle(DC: HDC; const R: TRect; AColor: Cardinal); inline;
  2. var
  3. hB: HBRUSH;
  4. begin
  5. hB := CreateSolidBrush(AColor);
  6. FillRect(DC, R, hB);
  7. DeleteObject(hB);
  8. end;

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

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

  1. class procedure TTreeViewSkin.DrawIcon(DC: HDC; R: TRect; ASrc: TBitmap; const
  2. Opacity: Byte = 255);
  3. var
  4. iXOff: Integer;
  5. iYOff: Integer;
  6. begin
  7. ///
  8. /// 绘制图标
  9. /// 绘制图标是会作居中处理
  10. iXOff := r.Left + (R.Right - R.Left - ASrc.Width) div 2;
  11. iYOff := r.Top + (r.Bottom - r.Top - ASrc.Height) div 2;
  12. DrawTransparentBitmap(ASrc, 0, 0, DC, iXOff, iYOff, ASrc.Width, ASrc.Height, Opacity);
  13. end;

四、鼠标事件响应

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

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

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

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

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

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

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

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

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

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

到点刷新界面,在Timer中增加褪色刷新处理

  1. // 是褪色定时器,那么刷新界面
  2. if message.TimerID = TIMID_FADE then
  3. UpdateFade;

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

  1. procedure TmtCustomToolbar.UpdateFade;
  2. var
  3. I: Integer;
  4. bHas: boolean;
  5. begin
  6. bHas := False;
  7. for I := 0 to FCount - 1 do
  8. if FItems[I].Visible and FItems[I].Enabled then
  9. begin
  10. // 设置褪色值
  11. // 鼠标:当前Button,那么趋向不透明(255)
  12. // 不再当前位置,趋向透明(0)
  13. if FHotIndex = I then
  14. FItems[I].Fade := GetShowAlpha(FItems[I].Fade)
  15. else if FItems[I].Fade > 0 then
  16. FItems[I].Fade := GetFadeAlpha(FItems[I].Fade);
  17. bHas := bHas or (FItems[I].Fade > 0);
  18. end;
  19. Invalidate;
  20. if not bHas and HandleAllocated then
  21. KillTimer(Handle, TIMID_FADE);
  22. end;

完成啦~

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

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

其实这个工具条主要目的是用于附着在其他控件上使用,比如某些控件的标题区域位置。当然如果想要搞的强大,那么代码量肯定会膨胀。但在实际项目中使用往往都比较简单,并不需要太过复杂的功能,和做通过控件是完全不同的概念。

到此 窗体皮肤实现 的基本处理完结。客户区实际会备停靠框架挤占,所以客户木内容。

后续会介绍一些其他控件的实现。由于某些原有,代码会直接使用C/C++写,不再使用Delphi

--

开发环境:

  • Delphi XE3
  • Win7

完整源代码:

窗体皮肤实现 - 增加Toolbar的交互性的更多相关文章

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

    自定义皮肤很方便,基础开发的工作也是很大的.不过还好一般产品真正需要开发的并不是很多.现在比较漂亮的界面产品都会有个大大的工具条. Toolbar工具条实现皮肤的方法还是可以使用Form的处理方案.每 ...

  2. C#做窗体皮肤

    网上有很好的皮肤控件 SkinEnigne可供使用: 具体步骤: 添加控件SkinEngine. 1.右键“工具箱”.“添加选项卡”,取名“皮肤”. 2.右键“皮肤”,“选择项”弹出对话框. 3.点击 ...

  3. Winform 自定义窗体皮肤组件

    分享一个很久之前写的一个Winform换肤组件. 主要利用CBT钩子,NativeWindow来实现.可实现动态换皮肤插件修改窗体显示外观. 我们先定义一个自定义组件 using Skin; usin ...

  4. 窗体皮肤实现 - 在VC中简单实现绘制(五)

    到第四部分Delphi XE3的代码能基本完成窗体界面的绘制.窗口中的其他控件的处理方法也是相同的,截获消息处理消息. 问题这个编译出来的个头可不小.Release版本竟然2.43M,完全是个胖子.系 ...

  5. Winform 窗体皮肤美化_IrisSkin

    1 先把IrisSkin2.dll文件添加到当前项目引用(解决方案资源管理器->当前项目->引用->右键->添加引用,找到IrisSkin2.dll文件.....之后就不用我说 ...

  6. WPF DataGrid中鼠标双击某一列,弹出窗体作为(增加、修改、详细)按钮的快捷键。

    跟触发器行为有关,什么是触发器什么是行为,百度其他人写的乱七八糟的,我并不能看懂.在此先强行记忆,后知后觉,再回来理解. <i:Interaction.Triggers> <i:Ev ...

  7. Winform的窗体美化心酸路

    我想做一位狂热的程序猿粪子! 其实一直都很想做点什么,工作原因林林种种导致停止了前进的脚步. 有时会为自己的一个目标狂热,但经常发觉激情过后更多的总是为自己找借口! 最近感觉奔三将近.逐有感而发,不能 ...

  8. Extjs 窗体居中,双重窗体弹出时清除父窗体的鼠标事件

    这个是监控窗体缩放的事件 缩放中居中主要在 'beforeshow' 和 'destroy'两个事件里面监控 var EditTempWindow; Ext.EventManager.onWindow ...

  9. Winfrom皮肤样式的使用

    IrisSkin类库提供了可供我们使用的设置窗体皮肤的类,简单地说,就是给我们提供了一个皮肤引擎,通过设置皮肤引擎来达到我们想要的窗体界面. 具体的开发步骤: (1)引入IrisSkin.dll文件 ...

随机推荐

  1. Android EditText禁止换行键(回车键enter)

    在EditText所在的xml文件中,设置android:singleLine="true", 则可以禁止掉虚拟键盘: maxlength为该EditText的最大输入长度: &l ...

  2. PDO 连接与连接管理

    连接是通过创建 PDO 基类的实例而建立的.不管使用哪种驱动程序,都是用 PDO 类名. 构造函数接收用于指定数据库源(所谓的 DSN)以及可能还包括用户名和密码(如果有的话)的参数. 连接到 MyS ...

  3. poj 2251 Dungeon Master 3维bfs(水水)

    Dungeon Master Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 21230   Accepted: 8261 D ...

  4. nyoj 题目20 吝啬的国度

    吝啬的国度 时间限制:1000 ms  |  内存限制:65535 KB 难度:3   描述 在一个吝啬的国度里有N个城市,这N个城市间只有N-1条路把这个N个城市连接起来.现在,Tom在第S号城市, ...

  5. iOS自定义控件创建原理(持续更新)

    前言 因为如果要创建各种自定义控件根据需求的不同会有很多的差别,所以我就在这里,分析一些自定义控件的创建实现方法 弹出视图 1.把要弹出的视图装在一个控制器里面,自定义转场动画 2.创建一个弹出视图, ...

  6. Python之面向对象:继承

    概念:子类继承父类的属性和方法. 一个派生类(derived class)继承基类(bass class)字段和方法.继承也允许把一个派生类的对象作为一个基类对象对待. 一.单继承 :推崇.特点和使用 ...

  7. Java EE 学习(6):IDEA + maven + spring 搭建 web(2)- 配置 Spring

    参考:https://my.oschina.net/gaussik/blog/513353 注:此文承接上一文:Java EE 学习(5):IDEA + maven + spring 搭建 web(1 ...

  8. Vijos[1028]魔族密码

    风之子刚走进他的考场,就……花花:当当当当~~偶是魅力女皇——花花!!^^(华丽出场,礼炮,鲜花)风之子:我呕……(杀死人的眼神)快说题目!否则……-_-###花花:……咦~~好冷~~我们现在要解决的 ...

  9. c#中类与结构的区别 struct与class的区别

    原文发布时间为:2008-11-23 -- 来源于本人的百度文章 [由搬家工具导入] 类与结构的实例比较   类与结构的差别   如何选择结构还是类   一.类与结构的示例比较:   结构示例:    ...

  10. linux中的strip命令简介------给文件脱衣服【转】

    转自:http://blog.csdn.net/stpeace/article/details/47090255 版权声明:本文为博主原创文章,转载时请务必注明本文地址, 禁止用于任何商业用途, 否则 ...