窗体边框自绘问题,快崩溃了

superzxf 2009-12-11 11:17:09
想要对窗体的边框(非客户区)进行自绘制,替换系统的默认绘制,比如对标题栏和最大化最小化关闭等按钮进行自定义外观和大小
网上能查到的通常的做法都是把窗体设为无边框FormBorderStyle = FormBorderStyle.None,然后在客户区绘制模拟边框,只需重载OnPaint就可以,但这种做法有个严重的缺陷就是设成无边框后,窗体的实际客户区和窗体的大小是一致的,也就是看上去客户区在中间,但往其中放入一个控件,把控件的Dock设成Fill看看,是不是控件把整个的窗体都遮住了?这是因为在客户区重绘只是欺骗了用户的肉眼,系统本身并不认为你画的边框就真的是边框,系统认为所有的内容都是客户区的内容,而且这种解决方法没法实现系统菜单(点击窗体左上角的图标显示的菜单),所以这种解决方法是不完善的。
后来在这种方法的基础上找到第二种方法,就是重载WndProc,对WM_NCCALCSIZE消息进行处理,修改客户区的大小,告诉系统真正的客户区大小,这样处理后貌似问题都解决了,但发现这样处理后和.net本身的窗口缩放机制不能很好的兼容,比如控件的Anchor属性可以设置4个方向的锚定,一旦设置了右侧和下方的锚定,每次在设计器中把窗口关闭再打开,发现控件的位置都会向左和向上移动,原因就是系统还是把窗体的原边界作为锚定的基准,修改了客户区后导致这个锚定出现错误,最终无法在设计器中对布局进行调整,重载了SetClientSizeCore等方法后也不能解决,最终只能放弃该方法。
后来又找到第三种解决方法,不设置窗体边框为None,直接重载WndProc,对WM_NCPAINT消息进行处理,实现自己的非客户区重绘,但搞了很长时间都没办法把系统的默认绘制完全替换,表面上看上去是替换了,但只要做某些操作(比如点击窗体的最大化最小化),系统本身的按钮就又会显示出来,整个画面被破坏,也就是说虽然自己绘制过程替换了系统的绘制过程,但系统原先绘制按钮的位置系统还是认为是原来的按钮,当鼠标点击上去后,系统又把对应的按钮给显示出来了,这个问题始终没法彻底解决,网上也找不到更详细的描述。
如何完美的解决自定义的边框?要求能够在设计和运行时都和原Form的处理保持一致。

...全文
772 14 打赏 收藏 转发到动态 举报
写回复
用AI写文章
14 条回复
切换为时间正序
请发表友善的回复…
发表回复
hkstb 2010-09-17
  • 打赏
  • 举报
回复
是要学习学习。
jbo126 2009-12-29
  • 打赏
  • 举报
回复
[Quote=引用 12 楼 yangglemu 的回复:]
http://yangglemu.download.csdn.net/
我已全部上传到CSDN资源,请下载去吧
希望能对你有所作用
[/Quote]
大姐,你真是好人!!我也去!!
  • 打赏
  • 举报
回复
http://yangglemu.download.csdn.net/
我已全部上传到CSDN资源,请下载去吧
希望能对你有所作用
  • 打赏
  • 举报
回复
我晕,注意到我对return的重点说明没有?
你用了return,就没有系统绘的按钮了
你肯定是base.Wndproc了,你按我上面的主意,用return试试,
如果你用了 return,没有自已的绘制函数加进去,那么窗体的边框都没有了,何来三个按钮

再不懂我就发源码了,保证没有你说的那些Bug
我不知道在这个论坛如何发图牌和源码(文件)

其实你的问题出现的根本原因是你和系统都在对非区进行绘制,所以有时候显示出来是你绘制的,有时候是系统在绘制,完全随机了
如果你还不明白,加我QQ304402075,我演示给你看
Jave.Lin 2009-12-12
  • 打赏
  • 举报
回复
mark study up...
superzxf 2009-12-12
  • 打赏
  • 举报
