如何利用C# Winform画一个简单的关系图
如何利用C# 自带的控件画一个比较简单的关系图,因为很少在Winform开发,所有对我来说,第一反应就是在winform中嵌入网页。因为在网页中,画图是比较容易的,然后利用WebBrowser 嵌入到窗体中。或者可以直接找第三方画流程图的控件进行绘画。但是,对于我来说,如何利用Winform创建,却很重要。在多方面的尝试中,一步一步的探索,终于画出了满意的图片。
在我这个小Demo中,第一、根据输入的一个数,查找相关数据之间的关系,所以数据源是动态的,即动态绘图;第二、数据源的逻辑,有点复杂。大概是这么个样子,针对数据之间的逻辑关系大致可以划分成以下情况:
(1)1个体 A;
(2)A有多个子节点;
(3)A有多个父节点;
(4)当A只有一个子节点时,需要知道列出该子节点的所有父节点或者只有一个父节点时,列出该父节点的所有子节点;是不是有点乱了,或许看图会好一些:

图(1)

图(2)

图(3)

图(4)
起初,我刚开始设计的时候,发现这些位置都应该是相对的。所以我先固定要寻找的点,当然这个点的在几种情况下是固定的,但是为了美观些,我将列分为三块区域,而我的起始点初步左中两个区域。不啰嗦,直接讲原理,每个数字就是一个picturebox,背景可以画长方形,园,椭圆,然后根据字数的长度以及背景图像的宽度,在画字的时候,让字居中显示。另外一个难点就是画线,需要创建一个类Lines,用来记住开始与终点的控件。
Lines类:
1 public class Lines 2 { 3 public Control StartControl { get; set; } 4 5 public Control EndControl { get; set; } 6 7 public Lines(Control startCtl, Control endCtl) 8 { 9 this.StartControl = startCtl; 10 this.EndControl = endCtl; 11 } 12 }
画picturebox,以及动态绑定Click事件,利用Graphics的DrawString来在图片上写字。默认情况的,图片或者线之类的可能出现锯齿,在画的时候设置一下 gra.SmoothingMode = SmoothingMode.HighQuality;下面是我画方框图的代码:
1 #region 画方框图 2 3 /// <summary> 4 /// 画方框图 5 /// </summary> 6 /// <param name="picBox"></param> 7 /// <param name="content"></param> 8 /// <param name="brush"></param> 9 private void DrawContentOnImage(PictureBox picBox, string content, Brush brush) 10 { 11 Bitmap bitmap = new Bitmap(picBox.Width, picBox.Width); 12 13 picBox.Image = bitmap; 14 Graphics gra = Graphics.FromImage(bitmap); 15 //Pen pen = new Pen(Color.Black);//画笔颜色 16 gra.SmoothingMode = SmoothingMode.HighQuality; 17 PointF p = new PointF(); 18 p.Y = 6; 19 int len = content.Trim().Length; 20 p.X = this._width / 2 - (float)3.6 * len; 21 gra.DrawString(content, new Font("宋体", 10), brush, p); 22 23 24 25 26 //gra.DrawEllipse(pen,0 ,0, 67, 26);//画椭圆的方法,x坐标、y坐标、宽、高,如果是100,则半径为50 27 28 //Point p=new Point(); 29 //p.Y = 6; 30 //int len = content.Trim().Length; 31 //p.X = 7; 32 //if (len <= 6) 33 //{ 34 // p.X = 31 - 4*len; 35 //} 36 //gra.DrawString(content, new Font("宋体", 10), brush,p); 37 } 38 39 /// <summary> 40 /// 根据字符串画picturebox 41 /// </summary> 42 /// <param name="contents"></param> 43 /// <param name="parent"></param> 44 /// <param name="startPoint"></param> 45 /// <returns></returns> 46 private List<Control> DrawContentContainers(List<Zdgx> contents, Control parent, Point startPoint, Brush brush) 47 { 48 List<Control> controlLst = new List<Control>(); 49 PictureBox picb; 50 int x = startPoint.X; 51 int y = startPoint.Y; 52 53 //Graphics g = parent.CreateGraphics(); 54 55 foreach (Zdgx gx in contents) 56 { 57 Point p = new Point(x, y); 58 picb = new PictureBox(); 59 picb.Location = p; 60 picb.Name = gx.Zdh; 61 62 picb.BorderStyle = BorderStyle.FixedSingle; 63 picb.Width = this._width; 64 picb.Height = this._height; 65 66 this._picControls.Add(picb); 67 picb.Click += new EventHandler(picb_Click); 68 parent.Controls.Add(picb); 69 70 controlLst.Add(picb); 71 72 DrawContentOnImage(picb, gx.Zdh, brush); 73 74 y = y + this._heightBetween; 75 } 76 77 return controlLst; 78 } 79 80 public void picb_Click(object sender, EventArgs e) 81 { 82 string zdj = ""; 83 PictureBox pb = sender as PictureBox; 84 MessageBox.Show(pb.Name); 85 } 86 #endregion
接下来就是画数据之间的两两关系了,就是画线了,这里我一般都会定义一个全局变量,List<Lines> _lineLst=newList<Lines>();用来存储有关系的pictcurebox。然后开始画线:
1 /// <summary> 2 /// 画图 3 /// </summary> 4 /// <param name="linesLst"></param> 5 public void DrawGxLine(List<Lines> linesLst) 6 { 7 if (linesLst != null) 8 { 9 foreach (Lines line in linesLst) 10 { 11 //右下角坐标 12 Point pLft = new Point(line.StartControl.Location.X + line.StartControl.Width, line.StartControl.Location.Y + line.StartControl.Height / 2); 13 14 //左上角图标 15 Point pRgt = new Point(line.EndControl.Location.X, line.EndControl.Location.Y + line.EndControl.Height / 2); 16 17 Pen p = new Pen(Color.Black); 18 p.DashStyle = System.Drawing.Drawing2D.DashStyle.Solid; 19 p.EndCap = System.Drawing.Drawing2D.LineCap.ArrowAnchor; 20 p.CustomEndCap = new System.Drawing.Drawing2D.AdjustableArrowCap(5, 5, true); 21 Graphics g = this.CreateGraphics(); 22 g.SmoothingMode = SmoothingMode.HighQuality; 23 g.DrawLine(p, pLft, pRgt); 24 } 25 } 26 }
然后是处理数据逻辑关系,与计算位置的方法了,大体上解释一下,我们从左往右思考,如果出现图(1)的情况,将起始位置处理成左边位置,起始的高度是一样的;接着,如果A的父节点B只有一个的情况下,我们就计算该父节点B下的所有子节点,根据B的子节点的个数,计算出B在这几个节点的左侧中间位置,同时记住中间_centerLastControl,与位置关系;如果A存在多个父节点,那就根据A节点的计算出左侧的picturebox的起始点的高度,每个节点的高度是一个定值。右边的逻辑,与左侧一样的。
1 public void InitInfo(string zdh) 2 { 3 if (string.IsNullOrEmpty(zdh)) return; 4 if (zdh.Length > 19) return; 5 initialIList(); 6 ClearZdGxT(); 7 8 //SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, false); 9 //UpdateStyles(); 10 11 this._picControls = new List<PictureBox>(); 12 this._zdh = zdh; 13 List<ZD_GX> leftList = GetData(this._zdh, true); 14 List<ZD_GX> rightList = GetData(this._zdh, false); 15 16 #region 17 if (leftList.Count == 0 && rightList.Count == 0) 18 { 19 //中间默认ID为0 20 this._centerZdGx.Add(new Zdgx(0, this._zdh)); 21 //画中间的 22 this._centerControlLst = DrawContentContainers(this._centerZdGx, this, new Point(this._startX, this._startY), Brushes.Blue); 23 Control cCenter = this._centerControlLst[0]; 24 //中间最下面一个Control 25 this._centerLastControl = cCenter; 26 27 //画图 28 DrawGxLine(this._lineLst); 29 return; 30 } 31 #endregion 32 33 Control ccCenter = new Control(); 34 35 36 if (leftList.Count > 0) 37 { 38 39 //中间默认ID为0 40 this._centerZdGx.Add(new Zdgx(0, this._zdh)); 41 //画中间的 42 this._centerControlLst = DrawContentContainers(this._centerZdGx, this, new Point(this._startX + 180, this._startY), Brushes.Blue); 43 ccCenter = this._centerControlLst[0]; 44 //中间最下面一个Control 45 this._centerLastControl = ccCenter; 46 47 this._leftZdGx = GetZdgxList(leftList, true); 48 int y = 0; 49 50 #region 51 if (this._leftZdGx.Count > 1) 52 { 53 y = 0; 54 int count = this._leftZdGx.Count; 55 //为了样式好看些 56 if (this._leftZdGx.Count % 2 == 0)//个数是偶数 57 { 58 y = this._startY - ((this._heightBetween - this._height) / 2 - this._height / 2) 59 - (count / 2) * this._height 60 - (count / 2 - 1) * (this._heightBetween - this._height); 61 } 62 else 63 { 64 y = this._startY - (count / 2) * this._height - (count / 2) * (this._heightBetween - this._height); 65 } 66 67 //画左边框 68 this._leftControlLst = DrawContentContainers(this._leftZdGx, this, new Point(this._startX, y), Brushes.Black); 69 //左边最下面一个 70 this._leftLastControl = this._leftControlLst[leftList.Count - 1]; 71 72 //保存左-->中的连线 73 foreach (Control c in this._leftControlLst) 74 { 75 _lineLst.Add(new Lines(c, ccCenter)); 76 } 77 } 78 79 80 //画左边扩展 81 if (leftList.Count == 1) 82 { 83 List<ZD_GX> list = GetData(leftList[0].LZDH_L, false); 84 this._leftExtendZdGx = GetZdgxList(list, false); 85 RemoveCenter(this._leftExtendZdGx); 86 if (this._leftExtendZdGx.Count > 0) 87 { 88 y = 0; 89 int count = this._leftExtendZdGx.Count + 1; 90 if (count % 2 == 0) 91 { 92 y = this._startY + ((this._heightBetween - this._height) / 2 - this._height / 2) 93 + (count / 2) * this._height 94 + (count / 2 - 1) * (this._heightBetween - this._height); 95 } 96 else 97 { 98 y = this._startY + (count / 2) * this._height + (count / 2) * (this._heightBetween - this._height); 99 } 100 101 102 //画左边框 103 this._leftControlLst = DrawContentContainers(this._leftZdGx, this, new Point(this._startX, y), Brushes.Black); 104 //左边最下面一个 105 this._leftLastControl = this._leftControlLst[leftList.Count - 1]; 106 107 //保存左-->中的连线 108 foreach (Control c in this._leftControlLst) 109 { 110 _lineLst.Add(new Lines(c, ccCenter)); 111 } 112 113 //画中间扩展 114 this._leftExtendControlLst = DrawContentContainers(this._leftExtendZdGx, this, new Point(this._centerLastControl.Location.X, this._centerLastControl.Location.Y + this._heightBetween), Brushes.Black); 115 //中间最后一个 116 this._centerLastControl = this._leftExtendControlLst[this._leftExtendZdGx.Count - 1]; 117 118 //左右连线 119 foreach (Control c in this._leftExtendControlLst) 120 { 121 _lineLst.Add(new Lines(this._leftLastControl, c)); 122 } 123 } 124 else 125 { 126 127 //画左边框 128 this._leftControlLst = DrawContentContainers(this._leftZdGx, this, new Point(this._startX, this._startY), Brushes.Black); 129 //左边最下面一个 130 this._leftLastControl = this._leftControlLst[leftList.Count - 1]; 131 132 //保存左-->中的连线 133 foreach (Control c in this._leftControlLst) 134 { 135 _lineLst.Add(new Lines(c, ccCenter)); 136 } 137 } 138 139 } 140 141 142 143 #endregion 144 } 145 else 146 { 147 //中间默认ID为0 148 this._centerZdGx.Add(new Zdgx(0, this._zdh)); 149 //画中间的 150 this._centerControlLst = DrawContentContainers(this._centerZdGx, this, new Point(this._startX, this._startY), Brushes.Blue); 151 ccCenter = this._centerControlLst[0]; 152 //中间最下面一个Control 153 this._centerLastControl = ccCenter; 154 } 155 156 157 158 159 160 #region 右边 161 162 163 164 //画右边 165 if (rightList.Count > 0) 166 { 167 this._rightZdGx = GetZdgxList(rightList, false); 168 int y = 0; 169 170 if (this._rightZdGx.Count > 1) 171 { 172 y = 0; 173 //为了样式好看些 174 int count = this._rightZdGx.Count; 175 if (this._leftZdGx.Count % 2 == 0)//个数是偶数 176 { 177 y = this._startY - ((this._heightBetween - this._height) / 2 - this._height / 2) 178 - (count / 2) * this._height 179 - (count / 2 - 1) * (this._heightBetween - this._height); 180 } 181 else 182 { 183 y = this._startY + (count / 2) * this._height + (count / 2) * (this._heightBetween - this._height); 184 } 185 186 //画右边边框 187 this._rightControlLst = DrawContentContainers(this._rightZdGx, this, new Point(ccCenter.Location.X + 180, y), Brushes.Black); 188 //右边最下面一个 189 this._rightLastControl = this._rightControlLst[rightList.Count - 1]; 190 191 //中右连线 192 foreach (Control c in this._rightControlLst) 193 { 194 _lineLst.Add(new Lines(this._centerControlLst[0], c)); 195 } 196 } 197 198 199 //画右边扩展 200 if (rightList.Count == 1) 201 { 202 List<ZD_GX> list = GetData(rightList[0].LZDH_X, true); 203 this._rightExtendZdGx = GetZdgxList(list, true); 204 RemoveCenter(this._rightExtendZdGx); 205 if (this._rightExtendZdGx.Count > 0) 206 { 207 y = 0; 208 int count = this._rightExtendZdGx.Count + this._leftExtendControlLst.Count + 1; 209 if (count % 2 == 0) 210 { 211 y = this._startY + (this._heightBetween - this._height) / 2 + this._height / 2 212 + (count / 2) * this._height 213 + (count / 2 - 1) * (this._heightBetween - this._height); 214 } 215 else 216 { 217 y = this._startY + (count / 2) * this._height + (count / 2) * (this._heightBetween - this._height); 218 } 219 220 //画右边边框 221 this._rightControlLst = DrawContentContainers(this._rightZdGx, this, new Point(ccCenter.Location.X + 180, y), Brushes.Black); 222 //右边最下面一个 223 this._rightLastControl = this._rightControlLst[rightList.Count - 1]; 224 225 //中右连线 226 foreach (Control c in this._rightControlLst) 227 { 228 _lineLst.Add(new Lines(this._centerControlLst[0], c)); 229 } 230 231 232 //画中间扩展 233 this._rightExtendControlLst = DrawContentContainers(this._rightExtendZdGx, this, new Point(this._centerLastControl.Location.X, this._centerLastControl.Location.Y + this._heightBetween), Brushes.Black); 234 //中间最后一个 235 this._centerLastControl = this._rightExtendControlLst[this._rightExtendZdGx.Count - 1]; 236 //中右连线 237 238 foreach (Control c in this._rightExtendControlLst) 239 { 240 _lineLst.Add(new Lines(c, this._rightLastControl)); 241 } 242 } 243 else 244 { 245 //画右边边框 246 this._rightControlLst = DrawContentContainers(this._rightZdGx, this, new Point(ccCenter.Location.X + 180, this._startY), Brushes.Black); 247 //右边最下面一个 248 this._rightLastControl = this._rightControlLst[rightList.Count - 1]; 249 250 //中右连线 251 foreach (Control c in this._rightControlLst) 252 { 253 _lineLst.Add(new Lines(this._centerControlLst[0], c)); 254 } 255 } 256 257 } 258 259 260 261 262 } 263 #endregion 264 265 //画图 266 DrawGxLine(this._lineLst); 267 }
清空程序,释放资源:
1 private void ClearLines() 2 { 3 this._lineLst = new List<Lines>(); 4 this.OnPaint(new PaintEventArgs(this.CreateGraphics(), new Rectangle(this.Location, new Size(this.Width, this.Height)))); 5 } 6 7 private void ClearPictureBox() 8 { 9 if (this._picControls.Count > 0) 10 { 11 foreach (PictureBox picBox in this._picControls) 12 { 13 picBox.Dispose(); 14 } 15 } 16 }
在这里会经常发现一个,就是对话框最小化或者放大缩小的时候,我们会发现,绘画的线不见了,那是因为我们在窗体上绘画的时候使用的是 Graphics g = this.CreateGraphics();这种情况下对窗体进行操作会触发它的OnPaint的事件,所以我们需要重写Onpaint的事件。
1 protected override void OnPaint(PaintEventArgs e) 2 { 3 base.OnPaint(e); 4 5 DrawGxLine(this._lineLst); 6 }
运行结果图:


浙公网安备 33010602011771号