随笔 - 44  文章 - 0 评论 - 321 trackbacks - 116
<2009年7月>
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678

本博客上的所有文章如非特别说明均为原创,如果要转载请注明文章出处。

与我联系

搜索

 

常用链接

留言簿

我参加的小组

我参与的团队

随笔分类(40)

随笔档案(44)

文章分类

联系我

友情链接

积分与排名

  • 积分 - 198936
  • 排名 - 236

最新评论

阅读排行榜

评论排行榜

       最近学习制作WinForm控件,自己动手写控件的时候才发现System.Windows.Forms.Control 竟然没有提供默认的border绘制。记得以前用API做控件的时候,只需要设置空间窗口的WS_BORDER 风格就可以。遍寻无方,只有自己绘制了,这里有出现一个,如果border在客户区,那么在OnPaint方法里不得不每次都要考虑Border所占用的区域,而且,如果从这个类派生的话,将无法获得准确的客户区。
      现在要解决的问题就是如何重新设置客户区的矩形区域的尺寸,查看了一下Control类的ClientRectangle属性:
public Rectangle ClientRectangle { get; }是个只读属性,看来是不能通过这个属性达到目的了。再查找Control类的文档,也没有这方面的说明,没有办法,只能用API搞定了。可以通过计算非客户区尺寸来设置客户区尺寸,Border在非客户绘制。下面就是主要的代码,就是通过重载WndProc方法,捕捉WM_NCCALCSIZE消息,实现自己的逻辑。
     
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case (int)WinAPI_WM.WM_NCCALCSIZE:
if (m.WParam.ToInt32() == 0)
{
WinAPI_RECT rc 
= (WinAPI_RECT)m.GetLParam(typeof(WinAPI_RECT));
rc.Left 
+= 1;
rc.Top 
+= 1
rc.Right 
-= 1
rc.Bottom 
-= 1;
Marshal.StructureToPtr(rc, m.LParam, 
true);
m.Result 
= IntPtr.Zero;
}

else
{
WinAPI_NCCALCSIZE_PARAMS csp;
csp 
= (WinAPI_NCCALCSIZE_PARAMS)m.GetLParam(typeof(WinAPI_NCCALCSIZE_PARAMS));
csp.rgrc0.Top 
+= 1
csp.rgrc0.Bottom 
-= 1;
csp.rgrc0.Left 
+= 1
csp.rgrc0.Right 
-= 1;

Marshal.StructureToPtr(csp, m.LParam, 
true);
//Return zero to preserve client rectangle
m.Result = IntPtr.Zero;
}

break;
case (int)WinAPI_WM.WM_NCPAINT:
{
m.WParam 
= NCPaint(m.WParam);
break;
}

}


base.WndProc(ref m);
}


