窗体皮肤实现 - 重绘窗体非客户区(一)

现在皮肤控件也很多,但每次装一堆控件,使用又繁琐。稍微研究一下内部机制,还是比较简单的。

主要会使用到下面几个消息

 1 const
 2     WM_NCUAHDRAWCAPTION = $00AE;
 3     WM_NCUAHDRAWFRAME = $00AF;
 4
 5 // 绘制非客户区消息
 6 procedure WMNCPaint(var message: TWMNCPaint); message WM_NCPAINT;
 7 // 在激活程序时需要相应的消息
 8 procedure WMNCActivate(var Message: TMessage); message WM_NCACTIVATE;
 9 // 鼠标按下时需要控制系统响应绘制
10 procedure WMNCLButtonDown(var Message: TWMNCHitMessage); message WM_NCLBUTTONDOWN;
11 // 下面这2个消息是Windows内部Bug处理,直接屏蔽处理(winxp下有)
12 procedure WMNCUAHDrawCaption(var Message: TMessage); message WM_NCUAHDRAWCAPTION;
13 procedure WMNCUAHDrawFrame(var Message: TMessage); message WM_NCUAHDRAWFRAME;

第一步直接覆盖WM_NCPAINT 消息进行外边框绘制。

会发现有2个问题:

1、点击右上角的系统按钮区域会出现系统按钮

2、当切换程序的时候窗体会恢复默认样式。

需要处理WM_NCACTIVATE 和 WM_NCLBUTTONDOWN 这两个消息,解决上面2个问题。

如果你是Win7或以上,那么恭喜!埋伏了个Bug。在WinXP下使用Spy++会出现下面消息

1 <00003> 00140124 S WM_NCHITTEST xPos:557 yPos:182
2 <00004> 00140124 R WM_NCHITTEST nHittest:HTTOPRIGHT
3 <00005> 00140124 S WM_SETCURSOR hwnd:00140124 nHittest:HTTOPRIGHT wMouseMsg:WM_MOUSEMOVE
4 <00006> 00140124 S message:0x00AE [未知] wParam:00001000 lParam:00000000
5 <00007> 00140124 R message:0x00AE [未知] lResult:00000000
6 <00008> 00140124 R WM_SETCURSOR fHaltProcessing:True
7 <00009> 00140124 P WM_NCMOUSEMOVE nHittest:HTTOPRIGHT xPos:557 yPos:182

Message:0x00AE 这个隐秘的消息,会让系统按钮重现江湖。网上查了下,是Windows的Bug。由于是自己控制绘制,所以直接可以丢弃此消息。另外还有个0x00AF的消息也一样处理。

通过上面5个消息,基本实现非客户区的绘制。现在怎么动都不会出现恢复系统样式我问题。

有全白的是正好切换到记事本,里面没内容。

  1 unit ufrmCaptionToolbar;
  2
  3 interface
  4
  5 uses
  6   Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  7   Types, Vcl.Controls, Vcl.Forms, Vcl.Dialogs;
  8
  9 type
 10   TTest = class
 11   strict private const
 12     WM_NCUAHDRAWCAPTION = $00AE;
 13     WM_NCUAHDRAWFRAME = $00AF;
 14   private
 15     FControl: TWinControl;
 16     //FFormActive: Boolean;
 17     FHandled: Boolean;
 18
 19     function  GetHandle: HWND;
 20     function GetForm: TCustomForm; inline;
 21
 22     procedure WMNCPaint(var message: TWMNCPaint); message WM_NCPAINT;
 23     procedure WMNCActivate(var Message: TMessage); message WM_NCACTIVATE;
 24     procedure WMNCUAHDrawCaption(var Message: TMessage); message WM_NCUAHDRAWCAPTION;
 25     procedure WMNCUAHDrawFrame(var Message: TMessage); message WM_NCUAHDRAWFRAME;
 26     procedure WMNCLButtonDown(var Message: TWMNCHitMessage); message WM_NCLBUTTONDOWN;
 27
 28     procedure WndProc(var message: TMessage);
 29   protected
 30     property Handle: HWND read GetHandle;
 31     procedure InvalidateNC;
 32     procedure PaintNC(ARGN: HRGN = 0);
 33   public
 34     constructor Create(AOwner: TWinControl);
 35     property Handled: Boolean read FHandled write FHandled;
 36     property Control: TWinControl read FControl;
 37     property Form: TCustomForm read GetForm;
 38   end;
 39
 40   TForm11 = class(TForm)
 41   private
 42     FTest: TTest;
 43   protected
 44     function DoHandleMessage(var message: TMessage): Boolean;
 45     procedure WndProc(var Message: TMessage); override;
 46   public
 47     constructor Create(AOwner: TComponent); override;
 48     destructor Destroy; override;
 49   end;
 50
 51 var
 52   Form11: TForm11;
 53
 54 implementation
 55
 56 {$R *.dfm}
 57
 58 { TForm11 }
 59
 60 constructor TForm11.Create(AOwner: TComponent);
 61 begin
 62   FTest := TTest.Create(Self);
 63   inherited;
 64 end;
 65
 66 destructor TForm11.Destroy;
 67 begin
 68   inherited;
 69   FreeAndNil(FTest);
 70 end;
 71
 72 function TForm11.DoHandleMessage(var message: TMessage): Boolean;
 73 begin
 74   FTest.WndProc(message);
 75   Result := FTest.Handled;
 76 end;
 77
 78 procedure TForm11.WndProc(var Message: TMessage);
 79 begin
 80   if not DoHandleMessage(Message) then
 81     inherited;
 82 end;
 83
 84 constructor TTest.Create(AOwner: TWinControl);
 85 begin
 86   FControl := AOwner;
 87 end;
 88
 89 function TTest.GetForm: TCustomForm;
 90 begin
 91   Result := TCustomForm(Control);
 92 end;
 93
 94 function TTest.GetHandle: HWND;
 95 begin
 96   if FControl.HandleAllocated then
 97     Result := FControl.Handle
 98   else
 99     Result := 0;
