一款开源且功能强大的C#甘特图控件.NET Winforms Gantt Chart Control
甘特图在项目管理中非常重要,甘特图的思想比较简单,即以图示的方式通过活动列表和时间刻度形象地表示出任何特定项目的活动顺序与持续时间。它直观地表明任务计划在什么时候进行,及实际进展与计划要求的对比。管理者由此可便利地弄清一项任务(项目)还剩下哪些工作要做,并可评估工作进度。甘特图可以显示几个部门、机器或设备的运行和闲置情况。这表示了该系统的有关工作负荷状况,这样可使管理人员了解何种调整是恰当的。
由于项目需要,在网上找了很久,经过大量的对比和评估,发现一款真正开源且功能强大的C#甘特图控件.NET Winforms Gantt Chart Control(http://ganttchart.codeplex.com/),效果图如下:

该款甘特图控件具有如下特征:
1、独立的时间单位数据结构规范;
2、支持单任务,分组任务,先例/依赖的任务,可以对任务进行拆分,并附加资源信息;
3、打印支持;
4、可对任务的计划和实际进行对比,以百分比进行进度跟踪;
5、在直接在甘特图上,对各种鼠标事件进行UI定制;
6、可以通过继承来修改默认的鼠标命令;
7、支持关键路径。
官方演示代码为:
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 Braincase.GanttChart
{
/// <summary>
/// An elaborate example on how the chart control might be used.
/// Start by collapsing all regions and then read the constructor.
/// Refer to IProjectManager interface for method description.
/// </summary>
public partial class ExampleFull : Form
{
OverlayPainter _mOverlay = new OverlayPainter(); ProjectManager _mManager = null; /// <summary>
/// Example starts here
/// </summary>
public ExampleFull()
{
InitializeComponent(); // Create a Project and some Tasks
_mManager = new ProjectManager();
var work = new MyTask(_mManager) { Name = "Prepare for Work" };
var wake = new MyTask(_mManager) { Name = "Wake Up" };
var teeth = new MyTask(_mManager) { Name = "Brush Teeth" };
var shower = new MyTask(_mManager) { Name = "Shower" };
var clothes = new MyTask(_mManager) { Name = "Change into New Clothes" };
var hair = new MyTask(_mManager) { Name = "Blow My Hair" };
var pack = new MyTask(_mManager) { Name = "Pack the Suitcase" }; _mManager.Add(work);
_mManager.Add(wake);
_mManager.Add(teeth);
_mManager.Add(shower);
_mManager.Add(clothes);
_mManager.Add(hair);
_mManager.Add(pack); // Create another 1000 tasks for stress testing
Random rand = new Random();
for (int i = ; i < ; i++)
{
var task = new MyTask(_mManager) { Name = string.Format("New Task {0}", i.ToString()) };
_mManager.Add(task);
_mManager.SetStart(task, rand.Next());
_mManager.SetDuration(task, rand.Next());
} // Set task durations, e.g. using ProjectManager methods
_mManager.SetDuration(wake, );
_mManager.SetDuration(teeth, );
_mManager.SetDuration(shower, );
_mManager.SetDuration(clothes, );
_mManager.SetDuration(hair, );
_mManager.SetDuration(pack, ); // demostrate splitting a task
_mManager.Split(pack, new MyTask(_mManager), new MyTask(_mManager), ); // Set task complete status, e.g. using newly created properties
wake.Complete = 0.9f;
teeth.Complete = 0.5f;
shower.Complete = 0.4f; // Give the Tasks some organisation, setting group and precedents
_mManager.Group(work, wake);
_mManager.Group(work, teeth);
_mManager.Group(work, shower);
_mManager.Group(work, clothes);
_mManager.Group(work, hair);
_mManager.Group(work, pack);
_mManager.Relate(wake, teeth);
_mManager.Relate(wake, shower);
_mManager.Relate(shower, clothes);
_mManager.Relate(shower, hair);
_mManager.Relate(hair, pack);
_mManager.Relate(clothes, pack); // Create and assign Resources.
// MyResource is just custom user class. The API can accept any object as resource.
var jake = new MyResource() { Name = "Jake" };
var peter = new MyResource() { Name = "Peter" };
var john = new MyResource() { Name = "John" };
var lucas = new MyResource() { Name = "Lucas" };
var james = new MyResource() { Name = "James" };
var mary = new MyResource() { Name = "Mary" };
// Add some resources
_mManager.Assign(wake, jake);
_mManager.Assign(wake, peter);
_mManager.Assign(wake, john);
_mManager.Assign(teeth, jake);
_mManager.Assign(teeth, james);
_mManager.Assign(pack, james);
_mManager.Assign(pack, lucas);
_mManager.Assign(shower, mary);
_mManager.Assign(shower, lucas);
_mManager.Assign(shower, john); // Initialize the Chart with our ProjectManager and CreateTaskDelegate
_mChart.Init(_mManager);
_mChart.CreateTaskDelegate = delegate() { return new MyTask(_mManager); }; // Attach event listeners for events we are interested in
_mChart.TaskMouseOver += new EventHandler<TaskMouseEventArgs>(_mChart_TaskMouseOver);
_mChart.TaskMouseOut += new EventHandler<TaskMouseEventArgs>(_mChart_TaskMouseOut);
_mChart.TaskSelected += new EventHandler<TaskMouseEventArgs>(_mChart_TaskSelected);
_mChart.PaintOverlay += _mOverlay.ChartOverlayPainter;
_mChart.AllowTaskDragDrop = true; // set some tooltips to show the resources in each task
_mChart.SetToolTip(wake, string.Join(", ", _mManager.ResourcesOf(wake).Select(x => (x as MyResource).Name)));
_mChart.SetToolTip(teeth, string.Join(", ", _mManager.ResourcesOf(teeth).Select(x => (x as MyResource).Name)));
_mChart.SetToolTip(pack, string.Join(", ", _mManager.ResourcesOf(pack).Select(x => (x as MyResource).Name)));
_mChart.SetToolTip(shower, string.Join(", ", _mManager.ResourcesOf(shower).Select(x => (x as MyResource).Name))); // Set Time information
_mManager.TimeScale = TimeScale.Day;
var span = DateTime.Today - _mManager.Start;
_mManager.Now = (int)Math.Round(span.TotalDays); // set the "Now" marker at the correct date
_mChart.TimeScaleDisplay = TimeScaleDisplay.DayOfWeek; // Set the chart to display days of week in header // Init the rest of the UI
_InitExampleUI();
} void _mChart_TaskSelected(object sender, TaskMouseEventArgs e)
{
_mTaskGrid.SelectedObjects = _mChart.SelectedTasks.Select(x => _mManager.IsPart(x) ? _mManager.SplitTaskOf(x) : x).ToArray();
_mResourceGrid.Items.Clear();
_mResourceGrid.Items.AddRange(_mManager.ResourcesOf(e.Task).Select(x => new ListViewItem(((MyResource)x).Name)).ToArray());
} void _mChart_TaskMouseOut(object sender, TaskMouseEventArgs e)
{
lblStatus.Text = "";
_mChart.Invalidate();
} void _mChart_TaskMouseOver(object sender, TaskMouseEventArgs e)
{
lblStatus.Text = string.Format("{0} to {1}", _mManager.GetDateTime(e.Task.Start).ToLongDateString(), _mManager.GetDateTime(e.Task.End).ToLongDateString());
_mChart.Invalidate();
} private void _InitExampleUI()
{
TaskGridView.DataSource = new BindingSource(_mManager.Tasks, null);
mnuFilePrint200.Click += (s, e) => _PrintDocument(2.0f);
mnuFilePrint150.Click += (s, e) => _PrintDocument(1.5f);
mnuFilePrint100.Click += (s, e) => _PrintDocument(1.0f);
mnuFilePrint80.Click += (s, e) => _PrintDocument(0.8f);
mnuFilePrint50.Click += (s, e) => _PrintDocument(0.5f);
mnuFilePrint25.Click += (s, e) => _PrintDocument(0.25f);
mnuFilePrint10.Click += (s, e) => _PrintDocument(0.1f); mnuFileImgPrint100.Click += (s, e) => _PrintImage(1.0f);
mnuFileImgPrint50.Click += (s, e) => _PrintImage(0.5f);
mnuFileImgPrint10.Click += (s, e) => _PrintImage(0.1f);
} #region Main Menu private void mnuFileSave_Click(object sender, EventArgs e)
{
using (var dialog = new SaveFileDialog())
{
dialog.InitialDirectory = System.IO.Path.GetDirectoryName(Application.ExecutablePath);
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
using (var fs = System.IO.File.OpenWrite(dialog.FileName))
{
System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bf = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
bf.Serialize(fs, _mManager);
}
}
}
} private void mnuFileOpen_Click(object sender, EventArgs e)
{
using (var dialog = new OpenFileDialog())
{
dialog.InitialDirectory = System.IO.Path.GetDirectoryName(Application.ExecutablePath);
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
using (var fs = System.IO.File.OpenRead(dialog.FileName))
{
System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bf = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
_mManager = bf.Deserialize(fs) as ProjectManager;
if (_mManager == null)
{
MessageBox.Show("Unable to load ProjectManager. Data structure might have changed from previous verions", "Gantt Chart", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
else
{
_mChart.Init(_mManager);
_mChart.Invalidate();
}
}
}
}
} private void mnuFileExit_Click(object sender, EventArgs e)
{
this.Close();
} private void mnuViewDaysDayOfWeek_Click(object sender, EventArgs e)
{
_mManager.TimeScale = TimeScale.Day;
_mChart.TimeScaleDisplay = TimeScaleDisplay.DayOfWeek;
_mChart.Invalidate();
} private void mnuFileNew_Click(object sender, EventArgs e)
{
// start a new Project and init the chart with the project
_mManager = new ProjectManager();
_mManager.Add(new Task() { Name = "New Task" });
_mChart.Init(_mManager);
_mChart.Invalidate();
} private void mnuHelpAbout_Click(object sender, EventArgs e)
{
if (MessageBox.Show("Please visit http://www.jakesee.com/net-c-winforms-gantt-chart-control/ for more help and details", "Braincase Solutions - Gantt Chart", MessageBoxButtons.OKCancel) == System.Windows.Forms.DialogResult.OK)
{
System.Diagnostics.Process.Start("http://www.jakesee.com/net-c-winforms-gantt-chart-control/");
}
} private void mnuViewRelationships_Click(object sender, EventArgs e)
{
_mChart.ShowRelations = mnuViewRelationships.Checked = !mnuViewRelationships.Checked;
_mChart.Invalidate();
} private void mnuViewSlack_Click(object sender, EventArgs e)
{
_mChart.ShowSlack = mnuViewSlack.Checked = !mnuViewSlack.Checked;
_mChart.Invalidate();
} private void mnuViewIntructions_Click(object sender, EventArgs e)
{
_mOverlay.PrintMode = !(mnuViewIntructions.Checked = !mnuViewIntructions.Checked);
_mChart.Invalidate();
} #region Timescale Views
private void mnuViewDays_Click(object sender, EventArgs e)
{
_mManager.TimeScale = TimeScale.Day;
mnuViewDays.Checked = true;
mnuViewWeek.Checked = false;
_mChart.Invalidate();
} private void mnuViewWeek_Click(object sender, EventArgs e)
{
_mManager.TimeScale = TimeScale.Week;
mnuViewDays.Checked = false;
mnuViewWeek.Checked = true;
_mChart.Invalidate();
} private void mnuViewDayOfWeek_Click(object sender, EventArgs e)
{
_mChart.TimeScaleDisplay = TimeScaleDisplay.DayOfWeek;
mnuViewDayOfWeek.Checked = true;
mnuViewDayOfMonth.Checked = false;
mnuViewWeekOfYear.Checked = false;
_mChart.Invalidate();
} private void mnuViewDayOfMonth_Click(object sender, EventArgs e)
{
_mChart.TimeScaleDisplay = TimeScaleDisplay.DayOfMonth;
mnuViewDayOfWeek.Checked = false;
mnuViewDayOfMonth.Checked = true;
mnuViewWeekOfYear.Checked = false;
_mChart.Invalidate();
} private void mnuViewWeekOfYear_Click(object sender, EventArgs e)
{
_mChart.TimeScaleDisplay = TimeScaleDisplay.WeekOfYear;
mnuViewDayOfWeek.Checked = false;
mnuViewDayOfMonth.Checked = false;
mnuViewWeekOfYear.Checked = true;
_mChart.Invalidate();
}
#endregion Timescale Views #endregion Main Menu #region Sidebar private void _mDateTimePicker_ValueChanged(object sender, EventArgs e)
{
_mManager.Start = _mStartDatePicker.Value;
var span = DateTime.Today - _mManager.Start;
_mManager.Now = (int)Math.Round(span.TotalDays);
if (_mManager.TimeScale == TimeScale.Week) _mManager.Now = (_mManager.Now % ) * ;
_mChart.Invalidate();
} private void _mPropertyGrid_SelectedGridItemChanged(object sender, SelectedGridItemChangedEventArgs e)
{
_mChart.Invalidate();
} private void _mNowDatePicker_ValueChanged(object sender, EventArgs e)
{
TimeSpan span = _mNowDatePicker.Value - _mStartDatePicker.Value;
_mManager.Now = span.Days + ;
if (_mManager.TimeScale == TimeScale.Week) _mManager.Now = _mManager.Now / + (_mManager.Now % > ? : );
_mChart.Invalidate();
} private void _mScrollDatePicker_ValueChanged(object sender, EventArgs e)
{
_mChart.ScrollTo(_mScrollDatePicker.Value);
_mChart.Invalidate();
} private void _mTaskGridView_SelectionChanged(object sender, EventArgs e)
{
if (TaskGridView.SelectedRows.Count > )
{
var task = TaskGridView.SelectedRows[].DataBoundItem as Task;
_mChart.ScrollTo(task);
}
} #endregion Sidebar #region Print private void _PrintDocument(float scale)
{
using (var dialog = new PrintDialog())
{
dialog.Document = new System.Drawing.Printing.PrintDocument();
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
// set the print mode for the custom overlay painter so that we skip printing instructions
dialog.Document.BeginPrint += (s, arg) => _mOverlay.PrintMode = true;
dialog.Document.EndPrint += (s, arg) => _mOverlay.PrintMode = false; // tell chart to print to the document at the specified scale
_mChart.Print(dialog.Document, scale);
}
}
} private void _PrintImage(float scale)
{
using (var dialog = new SaveFileDialog())
{
dialog.Filter = "Bitmap (*.bmp) | *.bmp";
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
// set the print mode for the custom overlay painter so that we skip printing instructions
_mOverlay.PrintMode = true;
// tell chart to print to the document at the specified scale var bitmap = _mChart.Print(scale);
_mOverlay.PrintMode = false; // restore printing overlays bitmap.Save(dialog.FileName, System.Drawing.Imaging.ImageFormat.Bmp);
}
}
} #endregion Print } #region overlay painter
/// <summary>
/// An example of how to encapsulate a helper painter for painter additional features on Chart
/// </summary>
public class OverlayPainter
{
/// <summary>
/// Hook such a method to the chart paint event listeners
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public void ChartOverlayPainter(object sender, ChartPaintEventArgs e)
{
// Don't want to print instructions to file
if (this.PrintMode) return; var g = e.Graphics;
var chart = e.Chart; // Demo: Static billboards begin -----------------------------------
// Demonstrate how to draw static billboards
// "push matrix" -- save our transformation matrix
e.Chart.BeginBillboardMode(e.Graphics); // draw mouse command instructions
int margin = ;
int left = ;
var color = chart.HeaderFormat.Color;
StringBuilder builder = new StringBuilder();
builder.AppendLine("THIS IS DRAWN BY A CUSTOM OVERLAY PAINTER TO SHOW DEFAULT MOUSE COMMANDS.");
builder.AppendLine("*******************************************************************************************************");
builder.AppendLine("Left Click - Select task and display properties in PropertyGrid");
builder.AppendLine("Left Mouse Drag - Change task starting point");
builder.AppendLine("Right Mouse Drag - Change task duration");
builder.AppendLine("Middle Mouse Drag - Change task complete percentage");
builder.AppendLine("Left Doubleclick - Toggle collaspe on task group");
builder.AppendLine("Right Doubleclick - Split task into task parts");
builder.AppendLine("Left Mouse Dragdrop onto another task - Group drag task under drop task");
builder.AppendLine("Right Mouse Dragdrop onto another task part - Join task parts");
builder.AppendLine("SHIFT + Left Mouse Dragdrop onto another task - Make drop task precedent of drag task");
builder.AppendLine("ALT + Left Dragdrop onto another task - Ungroup drag task from drop task / Remove drop task from drag task precedent list");
builder.AppendLine("SHIFT + Left Mouse Dragdrop - Order tasks");
builder.AppendLine("SHIFT + Middle Click - Create new task");
builder.AppendLine("ALT + Middle Click - Delete task");
builder.AppendLine("Left Doubleclick - Toggle collaspe on task group");
var size = g.MeasureString(builder.ToString(), e.Chart.Font);
var background = new Rectangle(left, chart.Height - margin, (int)size.Width, (int)size.Height);
background.Inflate(, );
g.FillRectangle(new System.Drawing.Drawing2D.LinearGradientBrush(background, Color.LightYellow, Color.Transparent, System.Drawing.Drawing2D.LinearGradientMode.Vertical), background);
g.DrawRectangle(Pens.Brown, background);
g.DrawString(builder.ToString(), chart.Font, color, new PointF(left, chart.Height - margin)); // "pop matrix" -- restore the previous matrix
e.Chart.EndBillboardMode(e.Graphics);
// Demo: Static billboards end -----------------------------------
} public bool PrintMode { get; set; }
}
#endregion overlay painter #region custom task and resource
/// <summary>
/// A custom resource of your own type (optional)
/// </summary>
[Serializable]
public class MyResource
{
public string Name { get; set; }
}
/// <summary>
/// A custom task of your own type deriving from the Task interface (optional)
/// </summary>
[Serializable]
public class MyTask : Task
{
public MyTask(ProjectManager manager)
: base()
{
Manager = manager;
} private ProjectManager Manager { get; set; } public new int Start { get { return base.Start; } set { Manager.SetStart(this, value); } }
public new int End { get { return base.End; } set { Manager.SetEnd(this, value); } }
public new int Duration { get { return base.Duration; } set { Manager.SetDuration(this, value); } }
public new float Complete { get { return base.Complete; } set { Manager.SetComplete(this, value); } }
}
#endregion custom task and resource
}
演示效果图为:

