一款开源且功能强大的C#甘特图控件.NET Winforms Gantt Chart Control
甘特图在项目管理中非常重要,甘特图的思想比较简单,即以图示的方式通过活动列表和时间刻度形象地表示出任何特定项目的活动顺序与持续时间。它直观地表明任务计划在什么时候进行,及实际进展与计划要求的对比。管理者由此可便利地弄清一项任务(项目)还剩下哪些工作要做,并可评估工作进度。甘特图可以显示几个部门、机器或设备的运行和闲置情况。这表示了该系统的有关工作负荷状况,这样可使管理人员了解何种调整是恰当的。
由于项目需要,在网上找了很久,经过大量的对比和评估,发现一款真正开源且功能强大的C#甘特图控件.NET Winforms Gantt Chart Control(http://ganttchart.codeplex.com/),效果图如下:
该款甘特图控件具有如下特征:
1、独立的时间单位数据结构规范;
2、支持单任务,分组任务,先例/依赖的任务,可以对任务进行拆分,并附加资源信息;
3、打印支持;
4、可对任务的计划和实际进行对比,以百分比进行进度跟踪;
5、在直接在甘特图上,对各种鼠标事件进行UI定制;
6、可以通过继承来修改默认的鼠标命令;
7、支持关键路径。
官方演示代码为:
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.Linq; 7 using System.Text; 8 using System.Windows.Forms; 9 10 namespace Braincase.GanttChart 11 { 12 /// <summary> 13 /// An elaborate example on how the chart control might be used. 14 /// Start by collapsing all regions and then read the constructor. 15 /// Refer to IProjectManager interface for method description. 16 /// </summary> 17 public partial class ExampleFull : Form 18 { 19 OverlayPainter _mOverlay = new OverlayPainter(); 20 21 ProjectManager _mManager = null; 22 23 /// <summary> 24 /// Example starts here 25 /// </summary> 26 public ExampleFull() 27 { 28 InitializeComponent(); 29 30 // Create a Project and some Tasks 31 _mManager = new ProjectManager(); 32 var work = new MyTask(_mManager) { Name = "Prepare for Work" }; 33 var wake = new MyTask(_mManager) { Name = "Wake Up" }; 34 var teeth = new MyTask(_mManager) { Name = "Brush Teeth" }; 35 var shower = new MyTask(_mManager) { Name = "Shower" }; 36 var clothes = new MyTask(_mManager) { Name = "Change into New Clothes" }; 37 var hair = new MyTask(_mManager) { Name = "Blow My Hair" }; 38 var pack = new MyTask(_mManager) { Name = "Pack the Suitcase" }; 39 40 _mManager.Add(work); 41 _mManager.Add(wake); 42 _mManager.Add(teeth); 43 _mManager.Add(shower); 44 _mManager.Add(clothes); 45 _mManager.Add(hair); 46 _mManager.Add(pack); 47 48 // Create another 1000 tasks for stress testing 49 Random rand = new Random(); 50 for (int i = 0; i < 1000; i++) 51 { 52 var task = new MyTask(_mManager) { Name = string.Format("New Task {0}", i.ToString()) }; 53 _mManager.Add(task); 54 _mManager.SetStart(task, rand.Next(300)); 55 _mManager.SetDuration(task, rand.Next(50)); 56 } 57 58 // Set task durations, e.g. using ProjectManager methods 59 _mManager.SetDuration(wake, 3); 60 _mManager.SetDuration(teeth, 5); 61 _mManager.SetDuration(shower, 7); 62 _mManager.SetDuration(clothes, 4); 63 _mManager.SetDuration(hair, 3); 64 _mManager.SetDuration(pack, 5); 65 66 // demostrate splitting a task 67 _mManager.Split(pack, new MyTask(_mManager), new MyTask(_mManager), 2); 68 69 // Set task complete status, e.g. using newly created properties 70 wake.Complete = 0.9f; 71 teeth.Complete = 0.5f; 72 shower.Complete = 0.4f; 73 74 // Give the Tasks some organisation, setting group and precedents 75 _mManager.Group(work, wake); 76 _mManager.Group(work, teeth); 77 _mManager.Group(work, shower); 78 _mManager.Group(work, clothes); 79 _mManager.Group(work, hair); 80 _mManager.Group(work, pack); 81 _mManager.Relate(wake, teeth); 82 _mManager.Relate(wake, shower); 83 _mManager.Relate(shower, clothes); 84 _mManager.Relate(shower, hair); 85 _mManager.Relate(hair, pack); 86 _mManager.Relate(clothes, pack); 87 88 // Create and assign Resources. 89 // MyResource is just custom user class. The API can accept any object as resource. 90 var jake = new MyResource() { Name = "Jake" }; 91 var peter = new MyResource() { Name = "Peter" }; 92 var john = new MyResource() { Name = "John" }; 93 var lucas = new MyResource() { Name = "Lucas" }; 94 var james = new MyResource() { Name = "James" }; 95 var mary = new MyResource() { Name = "Mary" }; 96 // Add some resources 97 _mManager.Assign(wake, jake); 98 _mManager.Assign(wake, peter); 99 _mManager.Assign(wake, john); 100 _mManager.Assign(teeth, jake); 101 _mManager.Assign(teeth, james); 102 _mManager.Assign(pack, james); 103 _mManager.Assign(pack, lucas); 104 _mManager.Assign(shower, mary); 105 _mManager.Assign(shower, lucas); 106 _mManager.Assign(shower, john); 107 108 // Initialize the Chart with our ProjectManager and CreateTaskDelegate 109 _mChart.Init(_mManager); 110 _mChart.CreateTaskDelegate = delegate() { return new MyTask(_mManager); }; 111 112 // Attach event listeners for events we are interested in 113 _mChart.TaskMouseOver += new EventHandler<TaskMouseEventArgs>(_mChart_TaskMouseOver); 114 _mChart.TaskMouseOut += new EventHandler<TaskMouseEventArgs>(_mChart_TaskMouseOut); 115 _mChart.TaskSelected += new EventHandler<TaskMouseEventArgs>(_mChart_TaskSelected); 116 _mChart.PaintOverlay += _mOverlay.ChartOverlayPainter; 117 _mChart.AllowTaskDragDrop = true; 118 119 // set some tooltips to show the resources in each task 120 _mChart.SetToolTip(wake, string.Join(", ", _mManager.ResourcesOf(wake).Select(x => (x as MyResource).Name))); 121 _mChart.SetToolTip(teeth, string.Join(", ", _mManager.ResourcesOf(teeth).Select(x => (x as MyResource).Name))); 122 _mChart.SetToolTip(pack, string.Join(", ", _mManager.ResourcesOf(pack).Select(x => (x as MyResource).Name))); 123 _mChart.SetToolTip(shower, string.Join(", ", _mManager.ResourcesOf(shower).Select(x => (x as MyResource).Name))); 124 125 // Set Time information 126 _mManager.TimeScale = TimeScale.Day; 127 var span = DateTime.Today - _mManager.Start; 128 _mManager.Now = (int)Math.Round(span.TotalDays); // set the "Now" marker at the correct date 129 _mChart.TimeScaleDisplay = TimeScaleDisplay.DayOfWeek; // Set the chart to display days of week in header 130 131 // Init the rest of the UI 132 _InitExampleUI(); 133 } 134 135 void _mChart_TaskSelected(object sender, TaskMouseEventArgs e) 136 { 137 _mTaskGrid.SelectedObjects = _mChart.SelectedTasks.Select(x => _mManager.IsPart(x) ? _mManager.SplitTaskOf(x) : x).ToArray(); 138 _mResourceGrid.Items.Clear(); 139 _mResourceGrid.Items.AddRange(_mManager.ResourcesOf(e.Task).Select(x => new ListViewItem(((MyResource)x).Name)).ToArray()); 140 } 141 142 void _mChart_TaskMouseOut(object sender, TaskMouseEventArgs e) 143 { 144 lblStatus.Text = ""; 145 _mChart.Invalidate(); 146 } 147 148 void _mChart_TaskMouseOver(object sender, TaskMouseEventArgs e) 149 { 150 lblStatus.Text = string.Format("{0} to {1}", _mManager.GetDateTime(e.Task.Start).ToLongDateString(), _mManager.GetDateTime(e.Task.End).ToLongDateString()); 151 _mChart.Invalidate(); 152 } 153 154 private void _InitExampleUI() 155 { 156 TaskGridView.DataSource = new BindingSource(_mManager.Tasks, null); 157 mnuFilePrint200.Click += (s, e) => _PrintDocument(2.0f); 158 mnuFilePrint150.Click += (s, e) => _PrintDocument(1.5f); 159 mnuFilePrint100.Click += (s, e) => _PrintDocument(1.0f); 160 mnuFilePrint80.Click += (s, e) => _PrintDocument(0.8f); 161 mnuFilePrint50.Click += (s, e) => _PrintDocument(0.5f); 162 mnuFilePrint25.Click += (s, e) => _PrintDocument(0.25f); 163 mnuFilePrint10.Click += (s, e) => _PrintDocument(0.1f); 164 165 mnuFileImgPrint100.Click += (s, e) => _PrintImage(1.0f); 166 mnuFileImgPrint50.Click += (s, e) => _PrintImage(0.5f); 167 mnuFileImgPrint10.Click += (s, e) => _PrintImage(0.1f); 168 } 169 170 #region Main Menu 171 172 private void mnuFileSave_Click(object sender, EventArgs e) 173 { 174 using (var dialog = new SaveFileDialog()) 175 { 176 dialog.InitialDirectory = System.IO.Path.GetDirectoryName(Application.ExecutablePath); 177 if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) 178 { 179 using (var fs = System.IO.File.OpenWrite(dialog.FileName)) 180 { 181 System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bf = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter(); 182 bf.Serialize(fs, _mManager); 183 } 184 } 185 } 186 } 187 188 private void mnuFileOpen_Click(object sender, EventArgs e) 189 { 190 using (var dialog = new OpenFileDialog()) 191 { 192 dialog.InitialDirectory = System.IO.Path.GetDirectoryName(Application.ExecutablePath); 193 if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) 194 { 195 using (var fs = System.IO.File.OpenRead(dialog.FileName)) 196 { 197 System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bf = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter(); 198 _mManager = bf.Deserialize(fs) as ProjectManager; 199 if (_mManager == null) 200 { 201 MessageBox.Show("Unable to load ProjectManager. Data structure might have changed from previous verions", "Gantt Chart", MessageBoxButtons.OK, MessageBoxIcon.Error); 202 } 203 else 204 { 205 _mChart.Init(_mManager); 206 _mChart.Invalidate(); 207 } 208 } 209 } 210 } 211 } 212 213 private void mnuFileExit_Click(object sender, EventArgs e) 214 { 215 this.Close(); 216 } 217 218 private void mnuViewDaysDayOfWeek_Click(object sender, EventArgs e) 219 { 220 _mManager.TimeScale = TimeScale.Day; 221 _mChart.TimeScaleDisplay = TimeScaleDisplay.DayOfWeek; 222 _mChart.Invalidate(); 223 } 224 225 private void mnuFileNew_Click(object sender, EventArgs e) 226 { 227 // start a new Project and init the chart with the project 228 _mManager = new ProjectManager(); 229 _mManager.Add(new Task() { Name = "New Task" }); 230 _mChart.Init(_mManager); 231 _mChart.Invalidate(); 232 } 233 234 private void mnuHelpAbout_Click(object sender, EventArgs e) 235 { 236 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) 237 { 238 System.Diagnostics.Process.Start("http://www.jakesee.com/net-c-winforms-gantt-chart-control/"); 239 } 240 } 241 242 private void mnuViewRelationships_Click(object sender, EventArgs e) 243 { 244 _mChart.ShowRelations = mnuViewRelationships.Checked = !mnuViewRelationships.Checked; 245 _mChart.Invalidate(); 246 } 247 248 private void mnuViewSlack_Click(object sender, EventArgs e) 249 { 250 _mChart.ShowSlack = mnuViewSlack.Checked = !mnuViewSlack.Checked; 251 _mChart.Invalidate(); 252 } 253 254 private void mnuViewIntructions_Click(object sender, EventArgs e) 255 { 256 _mOverlay.PrintMode = !(mnuViewIntructions.Checked = !mnuViewIntructions.Checked); 257 _mChart.Invalidate(); 258 } 259 260 #region Timescale Views 261 private void mnuViewDays_Click(object sender, EventArgs e) 262 { 263 _mManager.TimeScale = TimeScale.Day; 264 mnuViewDays.Checked = true; 265 mnuViewWeek.Checked = false; 266 _mChart.Invalidate(); 267 } 268 269 private void mnuViewWeek_Click(object sender, EventArgs e) 270 { 271 _mManager.TimeScale = TimeScale.Week; 272 mnuViewDays.Checked = false; 273 mnuViewWeek.Checked = true; 274 _mChart.Invalidate(); 275 } 276 277 private void mnuViewDayOfWeek_Click(object sender, EventArgs e) 278 { 279 _mChart.TimeScaleDisplay = TimeScaleDisplay.DayOfWeek; 280 mnuViewDayOfWeek.Checked = true; 281 mnuViewDayOfMonth.Checked = false; 282 mnuViewWeekOfYear.Checked = false; 283 _mChart.Invalidate(); 284 } 285 286 private void mnuViewDayOfMonth_Click(object sender, EventArgs e) 287 { 288 _mChart.TimeScaleDisplay = TimeScaleDisplay.DayOfMonth; 289 mnuViewDayOfWeek.Checked = false; 290 mnuViewDayOfMonth.Checked = true; 291 mnuViewWeekOfYear.Checked = false; 292 _mChart.Invalidate(); 293 } 294 295 private void mnuViewWeekOfYear_Click(object sender, EventArgs e) 296 { 297 _mChart.TimeScaleDisplay = TimeScaleDisplay.WeekOfYear; 298 mnuViewDayOfWeek.Checked = false; 299 mnuViewDayOfMonth.Checked = false; 300 mnuViewWeekOfYear.Checked = true; 301 _mChart.Invalidate(); 302 } 303 #endregion Timescale Views 304 305 #endregion Main Menu 306 307 #region Sidebar 308 309 private void _mDateTimePicker_ValueChanged(object sender, EventArgs e) 310 { 311 _mManager.Start = _mStartDatePicker.Value; 312 var span = DateTime.Today - _mManager.Start; 313 _mManager.Now = (int)Math.Round(span.TotalDays); 314 if (_mManager.TimeScale == TimeScale.Week) _mManager.Now = (_mManager.Now % 7) * 7; 315 _mChart.Invalidate(); 316 } 317 318 private void _mPropertyGrid_SelectedGridItemChanged(object sender, SelectedGridItemChangedEventArgs e) 319 { 320 _mChart.Invalidate(); 321 } 322 323 private void _mNowDatePicker_ValueChanged(object sender, EventArgs e) 324 { 325 TimeSpan span = _mNowDatePicker.Value - _mStartDatePicker.Value; 326 _mManager.Now = span.Days + 1; 327 if (_mManager.TimeScale == TimeScale.Week) _mManager.Now = _mManager.Now / 7 + (_mManager.Now % 7 > 0 ? 1 : 0); 328 _mChart.Invalidate(); 329 } 330 331 private void _mScrollDatePicker_ValueChanged(object sender, EventArgs e) 332 { 333 _mChart.ScrollTo(_mScrollDatePicker.Value); 334 _mChart.Invalidate(); 335 } 336 337 private void _mTaskGridView_SelectionChanged(object sender, EventArgs e) 338 { 339 if (TaskGridView.SelectedRows.Count > 0) 340 { 341 var task = TaskGridView.SelectedRows[0].DataBoundItem as Task; 342 _mChart.ScrollTo(task); 343 } 344 } 345 346 #endregion Sidebar 347 348 #region Print 349 350 private void _PrintDocument(float scale) 351 { 352 using (var dialog = new PrintDialog()) 353 { 354 dialog.Document = new System.Drawing.Printing.PrintDocument(); 355 if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) 356 { 357 // set the print mode for the custom overlay painter so that we skip printing instructions 358 dialog.Document.BeginPrint += (s, arg) => _mOverlay.PrintMode = true; 359 dialog.Document.EndPrint += (s, arg) => _mOverlay.PrintMode = false; 360 361 // tell chart to print to the document at the specified scale 362 _mChart.Print(dialog.Document, scale); 363 } 364 } 365 } 366 367 private void _PrintImage(float scale) 368 { 369 using (var dialog = new SaveFileDialog()) 370 { 371 dialog.Filter = "Bitmap (*.bmp) | *.bmp"; 372 if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) 373 { 374 // set the print mode for the custom overlay painter so that we skip printing instructions 375 _mOverlay.PrintMode = true; 376 // tell chart to print to the document at the specified scale 377 378 var bitmap = _mChart.Print(scale); 379 _mOverlay.PrintMode = false; // restore printing overlays 380 381 bitmap.Save(dialog.FileName, System.Drawing.Imaging.ImageFormat.Bmp); 382 } 383 } 384 } 385 386 #endregion Print 387 388 389 } 390 391 #region overlay painter 392 /// <summary> 393 /// An example of how to encapsulate a helper painter for painter additional features on Chart 394 /// </summary> 395 public class OverlayPainter 396 { 397 /// <summary> 398 /// Hook such a method to the chart paint event listeners 399 /// </summary> 400 /// <param name="sender"></param> 401 /// <param name="e"></param> 402 public void ChartOverlayPainter(object sender, ChartPaintEventArgs e) 403 { 404 // Don\'t want to print instructions to file 405 if (this.PrintMode) return; 406 407 var g = e.Graphics; 408 var chart = e.Chart; 409 410 // Demo: Static billboards begin ----------------------------------- 411 // Demonstrate how to draw static billboards 412 // "push matrix" -- save our transformation matrix 413 e.Chart.BeginBillboardMode(e.Graphics); 414 415 // draw mouse command instructions 416 int margin = 300; 417 int left = 20; 418 var color = chart.HeaderFormat.Color; 419 StringBuilder builder = new StringBuilder(); 420 builder.AppendLine("THIS IS DRAWN BY A CUSTOM OVERLAY PAINTER TO SHOW DEFAULT MOUSE COMMANDS."); 421 builder.AppendLine("*******************************************************************************************************"); 422 builder.AppendLine("Left Click - Select task and display properties in PropertyGrid"); 423 builder.AppendLine("Left Mouse Drag - Change task starting point"); 424 builder.AppendLine("Right Mouse Drag - Change task duration"); 425 builder.AppendLine("Middle Mouse Drag - Change task complete percentage"); 426 builder.AppendLine("Left Doubleclick - Toggle collaspe on task group"); 427 builder.AppendLine("Right Doubleclick - Split task into task parts"); 428 builder.AppendLine("Left Mouse Dragdrop onto another task - Group drag task under drop task"); 429 builder.AppendLine("Right Mouse Dragdrop onto another task part - Join task parts"); 430 builder.AppendLine("SHIFT + Left Mouse Dragdrop onto another task - Make drop task precedent of drag task"); 431 builder.AppendLine("ALT + Left Dragdrop onto another task - Ungroup drag task from drop task / Remove drop task from drag task precedent list"); 432 builder.AppendLine("SHIFT + Left Mouse Dragdrop - Order tasks"); 433 builder.AppendLine("SHIFT + Middle Click - Create new task"); 434 builder.AppendLine("ALT + Middle Click - Delete task"); 435 builder.AppendLine("Left Doubleclick - Toggle collaspe on task group"); 436 var size = g.MeasureString(builder.ToString(), e.Chart.Font); 437 var background = new Rectangle(left, chart.Height - margin, (int)size.Width, (int)size.Height); 438 background.Inflate(10, 10); 439 g.FillRectangle(new System.Drawing.Drawing2D.LinearGradientBrush(background, Color.LightYellow, Color.Transparent, System.Drawing.Drawing2D.LinearGradientMode.Vertical), background); 440 g.DrawRectangle(Pens.Brown, background); 441 g.DrawString(builder.ToString(), chart.Font, color, new PointF(left, chart.Height - margin)); 442 443 444 // "pop matrix" -- restore the previous matrix 445 e.Chart.EndBillboardMode(e.Graphics); 446 // Demo: Static billboards end ----------------------------------- 447 } 448 449 public bool PrintMode { get; set; } 450 } 451 #endregion overlay painter 452 453 #region custom task and resource 454 /// <summary> 455 /// A custom resource of your own type (optional) 456 /// </summary> 457 [Serializable] 458 public class MyResource 459 { 460 public string Name { get; set; } 461 } 462 /// <summary> 463 /// A custom task of your own type deriving from the Task interface (optional) 464 /// </summary> 465 [Serializable] 466 public class MyTask : Task 467 { 468 public MyTask(ProjectManager manager) 469 : base() 470 { 471 Manager = manager; 472 } 473 474 private ProjectManager Manager { get; set; } 475 476 public new int Start { get { return base.Start; } set { Manager.SetStart(this, value); } } 477 public new int End { get { return base.End; } set { Manager.SetEnd(this, value); } } 478 public new int Duration { get { return base.Duration; } set { Manager.SetDuration(this, value); } } 479 public new float Complete { get { return base.Complete; } set { Manager.SetComplete(this, value); } } 480 } 481 #endregion custom task and resource 482 }
演示效果图为:
水平有限,望各位园友不吝赐教!如果觉得不错,请点击推荐和关注!
出处:http://www.cnblogs.com/isaboy/
声明:本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
请发表评论