public IntPtr NCPaint(IntPtr region)
{
IntPtr hDC 
= GetWindowDC(this.Handle);
if (hDC != IntPtr.Zero)
{
Graphics grTemp 
= Graphics.FromHdc(hDC);

int ScrollBarWidth = SystemInformation.VerticalScrollBarWidth;
int ScrollBarHeight = SystemInformation.HorizontalScrollBarHeight;

WINDOWINFO wi 
= new WINDOWINFO();
wi.cbSize 
= (uint)Marshal.SizeOf(wi);

//得到当前控件的窗口信息
GetWindowInfo(Handle, ref wi);

wi.rcClient.Right
--;
wi.rcClient.Bottom
--;


//获得当前控件的区域
Region UpdateRegion = new Region(new Rectangle(wi.rcWindow.Top,wi.rcWindow.Left,wi.rcWindow.Right-wi.rcWindow.Left,wi.rcWindow.Bottom-wi.rcWindow.Top));

//获得客户区以外的区域
UpdateRegion.Exclude(new Rectangle(wi.rcClient.Top, wi.rcClient.Left, wi.rcClient.Right - wi.rcClient.Left, wi.rcClient.Bottom - wi.rcClient.Top));

if (IsHScrollVisible && IsVScrollVisible)
{
UpdateRegion.Exclude(Rectangle.FromLTRB
(wi.rcClient.Right 
+ 1, wi.rcClient.Bottom + 1,
wi.rcWindow.Right, wi.rcWindow.Bottom));
}


//得到当前区域的句柄
IntPtr hRgn = UpdateRegion.GetHrgn(grTemp);

//For Painting we need to zero offset the Rectangles.
Rectangle WindowRect = new Rectangle(wi.rcWindow.Top, wi.rcWindow.Left, wi.rcWindow.Right - wi.rcWindow.Left, wi.rcWindow.Bottom - wi.rcWindow.Top);

Point offset 
= Point.Empty - (Size)WindowRect.Location;

WindowRect.Offset(offset);

Rectangle ClientRect 
= WindowRect;

ClientRect.Inflate(
-1-1);

//Fill the BorderArea
Region PaintRegion = new Region(WindowRect);
PaintRegion.Exclude(ClientRect);
grTemp.FillRegion(SystemBrushes.Control, PaintRegion);

//Fill the Area between the scrollbars
if (IsHScrollVisible && IsVScrollVisible)
{
Rectangle ScrollRect 
= new Rectangle(ClientRect.Right - ScrollBarWidth,
ClientRect.Bottom 
- ScrollBarHeight, ScrollBarWidth + 2, ScrollBarHeight + 2);
ScrollRect.Offset(
-1-1);
grTemp.FillRectangle(SystemBrushes.Control, ScrollRect);
}


//Adjust ClientRect for Drawing Border.
ClientRect.Inflate(22);
ClientRect.Width
--;
ClientRect.Height
--;

//Draw Outer Raised Border
ControlPaint.DrawBorder3D(grTemp, WindowRect, Border3DStyle.Raised,
Border3DSide.Bottom 
| Border3DSide.Left | Border3DSide.Right | Border3DSide.Top);

//Draw Inner Sunken Border
ControlPaint.DrawBorder3D(grTemp, ClientRect, Border3DStyle.Sunken,
Border3DSide.Bottom 
| Border3DSide.Left | Border3DSide.Right | Border3DSide.Top);

ReleaseDC(Handle, hDC);

grTemp.Dispose();

return hRgn;

}


RefreshScrollBar();
return region;
}


posted on 2006-11-29 22:44 纶巾客 阅读(3542) 评论(13)  编辑 收藏 网摘 所属分类: WinForm Control

FeedBack:
#1楼 2006-11-29 22:58 neuhawk      
麻烦哦,还是wpf/html方便
  回复  引用  查看    
#2楼[楼主] 2006-11-29 23:23 纶巾客      
@neuhawk
呵呵,是啊,有一些麻烦。

  回复  引用  查看    
#3楼 2006-11-30 11:29 A.Z      
真有同感...
To @neuhawk
wpf/html
也是一个基于Application的交互环境,如果让你去实现里面的任意一个组件,也许你就知道在方便的内部有多复杂了。

  回复  引用  查看    
楼主的方法麻烦和不可靠,楼主既然知道Win32API可以控制控件边框样式干嘛不用呢?可以从System.Windows.Form.Control 派生自己的控件,然后添加如下代码即可

/// <summary>
/// 边框样式
/// </summary>
protected System.Windows.Forms.BorderStyle intBorderStyle = System.Windows.Forms.BorderStyle.Fixed3D ;
/// <summary>
/// 边框样式
/// </summary>
public System.Windows.Forms.BorderStyle BorderStyle
{
get{ return intBorderStyle;}
set{ intBorderStyle = value; this.RecreateHandle();}
}

/// <summary>
/// 初始化创建控件的参数对象
/// </summary>
protected override System.Windows.Forms.CreateParams CreateParams
{
get
{
System.Windows.Forms.CreateParams p = base.CreateParams ;
switch(intBorderStyle)
{
case System.Windows.Forms.BorderStyle.None :
break;
case System.Windows.Forms.BorderStyle.Fixed3D:
p.ExStyle = p.ExStyle | 0x00000200 ;
break;
case System.Windows.Forms.BorderStyle.FixedSingle :
p.ExStyle = p.ExStyle | 0x00020000 ;
break;
}
return p;
}
}

  回复  引用  查看    