一款开源且功能强大的C#甘特图控件.NET Winforms Gantt Chart Control的更多相关文章
- Victor 串口 VCL 控件 - 简单实用, 功能强大的 C++ Builder 串口控件!
源:Victor 串口 VCL 控件 - 简单实用, 功能强大的 C++ Builder 串口控件! 2014年02月06日发布控件的重要更新版本: Victor 串口控件 1.5.0.2 版本 (包 ...
- 两款不错的js甘特图控件
dhtmlx:https://docs.dhtmlx.com/ jQuery.Gantt:http://taitems.github.io/jQuery.Gantt/
- [开源]在iOS上实现Android风格的控件Toast
[开源]在iOS上实现Android风格的控件Toast iOS的风格和Apple其他产品一样,简单而粗暴.没有给人其他选择的余地,让你又爱又恨.同样的,Apple对待iOS平台的开发人员和对待大众消 ...
- TYAttributedLabel——简单,强大的iOS属性文本控件
本文转载至 http://www.mobile-open.com/2015/86578.html TYAttributedLabel 简单,强大的属性文本的控件(无需了解CoreText),支持图文混 ...
- Excel催化剂开源第13波-VSTO开发之DataGridView控件几个小坑
Excel催化剂内部大量使用了DataGridView,这其中有一些小坑,花了力气才解决的,在此给广大开发者作简单分享. 为何要使用DataGridView而不是其他控件如ListBox.ListVi ...
- Android开源系列:仿网易Tab分类排序控件实现
前言 产品:网易新闻那个Tab排序好帅. 开发:哦~ 然后这个东东在几天后就出现了..... (PS:差不多一年没回来写博客了~~~~(>_<)~~~~,顺便把名字从 enjoy风铃 修改 ...
- Android开源中国客户端学习 (自定义View)左右滑动控件ScrollLayout
左右滑动的控件我们使用的也是非常多了,但是基本上都是使用的viewpager 等 android基础的控件,那么我们有么有考虑过查看他的源码进行定制呢?当然,如果你自我感觉非常好的话可以自己定制一个, ...
- Android 比SwipeRefreshLayout更漂亮和强大的下拉刷新控件:Android-MaterialRefreshLayout
这是一个下拉刷新的控件,它比SwipeRefreshLayout更加漂亮和强大.它易于使用并且支持API LEVEL >= 8.希望你能够喜欢. Now let me talk about Ma ...
- .Net界面开发控件DevExpress Winforms v19.2发布!增强图表功能
DevExpress Winforms Controls 内置140多个UI控件和库,完美构建流畅.美观且易于使用的应用程序.无论是Office风格的界面,还是分析处理大批量的业务数据,DevExpr ...
随机推荐
- 纯CSS制作水平垂直居中“十字架”
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- 【.Net底层剖析】3.用IL来理解属性
.Net底层剖析目录章节 1.[深入浅出.Net IL]1.一个For循环引发的IL 2.[.Net底层剖析]2.stfld指令-给对象的字段赋值 3.[.Net底层剖析]3.用IL来理解属性 未完待 ...
- javascript中的错误处理机制
× 目录 [1]对象 [2]类型 [3]事件[4]throw[5]try[6]常见错误 前面的话 错误处理对于web应用程序开发至关重要,不能提前预测到可能发生的错误,不能提前采取恢复策略,可能导致较 ...
- 包管理器Bower
今天自己用Angular写东西的时候,下载了Angular-seed项目,发现需要用到bower,之前也使用过,没有仔细了解,今天趁机了解到一些. bower的官网地址: http://bower.i ...
- 一套后台管理html模版
最近自己需要一套后台管理的模版,然后去网上查找,模版的确很多,但是适合我的并不多.我需要的模版是不会很大,我能够控制代码,样式不要太古朴,最好有点CSS3的效果.最后终于找到一张主页,然后再根据这个主 ...
- docker 1.8.2 源代码编译
编译docker的必要条件 这阵子在公司搞docker container这些技术,docker编译网上查了一下木有靠谱的.只好自己动手丰衣足食了. 声明:你编译docker不需要git pull它的 ...
- 机器学习&数据挖掘笔记_23(PGM练习七:CRF中参数的学习)
前言: 本次实验主要任务是学习CRF模型的参数,实验例子和PGM练习3中的一样,用CRF模型来预测多张图片所组成的单词,我们知道在graph model的推理中,使用较多的是factor,而在grap ...
- 在ssh中利用Solr服务建立的界面化站内搜索---solr2
继上次匆匆搭建起结合solr和nutch的所谓站内搜索引擎之后,虽当时心中兴奋不已,可是看了看百度,再只能看看我的控制台的打印出每个索引项的几行文字,哦,好像差距还是有点大…… ...
- ASP.NET 程序提交表单数据中带有html标签不能提交或者提交报错问题
今天在公司做另外的一个项目,又奇葩的遇到一个问题. 在本地自己电脑上怎么测试都是正常的.但是先上服务器就出问题: 用富文本编辑器上传一篇文章,始终报错,又没提示具体什么错误,也没说代码错误,点击提交按 ...
- Android中动态更新ListView(转)
在使用ListView时,会遇到当ListView列表滑动到最底端时,添加新的列表项的问题,本文通过代码演示如何动态的添加新的列表项到ListView中.实现步骤:调用ListView的setOnSc ...