直接上效果
想要实现的效果,不用解释也看得出来了,为了给窗体节省空间,让它可以贴附在窗体的边缘。
那么怎么实现这个效果呢?原理在于对Padding这个属性的妙用。
另外可以看见,窗体在设计的时候也是可以进行事件的交互的,就像TabControl在设计的时候可以点击每一个Page一样,关于这个如果有兴趣,就可以参考一下msdn 关于ParentControlDesigner和ControlDesigner给的例子了。
好了废话不多说先上容器的代码
1 [Designer(typeof(MiracleControls.ControlDesigner.LayoutPanelDesigner))] 2 [ToolboxBitmap(typeof(LayoutPanel))] 3 public class LayoutPanel : Panel 4 { 5 private System.ComponentModel.Container components = null; 6 public LayoutPanel() 7 { 8 components = new System.ComponentModel.Container(); 9 DoubleBuffered = true; 10 BackColor = Color.Transparent; 11 Padding = new Padding(0, 0, _controlSize, 0); 12 if (_state == States.Open) 13 buttonRect = new Rectangle(Width - _controlSize, Height / 2 - _controlSize / 2, _controlSize, _controlSize); 14 else 15 buttonRect = new Rectangle(0, Height / 2 - _controlSize / 2, _controlSize, _controlSize); 16 Dock = DockStyle.Right; 17 } 18 #region Properties 19 private Color _arrowColor = Color.White; 20 private int _controlSize = 20; 21 private Color _buttonColor = Color.FromArgb(55, 55, 55); 22 private Color _hoverColor = Color.Orange; 23 private States _state = States.Open; 24 private int _memorySize = 50; 25 private bool _hover = false; 26 private bool _down = false; 27 28 public override DockStyle Dock 29 { 30 get { return base.Dock; } 31 set { base.Dock = value == DockStyle.Left || value == DockStyle.Right ? value : Dock; } 32 } 33 public int ButtonSize 34 { 35 get { return _controlSize; } 36 set { _controlSize = value >= 20 ? value : 20; changeSize(); Invalidate(); } 37 } 38 public Color ArrowColor 39 { 40 get { return _arrowColor; } 41 set { _arrowColor = value; Invalidate(); } 42 } 43 public Color ButtonColor 44 { 45 get { return _buttonColor; } 46 set { _buttonColor = value; Invalidate(); } 47 } 48 public Color HoverColor 49 { 50 get { return _hoverColor; } 51 set { _hoverColor = value;Invalidate(buttonRect); } 52 } 53 private bool Hover 54 { 55 get { return _hover; } 56 set 57 { 58 _hover = value; 59 if (_hover) 60 Cursor = Cursors.Hand; 61 else 62 Cursor = Cursors.Default; 63 Invalidate(buttonRect); 64 } 65 } 66 private bool Down 67 { 68 get { return _down; } 69 set { _down = value; } 70 } 71 [Browsable(false)] 72 public int MemorySize 73 { 74 get { return _memorySize; } 75 set { _memorySize = value; } 76 } 77 public States State 78 { 79 get { return _state; } 80 set 81 { 82 _state = value; 83 changeSize(); 84 Invalidate(buttonRect); 85 } 86 } 87 public enum States 88 { Open, Close }; 89 #endregion 90 91 private void changeSize() 92 { 93 if (State == States.Close) 94 { 95 if (Dock == DockStyle.Left) 96 { 97 Size = new Size(_controlSize, Height); 98 Padding = new Padding(_controlSize, 0, 0, 0); 99 } 100 else if (Dock == DockStyle.Right) 101 { 102 Size = new Size(_controlSize, Height); 103 Padding = new Padding(_controlSize, 0, 0, 0); 104 } 105 } 106 else 107 { 108 if (Dock == DockStyle.Left) 109 { 110 Padding = new Padding(0, 0, _controlSize, 0); 111 Size = new Size(_memorySize, Height); 112 } 113 else if (Dock == DockStyle.Right) 114 { 115 Size = new Size(MemorySize, Height); 116 Padding = new Padding(_controlSize, 0, 0, 0); 117 } 118 } 119 } 120 Rectangle buttonRect; 121 protected override void OnPaint(PaintEventArgs e) 122 { 123 base.OnPaint(e); 124 Graphics G = e.Graphics; 125 G.SmoothingMode = SmoothingMode.HighQuality; 126 //绘制虚线框 127 if (Site != null) 128 using (Pen pen = new Pen(Color.Blue)) 129 { 130 pen.DashStyle = DashStyle.Custom; 131 pen.DashPattern = new float[] { 5, 5 }; 132 G.DrawRectangle(pen, new Rectangle(Padding.Left, Top, Width - Padding.Right - 1, Height - Padding.Bottom - 1)); 133 } 134 135 Rectangle circleRect = new Rectangle(buttonRect.X + 1, buttonRect.Y + 1, buttonRect.Width - 2, buttonRect.Height - 2); 136 using (LinearGradientBrush lb = new LinearGradientBrush(Dock == DockStyle.Left ? buttonRect : 137 new Rectangle(buttonRect.X, buttonRect.Y, buttonRect.Width + 1, buttonRect.Height), BackColor, Color.FromArgb(180, _buttonColor), 0F)) 138 { 139 lb.SetBlendTriangularShape(0.5F); 140 using (GraphicsPath GP = CreatePath()) 141 G.FillPath(lb, GP); 142 } 143 using (SolidBrush sb = new SolidBrush(ControlPaint.Dark(_buttonColor, 0.2F))) 144 G.FillEllipse(sb, buttonRect); 145 using (SolidBrush sb = new SolidBrush(_buttonColor)) 146 { 147 G.FillEllipse(sb, circleRect); 148 } 149 if (Dock == DockStyle.Left || Dock == DockStyle.Right) 150 DrawLeftRight(G, circleRect); 151 } 152 private void DrawLeftRight(Graphics G, Rectangle circleRect) 153 { 154 using (Pen pen = new Pen(_hover ? _hoverColor : _arrowColor, 2)) 155 { 156 if (Dock == DockStyle.Left) 157 if (State == States.Open) 158 { 159 G.DrawLine(pen, circleRect.X + circleRect.Width / 8 + circleRect.Width / 4, circleRect.Y + 1 + circleRect.Height / 2, 160 circleRect.X + circleRect.Width / 8 + circleRect.Width / 2, circleRect.Y + 1 + circleRect.Height / 4); 161 G.DrawLine(pen, circleRect.X + circleRect.Width / 8 + circleRect.Width / 4, circleRect.Y + circleRect.Height / 2, 162 circleRect.X + circleRect.Width / 8 + circleRect.Width / 2, circleRect.Y + circleRect.Height - circleRect.Height / 4); 163 } 164 else 165 { 166 G.DrawLine(pen, circleRect.X + circleRect.Width / 2 - circleRect.Width / 8, circleRect.Y + 1 + circleRect.Height / 4, 167 circleRect.X - circleRect.Width / 8 + circleRect.Width - circleRect.Width / 4, circleRect.Y + 1 + circleRect.Height / 2); 168 G.DrawLine(pen, circleRect.X + circleRect.Width / 2 - circleRect.Width / 8, circleRect.Y + circleRect.Height - circleRect.Height / 4, 169 circleRect.X - circleRect.Width / 8 + circleRect.Width - circleRect.Width / 4, circleRect.Y + circleRect.Height / 2); 170 } 171 else 172 if (State == States.Close) 173 { 174 G.DrawLine(pen, circleRect.X + circleRect.Width / 8 + circleRect.Width / 4, circleRect.Y + 1 + circleRect.Height / 2, 175 circleRect.X + circleRect.Width / 8 + circleRect.Width / 2, circleRect.Y + 1 + circleRect.Height / 4); 176 G.DrawLine(pen, circleRect.X + circleRect.Width / 8 + circleRect.Width / 4, circleRect.Y + circleRect.Height / 2, 177 circleRect.X + circleRect.Width / 8 + circleRect.Width / 2, circleRect.Y + circleRect.Height - circleRect.Height / 4); 178 } 179 else 180 { 181 G.DrawLine(pen, circleRect.X + circleRect.Width / 2 - circleRect.Width / 8, circleRect.Y + 1 + circleRect.Height / 4, 182 circleRect.X - circleRect.Width / 8 + circleRect.Width - circleRect.Width / 4, circleRect.Y + 1 + circleRect.Height / 2); 183 G.DrawLine(pen, circleRect.X + circleRect.Width / 2 - circleRect.Width / 8, circleRect.Y + circleRect.Height - circleRect.Height / 4, 184 circleRect.X - circleRect.Width / 8 + circleRect.Width - circleRect.Width / 4, circleRect.Y + circleRect.Height / 2); 185 } 186 } 187 } 188 private GraphicsPath CreatePath() 189 { 190 GraphicsPath GP = new GraphicsPath(); 191 if (Dock == DockStyle.Left) 192 GP.AddLines(new PointF[] {new PointF(Width-_controlSize,Height/2-2*_controlSize), 193 new Point(Width-_controlSize/2,Height/2-_controlSize),new PointF(Width-_controlSize/2,Height/2+_controlSize) 194 ,new PointF(Width-_controlSize,Height/2+2*_controlSize),new PointF(Width-_controlSize,Height/2-2*_controlSize)}); 195 else if (Dock == DockStyle.Right) 196 GP.AddLines(new PointF[] {new PointF(_controlSize,Height/2-2*_controlSize), 197 new Point(_controlSize/2,Height/2-_controlSize),new PointF(_controlSize/2,Height/2+_controlSize) 198 ,new PointF(_controlSize,Height/2+2*_controlSize),new PointF(_controlSize,Height/2-2*_controlSize)}); 199 return GP; 200 } 201 protected override void OnMouseDown(MouseEventArgs e) 202 { 203 base.OnMouseDown(e); 204 if (e.X > buttonRect.X && e.X < buttonRect.X + buttonRect.Width && e.Y > buttonRect.Y && e.Y < buttonRect.Y + buttonRect.Height) 205 { 206 if (!Down) 207 Down = true; 208 } 209 else { if (Down) Down = false; } 210 } 211 protected override void OnMouseLeave(EventArgs e) 212 { 213 base.OnMouseLeave(e); 214 Hover = false; 215 } 216 protected override void OnMouseUp(MouseEventArgs e) 217 { 218 base.OnMouseUp(e); 219 Down = false; 220 } 221 protected override void OnMouseMove(MouseEventArgs e) 222 { 223 base.OnMouseMove(e); 224 if (e.X > buttonRect.X && e.X < buttonRect.X + buttonRect.Width && e.Y > buttonRect.Y && e.Y < buttonRect.Y + buttonRect.Height) 225 { 226 if (!Hover) 227 Hover = true; 228 } 229 else { if (Hover) Hover = false; } 230 } 231 protected override void OnMouseClick(MouseEventArgs e) 232 { 233 base.OnMouseClick(e); 234 if (Hover && Down) 235 State = _state == States.Open ? States.Close : States.Open; 236 } 237 protected override void OnResize(EventArgs eventargs) 238 { 239 base.OnResize(eventargs); 240 if (State != States.Close && this.Width > _controlSize) 241 MemorySize = Width; 242 if (Dock == DockStyle.Left) 243 { 244 if (_state == States.Open) 245 buttonRect = new Rectangle(Width - _controlSize, Height / 2 - _controlSize / 2, _controlSize - 1, _controlSize - 1); 246 else 247 buttonRect = new Rectangle(0, Height / 2 - _controlSize / 2, _controlSize - 1, _controlSize - 1); 248 } 249 else if (Dock == DockStyle.Right) 250 { 251 if (_state == States.Open) 252 buttonRect = new Rectangle(0, Height / 2 - _controlSize / 2, _controlSize - 1, _controlSize - 1); 253 else 254 buttonRect = new Rectangle(0, Height / 2 - _controlSize / 2, _controlSize - 1, _controlSize - 1); 255 } 256 Invalidate(); 257 } 258 protected override void Dispose(bool disposing) 259 { 260 if (disposing) 261 { 262 if (components != null) 263 components.Dispose(); 264 } 265 base.Dispose(disposing); 266 } 267 }
首先 映入眼帘的有这样一行特性[Designer(typeof(MiracleControls.ControlDesigner.LayoutPanelDesigner))]这是什么意思呢,这就是让这个容器在窗体设计器上可以进行交互的一个必不可少的东西了,至于MiracleControls.ControlDesigner这个命名空间,可以不用关心,因为这是写在我自己的类库中的,有点懒,代码中出现类似的命名空间,均不用关心。
好了,先提供设计器的类:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.ComponentModel; 6 using System.Drawing; 7 using MiracleControls.ContainerControls; 8 using System.Windows.Forms; 9 using System.Windows.Forms.Design; 10 11 namespace MiracleControls.ControlDesigner 12 { 13 public class LayoutPanelDesigner : System.Windows.Forms.Design.ParentControlDesigner 14 { 15 public override SelectionRules SelectionRules 16 { 17 get 18 { 19 if ((Control as LayoutPanel).State == LayoutPanel.States.Close) 20 return System.Windows.Forms.Design.SelectionRules.None;//让容器在关闭的状态下不可以被调整 21 else 22 return SelectionRules.AllSizeable; 23 } 24 } 25 protected override bool GetHitTest(Point point) 26 { 27 LayoutPanel lay = base.Control as LayoutPanel;//得到宿主控件 28 Point e = lay.PointToClient(point);//得到鼠标的坐标 29 Rectangle buttonRect = new Rectangle(lay.Width - lay.ButtonSize, lay.Height / 2 - lay.ButtonSize / 2, lay.ButtonSize - 1, lay.ButtonSize - 1);//定义可以点击的按钮区域
30 if (lay.Dock == DockStyle.Left) 31 { 32 if (lay.State == LayoutPanel.States.Open) 33 buttonRect = new Rectangle(lay.Width - lay.ButtonSize, lay.Height / 2 - lay.ButtonSize / 2, lay.ButtonSize - 1, lay.ButtonSize - 1); 34 else 35 buttonRect = new Rectangle(0, lay.Height / 2 - lay.ButtonSize / 2, lay.ButtonSize - 1, lay.ButtonSize - 1); 36 } 37 else if (lay.Dock == DockStyle.Right) 38 { 39 if (lay.State == LayoutPanel.States.Open) 40 buttonRect = new Rectangle(0, lay.Height / 2 - lay.ButtonSize / 2, lay.ButtonSize - 1, lay.ButtonSize - 1); 41 else 42 buttonRect = new Rectangle(0, lay.Height / 2 - lay.ButtonSize / 2, lay.ButtonSize - 1, lay.ButtonSize - 1); 43 } 44 if (e.X > buttonRect.X && e.X < buttonRect.X + buttonRect.Width && e.Y > buttonRect.Y && e.Y < buttonRect.Y + buttonRect.Height) 45 { 46 if (isMouseDown) 47 { 48 lay.State = lay.State == LayoutPanel.States.Close ? LayoutPanel.States.Open : LayoutPanel.States.Close; 49 isMouseDown = false; 50 } 51 } 52 else 53 { isMouseDown = false; } 54 return false; 55 } 56 bool isMouseDown = false; 57 58 protected override void WndProc(ref Message m) 59 { 60 if (m.Msg == 0x0202) 61 { isMouseDown = true; } 62 base.WndProc(ref m); 63 } 64 } 65 }
代码中没什么解释,这也是我一个不好的习惯。恐怕有一天自己都看不懂了。
先看图
这是一个 继承Panel的容器,这里为了空出位置 绘制可以点击的那个按钮,所以我们把这个容器的Padding改了,定义按钮的尺寸为ButtonSize 那么上面的情况,按钮的的位置也就好计算了,自己去悟吧。
收缩的效果如上图,只显示一个可以展开的按钮,收缩的原理猜也能够猜测到了,就是改变了容器的尺寸。假如我们把容器的尺寸改变成ButtonSize 并且这个容器的Dock属性 改为Right 那么是不是就可以贴附在窗体的右侧了呢?想想这个道理就很简单对不对。那么此时我们有一个问题,就是收缩之后怎么恢复呢?在代码1中可以看到我写的,我用一个属性专门记录了它展开时候的宽度,如果重新展开那么,恢复尺寸即可。
那么上图的这个 点击事件 我们怎么判断呢,这很简单,判断鼠标按下和移动的位置,就可以得到用户是否点击的这里了。提醒一下哦,鼠标移动和按下必须是同一个区域。至于按钮的形状,重写OnPaint就可以了。