就是把组织机构图做成思维导图的样子
本文是在别人的工作上进行了一定的更改,但是找不到这个链接了。
注:
1.组织结构图是用Graphics画的,因此他是一个图片。
2.本文给出了对图片进行点击时的一些响应事件。
先看一下结果图:
水平组织结构图(鼠标悬浮北京公司)
垂直组织机构图(鼠标点击上海公司)
C# 源代码给出如下
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;
namespace DrawOrgChart
{
public enum Orientation { Horizontal, Vertical };
public class Tree<T>
{
Tree<T> _Parent = null;
T _Content;
List<Tree<T>> _Childs = new List<Tree<T>>();
SizeF _Size;
public Rectangle _Rec;
private bool isHover = false;//指示鼠标是否在组织机构块上
private bool isClick = false;//指示鼠标是否对组织结构进行了点击
private int maxWidth;//组织机构图的最大宽度
private int maxHeight;//组织结构图的最大高度
private Pen pen = Pens.Blue;//画笔
private Color clickColor = Color.BlueViolet;
private int recHeight = 24;//矩形高度
private int hInterval = 25;//水平间距
private int vInterval = 16;//垂直间距
private int borderWidth = 26;//画布两边宽度
private int borderHeight = 26;//画布上下宽度
private int verPadding = 5;//文字距边框的垂直距离
private int horPadding = 10;//文字距边框的水平距离
public bool IsHover { get { return isHover; } set { isHover = value; } }
public bool IsClick { get { return isClick; } set { isClick = value; } }
public int MaxWidth { get { return maxWidth; } set { maxWidth = value; } }
public int MaxHeight { get { return maxHeight; } set { maxHeight = value; } }
public int HInterval { get { return hInterval; } set { hInterval = value; } }
public int VInterval { get { return vInterval; } set { vInterval = value; } }
public Orientation OrientationMode = Orientation.Vertical;//用来指示组织机构图是水平的还是垂直的
public Tree(Tree<T> parent, T content)
{
_Parent = parent;
_Content = content;
}
public Tree<T> Add(T content)
{
Tree<T> tree = new Tree<T>(this, content);
_Childs.Add(tree);
return tree;
}
public Tree<T> Parent { get { return _Parent; } }
public T Content { get { return _Content; } set { _Content = value; } }
public List<Tree<T>> Childs { get { return _Childs; } }
public SizeF Size { get { return _Size; } set { _Size = value; } }
public Rectangle Rec { get { return _Rec; } set { _Rec = value; } }
/// <summary>
/// 量测矩形框的完全放置字符串所需的大小
/// </summary>
/// <param name="g"></param>
/// <param name="font"></param>
/// <param name="addWidth">左右边距</param>
/// <param name="addHeight">上下边距</param>
void MeatureAllSize(Graphics g, Font font, int addWidth, int addHeight)
{
string s = _Content.ToString();
SizeF size = g.MeasureString("我爱你中国", font);//用来获取5个字时的边框,水平分布时边框的宽度需要一定
if (OrientationMode == Orientation.Vertical)//画垂直图时执行
{
_Size = g.MeasureString(s, font);
_Size.Width += addWidth;
}
else//画水平图时执行
{
StringBuilder sb = new StringBuilder();
int i=0;
//将内容按5个字换行
while (i < s.Length)
{
if (i>0&&i % 5 == 0)
{
sb.Append("\r\n");
}
sb.Append(s[i]);
i++;
}
if (s.Length > 5)
{
_Size = g.MeasureString(sb.ToString(), font);
}
else
{
_Size = size;//小于5个字的情形
}
_Size.Width += addWidth;
_Size.Height += addHeight;
}
foreach (Tree<T> tree in Childs)
tree.MeatureAllSize(g, font, addWidth,addHeight);//为所有节点计算边框
}
/// <summary>
/// 获取扁平化结构,画图的思想是将组织机构图的每一代节点放在一个列表里面,然后一个列表一个列表画
/// </summary>
/// <returns></returns>
List<List<Tree<T>>> GetTreeLayers()
{
List<List<Tree<T>>> layers = new List<List<Tree<T>>>();
GetTreeLayers(layers, new List<Tree<T>>(new Tree<T>[] { this }), 0);
return layers;
}
void GetTreeLayers(List<List<Tree<T>>> layers, List<Tree<T>> childs, int level)
{
if (childs.Count == 0) return;
if (layers.Count <= level) layers.Add(new List<Tree<T>>());
for (int i = 0; i < childs.Count; i++)
{
layers[level].Add(childs[i]);
GetTreeLayers(layers, childs[i].Childs, level + 1);
}
}
/// <summary>
/// 设置显示区域(从最后一层最左或最后一列最上开始)
/// </summary>
/// <param name="level"></param>
/// <param name="height"></param>
/// <param name="interval"></param>
/// <param name="left"></param>
void SetRectangle(int level, int height, int hInterval, int vInterval, int leftOrTop, Orientation orientation)
{
int index = 0;
if (Parent != null) index = Parent.Childs.IndexOf(this);
if (orientation == Orientation.Vertical)//垂直时
{
int left = leftOrTop;
if (Childs.Count == 0)
{
// 没有儿子,就向前靠
if (left > 0) left += hInterval;
}
else
{
// 有儿子,就在儿子中间
int centerX = (Childs[0].Rec.Left + Childs[Childs.Count - 1].Rec.Right) / 2;
left = centerX - (int)_Size.Width / 2;
// 并且不能和前面的重复,如果重复,联同子孙和子孙的右边节点右移
if (Parent != null && index > 0)
{
int ex = (Parent.Childs[index - 1].Rec.Right + hInterval) - left;
if (index > 0 && ex > 0)
{
for (int i = index; i < Parent.Childs.Count; i++)
Parent.Childs[i].RightChilds(ex);
left += ex;
}
}
}
_Rec = new Rectangle(left, (height + vInterval) * level, (int)_Size.Width, height);
}
else//水平时
{
int top = leftOrTop;
if (Childs.Count == 0)
{
// 没有儿子,就向前靠
if (top > 0) top += vInterval;
}
else
{
// 有儿子,就在儿子中间
int centerX = (Childs[0].Rec.Top + Childs[Childs.Count - 1].Rec.Bottom) / 2;
top = centerX - (int)_Size.Height / 2;
// 并且不能和前面的重复,如果重复,联同子孙和子孙的右边节点右移
if (Parent != null && index > 0)
{
int ex = (Parent.Childs[index - 1].Rec.Bottom + vInterval) - top;
if (index > 0 && ex > 0)
{
for (int i = index; i < Parent.Childs.Count; i++)
Parent.Childs[i].DownChilds(ex);
top += ex;
}
}
}
_Rec = new Rectangle(((int)_Size.Width+hInterval)*level, top, (int)_Size.Width, (int)_Size.Height);
}
}
/// <summary>
/// 所有子孙向右平移
/// </summary>
/// <param name="ex"></param>
void RightChilds(int ex)
{
Rectangle rec;
for (int i = 0; i < _Childs.Count; i++)
{
rec = _Childs[i].Rec;
rec.Offset(ex, 0);
_Childs[i].Rec = rec;
_Childs[i].RightChilds(ex);
}
}
/// <summary>
/// 所有子孙向下平移
/// </summary>
/// <param name="ex"></param>
void DownChilds(int ex)
{
Rectangle rec;
for (int i = 0; i < _Childs.Count; i++)
{
rec = _Childs[i].Rec;
rec.Offset(0, ex);
_Childs[i].Rec = rec;
_Childs[i].DownChilds(ex);
}
}
void Offset(int x, int y)
{
_Rec.Offset(x, y);
for (int i = 0; i < _Childs.Count; i++)
_Childs[i].Offset(x, y);
}
public Bitmap DrawAsImage()
{
return DrawAsImage(pen, new Font("宋体", 10.5f), recHeight, horPadding,verPadding, hInterval, vInterval, borderWidth, borderHeight,OrientationMode);
}
public Bitmap DrawAsImage(Pen pen, Font font, int h, int horPadding,int verPadding,
int horInterval, int verInterval, int borderWidth,int borderHeight,Orientation orientation)
{
Bitmap bmp = new Bitmap(1, 1);
Graphics g = Graphics.FromImage(bmp);
// 把树扁平化
List<List<Tree<T>>> layers = GetTreeLayers();
// 算出每个单元的大小
MeatureAllSize(g, font, horPadding,verPadding);
g.Dispose();
bmp.Dispose();
if (OrientationMode == Orientation.Vertical)//水平时
{
// 从最后一层开始排列
//int left = 0;
for (int i = layers.Count - 1; i >= 0; i--)
{
int left = 0;
for (int j = 0; j < layers[i].Count; j++)
{
layers[i][j].SetRectangle(i, h, horInterval, verInterval, left, OrientationMode);
left = layers[i][j].Rec.Right;
}
}
Offset(borderWidth, borderWidth);
// 获取画布需要的大小
maxHeight = (h + verInterval) * layers.Count - verInterval + borderWidth * 2;
maxWidth = 0;
for (int i = layers.Count - 1; i >= 0; i--)
{
for (int j = 0; j < layers[i].Count; j++)
{
if (layers[i][j].Rec.Right > maxWidth)
maxWidth = layers[i][j].Rec.Right;
}
}
maxWidth += borderWidth; // 边宽
// 画
bmp = new Bitmap(maxWidth, maxHeight);
}
else//水平时
{
// 从最后一列开始排列
for (int i = layers.Count - 1; i >= 0; i--)
{
int top = 0;
for (int j = 0; j < layers[i].Count; j++)
{
layers[i][j].SetRectangle(i, h, horInterval, verInterval, top, OrientationMode);
top = layers[i][j].Rec.Bottom;
}
}
Offset(borderWidth, borderWidth);
// 获取画布需要的大小
maxHeight=0;
maxWidth = 0;
for (int i = layers.Count - 1; i >= 0; i--)
{
for (int j = 0; j < layers[i].Count; j++)
{
if (layers[i][j].Rec.Right > maxWidth)
maxWidth = layers[i][j].Rec.Right;
if(layers[i][j].Rec.Bottom>maxHeight)
maxHeight = layers[i][j].Rec.Bottom;
}
}
maxWidth += borderWidth; // 边宽
maxHeight += borderHeight;
// 画
bmp = new Bitmap(maxWidth, maxHeight);
}
g = Graphics.FromImage(bmp);
//这里主要针对贝塞尔曲线的抗锯齿,但是效果不怎么理想
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; //图片柔顺模式选择
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBilinear;//高质量
g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;//再加一点
g.Clear(Color.White);
StringFormat format = (StringFormat)StringFormat.GenericDefault.Clone();
format.Alignment = StringAlignment.Center;
format.LineAlignment = StringAlignment.Far;
Rectangle rec, recParent;
for (int i = 0; i < layers.Count; i++)
{
for (int j = 0; j < layers[i].Count; j++)
{
// 画字
rec = (Rectangle)layers[i][j].Rec;
//矩形内矩形
Rectangle rec2 = new Rectangle(rec.Left + 2, rec.Top + 2, rec.Width - 4, rec.Height - 4);
g.DrawRectangle(pen, rec);
//画阴影
g.DrawLine(Pens.DimGray, new Point(rec.Left + 1, rec.Bottom + 1), new Point(rec.Right + 1, rec.Bottom + 1));
g.DrawLine(Pens.DimGray, new Point(rec.Right + 1, rec.Top + 1), new Point(rec.Right + 1, rec.Bottom + 1));
g.DrawLine(Pens.Gray, new Point(rec.Left + 2, rec.Bottom + 2), new Point(rec.Right + 2, rec.Bottom + 2));
g.DrawLine(Pens.Gray, new Point(rec.Right + 2, rec.Top + 2), new Point(rec.Right + 2, rec.Bottom + 2));
//g.DrawEllipse(pen, rec);
if (layers[i][j].isHover && !layers[i][j].isClick)//进入矩形时执行
{
g.FillRectangle(new LinearGradientBrush(rec, Color.White, Color.LightBlue, LinearGradientMode.ForwardDiagonal), rec);
//g.FillEllipse(new SolidBrush(hoverColor), rec);
}
if (layers[i][j].isHover && layers[i][j].isClick)//点击矩形时执行
{
g.DrawRectangle(Pens.Black, rec2);
g.FillRectangle(new LinearGradientBrush(rec, Color.White, Color.AliceBlue, LinearGradientMode.ForwardDiagonal), rec2);
//g.FillEllipse(new SolidBrush(hoverColor), rec);
}
g.DrawString(layers[i][j].Content.ToString(), font, new SolidBrush(pen.Color),
rec, format);
// 画到父亲的线
if (layers[i][j].Parent != null)
{
recParent = layers[i][j].Parent.Rec;
//画贝塞尔线
if (orientation == Orientation.Vertical)
{
g.DrawBezier(pen, new Point(rec.Left + rec.Width / 2, rec.Top), new Point(rec.Left + rec.Width / 2, rec.Top - verInterval / 2), new Point(recParent.Left + recParent.Width / 2, recParent.Bottom + verInterval / 2), new Point(recParent.Left + recParent.Width / 2, recParent.Bottom));
}
else
{
g.DrawBezier(pen, new Point(rec.Left, rec.Top + rec.Height / 2), new Point(rec.Left - horInterval / 2, rec.Top + rec.Height / 2), new Point(recParent.Right + horInterval / 2, recParent.Bottom - recParent.Height / 2), new Point(recParent.Right, recParent.Bottom - recParent.Height / 2));
}
}
}
}
g.Flush();
g.Dispose();
return bmp;
}
}
}
form程序如下:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace DrawOrgChart
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
List<Tree<string>> nodes = new List<Tree<string>>();
Tree<string> tree;
private void Form1_Load(object sender, EventArgs e)
{
cmbOrientation.SelectedIndex = 0;
tree = new Tree<string>(null, "董事会");
nodes.Add(tree);
nodes.Add(tree.Add("董事秘书室特殊机构"));
nodes.Add(tree.Add("北京公司"));
nodes.Add(tree.Add("上海公司"));
nodes.Add(tree.Add("山西公司"));
nodes.Add(tree.Childs[1].Add("总经理办公室"));
nodes.Add(tree.Childs[1].Add("财务部"));
nodes.Add(tree.Childs[1].Add("销售部"));
nodes.Add(tree.Childs[2].Add("上海销售部"));
nodes.Add(tree.Childs[2].Add("上海秘书处"));
nodes.Add(tree.Childs[3].Add("太原分部"));
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
Point p = e.Location;
double scaleX = (double)tree.MaxWidth / pictureBox1.Width;
double scaleY = (double)tree.MaxHeight / pictureBox1.Height;
switch (pictureBox1.SizeMode)
{
case PictureBoxSizeMode.Zoom:
double scale = scaleX < scaleY ? scaleY : scaleX;
if (scale == scaleX)
{
p.Y = (int)((p.Y - (pictureBox1.Height - tree.MaxHeight / scale) / 2) * scale);
p.X = (int)(p.X * scale);
}
else
{
p.X = (int)((p.X - (pictureBox1.Width - tree.MaxWidth / scale) / 2) * scale);
p.Y = (int)(p.Y * scale);
}
break;
case PictureBoxSizeMode.StretchImage:
p.X = (int)(p.X * scaleX);
p.Y = (int)(p.Y * scaleY);
break;
}
bool changed = false;
foreach (var node in nodes)
{
if (node.Rec.Contains(p))
{
if (!node.IsHover)
{
node.IsHover = true;
changed = true;
}
break;
}
else
{
if (node.IsHover)
{
node.IsHover = false;
changed = true;
}
}
}
if (changed)
{
Bitmap bmp = tree.DrawAsImage();
pictureBox1.Image = bmp;
pictureBox1.Refresh();
}
}
private void pictureBox1_MouseClick(object sender, MouseEventArgs e)
{
Point p = pictureBox1.PointToClient(MousePosition);
double scaleX = (double)tree.MaxWidth / pictureBox1.Width;
double scaleY = (double)tree.MaxHeight / pictureBox1.Height;
switch (pictureBox1.SizeMode)
{
case PictureBoxSizeMode.Zoom:
double scale = scaleX < scaleY ? scaleY : scaleX;
if (scale == scaleX)
{
p.Y = (int)((p.Y - (pictureBox1.Height - tree.MaxHeight / scale) / 2) * scale);
p.X = (int)(p.X * scale);
}
else
{
p.X = (int)((p.X - (pictureBox1.Width - tree.MaxWidth / scale) / 2) * scale);
p.Y = (int)(p.Y * scale);
}
break;
case PictureBoxSizeMode.StretchImage:
p.X = (int)(p.X * scaleX);
p.Y = (int)(p.Y * scaleY);
break;
}
Tree<string> nodeB = null;
bool rectClicked = false;
foreach (var node in nodes)
{
if (node.Rec.Contains(p))
{
nodeB = node;
rectClicked = true;
break;
}
}
if (e.Button == MouseButtons.Left && e.Clicks == 1)
{
if (rectClicked)
{
Form2 frm = new Form2();
frm.Text = nodeB.Content.ToString();
frm.Show();
//MessageBox.Show(nodeB.Content.ToString());
}
}
}
private void contextMenuStrip1_Opening(object sender, CancelEventArgs e)
{
pictureBox1.ContextMenuStrip.Enabled = true;
Point p = pictureBox1.PointToClient(MousePosition);
double scaleX = (double)tree.MaxWidth / pictureBox1.Width;
double scaleY = (double)tree.MaxHeight / pictureBox1.Height;
switch (pictureBox1.SizeMode)
{
case PictureBoxSizeMode.Zoom:
double scale = scaleX < scaleY ? scaleY : scaleX;
if (scale == scaleX)
{
p.Y = (int)((p.Y - (pictureBox1.Height - tree.MaxHeight / scale) / 2) * scale);
p.X = (int)(p.X * scale);
}
else
{
p.X = (int)((p.X - (pictureBox1.Width - tree.MaxWidth / scale) / 2) * scale);
p.Y = (int)(p.Y * scale);
}
break;
case PictureBoxSizeMode.StretchImage:
p.X = (int)(p.X * scaleX);
p.Y = (int)(p.Y * scaleY);
break;
}
bool rectClicked = false;
foreach (var node in nodes)
{
if (node.Rec.Contains(p))
{
rectClicked = true;
break;
}
}
if (!rectClicked)
{
contextMenuStrip1.Items[0].Enabled = true;
contextMenuStrip1.Items[1].Enabled = false;
contextMenuStrip1.Items[2].Enabled = false;
}
else
{
contextMenuStrip1.Items[0].Enabled = true;
contextMenuStrip1.Items[1].Enabled = true;
contextMenuStrip1.Items[2].Enabled = true;
}
}
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
Point p = pictureBox1.PointToClient(MousePosition);
double scaleX = (double)tree.MaxWidth / pictureBox1.Width;
double scaleY = (double)tree.MaxHeight / pictureBox1.Height;
switch (pictureBox1.SizeMode)
{
case PictureBoxSizeMode.Zoom:
double scale = scaleX < scaleY ? scaleY : scaleX;
if (scale == scaleX)
{
p.Y = (int)((p.Y - (pictureBox1.Height - tree.MaxHeight / scale) / 2) * scale);
p.X = (int)(p.X * scale);
}
else
{
p.X = (int)((p.X - (pictureBox1.Width - tree.MaxWidth / scale) / 2) * scale);
p.Y = (int)(p.Y * scale);
}
break;
case PictureBoxSizeMode.StretchImage:
p.X = (int)(p.X * scaleX);
p.Y = (int)(p.Y * scaleY);
break;
}
Tree<string> nodeB = null;
bool rectClicked = false;
foreach (var node in nodes)
{
if (node.Rec.Contains(p))
{
nodeB = node;
rectClicked = true;
break;
}
}
if (rectClicked)
{
nodeB.IsClick = true;
Bitmap bmp = tree.DrawAsImage();
pictureBox1.Image = bmp;
pictureBox1.Refresh();
}
}
}
private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
Point p = pictureBox1.PointToClient(MousePosition);
double scaleX = (double)tree.MaxWidth / pictureBox1.Width;
double scaleY = (double)tree.MaxHeight / pictureBox1.Height;
switch (pictureBox1.SizeMode)
{
case PictureBoxSizeMode.Zoom:
double scale = scaleX < scaleY ? scaleY : scaleX;
if (scale == scaleX)
{
p.Y = (int)((p.Y - (pictureBox1.Height - tree.MaxHeight / scale) / 2) * scale);
p.X = (int)(p.X * scale);
}
else
{
p.X = (int)((p.X - (pictureBox1.Width - tree.MaxWidth / scale) / 2) * scale);
p.Y = (int)(p.Y * scale);
}
break;
case PictureBoxSizeMode.StretchImage:
p.X = (int)(p.X * scaleX);
p.Y = (int)(p.Y * scaleY);
break;
}
Tree<string> nodeB = null;
bool rectClicked = false;
foreach (var node in nodes)
{
if (node.Rec.Contains(p))
{
nodeB = node;
rectClicked = true;
break;
}
}
if (rectClicked)
{
nodeB.IsClick = false;
Bitmap bmp = tree.DrawAsImage();
pictureBox1.Image = bmp;
pictureBox1.Refresh();
}
}
}
private void btn1_Click(object sender, EventArgs e)
{
switch (cmbOrientation.SelectedItem.ToString())
{
case "水平":
foreach(var tree in nodes)
tree.OrientationMode = Orientation.Horizontal;
break;
case "垂直":
foreach(var tree in nodes)
tree.OrientationMode = Orientation.Vertical;
break;
}
int hinterval,vinterval;
if (!int.TryParse(txtHinterval.Text, out hinterval) || !int.TryParse(txtVinterval.Text, out vinterval))
{
MessageBox.Show("横向间距和纵向间距只能设置为整数");
return;
}
else {
tree.HInterval = hinterval;
tree.VInterval = vinterval;
}
Bitmap bmp = tree.DrawAsImage();
pictureBox1.Image = bmp;
pictureBox1.SizeMode = PictureBoxSizeMode.Zoom;
pictureBox1.Refresh();
}
}
}