100 end;
101
102 procedure TTest.InvalidateNC;
103 begin
104   if FControl.HandleAllocated then
105     SendMessage(Handle, WM_NCPAINT, 1, 0);
106 end;
107
108 procedure TTest.PaintNC(ARGN: HRGN = 0);
109 var
110   DC: HDC;
111   Flags: cardinal;
112   hb: HBRUSH;
113   P: TPoint;
114   r: TRect;
115 begin
116   Flags := DCX_CACHE or DCX_CLIPSIBLINGS or DCX_WINDOW or DCX_VALIDATE;
117   if (ARgn = 1) then
118     DC := GetDCEx(Handle, 0, Flags)
119   else
120     DC := GetDCEx(Handle, ARgn, Flags or DCX_INTERSECTRGN);
121
122   if DC <> 0 then
123   begin
124     P := Point(0, 0);
125     Windows.ClientToScreen(Handle, P);
126     Windows.GetWindowRect(Handle, R);
127     P.X := P.X - R.Left;
128     P.Y := P.Y - R.Top;
129     Windows.GetClientRect(Handle, R);
130
131     ExcludeClipRect(DC, P.X, P.Y, R.Right - R.Left + P.X, R.Bottom - R.Top + P.Y);
132
133     GetWindowRect(handle, r);
134     OffsetRect(R, -R.Left, -R.Top);
135
136     hb := CreateSolidBrush($00bf7b18);
137     FillRect(dc, r, hb);
138     DeleteObject(hb);
139
140     SelectClipRgn(DC, 0);
141
142     ReleaseDC(Handle, dc);
143   end;
144 end;
145
146 procedure TTest.WMNCActivate(var Message: TMessage);
147 begin
148   //FFormActive := Message.WParam > 0;
149   Message.Result := 1;
150   InvalidateNC;
151   Handled := True;
152 end;
153
154 procedure TTest.WMNCLButtonDown(var Message: TWMNCHitMessage);
155 begin
156   inherited;
157
158   if (Message.HitTest = HTCLOSE) or (Message.HitTest = HTMAXBUTTON) or
159      (Message.HitTest = HTMINBUTTON) or (Message.HitTest = HTHELP) then
160   begin
161     //FPressedButton := Message.HitTest;
162     InvalidateNC;
163     Message.Result := 0;
164     Message.Msg := WM_NULL;
165     Handled := True;
166   end;
167 end;
168
169 procedure TTest.WMNCPaint(var message: TWMNCPaint);
170 begin
171   PaintNC(message.RGN);
172   Handled := True;
173 end;
174
175 procedure TTest.WMNCUAHDrawCaption(var Message: TMessage);
176 begin
177   ///  这个消息会在winxp下产生,是内部Bug处理,直接丢弃此消息
178   Handled := True;
179 end;
180
181 procedure TTest.WMNCUAHDrawFrame(var Message: TMessage);
182 begin
183   ///  这个消息会在winxp下产生,是内部Bug处理,直接丢弃此消息
184   Handled := True;
185 end;
186
187 procedure TTest.WndProc(var message: TMessage);
188 begin
189   FHandled := False;
190   Dispatch(message);
191 end;
192
193 end.

全部代码

时间: 2024-11-25 18:50:46

窗体皮肤实现 - 重绘窗体非客户区(一)的相关文章

窗体皮肤实现 - 重绘窗体非客户区(三)

