发布一个一年前帮朋友写的 C# 干特图 代码

一年前,朋友说他们的项目中需要一个简单直观显示航班
时间安排的干特图,要我帮他写一个,于是就有了这个东西。
吧代码发出来纪念一下:

这个是代码:

/**
 * @authoer 周丕中
 * @site http://zpz.name
 * @email zpz@zpz.name
 * @time 2011-03-27
 * */

//@TODO 节点颜色定义
//@TODO 节点预先排序

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;

namespace name.zpz.gantt
{
    class GanttView : UserControl
    {
        private System.ComponentModel.IContainer components = null;

        private HScrollBar oHScrollBar;
        private VScrollBar oVScrollBar;
        private ToolTip oToolTip;
        private PictureBox oPictureBox;

        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        private void InitializeComponent()
        {
            this.components = new System.ComponentModel.Container();
            this.oHScrollBar = new System.Windows.Forms.HScrollBar();
            this.oVScrollBar = new System.Windows.Forms.VScrollBar();
            this.oPictureBox = new System.Windows.Forms.PictureBox();
            this.oToolTip = new System.Windows.Forms.ToolTip(this.components);
            ((System.ComponentModel.ISupportInitialize)(this.oPictureBox)).BeginInit();
            this.SuspendLayout();
            // 
            // oHScrollBar
            // 
            this.oHScrollBar.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
                        | System.Windows.Forms.AnchorStyles.Right)));
            this.oHScrollBar.LargeChange = 1;
            this.oHScrollBar.Location = new System.Drawing.Point(1, 287);
            this.oHScrollBar.Maximum = 10;
            this.oHScrollBar.Name = "oHScrollBar";
            this.oHScrollBar.Size = new System.Drawing.Size(310, 16);
            this.oHScrollBar.TabIndex = 0;
            this.oHScrollBar.Scroll += new System.Windows.Forms.ScrollEventHandler(this.oHScrollBar_Scroll);
            // 
            // oVScrollBar
            // 
            this.oVScrollBar.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
                        | System.Windows.Forms.AnchorStyles.Right)));
            this.oVScrollBar.LargeChange = 1;
            this.oVScrollBar.Location = new System.Drawing.Point(311, 0);
            this.oVScrollBar.Maximum = 10;
            this.oVScrollBar.Name = "oVScrollBar";
            this.oVScrollBar.Size = new System.Drawing.Size(16, 287);
            this.oVScrollBar.TabIndex = 1;
            this.oVScrollBar.Scroll += new System.Windows.Forms.ScrollEventHandler(this.oVScrollBar_Scroll);
            // 
            // oPictureBox
            // 
            this.oPictureBox.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
                        | System.Windows.Forms.AnchorStyles.Left)
                        | System.Windows.Forms.AnchorStyles.Right)));
            this.oPictureBox.BackColor = System.Drawing.SystemColors.InactiveCaptionText;
            this.oPictureBox.Location = new System.Drawing.Point(0, 0);
            this.oPictureBox.Name = "oPictureBox";
            this.oPictureBox.Size = new System.Drawing.Size(310, 285);
            this.oPictureBox.TabIndex = 2;
            this.oPictureBox.TabStop = false;
            this.oPictureBox.MouseMove += new System.Windows.Forms.MouseEventHandler(this.oPictureBox_MouseMove);
            // 
            // GanttView
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.BackColor = System.Drawing.SystemColors.InactiveCaptionText;
            this.Controls.Add(this.oVScrollBar);
            this.Controls.Add(this.oHScrollBar);
            this.Controls.Add(this.oPictureBox);
            this.Name = "GanttView";
            this.Size = new System.Drawing.Size(329, 305);
            this.Paint += new System.Windows.Forms.PaintEventHandler(this.GanttView_Paint);
            ((System.ComponentModel.ISupportInitialize)(this.oPictureBox)).EndInit();
            this.ResumeLayout(false);

        }

        /// <summary>
        /// 构造函数
        /// </summary>
        public GanttView()
        {
            InitializeComponent();
        }

        /// <summary>
        /// 头部行高
        /// </summary>
        private int iHeaderLineHeight = 20;

        /// <summary>
        /// 头部行高
        /// </summary>
        public int HeaderLineHeight
        {
            get
            {
                return this.iHeaderLineHeight;
            }
            set
            {
                this.iHeaderLineHeight = value;
                this.draw();
            }
        }


        /// <summary>
        /// 每一分钟的宽度
        /// </summary>
        private int iMinuteWidth = 6;

        /// <summary>
        /// 每一分钟的宽度
        /// </summary>
        public int MinuteWidth
        {
            get
            {
                return this.iMinuteWidth;
            }
            set
            {
                this.iMinuteWidth = value;
                this.draw();
            }
        }

        /// <summary>
        /// 节点的高度
        /// </summary>
        private int iNodeHeight = 18;

        /// <summary>
        /// 节点的高度
        /// </summary>
        public int NodeHeight
        {
            get
            {
                return this.iNodeHeight;
            }
            set
            {
                this.iNodeHeight = value;
                this.draw();
            }
        }

        /// <summary>
        /// 节点的垂直为值
        /// </summary>
        private int iVerticalSpace = 2;

        /// <summary>
        /// 节点的垂直为值
        /// </summary>
        public int VerticalSpace
        {
            get
            {
                return this.iVerticalSpace;
            }
            set
            {
                this.iVerticalSpace = value;
                this.draw();
            }
        }

        /// <summary>
        /// 记录水平滚动的值
        /// </summary>
        private int iVScroll = 0;

        /// <summary>
        /// 记录垂直滚动的值
        /// </summary>
        private int iHScroll = 0;

        /// <summary>
        /// 每次水平滚动所长生的分钟差值
        /// </summary>
        private int iMinutesPreScroll = 10;

        /// <summary>
        /// 每次垂直滚动滚动的行数
        /// </summary>
        private int iLinesPreScroll = 8;


        /// <summary>
        /// 将DateTime的Ticks转换为分钟
        /// </summary>
        const int TRANSLATE_DELTA = 1000 * 1000 * 10 * 60;

        /// <summary>
        /// 将一个时间转换为分钟数
        /// </summary>
        /// <param name="dDate"></param>
        /// <returns></returns>
        public static int dateToMinutes(DateTime dDate)
        {
            return (int)(dDate.Ticks / GanttView.TRANSLATE_DELTA);
        }

        /// <summary>
        /// 添加节点
        /// </summary>
        /// <param name="sName">名称</param>
        /// <param name="iCompletePrecent">完成百分比,是个0-100之间的整数</param>
        /// <param name="dStartTime">开始时间</param>
        /// <param name="dEndTime">结束时间</param>
        public void add(string sName, int iCompletePrecent, DateTime dStartTime, DateTime dEndTime)
        {
            this.oNodeList.add(new Node(this, sName, iCompletePrecent, dStartTime, dEndTime));
        }

        /// <summary>
        /// 绘制
        /// </summary>
        private void draw()
        {
            this.searchNodeInView();
            try
            {
                this.oPictureBox.Image.Dispose();
            }
            catch (Exception)
            {
            }

            if (this.oPictureBox.Width > 0 && this.oPictureBox.Height > 0)
            {
                this.NodeList.rebuild();

                this.oPictureBox.Image = new Bitmap(this.oPictureBox.Width, this.oPictureBox.Height);
                Graphics oGraphics = Graphics.FromImage(this.oPictureBox.Image);

                this.drawHeader(oGraphics);
                this.drawNodes(oGraphics);
            }
        }

        /// <summary>
        /// 当前视图所能容纳的分钟数
        /// </summary>
        public int MinutesSpan
        {
            get
            {
                if (this.oPictureBox.Image == null)
                {
                    return 0;
                }
                else
                {
                    return (int)(this.oPictureBox.Width / this.iMinuteWidth);

                }

            }
        }

        /// <summary>
        /// 当期视图所能容纳的行数
        /// </summary>
        public int DrawLines
        {
            get
            {
                if (this.oPictureBox.Image == null)
                {
                    return 0;
                }
                else
                {
                    return (int)(this.oPictureBox.Height / (this.iNodeHeight + this.iVerticalSpace));

                }

            }
        }

        /// <summary>
        /// 头部高度,根据头部绘制情况动态变化
        /// </summary>
        private int iHeaderHeight;

        /// <summary>
        /// 绘制头部
        /// </summary>
        private void drawHeader(Graphics oGraphics)
        {
            this.iHeaderHeight = this.iHeaderLineHeight;

            //确定开始时间,假如没有节点,就从当前时间开始绘制
            DateTime dStartTime = DateTime.Now;

            Node oFirstNode = this.oNodeList.TheFirstNode;
            if (oFirstNode != null)
            {
                dStartTime = oFirstNode.StartTime;
            }

            //要绘制的中分钟数
            int iDrawMinutesSpan = this.MinutesSpan;

            dStartTime = dStartTime.AddMinutes(this.iMinutesPreScroll * this.iHScroll);

            StringFormat oStringFormat = new StringFormat();
            oStringFormat.LineAlignment = StringAlignment.Near;
            oStringFormat.LineAlignment = StringAlignment.Center;

            //最小4像素,避免小于1产生异常
            int iFontSize = Math.Max(4, (int)(this.iHeaderLineHeight / 2));
            Pen oLinePen = new Pen(Brushes.Black, 1);
            Font oTextFont = new Font(SystemFonts.DefaultFont.FontFamily, iFontSize, FontStyle.Bold);//new Font("宋体", 9.0f);
            Brush oTextBrush = new SolidBrush(Color.Black);

            //日期绘制 
            //绘制当前的小时
            DateTime dCountDate = new DateTime(dStartTime.Ticks);
            int iHour = dStartTime.Hour + 1;
            int iMinutes = 60 * (24 - iHour) + (60 - dStartTime.Minute);
            Rectangle oDateRect = new Rectangle(
                0,
                0,
                iMinutes * this.iMinuteWidth,
                this.iHeaderLineHeight
            );
            oGraphics.DrawRectangle(oLinePen, oDateRect);
            oGraphics.DrawString(
                dCountDate.ToString("yyyy年MM月dd日"),
                oTextFont,
                oTextBrush,
                oDateRect,
                oStringFormat
            );

            //还需要绘制下一个日期
            if (iMinutes < iDrawMinutesSpan)
            {
                oDateRect.Width = 24 * 60 * this.iMinuteWidth;
                do
                {
                    oDateRect.X = iMinutes * this.iMinuteWidth;
                    iMinutes += 60 * 24;
                    dCountDate = dCountDate.AddDays(1);
                    oGraphics.DrawRectangle(oLinePen, oDateRect);
                    oGraphics.DrawString(
                        dCountDate.ToString("yyyy年MM月dd日"),
                        oTextFont,
                        oTextBrush,
                        oDateRect,
                        oStringFormat
                    );
                } while (iMinutes < iDrawMinutesSpan);
            }

            this.iHeaderHeight += this.iHeaderLineHeight;
            //绘制当前的小时
            dCountDate = new DateTime(dStartTime.Ticks);
            iMinutes = 60 - dStartTime.Minute;
            Rectangle oHourRect = new Rectangle(
                0,
                this.iHeaderLineHeight,
                iMinutes * this.iMinuteWidth,
                this.iHeaderLineHeight
            );
            oGraphics.DrawRectangle(oLinePen, oHourRect);
            oGraphics.DrawString(
                dCountDate.Hour.ToString() + "点",
                oTextFont,
                oTextBrush,
                oHourRect,
                oStringFormat
            );

            //还需要绘制下一个小时
            if (iMinutes < iDrawMinutesSpan)
            {
                oHourRect.Width = 60 * this.iMinuteWidth;
                do
                {
                    oHourRect.X = iMinutes * this.iMinuteWidth;
                    iMinutes += 60;
                    dCountDate = dCountDate.AddHours(1);
                    oGraphics.DrawRectangle(oLinePen, oHourRect);
                    oGraphics.DrawString(
                        dCountDate.Hour.ToString() + "点",
                        oTextFont,
                        oTextBrush,
                        oHourRect,
                        oStringFormat
                    );
                } while (iMinutes < iDrawMinutesSpan);
            }

            //只有当分钟的宽度大于2是绘制风中格子
            if (this.iMinuteWidth > 2)
            {

                this.iHeaderHeight += this.iHeaderLineHeight;
                //分钟绘制
                dCountDate = new DateTime(dStartTime.Ticks);
                int iMinutsY = 2 * this.iHeaderLineHeight;
                Rectangle oMinutsRect = new Rectangle(
                    0,
                    iMinutsY,
                    this.iMinuteWidth,
                    this.iHeaderLineHeight
                );

                //绘制所有的分钟
                for (int i = 0; i <= iDrawMinutesSpan; i++)
                {
                    //绘制分钟矩形
                    oGraphics.DrawRectangle(oLinePen, oMinutsRect);
                    oMinutsRect.X += this.iMinuteWidth;
                }
            }
        }

        /// <summary>
        /// 绘制节点
        /// </summary>
        private void drawNodes(Graphics oGraphics)
        {
            //绘制当前视图范围内的节点
            //获取竖直范围的内的节点
            int iCount = this.oNodeInView.Count;
            for (int iIndex = 0; iIndex < iCount; iIndex++)
            {
                oNodeList[iIndex].draw(
                            oGraphics,
                            this.iViewPixelLeft,
                            this.iViewPixelTop,
                            this.iMinuteWidth,
                            this.iNodeHeight,
                            this.iVerticalSpace
                        );
            }
        }

        /// <summary>
        /// 视图显示区域中的节点
        /// </summary>
        private List<Node> oNodeInView = new List<Node>();
        private int iViewPixelLeft = 0;
        private int iViewPixelTop = 0;

        //获取所有在显示区域中的节点
        public void searchNodeInView()
        {
            oNodeInView.Clear();
            //重绘节点,只绘制当前显示视图范围内的节点
            if (this.oNodeList.Count > 0)
            {
                Node oFirstNode = this.oNodeList.TheFirstNode;
                Node oLastestNode = this.oNodeList.TheLatestNode;

                if (oFirstNode != null)
                {
                    //竖直范围,行数
                    int iDrawStartLine = this.iVScroll * this.iLinesPreScroll;

                    //绘制范围之内的节点
                    int iAllLines = this.oNodeList.Count;

                    //确定索引范围,以免产生空指针异常
                    if (iDrawStartLine < iAllLines && iDrawStartLine >= 0)
                    {
                        int iDrawLines = this.DrawLines + iDrawStartLine;

                        //水平范围,分钟为单位
                        int iStartMinute = oFirstNode.StartMinute;
                        int iEndMinute = oLastestNode.EndMinute;

                        int iDrawStartMinute = iStartMinute + (this.iMinutesPreScroll * this.iHScroll);
                        int iDrawMinutsSpan = this.MinutesSpan;


                        //按照最左的节点的开始时间的坐标为0计算
                        this.iViewPixelLeft = iDrawStartMinute * this.iMinuteWidth;
                        this.iViewPixelTop = iDrawStartLine * (this.iNodeHeight + this.iVerticalSpace);

                        //头部高度
                        this.iViewPixelTop -= this.iHeaderHeight + this.iVerticalSpace;


                        //绘制当前视图范围内的节点
                        //获取竖直范围的内的节点
                        for (int iLine = iDrawStartLine; iLine < iDrawLines && iLine < iAllLines; iLine++)
                        {
                            Node oNode = this.oNodeList[iLine];
                            if (oNode != null && (oNode.inMinutesRange(iDrawStartMinute, iDrawMinutsSpan)))
                            {
                                oNodeInView.Add(oNode);
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// 项目节点集合
        /// </summary>
        private NodeList oNodeList = new NodeList();

        public NodeList NodeList
        {
            get
            {
                return oNodeList;
            }
        }

        /// <summary>
        /// 处理水平滚动条事件
        /// </summary>
        /// <param name="iValue"></param>
        private void oHScrollBar_Scroll(object sender, ScrollEventArgs e)
        {
            this.iHScroll += e.NewValue - e.OldValue;

            //值未发生改变,表示移动到了尽头
            if (e.NewValue == e.OldValue)
            {
                //移动到了最小值
                this.iHScroll += (e.NewValue == ((HScrollBar)sender).Minimum) ? -1 : 1;
            }

            //this.oHScrollBar.Value = Math.Max(this.oHScrollBar.Minimum, Math.Min(this.oHScrollBar.Maximum, this.iHScroll));
            //this.oHScrollBar.Refresh();

            this.draw();
        }

        /// <summary>
        /// 处理竖直滚动条事件
        /// </summary>
        /// <param name="iValue"></param>  
        private void oVScrollBar_Scroll(object sender, ScrollEventArgs e)
        {
            this.iVScroll += e.NewValue - e.OldValue;

            //值未发生改变,表示移动到了尽头
            if (e.NewValue == e.OldValue)
            {
                //移动到了最小值   
                iVScroll += (e.NewValue == ((VScrollBar)sender).Minimum) ? -1 : 1;
            }

            //垂直滚动条只需要处理向下滚动  
            this.iVScroll = Math.Max(0, this.iVScroll);

            //修正滚动条显示
            //bug无效
            //this.oVScrollBar.Value = Math.Max(this.oVScrollBar.Minimum, Math.Min(this.oVScrollBar.Maximum, this.iVScroll));
            //this.oVScrollBar.Refresh();

            this.draw();
        }

        /// <summary>
        /// 重绘视图
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void GanttView_Paint(object sender, PaintEventArgs e)
        {
            this.draw();
        }

        /// <summary>
        /// ToolTip提示信息
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void oPictureBox_MouseMove(object sender, MouseEventArgs e)
        {
            bool bFind = false;
            int iCount = this.oNodeInView.Count;
            for (int iIndex = 0; iIndex < iCount; iIndex++)
            {
                Node oNode = this.oNodeInView[iIndex];
                if (oNode.getRectangle(
                    this.iViewPixelLeft,
                    this.iViewPixelTop,
                    this.iMinuteWidth,
                    this.iNodeHeight,
                    this.iVerticalSpace
                    ).Contains(new Point(e.X, e.Y))
                ) {
                    this.oToolTip.ToolTipTitle = oNode.ToolTipTitle;
                    this.oToolTip.Show(
                        oNode.ToolTipConent,
                        this.oPictureBox,
                        new Point(e.X, e.Y)
                    );
                    bFind = true;
                }
            }
            if (bFind == false)
            {
                this.oToolTip.Hide(this.oPictureBox);
            }
        }

    }

    /// <summary>
    /// 节点集合
    /// </summary>
    class NodeList
    {
        private IList<Node> aNodes = new List<Node>();

        /// <summary>
        /// 开始时间最早的节点
        /// </summary>
        private Node oTheFirstNode;

        /// <summary>
        /// 结束时间最晚的节点
        /// </summary>
        private Node oTheLatestNode;

        public Node TheFirstNode
        {
            get
            {
                return this.oTheFirstNode;
            }
        }

        public Node TheLatestNode
        {
            get
            {
                return this.oTheLatestNode;
            }
        }

        public Node this[int iIndex]
        {
            get
            {
                if (iIndex > this.aNodes.Count - 1 || iIndex < 0)
                {
                    return null;
                }
                return this.aNodes[iIndex];
            }
        }

        /// <summary>
        /// 节点的总数目
        /// </summary>
        public int Count
        {
            get
            {
                return aNodes.Count;
            }
        }

        /// <summary>
        /// 是否需要重新构范围信息
        /// </summary>
        private bool bNeedRebuild = false;

        /// <summary>
        /// 是否需要重新构范围信息
        /// </summary>
        public bool NeedRebuild
        {
            get
            {
                return this.bNeedRebuild;
            }
            set
            {
                this.bNeedRebuild = value;
            }
        }

        /// <summary>
        /// 添加节点
        /// </summary>
        /// <param name="oNode"></param>
        public void add(Node oNode)
        {
            if (oNode != null)
            {
                this.aNodes.Add(oNode);
                this.bNeedRebuild = true;
            }
        }

        /// <summary>
        /// 删除索引处的节点
        /// </summary>
        /// <param name="iIndex"></param>
        public void removeAt(int iIndex)
        {
            if (iIndex >= 0 && iIndex < this.aNodes.Count)
            {
                this.aNodes.RemoveAt(iIndex);
                this.bNeedRebuild = true;
            }
        }

        /// <summary>
        /// 清除所有节点
        /// </summary>
        public void clear()
        {
            this.aNodes.Clear();
            this.bNeedRebuild = true;
        }

        /// <summary>
        /// 重建范围信息
        /// </summary>
        public void rebuild()
        {
            if (this.bNeedRebuild == true)
            {  
                this.oTheFirstNode = null;
                this.oTheLatestNode = null;
                int iCount = this.Count;
                for (int i = 0; i < iCount; i++)
                {
                    Node oNode = this.aNodes[i];
                    oNode.Index = i;
                    if (this.oTheFirstNode == null)
                    {
                        this.oTheFirstNode = oNode;
                    }
                    if (this.oTheLatestNode == null)
                    {
                        this.oTheLatestNode = oNode;
                    }

                    if (oNode.StartMinute < this.oTheFirstNode.StartMinute)
                    {
                        this.oTheFirstNode = oNode;
                    }

                    if (oNode.EndMinute > this.oTheLatestNode.EndMinute)
                    {
                        this.oTheLatestNode = oNode;
                    }
                }
            }

            this.bNeedRebuild = false;
        }
    }

    /// <summary>
    /// 项目节点
    /// </summary>
    class Node
    {
        /// <summary>
        /// 对应的gantt图
        /// </summary>
        private GanttView oGantt;

        /// <summary>
        /// 节点名称
        /// </summary>
        private string sName;

        /// <summary>
        /// 完成百分比,一个0-100之间的数字
        /// </summary>
        private int iCompletePrecent;

        /// <summary>
        /// 起始时间
        /// </summary>
        private DateTime dStartTime;

        /// <summary>
        /// 结束时间
        /// </summary>
        private DateTime dEndTime;

        /// <summary>
        /// 转换之后的起始分钟数
        /// </summary>
        private int iStartMinute;

        /// <summary>
        /// 转化之后的结束分钟数
        /// </summary>
        private int iEndMinute;

        /// <summary>
        /// 时间跨度的分钟数
        /// </summary>
        private int iMinutesSpan;

        /// <summary>
        /// 时间跨度的分钟数
        /// </summary>
        public int MinutesSpan
        {
            get
            {
                return iMinutesSpan;
            }
        }

        private int iIndex;
        /// <summary>
        /// 此节点在节点列表中的索引
        /// </summary>
        public int Index
        {
            get
            {
                return this.iIndex;
            }
            set
            {
                this.iIndex = value;
            }
        }

        /// <summary>
        /// 节点名称
        /// </summary>
        public string Name
        {
            get
            {
                return sName;
            }
            set
            {
                sName = value;
            }
        }

        public int StartMinute
        {
            get { return iStartMinute; }
        }

        public int EndMinute
        {
            get { return iEndMinute; }
        }

        public DateTime StartTime
        {
            get
            {
                return dStartTime;
            }
            set
            {
                dStartTime = value;
                this.reCalcMinutesSpan();
            }
        }

        /// <summary>
        /// 提示信息标题
        /// </summary>
        public string ToolTipTitle
        {
            get
            {
                return this.Name;
            }
        }

        /// <summary>
        /// 信息提示内容
        /// </summary>
        public string ToolTipConent
        {
            get
            {
                return "完成百分比:" + this.iCompletePrecent + "%\n";
            }
        }

        /// <summary>
        /// 重新计算时间跨度分钟数,并设置重新计算范围
        /// </summary>
        private void reCalcMinutesSpan()
        {
            iStartMinute = GanttView.dateToMinutes(dStartTime);
            iEndMinute = GanttView.dateToMinutes(dEndTime);
            iMinutesSpan = iEndMinute - iStartMinute;

            //时间开始和结束发生改变,所以标记重新计算范围
            if (this.oGantt != null)
            {
                this.oGantt.NodeList.NeedRebuild = true;
            }
        }

        public DateTime EndTime
        {
            get
            {
                return dEndTime;
            }
            set
            {
                dEndTime = value;
                this.reCalcMinutesSpan();
            }
        }

        public int CompletePrecent
        {
            get
            {
                return iCompletePrecent;
            }
            set
            {
                //修正范围
                iCompletePrecent = Math.Max(0, Math.Min(100, value));
            }
        }

        /// <summary>
        /// 构造函数
        /// </summary>
        /// </summary>
        /// <param name="oGantt"></param>
        /// <param name="sName">节点名称</param>
        /// <param name="iCompletePrecent">节点完成进度</param>
        /// <param name="dStartTime">节点的开始时间</param>
        /// <param name="dEndTime">节点的结束时间</param>
        public Node(GanttView oGantt, string sName, int iCompletePrecent, DateTime dStartTime, DateTime dEndTime)
        {
            this.oGantt = oGantt;

            this.sName = sName;
            this.EndTime = dEndTime;
            this.StartTime = dStartTime;
            this.iCompletePrecent = iCompletePrecent;
        }

        const int TRANSLATE_DELTA = 1000 * 1000 * 10 * 60;

        /// <summary>
        /// 时间跨度的分钟数
        /// </summary>
        /// <returns>此节点的时间跨度的分钟数</returns>
        public int spanMinutes()
        {
            return (int)((this.dEndTime.Ticks - this.dStartTime.Ticks) / Node.TRANSLATE_DELTA);
        }

        /// <summary>
        /// 当前节点是否在制定的分钟范围之内
        /// </summary>
        /// <param name="iStartMinuts">其实分钟数</param>
        /// <param name="iMinutsSpan">分钟跨度</param>
        /// <returns></returns>
        public bool inMinutesRange(int iStartMinutes, int iMinutsSpan)
        {
            return (this.iStartMinute <= iStartMinutes + iMinutsSpan) || (this.iEndMinute < iStartMinutes);
        }
        

        /// <summary>
        /// 获取节点在视图区域中的矩形
        /// </summary>
        /// <param name="oGraphics"></param>
        /// <param name="iLine">此节点所在的行数</param>
        /// <param name="iPixelLeft">当前视图的x坐标相对于最早节点的像素值</param>
        /// <param name="iPixelTop">当前视图的y坐标相对于最早节点的像素值</param>
        /// <param name="iMinutesWidth">每分钟的宽度像素值</param>
        /// <param name="iNodeHeight">节点的高度像素值</param>
        /// <param name="iLineSpace">节点之间竖直方向上的间隔像素值</param>
        public Rectangle getRectangle(
            int iPixelLeft,
            int iPixelTop,
            int iMinutesWidth,
            int iNodeHeight,
            int iLineSpace
        )
        { 
            //节点的整个矩形
            return new Rectangle(
                (this.iStartMinute * iMinutesWidth) - iPixelLeft,
                (this.iIndex * (iNodeHeight + iLineSpace)) - iPixelTop,
                this.MinutesSpan * iMinutesWidth,
                iNodeHeight
            );
        }

        /// <summary>
        /// 绘制节点
        /// </summary>
        /// <param name="oGraphics"></param>
        /// <param name="iLine">此节点所在的行数</param>
        /// <param name="iPixelLeft">当前视图的x坐标相对于最早节点的像素值</param>
        /// <param name="iPixelTop">当前视图的y坐标相对于最早节点的像素值</param>
        /// <param name="iMinutesWidth">每分钟的宽度像素值</param>
        /// <param name="iNodeHeight">节点的高度像素值</param>
        /// <param name="iLineSpace">节点之间竖直方向上的间隔像素值</param>
        public void draw(
            Graphics oGraphics,
            int iPixelLeft,
            int iPixelTop,
            int iMinutesWidth,
            int iNodeHeight,
            int iLineSpace
        )
        {
            //节点的整个矩形
            Rectangle oPaintRect = new Rectangle(
                (this.iStartMinute * iMinutesWidth) - iPixelLeft,
                (this.iIndex * (iNodeHeight + iLineSpace)) - iPixelTop,
                this.MinutesSpan * iMinutesWidth,
                iNodeHeight
            );
            HatchBrush oBlackBrush = new HatchBrush(
                HatchStyle.Percent50,
                Color.Red,
                Color.White
            );

            //画背景
            oGraphics.FillRectangle(oBlackBrush, oPaintRect);

            //画边框
            oGraphics.DrawRectangle(new Pen(Brushes.Black, 1), oPaintRect);

            //画完成进度矩形
            Rectangle oCompleteRect = new Rectangle(
                oPaintRect.Left + 2,
                oPaintRect.Top + 2,
                (int)((oPaintRect.Width * this.iCompletePrecent) / 100) - 3,
                oPaintRect.Height - 3
            );
            SolidBrush oCompleteBrush = new SolidBrush(Color.Red);
            oGraphics.FillRectangle(oCompleteBrush, oCompleteRect);

            //绘制文本和完成进度文本
            StringFormat oStringFormt = new StringFormat();
            oStringFormt.Alignment = StringAlignment.Near;
            oStringFormt.LineAlignment = StringAlignment.Center;

            //重新计算字号                         
            //最小4像素,避免小于1产生异常
            int iFontSize = Math.Max(4, (int)(iNodeHeight / 2));
            Font oFont = new Font(SystemFonts.DefaultFont.FontFamily, iFontSize, FontStyle.Regular);//new Font("宋体", 8, FontStyle.Regular);
            String sDrawText = this.sName + "(" + this.iCompletePrecent + "%)";

            //重新计算文本的坐标,以便能在尽可能多是时间显示文本
            int iTextX = Math.Max(0, oPaintRect.X);
            //int iWidth = oPaintRect.Right - iTextX; //尝试居中显示,需要视图区域的宽度

            //若果超出视图范围就不显示文本
            if (iTextX < oPaintRect.Right)
            {
                oPaintRect.X = Math.Max(0, oPaintRect.X);
                //oPaintRect.Width = iWidth;
                oGraphics.DrawString(
                    sDrawText,
                    oFont,
                    Brushes.Black,
                    oPaintRect,
                    oStringFormt
                );
            }
        }
    }
}

完整代码包下载:
csharp-gantt 项目

Post a Comment

Your email is never shared. Required fields are marked *

*
*