其实本文标题说得有点大,一个窗体设计器包含的功能实在是太多而且非常复杂,网上有很多地方也讲到这方面的内容,不过基本上都是E文,http://www.codeproject.com/Articles/24385/Have-a-Great-DesignTime-Experience-with-a-Powerful该处作者实现了一个简单的Form Designer,而且附有源码,不过也只是点到为止,功能不多,还有一个老外的开源项目http://www.icsharpcode.net/OpenSource/SD/,基本上涵盖了界面设计、生成代码、编译、调试等功能,能和VS IDE有一拼,源码压缩包有几十M,复杂度可想而知,我大概看了一下这两个的源码结构,基本上都是利用微软FCL中提供的接口、类来实现的,它们主要存在System.ComponentModel,Design命名空间中,比如DesignSurface、IDesignerHost、ISelectionService等,诸位也知道,微软的东西用起来很方便,理解起来嘛,那是相当的困难,因为你根本不知道为啥要那么搞,所以本篇文章我打算试着从底层来说明“窗体可视化设计”到底是个什么东西,诸位看官如果之前了解也不妨看看,不了解的话也没事,基本上涉及到的都是平时用到的技术,不涉及到System.ComponentModel,Design中的任何东西。文章最后附带Demo源码下载地址。
我不知道窗体设计器对诸位来说是个什么样的概念,从开始学习可视化编程时,我们就开始接触了它,就我来说,用过VC6.0、VB以及现在的VS,基本上都是一样,在界面上拖拖控件,写写事件处理程序,编译后就能运行一个简单功能的Windows桌面应用程序了,但是,有以下疑问(Winform为例):
暂时先列这么几个问题,其实还有很多,只是其他的本文没有给出解释,比如双击Button按钮时,怎么自动生成事件代码?最重要的是,怎样根据设计的界面生成代码?这些问题其实利用.net中提供的类都能实现,只是不太容易去探究它底层的具体实现细节。下面我就来原汁原味地解释一下以上提出的疑问,Demo中我用自己的方法从底层开始实现了跟常用设计器效果基本一样的功能,当然我不保证.net类库中的类底层就是像我解释的这样去实现的,因为我也没看该部分类的源码,呵呵。
以下就是解释:
图1
如你们所见,整个设计器可以分为三层,底层的灰色部分可以看作整个设计器的容器,中间层的蓝色部分就是设计器中的默认窗体,所有拖进设计器中的控件都是放在蓝色部分中,在蓝色部分上面,有一层透明的遮罩层(用橘黄色方框表示),大小跟底层的灰色部分一致,在用户(Programer)看来,最上面的遮罩层是“不存在”的,但是事实上,所有的鼠标键盘操作都被遮罩层拦截,由它进行处理,因此,中间层的窗体和控件不会接受任何用户输入。注:中间层的蓝色部分和最上面的橘黄色部分都属于底层灰色部分,如果把它们都看做控件,那么蓝色部分和橘黄色是平行关系,都属于灰色部分的子控件,只是Z轴顺序不同(Demo中的源码有所体现)。
4.既然最上面有一个透明的遮罩层,那么当选中一个控件时,周围出现的选中方框可以把它画在这个遮罩层上,任何时候遮罩层中的方框跟中间层的选中控件相对应,保持一致,也就是说,无论方框大小还是位置,都跟中间层的选中控件一样,让人看起来,它两重合 ( 虽然不在同一个层面)。鼠标操作的是顶层的方框,中间层的控件跟着移动、变大、变小。
5.在设计器中,如果选中多个控件,就会出现多个选中方框,道理一样,所有的选中方框都在最顶层,每一个方框都与中间层的某一控件相对应,操作跟4中一样。复制时,可以将选中控件的完整名称、属性、以及属性值收集起来存放在ClipBoard中,粘贴时,根据 完整名称新建控件,将属性值赋予新建控件,将其加到中间层。
以上就是从底层开始解释一个窗体设计器的简单原理,在图1中,底层灰色部分DesignerControl是一个UserControl,HostFrame是一个控件容器(Form),Overlayer也是一个透明的UserControl,Recter为虚线效果的选中框,这些名称分别与Demo中的源码对应,诸位可以对照参考。
另外,我说几个关键点:
1 protected override CreateParams CreateParams 2 { 3 get 4 { 5 CreateParams para = base.CreateParams; 6 para.ExStyle |= 0x00000020; //WS_EX_TRANSPARENT 透明支持 7 return para; 8 } 9 } 10 protected override void OnPaintBackground(PaintEventArgs e) //不画背景 11 { 12 //base.OnPaintBackground(e); 13 }
1 class MessageFilter : IMessageFilter 2 { 3 HostFrame _thehost; //中间层控件容器 4 DesignerControl _theDesignerBoard; //设计面板 5 public MessageFilter(HostFrame hostFrame, DesignerControl designer) 6 { 7 _thehost = hostFrame; 8 _theDesignerBoard = designer; 9 } 10 #region IMessageFilter 成员 11 public bool PreFilterMessage(ref Message m) //过滤所有控件的WM_PAINT消息 12 { 13 Control ctrl = (Control)Control.FromHandle(m.HWnd); 14 if (_thehost != null && _theDesignerBoard != null && _thehost.Controls.Contains(ctrl) && m.Msg == 0x000F) // 0x000F == WM_PAINT 15 { 16 _theDesignerBoard.Refresh(); 17 return true; 18 } 19 return false; 20 } 21 #endregion 22 }
使用时,用Application.AddMessageFilter()方法就可以,MSDN上对该方法有一个解释,大概意义是说此操作只对当前UI线程有效,也就是说如果一个应用程序有两个或者多个UI线程,只能过滤当前UI线程中的某一消息。当然这个不是重点,我只是简单的提一下,有关Windows消息以及WInform中的消息流动问题,请参考我之前的博客。
1 /// <summary> 2 /// 选中控件时,周围出现的方框 3 /// </summary> 4 class Recter 5 { 6 Rectangle _rect = new Rectangle(); 7 bool _isform = false; //是否为窗体周围的方框(如果是,则位置不可改变,且只有从下、右、右下三个方向改变大小) 8 9 public Rectangle Rect 10 { 11 get 12 { 13 return _rect; 14 } 15 set 16 { 17 _rect = value; 18 } 19 } 20 public bool IsForm 21 { 22 set 23 { 24 _isform = value; 25 } 26 } 27 public Recter() 28 { 29 30 } 31 /// <summary> 32 /// 绘制方框 33 /// </summary> 34 /// <param name="g"></param> 35 public void Draw(Graphics g) 36 { 37 Rectangle rect = _rect; 38 using (Pen p = new Pen(Brushes.Black,1)) 39 { 40 p.DashStyle = DashStyle.Dot; 41 rect.Inflate(new Size(+1, +1)); 42 g.DrawRectangle(p, rect); //方框 43 44 p.DashStyle = DashStyle.Solid; 45 46 //8个方块 47 g.FillRectangle(Brushes.White, new Rectangle(rect.Left - 6, rect.Top - 6, 6, 6)); 48 g.FillRectangle(Brushes.White, new Rectangle(rect.Left + rect.Width / 2 - 3, rect.Top - 6, 6, 6)); 49 g.FillRectangle(Brushes.White, new Rectangle(rect.Left + rect.Width, rect.Top - 6, 6, 6)); 50 g.FillRectangle(Brushes.White, new Rectangle(rect.Left - 6, rect.Top + rect.Height / 2 - 3, 6, 6)); 51 g.FillRectangle(Brushes.White, new Rectangle(rect.Left - 6, rect.Top + rect.Height, 6, 6)); 52 g.FillRectangle(Brushes.White, new Rectangle(rect.Left + rect.Width, rect.Top + rect.Height / 2 - 3, 6, 6)); 53 g.FillRectangle(Brushes.White, new Rectangle(rect.Left + rect.Width / 2 - 3, rect.Top + rect.Height, 6, 6)); 54 g.FillRectangle(Brushes.White, new Rectangle(rect.Left + rect.Width, rect.Top + rect.Height, 6, 6)); 55 56 g.DrawRectangle(p, new Rectangle(rect.Left - 6, rect.Top - 6, 6, 6)); 57 g.DrawRectangle(p, new Rectangle(rect.Left + rect.Width / 2 - 3, rect.Top - 6, 6, 6)); 58 g.DrawRectangle(p, new Rectangle(rect.Left + rect.Width, rect.Top - 6, 6, 6)); 59 g.DrawRectangle(p, new Rectangle(rect.Left - 6, rect.Top + rect.Height / 2 - 3, 6, 6)); 60 g.DrawRectangle(p, new Rectangle(rect.Left - 6, rect.Top + rect.Height, 6, 6)); 61 g.DrawRectangle(p, new Rectangle(rect.Left + rect.Width, rect.Top + rect.Height / 2 - 3, 6, 6)); 62 g.DrawRectangle(p, new Rectangle(rect.Left + rect.Width / 2 - 3, rect.Top + rect.Height, 6, 6)); 63 g.DrawRectangle(p, new Rectangle(rect.Left + rect.Width, rect.Top + rect.Height, 6, 6)); 64 } 65 66 } 67 /// <summary> 68 /// 判断鼠标操作类型 69 /// </summary> 70 /// <param name="p"></param> 71 /// <returns></returns> 72 public DragType GetMouseDragType(Point p) 73 { 74 Rectangle _rect = this._rect; 75 _rect.Inflate(new Size(3, 3)); 76 if (new Rectangle(_rect.Left - 2, _rect.Top - 2, 4, 4).Contains(p) && !_isform) 77 { 78 return DragType.LeftTop; 79 } 80 if (new Rectangle(_rect.Left + 2, _rect.Top - 2, _rect.Width - 4, 4).Contains(p) && !_isform) 81 { 82 return DragType.Top; 83 } 84 if (new Rectangle(_rect.Left - 2, _rect.Top + 2, 4, _rect.Height - 4).Contains(p) && !_isform) 85 { 86 return DragType.Left; 87 } 88 if (new Rectangle(_rect.Left - 2, _rect.Top + _rect.Height - 2, 4, 4).Contains(p) && !_isform) 89 { 90 return DragType.LeftBottom; 91 } 92 if (new Rectangle(_rect.Left + 2, _rect.Top + _rect.Height - 2, _rect.Width - 4, 4).Contains(p)) 93 { 94 return DragType.Bottom; 95 } 96 if (new Rectangle(_rect.Left + _rect.Width - 2, _rect.Top + _rect.Height - 2, 4, 4).Contains(p)) 97 { 98 return DragType.RightBottom; 99 } 100 if (new Rectangle(_rect.Left + _rect.Width - 2, _rect.Top + 2, 4, _rect.Height - 4).Contains(p)) 101 { 102 return DragType.Right; 103 } 104 if (new Rectangle(_rect.Left + _rect.Width - 2, _rect.Top - 2, 4, 4).Contains(p) && !_isform) 105 { 106 return DragType.RightTop; 107 } 108 if (new Rectangle(_rect.Left + 2, _rect.Top + 2, _rect.Width - 4, _rect.Height - 4).Contains(p) && !_isform) 109 { 110 return DragType.Center; 111 } 112 return DragType.None; 113 } 114 115 }
其余的都很简单,用到的都是简单技术,详细请参考源码。XP .net3.5测试通过,源码下载地址:http://download.csdn.net/detail/xiaozhi_5638/5185939。源码中没有剪切、复制、自动对齐等功能。
上一张Demo效果图:
图2
希望有帮助,O(∩_∩)O~。感觉本文对自己有用的朋友,给个“推荐”或者建议啥的都是可以滴,非常感谢~