窗体边框基本的绘制和控制完成,在第二篇中主要遗留的问题. 标题区域图标和按钮没绘制 缩放时客户区显示有问题 解决完下面的问题,皮肤处理基本完整.大致的效果GIF GIF中TShape的颜色表现有些问题,实际是正常的. 绘制标题区域内容 获取标题有效区域 绘制窗体图标 绘制按钮 绘制标题 标题区域主要考虑窗体是否在最大化状态,最大化后实际的标题绘制区域会有变化.可以通过 IsZoomed 或 GetWindowLong(Handle, GWL_STYLE) and WS_MAXIMIZE = WS

窗体皮肤实现 - 重绘窗体非客户区(二)

第一个实现了基本处理.窗体边框的宽度有些肥大,需要进行瘦身. 实现:     1.改变外框线宽度 (WM_NCCALCSIZE)     2.改变外框样式 (WM_WINDOWPOSCHANGING) 通过 WM_NCCALCSIZE 消息可以实现目的. procedure WMNCCalcSize(var message: TWMNCCalcSize); message WM_NCCALCSIZE; procedure TTest.WMNCCalcSize(var message: TWMNC

winform重绘窗体成圆角(网上借鉴)

winform做圆角窗体: 1 //重绘窗体为圆角 2 private void frmMain_Paint(object sender, PaintEventArgs e) 3 { 4 #region 5 6 List<Point> list = new List<Point>(); 7 int width = this.Width; 8 int height = this.Height; 9 10 #region 四个圆角 11 12 //左上 13 list.Add(new

C# 绘制窗体客户非客户区要用WM_PAINT和WM_NCPAINT

窗体分为两部分:客户区(Client area)和非客户区(Non-Client area) WM_PAINT消息.OnPaint()方法.GetDC()API函数都是处理窗体客户区绘制的   而标题栏处于非客户区中,所以WM_PAINT消息.OnPaint()方法.GetDC()API函数都用不上   GetWindowDC()是获得整个窗体的画布句柄(Device Context翻译为:设备清单,我习惯称为画布句柄),包括非客户区   GDI的绘制都离不开DC,因为操作系统必须知道你要在什么

EVT_NC_PAINT 窗口非客户区的绘制

说明 NC就是non client意思,非客户区主要包括标题栏,状态栏,工具栏和边框等区域,目前wxwidgets只是提供了EVT_NC_PAINT 唯一的一个非客户区的事件,在MFC中,还有提供WM_NCLBUTTONDOWN WM_NCLBUTTONUP WM_NCMOUSEMOVE等事件操作,如果需要关联这些事件,就必须要过滤该事件,或者在源码中实现该类事件 目前需要拖动边框的时候,客户区域不变,只是边框变大,等到边框拖动到合适的地方,鼠标弹起,客户区域重绘

窗口非客户区的绘制

1.根据需要,要改变nc区的大小. 响应WM_NCCALCSIZE可改变nc区的大小.关键在于lParam,msdn上说当wParam为True时, lParam为LPNCCALCSIZE_PARAMS的指定,当wParam为False时为LPRECT,做了些实验发现 只有第一次时wParam为False,LPNCCALCSIZE_PARAMS的语义相当复杂,不过我们只用 看第一个RECT就成.它表是窗口客户区的大小.当lParam为LPRECT时,也同样.只用改变 客户区的大小,相应的就改变了

Windows的三种坐标系:屏幕坐标系,非客户区坐标系,客户区坐标系

1. 屏幕坐标系:以屏幕的左上角为原点,如图所示GetWindowRect() 函数获得的 RECT 就是以屏幕坐标系算的. 2. 非客户区坐标系(窗口坐标系)包括标题栏的部分.GetWindowDC 返回的设备环境就是基于此坐标系,一般只在 WM_NCPAINT 消息中使用. 3. 客户区坐标系不包括标题栏,坐标的原点在标题栏下的客户区的左上角.BeginPaint 函数返回的设备环境是基于客户区坐标系的,只在 WM_PAINT 消息中使用,与 EndPaint 函数成对使用.GetDC 函数

【转】【C#】C#重绘windows窗体标题栏和边框

摘要 windows桌面应用程序都有标准的标题栏和边框,大部分程序也默认使用这些样式,一些对视觉效果要求较高的程序,如QQ, MSN,迅雷等聊天工具的样式则与传统的windows程序大不相同,其中迅雷还将他们的BOLT界面引擎开放,使得大家也可以创建类似迅雷一样的界面.那么这些软件的界面是怎样实现的呢,使用C#是否也可以实现类似界面? 重绘方式 常见的自定义标题栏和边框的方式有两种,一种是隐藏标题栏和边框(称为非客户区),然后在客户区(可以放置控件的空间)使用一些常用的控件和图片来表示边框,这种

Winform 自定义窗体皮肤组件

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