回复
[Quote=引用 6 楼 yangglemu 的回复:]
protected override void WndProc(ref Message m)
        {
            switch (m.Msg)
            {
                case WM_NCPAINT:
                    DrawTitle();//自画非客户区
                    DrawTitleButton("Min");自画最小化按钮
                    DrawTitleButton("Max");自画最大化按钮
                    DrawTitleButton("Close");自画关闭按钮
                    return;//注意这里是关键技术点,用了return,函数直接返回,意思就是不让系统画按钮啥的了,如果是用base.WndProc(ref m),则系统还是会绘制边框及按钮等非客户区内容的,这不是我们需要的,所以我们直接Return了
case WM_NCCALCSIZE://
                    //获取窗体的Rectangle
                    Rectangle r = (Rectangle)(m.GetLParam(typeof(Rectangle)));
                    int X = r.Left + 10;//左非框往右扩展10个像素
                    int Y = r.Top + 20;//上非框往下扩展20个像素
                    int W = r.Width - 10;//
                    int H = r.Height - 10;
                    // r就是我需要的新的窗体尺寸(包括非区和客户区尺寸)
                    r = new Rectangle(X, Y, W, H);
                    //用新的r替换m里面的参数
                    Marshal.StructureToPtr(r, m.LParam, true);
                    //最后记得要调用基类WndProc函数处理m
                    base.WndProc(ref m)
这样系统就会以我修改后的尺寸绘制窗体了



private void DrawTitle()
        {
            IntPtr hDC = WinAPIs.GetWindowDC(mainForm.Handle);
            Graphics g = Graphics.FromHdc(hDC);
这个Graphics能在非客户区绘图,可以用它绘制Logo等,记得是以窗体左上角(0,0)为起点(包括非客户区)
……
DrawTitleButton……(省略绘制按钮的函数)
问一下,如何在本论坛插入图片?



[/Quote]

这个方法我也试过,但不能保证在所有情况下都能正确绘制,在某些特定的情况下,系统默认的右上角的三个按钮老是会显示出来
现在需要做到在windows2000、windowsxp、windowsxp(关闭主题)等各种情况下都能显示正确的边框。
不知道像office2007之类的是如何做到的,这类程序用spy++查看会发现并不是把边框设成None的
superzxf 2009-12-12
  • 打赏
  • 举报
回复
[Quote=引用 4 楼 starj1 的回复:]
LZ,用过SplitContainer没有?
把SplitContainer的Orietation设成Horizontal,再把FixPanel设成Panel1,把IsSplitterFixed设成True。不就什么都解决了?
[/Quote]

通过控件的方法不可行,因为代码和原先的不兼容,需要实现的是仅仅修改原窗体类的基类就能更换整个的界面,不需要对其中的代码或者位置尺寸做任何调整
  • 打赏
  • 举报
回复
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_NCPAINT:
DrawTitle();//自画非客户区
DrawTitleButton("Min");自画最小化按钮
DrawTitleButton("Max");自画最大化按钮
DrawTitleButton("Close");自画关闭按钮
return;//注意这里是关键技术点,用了return,函数直接返回,意思就是不让系统画按钮啥的了,如果是用base.WndProc(ref m),则系统还是会绘制边框及按钮等非客户区内容的,这不是我们需要的,所以我们直接Return了
case WM_NCCALCSIZE://
//获取窗体的Rectangle
Rectangle r = (Rectangle)(m.GetLParam(typeof(Rectangle)));
int X = r.Left + 10;//左非框往右扩展10个像素
int Y = r.Top + 20;//上非框往下扩展20个像素
int W = r.Width - 10;//
int H = r.Height - 10;
// r就是我需要的新的窗体尺寸(包括非区和客户区尺寸)
r = new Rectangle(X, Y, W, H);
//用新的r替换m里面的参数
Marshal.StructureToPtr(r, m.LParam, true);
//最后记得要调用基类WndProc函数处理m
base.WndProc(ref m)
这样系统就会以我修改后的尺寸绘制窗体了




private void DrawTitle()
{
IntPtr hDC = WinAPIs.GetWindowDC(mainForm.Handle);
Graphics g = Graphics.FromHdc(hDC);
这个Graphics能在非客户区绘图,可以用它绘制Logo等,记得是以窗体左上角(0,0)为起点(包括非客户区)
……
DrawTitleButton……(省略绘制按钮的函数)
问一下,如何在本论坛插入图片?



superzxf 2009-12-12
  • 打赏
  • 举报
回复
自己画完就直接return了,多数情况下都没有问题,但偶尔系统还是会显示原先的三个按钮,比如在标题栏上点击鼠标右键,但奇怪的是不是每次都出现,偶尔会出现个一两次
如果有源码可以发我试一下,wxzhangxf@gmail.com,先谢了
dylike 2009-12-12
  • 打赏
  • 举报
回复
比如好的方法仍然是设置为无边框,标题栏部分进行自绘.需要注意的是每个控件坐标需要加上你的非客户区的宽度和高度.
starj1 2009-12-12
  • 打赏
  • 举报
回复
LZ,用过SplitContainer没有?
把SplitContainer的Orietation设成Horizontal,再把FixPanel设成Panel1,把IsSplitterFixed设成True。不就什么都解决了?
superzxf 2009-12-12
  • 打赏
  • 举报
回复
[Quote=引用 1 楼 yangglemu 的回复:]
你这个需求,我前段时间无聊刚好研究过,通过Hook及截取Win消息对Form边框进行重绘(改变边框大小尺寸及颜色)完全就是你要的那样

并且做了个换肤及卸载皮肤的功能,后来心情好,还做了个皮肤生产器,
我在BCCN曾经传过源码(才被版主+20分)
不过这段时间BCCN因为某种原因暂停了,过几天开了给个网址你去看看

如果要的急,把分给我,我再做出来(我电脑没有保存)
[/Quote]

我把相关的几个消息都拦截了,还是不能彻底解决,偶尔还会时不时的冒出原先的几个按钮(最大化最小化和关闭),比如在窗体的任务栏图标上点击鼠标右键,弹出系统菜单时就会时不时的显示,而且没有什么规律
WM_NCHITTEST
WM_NCLBUTTONDOWN
WM_NCLBUTTONDBLCLK
WM_NCMOUSEMOVE
WM_NCPAINT
WM_NCACTIVATE
WM_NCCALCSIZE
WM_SYSCOMMAND
不知道还有其他的什么消息可能会导致系统重绘那几个按钮的了?
wuyq11 2009-12-11
  • 打赏
  • 举报
回复
重绘标题栏,在标题栏加按钮。
参考
protected override void WndProc(ref Message msg)
{
const int WM_SYSCOMMAND = 0x0112;
const int SC_CLOSE = 0xF060;
if (msg.Msg == WM_SYSCOMMAND && ((int)msg.WParam == SC_CLOSE))
{
return;
}
base.WndProc(ref msg);
}
  • 打赏
  • 举报
回复
你这个需求,我前段时间无聊刚好研究过,通过Hook及截取Win消息对Form边框进行重绘(改变边框大小尺寸及颜色)完全就是你要的那样

并且做了个换肤及卸载皮肤的功能,后来心情好,还做了个皮肤生产器,
我在BCCN曾经传过源码(才被版主+20分)
不过这段时间BCCN因为某种原因暂停了,过几天开了给个网址你去看看

如果要的急,把分给我,我再做出来(我电脑没有保存)

110,502

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