#5楼[楼主] 2006-11-30 23:04 纶巾客      
@新型报表工具 xdesigner
非常感谢你的关注,你所提供的方法我还没有使用过,有空试试。其实设置控件的Border只是一个引子,我写这篇文章的目标正如题目所现实,是为了“设置Winform控件的ClientRectangle”,因为有时候我们确实有改变客户区矩形的需求,比如添加滚动条,又不想将滚动条算在客户区内。我的方法虽然显得麻烦,但是并非不可靠,我已经在多个控件中运用。不过到目前位置我还没有找到更好的改变ClientRectangle的方法。如果 新型报表工具 xdesigner兄有更好的方法,别忘了给我说一声,共同提高,谢谢。

  回复  引用  查看    
怎么不用ControlPaint.DrawBorder3D方法呐
  回复  引用  查看    
#7楼[楼主] 2006-12-04 22:30 纶巾客      
@青青子衿,悠悠我心
我的方法里用的就是ControlPaint.DrawBorder3D,只是我们讨论的不是如何绘制,而是在哪里绘,如何改变客户区的大少。

  回复  引用  查看    
不错
上面几位兄弟可能对ClientRectangle(客户区)认识不足

大家可以搜索一下 开源项目 ThemeKit

一个窗体是有客户区和边框构成的
我们画界面 也仅仅是画在客户区里而已
边框在.net里面是无法自定义的,边框是由系统画出来的(随操作系统以及主题的不同而不同)
WinForm的width以及height属性都是客户区+边框的结果
实际上 我们能够绘制的区域是ClientSize 属性
一般会比Width和Height少两个象素

通过处理WinAPI_WM.WM_NCCALCSIZE消息
可以改变ClientRectangle(客户区)的大小 这样 我们就可以完全控制我们的界面了
而且继承后的类也不会出现定位问题

  回复  引用    
可能有人会说 我把Border设为none不就行了嘛!

这样做,窗体的其他功能(拉伸 拖动 等等)没有了,窗体最大化也会出现问题(覆盖任务栏),当然也可以通过处理窗体消息来解决,但是太麻烦了,而且自己模拟拉伸拖动的时候还不能启用双缓冲(拖动过程中的虚线会被窗体覆盖).

本人在这方面做了很多尝试 还是觉得 处理WinAPI_WM.WM_NCCALCSIZE消息是自定义界面的最佳方案!

如有不对 请指正!

下面这篇 卢彦 的垃圾文章 大家千万别学着做,否则后悔死你

使用Visual C#制作可伸缩个性化窗体
http://www.microsoft.com/china/community/program/originalarticles/techdoc/csharpui.mspx

  回复  引用    
WM_NCCALCSIZE不知道你试过没有,这里有个问题.就是窗口会不停的变小.你自己试下.就不停的点状态栏的标题让程序不停的在最小化和还原之间切换.就会出现我说的这个问题.用C++这样写就没有问题.不知道是为什么.你的程序是不是会有这个问题.如果没有,麻烦你加我的QQ一起讨论下这个问题.78702425
  回复  引用    
#11楼 2009-01-14 13:39 HeroBeast      
楼主的观点是对的,支持楼主,希望楼主能公开一下你的代码,我们大家学习学习!
  回复  引用  查看    
#12楼 2009-04-11 13:25 Kitfa      
你好,我想问一下,我在TextBox.Controls添加了一Label,想把Label放到TextBox的输入框以后,就有点像你说的放到非客户区里,但我发现按你上面代码编写后Label不能响应消息了,包括重绘。希望你能帮帮忙。我的联系E-Mail:Kitfa@163.com。感激不尽。对于WINFORM我也有浓厚的兴趣,希望与你交流。
  回复  引用  查看    



发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 576961




相关文章:

相关链接: