using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Data; using System.Linq; using System.Text; using System.Windows.Forms; using System.Drawing.Drawing2D; using System.Drawing.Printing; namespace Braincase.GanttChart { static class GDIExtention { public static void DrawRectangle(this Graphics graphics, Pen pen, RectangleF rectangle) { graphics.DrawRectangle(pen, rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height); } } /// /// Gantt Chart control /// public partial class Chart : UserControl { /// /// Construct a gantt chart /// public Chart() { // Designer values InitializeComponent(); // Factory values //HeaderOneHeight = 32; //HeaderTwoHeight = 20; HeaderOneHeight = 32; HeaderTwoHeight = 20; BarSpacing = 16; BarHeight = 10; BarWidth = 40; ZoomScaleIndex = 4; this.DoubleBuffered = true; var viewport = new ControlViewport(this); viewport.WheelDelta = BarSpacing; _mViewport = viewport; TimeScaleDisplay = GanttChart.TimeScaleDisplay.DayOfMonth; AllowTaskDragDrop = true; ShowRelations = true; ShowSlack = false; AccumulateRelationsOnGroup = false; ShowTaskLabels = true; this.Dock = DockStyle.Fill; this.Margin = new Padding(0, 0, 0, 0); this.Padding = new Padding(0, 0, 0, 0); // Formatting TaskFormat = new GanttChart.TaskFormat() { Color = Brushes.Black, Border = Pens.Maroon, BackFill = Brushes.MediumSlateBlue, ForeFill = Brushes.YellowGreen, SlackFill = new System.Drawing.Drawing2D.HatchBrush( System.Drawing.Drawing2D.HatchStyle.LightDownwardDiagonal, Color.Blue, Color.Transparent) }; CriticalTaskFormat = new GanttChart.TaskFormat() { Color = Brushes.Black, Border = Pens.Maroon, BackFill = Brushes.Crimson, ForeFill = Brushes.YellowGreen, SlackFill = new System.Drawing.Drawing2D.HatchBrush( System.Drawing.Drawing2D.HatchStyle.LightDownwardDiagonal, Color.Red, Color.Transparent) }; HeaderFormat = new GanttChart.HeaderFormat() { Color = Brushes.Black, Border = new Pen(SystemColors.ActiveBorder), GradientLight = SystemColors.ButtonHighlight, GradientDark = SystemColors.ButtonFace }; } /// /// Delegate method for creating a new Task. Creates Task by default. /// public Func CreateTaskDelegate = delegate() { return new Task(); }; /// /// Get the selected tasks /// public IEnumerable SelectedTasks { get { return _mSelectedTasks.ToArray(); } } /// /// Get the latest selected task /// public Task SelectedTask { get { return _mSelectedTasks.LastOrDefault(); } } /// /// Get or set header1 pixel height /// [DefaultValue(32)] public int HeaderOneHeight { get; set; } /// /// Get or set the DateTime string format. Default value is D/M/YYYY /// [DefaultValue("D/M/YYYY")] public string FullDateStringFormat { get; set; } /// /// Get or set header2 pixel height /// [DefaultValue(20)] public int HeaderTwoHeight { get; set; } /// /// Get or set pixel distance from top of each Task to the next /// [DefaultValue(26)] public int BarSpacing { get; set; } /// /// Get or set pixel height of each Task /// [DefaultValue(20)] public int BarHeight { get; set; } /// /// Get or set pixel width of each unit of period /// [DefaultValue(20)] public float BarWidth { get; set; } /// /// Get or set format for Tasks /// public TaskFormat TaskFormat { get; set; } /// /// Get or set format for critical Tasks /// public TaskFormat CriticalTaskFormat { get; set; } /// /// Get or set format for headers /// public HeaderFormat HeaderFormat { get; set; } /// /// Get or set format for relations /// public RelationFormat RelationFormat { get; set; } /// /// Get or set whether dragging of Tasks is allowed. Set to false when not dragging to skip drag(drop) tracking. /// [DefaultValue(true)] public bool AllowTaskDragDrop { get; set; } /// /// Get or set whether to show relations /// [DefaultValue(true)] public bool ShowRelations { get; set; } /// /// Get or set format for headers wangdequan /// [DefaultValue(1f)] public float HeaderScale { get; set; } /// /// Get or set format for headers wangdequan Max = 10;Min=0 /// [DefaultValue(4)] public int ZoomScaleIndex { get; set; } public float[] ZoomScaleList = {0.01f, 0.2f, 0.4f, 0.5f, 1f, 2f, 4f, 5f, 10f, 20f, 60f}; /// /// Get or set whether to show task labels /// [DefaultValue(true)] public bool ShowTaskLabels { get; set; } /// /// Get or set whether to accumulate relations on group tasks and show relations even when group is collapsed. (Not working well; still improving on it) /// [DefaultValue(false)] public bool AccumulateRelationsOnGroup { get; set; } /// /// Get or set whether to show slack /// [DefaultValue(false)] public bool ShowSlack { get; set; } /// /// Get or set the time scale display format /// [DefaultValue(TimeScaleDisplay.DayOfMonth)] public TimeScaleDisplay TimeScaleDisplay { get; set; } /// /// Occurs when the mouse is moving over a Task /// public event EventHandler TaskMouseOver = null; /// /// Occurs when the mouse leaves a Task /// public event EventHandler TaskMouseOut = null; /// /// Occurs when a Task is clicked /// public event EventHandler TaskMouseClick = null; /// /// TaskMouseDown /// public event EventHandler TaskMouseDown = null; /// /// Occurs when a Task is double clicked by the mouse /// public event EventHandler TaskMouseDoubleClick = null; /// /// Occurs when a Task is being dragged by the mouse /// public event EventHandler TaskMouseDrag = null; /// /// Occurs when a dragged Task is being dropped by releasing any previously pressed mouse button. /// public event EventHandler TaskMouseDrop = null; /// /// Occurs when a task is selected. /// public event EventHandler TaskSelected = null; /// /// Occurs before one or more tasks are being deselected. All Task in Chart.SelectedTasks will be deselected. /// public event EventHandler TaskDeselecting = null; /// /// Occurs before a Task gets painted /// public event EventHandler PaintTask = null; /// /// Occurs before overlays get painted /// public event EventHandler PaintOverlay = null; /// /// Occurs before the header gets painted /// public event EventHandler PaintHeader = null; /// /// Get the line number of the specified task /// /// /// /// public bool TryGetRow(Task task, out int row) { row = 0; if (_mChartTaskHitRects.ContainsKey(task)) { // collection contains parts row = _ChartCoordToChartRow(_mChartTaskHitRects[task].Top); return true; } else if (_mChartTaskRects.ContainsKey(task)) { // collectino contains splits row = _ChartCoordToChartRow(_mChartTaskRects[task].Top); return true; } return false; } /// /// Get the task at the specified line number /// /// /// /// public bool TryGetTask(int row, out Task task) { task = null; if (row > 0 && row < _mProject.Tasks.Count()) { task = _mChartTaskRects.ElementAtOrDefault(row - 1).Key; return true; } return false; } /// /// Initialize this Chart with a Project /// /// public void Init(ProjectManager project) { _mProject = project; HeaderScale = 1.0f; _GenerateModels(); } /// /// Print the Chart to the specified PrintDocument. /// public void Print(PrintDocument document, float scale = 1.0f) { // save a copy of the current viewport and swap it with PrintViewport var viewport = _mViewport; float x = 0; // viewport world x, y coords float y = 0; int pageCount = 0; document.PrintPage += (s, e) => { e.HasMorePages = false; pageCount++; // create a PrintViewport to navigate the world var printViewport = new PrintViewport(e.Graphics, viewport.WorldWidth, viewport.WorldHeight, e.MarginBounds.Width, e.MarginBounds.Height, e.PageSettings.Margins.Left, e.PageSettings.Margins.Right); printViewport.Scale = scale; _mViewport = printViewport; // move the viewport printViewport.X = x; printViewport.Y = y; // set clip and draw e.Graphics.SetClip(e.MarginBounds); _Draw(e.Graphics, e.PageBounds); // check if reached end of printing if (printViewport.Rectangle.Right < printViewport.WorldWidth) { // continue horizontally x += printViewport.Rectangle.Width; e.HasMorePages = true; } else { // reached end of worldwidth so we go down vertically once x = 0; if (printViewport.Rectangle.Bottom < printViewport.WorldHeight) { y += printViewport.Rectangle.Height; e.HasMorePages = true; } } }; document.Print(); // restore the viewport _mViewport = viewport; } /// /// Print the Chart to the specified Image /// /// Scale to print the image at. public Bitmap Print(float scale = 1.0f) { var viewport = _mViewport; _mViewport = new ImageViewport(scale, viewport.WorldWidth, viewport.WorldHeight); Bitmap image = new Bitmap((int) Math.Ceiling(viewport.WorldWidth * scale), (int) Math.Ceiling(viewport.WorldHeight * scale)); var graphics = Graphics.FromImage(image); _Draw(graphics, Rectangle.Ceiling(viewport.Rectangle)); _mViewport = viewport; return image; } /// /// Get information about the chart area at the mouse coordinate of the chart /// /// /// public ChartInfo GetChartInfo(Point mouse) { var row = _ChartCoordToChartRow(mouse.Y); var col = _GetDeviceColumnUnderMouse(mouse); var task = _GetTaskUnderMouse(mouse); return new ChartInfo(row, _mHeaderInfo.Dates[col], task); } /// /// Set tool tip for the specified task /// /// /// public void SetToolTip(Task task, string text) { if (task != null && text != string.Empty) _mTaskToolTip[task] = text; } /// /// Get tool tip currently set for the specified task /// /// /// /// public string GetToolTip(Task task) { if (task != null) return _mTaskToolTip[task]; else return string.Empty; } /// /// Clear tool tip for the specified task /// /// public void ClearToolTip(Task task) { if (task != null) _mTaskToolTip.Remove(task); } /// /// Clear all tool tips /// public void ClearToolTips() { _mTaskToolTip.Clear(); } /// /// Scroll to the specified DateTime /// /// public void ScrollTo(DateTime datetime) { TimeSpan span = datetime - _mProject.Start; _mViewport.X = span.Days * this.BarWidth; } /// /// Scroll to the specified task /// /// public void ScrollTo(Task task) { if (_mChartTaskRects.ContainsKey(task)) { var rect = _mChartTaskRects[task]; _mViewport.X = rect.Left - this.BarWidth; _mViewport.Y = rect.Top - this.HeaderOneHeight - this.HeaderTwoHeight; } } /// /// Begin billboard mode. Graphics must orginate from Chart and be same as that used in EndBillboardMode. /// /// public void BeginBillboardMode(Graphics graphics) { graphics.Transform = ControlViewport.Identity; } /// /// End billboard mode. Graphics must orginate from Chart and be same as that used in BeginBillboardMode. /// /// public void EndBillboardMode(Graphics graphics) { graphics.Transform = _mViewport.Projection; } #region UserControl Events /// /// Raises the System.Windows.Forms.Control.Paint event /// /// protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); if (!this.DesignMode) this._Draw(e.Graphics, e.ClipRectangle); } /// /// Raises the System.Windows.Forms.Control.MouseMove event /// /// protected override void OnMouseMove(MouseEventArgs e) { // Hot tracking var task = _GetTaskUnderMouse(e.Location); if (_mMouseEntered != null && task == null) { OnTaskMouseOut(new TaskMouseEventArgs(_mMouseEntered, RectangleF.Empty, e.Button, e.Clicks, e.X, e.Y, e.Delta)); _mMouseEntered = null; } else if (_mMouseEntered == null && task != null) { _mMouseEntered = task; OnTaskMouseOver(new TaskMouseEventArgs(_mMouseEntered, _mChartTaskHitRects[task], e.Button, e.Clicks, e.X, e.Y, e.Delta)); } // Dragging if (AllowTaskDragDrop && _mDragSource != null) { try { Task target = task; if (target == _mDragSource) target = null; RectangleF targetRect = target == null ? RectangleF.Empty : _mChartTaskHitRects[target]; int row = _DeviceCoordToChartRow(e.Location.Y); OnTaskMouseDrag(new TaskDragDropEventArgs(_mDragStartLocation, _mDragLastLocation, _mDragSource, _mChartTaskHitRects[_mDragSource], target, targetRect, row, e.Button, e.Clicks, e.X, e.Y, e.Delta)); _mDragLastLocation = e.Location; } catch { } } //TaskMouseDrop(this, e); //绘制播放滑块 OnMouseMove_PlaySlider(e); base.OnMouseMove(e); } /// /// Raises the System.Windows.Forms.Control.MouseClick event /// /// protected override void OnMouseClick(MouseEventArgs e) { var task = _GetTaskUnderMouse(e.Location); if (task != null) { OnTaskMouseClick(new TaskMouseEventArgs(task, _mChartTaskHitRects[task], e.Button, e.Clicks, e.X, e.Y, e.Delta)); } else { OnTaskDeselecting(new TaskMouseEventArgs(task, RectangleF.Empty, e.Button, e.Clicks, e.X, e.Y, e.Delta)); this.Invalidate(); } base.OnMouseClick(e); } /// /// Raises the System.Windows.Forms.Control.MouseDown event /// /// protected override void OnMouseDown(MouseEventArgs e) { // Begin Drag if (AllowTaskDragDrop) { _mDragSource = _GetTaskUnderMouse(e.Location); if (_mDragSource != null) { _mDragStartLocation = e.Location; _mDragLastLocation = e.Location; } } //拖动播放条 OnMouseDown_PlaySlider(e); //2021.8.19.LLX try { var task = _GetTaskUnderMouse(e.Location); if (task != null) { if (TaskMouseDown != null) TaskMouseDown.Invoke(this, new TaskMouseEventArgs(task, _mChartTaskHitRects[task], e.Button, e.Clicks, e.X, e.Y, e.Delta) ); } } catch (Exception err) { } base.OnMouseDown(e); } /// /// Raises the System.Windows.Forms.Control.MouseUp event /// /// protected override void OnMouseUp(MouseEventArgs e) { // Drop task if (AllowTaskDragDrop && _mDragSource != null) { try { var target = _GetTaskUnderMouse(e.Location); if (target == _mDragSource) target = null; var targetRect = target == null ? RectangleF.Empty : _mChartTaskHitRects[target]; int row = _DeviceCoordToChartRow(e.Location.Y); OnTaskMouseDrop(new TaskDragDropEventArgs(_mDragStartLocation, _mDragLastLocation, _mDragSource, _mChartTaskHitRects[_mDragSource], target, targetRect, row, e.Button, e.Clicks, e.X, e.Y, e.Delta)); _mDragSource = null; _mDragLastLocation = Point.Empty; _mDragStartLocation = Point.Empty; } catch { } } //if (!_mHeaderInfo.H1Rect.Contains(chartcoord) // && !_mHeaderInfo.H2Rect.Contains(chartcoord)) //{ // foreach (var task in _mChartTaskHitRects.Keys) // { // if (_mChartTaskHitRects[task].Contains(chartcoord)) // return task; // } //} //PlayMaskRectDragDrop base.OnMouseUp(e); } /// /// Raises the System.Windows.Forms.Control.MouseDoubleClick event /// /// protected override void OnMouseDoubleClick(MouseEventArgs e) { var task = _GetTaskUnderMouse(e.Location); if (task != null) { OnTaskMouseDoubleClick(new TaskMouseEventArgs(task, _mChartTaskHitRects[task], e.Button, e.Clicks, e.X, e.Y, e.Delta)); } base.OnMouseDoubleClick(e); } #endregion UserControl Events #region Chart Events /// /// Raises the TaskMouseOver event /// /// protected virtual void OnTaskMouseOver(TaskMouseEventArgs e) { if (TaskMouseOver != null) TaskMouseOver(this, e); this.Cursor = Cursors.Hand; var task = e.Task; if (_mProject.IsPart(e.Task)) task = _mProject.SplitTaskOf(task); if (_mTaskToolTip.ContainsKey(task)) { _mOverlay.ShowToolTip(_mViewport.DeviceToWorldCoord(e.Location), _mTaskToolTip[task]); this.Invalidate(); } } /// /// Raises the TaskMouseOver event /// /// protected virtual void OnTaskMouseOut(TaskMouseEventArgs e) { if (TaskMouseOut != null) TaskMouseOut(this, e); this.Cursor = Cursors.Default; _mOverlay.HideToolTip(); this.Invalidate(); } /// /// Raises the TaskMouseDrag( event /// /// protected virtual void OnTaskMouseDrag(TaskDragDropEventArgs e) { //// Default drag behaviors ********************************** //if (e.Button == System.Windows.Forms.MouseButtons.Middle) //{ // var complete = e.Source.Complete + (float)(e.X - e.PreviousLocation.X) / (e.Source.Duration * this.BarWidth); // _mProject.SetComplete(e.Source, complete); //} //else if (e.Button == System.Windows.Forms.MouseButtons.Right) //{ // if (e.Target == null) // { // var delta = (e.PreviousLocation.X - e.StartLocation.X); // _mOverlay.DraggedRect = e.SourceRect; // _mOverlay.DraggedRect.Width += delta; // } // else // drop targetting (join) // { // _mOverlay.DraggedRect = e.TargetRect; // _mOverlay.Row = int.MinValue; // } //} //else if (e.Button == System.Windows.Forms.MouseButtons.Left) { _mOverlay.Clear(); if (e.Target == null) { // 屏蔽 //if (Control.ModifierKeys.HasFlag(Keys.Shift)) //{ // // insertion line // _mOverlay.Row = e.Row; //} //else { // displacing horizontally _mOverlay.DraggedRect = e.SourceRect; _mOverlay.DraggedRect.Offset((e.X - e.StartLocation.X) / this.BarWidth * this.BarWidth, 0); } } // 屏蔽 //else // drop targetting (subtask / predecessor) //{ // _mOverlay.DraggedRect = e.TargetRect; // _mOverlay.Row = int.MinValue; //} } // fire listeners if (TaskMouseDrag != null) TaskMouseDrag(this, e); this.Invalidate(); } /// /// Raises the TaskMouseDrop event /// /// protected virtual void OnTaskMouseDrop(TaskDragDropEventArgs e) { // Fire event //if (TaskMouseDrop != null) // TaskMouseDrop(this, e); var delta = (e.PreviousLocation.X - e.StartLocation.X) / this.BarWidth; delta = (float) Math.Round(delta, 1); if (e.Button == System.Windows.Forms.MouseButtons.Left) { if (e.Target == null) { // 屏蔽修改顺序 //if (Control.ModifierKeys.HasFlag(Keys.Shift)) //{ // // insert // int from; // Task source = e.Source; // if(_mProject.IsPart(source)) source = _mProject.SplitTaskOf(source); // if (this.TryGetRow(source, out from)) // _mProject.Move(source, e.Row - from); //} //else { // displace horizontally var start = e.Source.Start + delta; var parent = _mProject.DirectPrecedentsOf(e.Source); if (parent != null && parent.Count() > 0) { var parentTask = parent.First(); if (start < parentTask.End) { start = parentTask.End; } } _mProject.SetStart(e.Source, start); } } //// 屏蔽拖动到其他Task //else // have drop target //{ // if (Control.ModifierKeys.HasFlag(Keys.Shift)) // { // _mProject.Relate(e.Target, e.Source); // } // else if (Control.ModifierKeys.HasFlag(Keys.Alt)) // { // var source = e.Source; // if (_mProject.IsPart(source)) source = _mProject.SplitTaskOf(source); // if (_mProject.ParentOf(source) == e.Target) // { // _mProject.Ungroup(e.Target, e.Source); // } // else // { // _mProject.Unrelate(e.Target, source); // } // } // else // { // //_mProject.Group(e.Target, e.Source); // } //} } //// 屏蔽右键拖动 //else if (e.Button == System.Windows.Forms.MouseButtons.Right) //{ // if (e.Target == null) // { // var duration = e.Source.Duration + delta; // _mProject.SetDuration(e.Source, duration); // } // else // have target then we do a join // { // _mProject.Join(e.Target, e.Source); // } //} if (TaskMouseDrop != null && delta > 0.1) TaskMouseDrop(this, e); _mOverlay.Clear(); this.Invalidate(); } /// /// Raises the TaskMouseClick event /// /// protected virtual void OnTaskMouseClick(TaskMouseEventArgs e) { if (TaskMouseClick != null) TaskMouseClick(this, e); if (e.Button == System.Windows.Forms.MouseButtons.Left) { if (ModifierKeys.HasFlag(Keys.Control)) { _mSelectedTasks.Add(e.Task); } else { OnTaskDeselecting(e); _mSelectedTasks.Add(e.Task); } OnTaskSelected(e); } else if (e.Button == System.Windows.Forms.MouseButtons.Middle) { if (ModifierKeys.HasFlag(Keys.Shift)) { var newtask = CreateTaskDelegate(); _mProject.Add(newtask); _mProject.SetStart(newtask, e.Task.Start); _mProject.SetDuration(newtask, 5); if (_mProject.IsPart(e.Task)) _mProject.Move(newtask, _mProject.IndexOf(_mProject.SplitTaskOf(e.Task)) + 1 - _mProject.IndexOf(newtask)); else _mProject.Move(newtask, _mProject.IndexOf(e.Task) + 1 - _mProject.IndexOf(newtask)); } else if (Control.ModifierKeys.HasFlag(Keys.Alt)) _mProject.Delete(e.Task); } this.Invalidate(); } /// /// Raises the TaskMouseDoubleClick event /// /// protected virtual void OnTaskMouseDoubleClick(TaskMouseEventArgs e) { if (TaskMouseDoubleClick != null) TaskMouseDoubleClick(this, e); if (e.Button == System.Windows.Forms.MouseButtons.Left) { e.Task.IsCollapsed = !e.Task.IsCollapsed; } else if (e.Button == System.Windows.Forms.MouseButtons.Right) { int duration = (int) ((_mViewport.DeviceToWorldCoord(e.Location).X - e.Rectangle.Left) / this.BarWidth); if (_mProject.IsPart(e.Task)) _mProject.Split(e.Task, new Task(), duration); else _mProject.Split(e.Task, CreateTaskDelegate(), CreateTaskDelegate(), duration); } this.Invalidate(); } /// /// Raises the TaskSelected event /// /// protected virtual void OnTaskSelected(TaskMouseEventArgs e) { if (TaskSelected != null) TaskSelected(this, e); } /// /// Raises the TaskDeselecting event /// /// protected virtual void OnTaskDeselecting(TaskMouseEventArgs e) { if (TaskDeselecting != null) TaskDeselecting(this, e); // deselect all tasks _mSelectedTasks.Clear(); } /// /// Raises the PaintOverlay event /// /// protected virtual void OnPaintOverlay(ChartPaintEventArgs e) { if (this.PaintOverlay != null) PaintOverlay(this, e); } #endregion Chart Events #region OverlayPainter private ChartOverlay _mOverlay = new ChartOverlay(); class ChartOverlay { public void Paint(ChartPaintEventArgs e) { var g = e.Graphics; var chart = e.Chart; // dragging outline / trail if (DraggedRect != RectangleF.Empty) g.DrawRectangle(Pens.Red, DraggedRect); // insertion indicator line if (Row != int.MinValue) { float y = e.Chart._ChartRowToChartCoord(Row) + e.Chart.BarHeight / 2.0f; g.DrawLine(Pens.CornflowerBlue, new PointF(0, y), new PointF(e.Chart.Width, y)); } // tool tip if (_mToolTipMouse != Point.Empty && _mToolTipText != string.Empty) { var size = g.MeasureString(_mToolTipText, chart.Font).ToSize(); var tooltiprect = new RectangleF(_mToolTipMouse, size); tooltiprect.Offset(0, -tooltiprect.Height); var textstart = new PointF(tooltiprect.Left, tooltiprect.Top); tooltiprect.Inflate(5, 5); g.FillRectangle(Brushes.LightYellow, tooltiprect); g.DrawString(_mToolTipText, chart.Font, Brushes.Black, textstart); } } public void ShowToolTip(PointF worldcoord, string text) { _mToolTipMouse = worldcoord; _mToolTipText = text; } public void HideToolTip() { _mToolTipMouse = Point.Empty; _mToolTipText = string.Empty; } public void Clear() { DraggedRect = RectangleF.Empty; Row = int.MinValue; } private PointF _mToolTipMouse = PointF.Empty; private string _mToolTipText = string.Empty; public RectangleF DraggedRect = RectangleF.Empty; public int Row = int.MinValue; } #endregion #region Private Helper Methods private Task _GetTaskUnderMouse(Point mouse) { var chartcoord = _mViewport.DeviceToWorldCoord(mouse); if (!_mHeaderInfo.H1Rect.Contains(chartcoord) && !_mHeaderInfo.H2Rect.Contains(chartcoord)) { foreach (var task in _mChartTaskHitRects.Keys) { if (_mChartTaskHitRects[task].Contains(chartcoord)) return task; } } return null; } private bool _GetIsPlayMaskDragDropUnderMouse(Point mouse) { var chartcoord = _mViewport.DeviceToWorldCoord(mouse); //if (!_mHeaderInfo.H1Rect.Contains(chartcoord) // && !_mHeaderInfo.H2Rect.Contains(chartcoord)) //{ // foreach (var task in _mChartTaskHitRects.Keys) // { // if (_mChartTaskHitRects[task].Contains(chartcoord)) // return task; // } //} return true; } private int _GetDeviceColumnUnderMouse(Point mouse) { var worldcoord = _mViewport.DeviceToWorldCoord(mouse); return _mHeaderInfo.Columns.Select((x, i) => new {x, i}).FirstOrDefault(x => x.x.Contains(worldcoord)).i; } /// /// Convert view Y coordinate to zero based row number /// /// /// private int _DeviceCoordToChartRow(float y) { y = _mViewport.DeviceToWorldCoord(new PointF(0, y)).Y; var row = (int) ((y - this.BarSpacing - this.HeaderOneHeight) / this.BarSpacing); return row < 0 ? 0 : row; } /// /// Convert world Y coordinate to zero-based row number /// /// /// private int _ChartCoordToChartRow(float y) { var row = (int) ((y - this.HeaderTwoHeight - this.HeaderOneHeight) / this.BarSpacing); return row < 0 ? 0 : row; } /// /// Convert zero based row number to client Y coordinates /// /// /// private float _ChartRowToChartCoord(int row) { return row * this.BarSpacing + this.HeaderTwoHeight + this.HeaderOneHeight; } /// /// Generate the task models and resize the world accordingly /// public void _GenerateModels() { // Clear Models _mChartTaskRects.Clear(); _mChartTaskHitRects.Clear(); _mChartSlackRects.Clear(); _mChartTaskPartRects.Clear(); var pHeight = this.Parent == null ? this.Width : this.Parent.Height; var pWidth = this.Parent == null ? this.Height : this.Parent.Width; // loop over the tasks and pick up items float end = int.MinValue; int row = 0; foreach (var task in _mProject.Tasks) { if (!_mProject.AncestorsOf(task).Any(x => x.IsCollapsed)) { int y_coord = row * this.BarSpacing + this.HeaderTwoHeight + this.HeaderOneHeight + (this.BarSpacing - this.BarHeight) / 2; RectangleF taskRect; // Compute task rectangle //taskRect = new RectangleF(task.Start * this.BarWidth + this.BarWidth / 2, y_coord, task.Duration * this.BarWidth, this.BarHeight); //wangdequan 20200531 改成 task X从 0点开始 taskRect = new RectangleF(task.Start * this.BarWidth / HeaderScale, y_coord, task.Duration * this.BarWidth / HeaderScale, this.BarHeight); _mChartTaskRects.Add(task, taskRect); if (!_mProject.IsSplit(task)) { // Add normal Task Rectangles to hitRect collection for hit testing _mChartTaskHitRects.Add(task, taskRect); } else // Compute task part rectangles if task is a split task { var parts = new List>(); _mChartTaskPartRects.Add(task, parts); foreach (var part in _mProject.PartsOf(task)) { //wangdequan 20200513 taskRect = new RectangleF(part.Start * this.BarWidth, y_coord, part.Duration * this.BarWidth, this.BarHeight); //taskRect = new RectangleF(part.Start * this.BarWidth + this.BarWidth / 2, y_coord, part.Duration * this.BarWidth, this.BarHeight); parts.Add(new KeyValuePair(part, taskRect)); // Parts are mouse enabled, add to hitRect collection _mChartTaskHitRects.Add(part, taskRect); } } // Compute Slack Rectangles if (this.ShowSlack) { var slackRect = new RectangleF(task.End * this.BarWidth + this.BarWidth / 2, y_coord, task.Slack * this.BarWidth, this.BarHeight); _mChartSlackRects.Add(task, slackRect); } // Find maximum end time if (task.End > end) end = task.End; row++; } } row += 5; _mViewport.WorldHeight = Math.Max(pHeight, row * this.BarSpacing + this.BarHeight); _mViewport.WorldWidth = Math.Max(pWidth, end * this.BarWidth + 200); } /// /// Generate the task models and resize the world accordingly wangdequan 20200531 /// private void _GenerateModels_DT() { // Clear Models _mChartTaskRects.Clear(); _mChartTaskHitRects.Clear(); _mChartSlackRects.Clear(); _mChartTaskPartRects.Clear(); var pHeight = this.Parent == null ? this.Width : this.Parent.Height; var pWidth = this.Parent == null ? this.Height : this.Parent.Width; // loop over the tasks and pick up items float end = int.MinValue; int row = 0; foreach (var task in _mProject.Tasks) { if (!_mProject.AncestorsOf(task).Any(x => x.IsCollapsed)) { int y_coord = row * this.BarSpacing + this.HeaderTwoHeight + this.HeaderOneHeight + (this.BarSpacing - this.BarHeight) / 2; RectangleF taskRect; // Compute task rectangle //taskRect = new RectangleF(task.Start * this.BarWidth + this.BarWidth / 2, y_coord, task.Duration * this.BarWidth, this.BarHeight); //wangdequan 20200531 改成 task X从 0点开始 taskRect = new RectangleF(task.Start * this.BarWidth, y_coord, task.Duration * this.BarWidth, this.BarHeight); _mChartTaskRects.Add(task, taskRect); if (!_mProject.IsSplit(task)) { // Add normal Task Rectangles to hitRect collection for hit testing _mChartTaskHitRects.Add(task, taskRect); } else // Compute task part rectangles if task is a split task { var parts = new List>(); _mChartTaskPartRects.Add(task, parts); foreach (var part in _mProject.PartsOf(task)) { //wangdequan 20200513 taskRect = new RectangleF(part.Start * this.BarWidth, y_coord, part.Duration * this.BarWidth, this.BarHeight); //taskRect = new RectangleF(part.Start * this.BarWidth + this.BarWidth / 2, y_coord, part.Duration * this.BarWidth, this.BarHeight); parts.Add(new KeyValuePair(part, taskRect)); // Parts are mouse enabled, add to hitRect collection _mChartTaskHitRects.Add(part, taskRect); } } // Compute Slack Rectangles if (this.ShowSlack) { var slackRect = new RectangleF(task.End * this.BarWidth + this.BarWidth / 2, y_coord, task.Slack * this.BarWidth, this.BarHeight); _mChartSlackRects.Add(task, slackRect); } // Find maximum end time if (task.End > end) end = task.End; row++; } } row += 5; _mViewport.WorldHeight = Math.Max(pHeight, row * this.BarSpacing + this.BarHeight); _mViewport.WorldWidth = Math.Max(pWidth, end * this.BarWidth + 200); } /// /// Generate Header rectangles and dates wangdequan /// private void _GenerateHeaders_DT() { // only generate the necessary headers by determining of current viewport location var h1Rect = new RectangleF(_mViewport.X, _mViewport.Y, _mViewport.Rectangle.Width, this.HeaderOneHeight); var h2Rect = new RectangleF(h1Rect.Left, h1Rect.Bottom, _mViewport.Rectangle.Width, this.HeaderTwoHeight); var h1LabelRects = new List(); var h2LabelRects = new List(); var columns = new List(); var h1labels = new List(); var h2labels = new List(); var dates = new List(); var dates_DT = new List(); // generate columns across the viewport area int curStartIndex = (int) (_mViewport.X / this.BarWidth); string lable1; //var curDate = __CalculateViewportStart(); var h2LabelRect_Y = _mViewport.Y + this.HeaderOneHeight; var columns_Y = h2LabelRect_Y + this.HeaderTwoHeight; for (float x = (_mViewport.X - _mViewport.X % this.BarWidth); x < _mViewport.Rectangle.Right; x += this.BarWidth) { float d = curStartIndex * HeaderScale * 1000f; lable1 = (d / 1000f).ToString(); dates_DT.Add(lable1); //dates.Add(curDate); h2LabelRects.Add(new RectangleF(x, h2LabelRect_Y, this.BarWidth, this.HeaderTwoHeight)); columns.Add(new RectangleF(x, columns_Y, this.BarWidth, _mViewport.Rectangle.Height)); curStartIndex = curStartIndex + 1; } _mHeaderInfo.H1Rect = h1Rect; _mHeaderInfo.H2Rect = h2Rect; _mHeaderInfo.H1LabelRects = h1LabelRects; _mHeaderInfo.H2LabelRects = h2LabelRects; _mHeaderInfo.Columns = columns; _mHeaderInfo.Dates = dates; _mHeaderInfo.Dates_DT = dates_DT; } private void _GenerateHeaders_DT_back() { // only generate the necessary headers by determining of current viewport location var h1Rect = new RectangleF(_mViewport.X, _mViewport.Y, _mViewport.Rectangle.Width, this.HeaderOneHeight); var h2Rect = new RectangleF(h1Rect.Left, h1Rect.Bottom, _mViewport.Rectangle.Width, this.HeaderTwoHeight); var h1LabelRects = new List(); var h2LabelRects = new List(); var columns = new List(); var h1labels = new List(); var h2labels = new List(); var dates = new List(); var dates_DT = new List(); // generate columns across the viewport area int curStartIndex = (int) (_mViewport.X / this.BarWidth); string lable1; //var curDate = __CalculateViewportStart(); var h2LabelRect_Y = _mViewport.Y + this.HeaderOneHeight; var columns_Y = h2LabelRect_Y + this.HeaderTwoHeight; for (float x = (_mViewport.X - _mViewport.X % this.BarWidth); x < _mViewport.Rectangle.Right; x += this.BarWidth) { float d = curStartIndex * HeaderScale * 1000f; lable1 = (d / 1000f).ToString(); dates_DT.Add(lable1); //dates.Add(curDate); h2LabelRects.Add(new RectangleF(x, h2LabelRect_Y, this.BarWidth, this.HeaderTwoHeight)); columns.Add(new RectangleF(x, columns_Y, this.BarWidth, _mViewport.Rectangle.Height)); curStartIndex = curStartIndex + 1; } _mHeaderInfo.H1Rect = h1Rect; _mHeaderInfo.H2Rect = h2Rect; _mHeaderInfo.H1LabelRects = h1LabelRects; _mHeaderInfo.H2LabelRects = h2LabelRects; _mHeaderInfo.Columns = columns; _mHeaderInfo.Dates = dates; _mHeaderInfo.Dates_DT = dates_DT; } /// /// Generate Header rectangles and dates /// private void _GenerateHeaders() { // only generate the necessary headers by determining of current viewport location var h1Rect = new RectangleF(_mViewport.X, _mViewport.Y, _mViewport.Rectangle.Width, this.HeaderOneHeight); var h2Rect = new RectangleF(h1Rect.Left, h1Rect.Bottom, _mViewport.Rectangle.Width, this.HeaderTwoHeight); var h1LabelRects = new List(); var h2LabelRects = new List(); var columns = new List(); var h1labels = new List(); var h2labels = new List(); var dates = new List(); // generate columns across the viewport area var curDate = __CalculateViewportStart(); var h2LabelRect_Y = _mViewport.Y + this.HeaderOneHeight; var columns_Y = h2LabelRect_Y + this.HeaderTwoHeight; for (float x = (_mViewport.X - _mViewport.X % this.BarWidth); x < _mViewport.Rectangle.Right; x += this.BarWidth) { dates.Add(curDate); h2LabelRects.Add(new RectangleF(x, h2LabelRect_Y, this.BarWidth, this.HeaderTwoHeight)); columns.Add(new RectangleF(x, columns_Y, this.BarWidth, _mViewport.Rectangle.Height)); __NextColumn(ref curDate); } _mHeaderInfo.H1Rect = h1Rect; _mHeaderInfo.H2Rect = h2Rect; _mHeaderInfo.H1LabelRects = h1LabelRects; _mHeaderInfo.H2LabelRects = h2LabelRects; _mHeaderInfo.Columns = columns; _mHeaderInfo.Dates = dates; } /// /// Calculate the date in the first visible column in the viewport /// /// private DateTime __CalculateViewportStart() { int vpTime = (int) (_mViewport.X / this.BarWidth); if (_mProject.TimeScale == TimeScale.Day) { return _mProject.Start.AddDays(vpTime); } else /* if (_mProject.TimeScale == TimeScale.Week) */ { var startWeek = _mProject.Start.AddDays(-(int) _mProject.Start.DayOfWeek); return startWeek.AddDays(vpTime * 7); } } /// /// Integrates the current DateTime by one column of TimeScale /// /// private void __NextColumn(ref DateTime current) { if (_mProject.TimeScale == TimeScale.Day) current = current.AddDays(1); else // if (_mProject.TimeScale == TimeScale.Week) current = current.AddDays(7); } /// /// Draw the Chart using the specified graphics /// private void _Draw(Graphics graphics, Rectangle clipRect) { graphics.Clear(Color.White); int row = 0; if (_mProject != null) { HeaderScale = ZoomScaleList[ZoomScaleIndex]; // generate rectangles _GenerateModels(); //wangdequan 20200531 //_GenerateHeaders(); _GenerateHeaders_DT(); // set model view matrix graphics.Transform = _mViewport.Projection; // draw columns in the background _DrawColumns(graphics); // draw bar charts row = this._DrawTasks(graphics, clipRect); // draw predecessor arrows if (this.ShowRelations) this._DrawPredecessorLines(graphics); // draw the header _DrawHeader(graphics, clipRect); // Paint overlays ChartPaintEventArgs paintargs = new ChartPaintEventArgs(graphics, clipRect, this); OnPaintOverlay(paintargs); _mOverlay.Paint(paintargs); } else { // nothing to draw } //播放时,绘制播放线 if (_mProject.IsPlaye) { OnPaint_PlaySlider(graphics); } // flush graphics.Flush(); } private void _DrawHeader(Graphics graphics, Rectangle clipRect) { var info = _mHeaderInfo; var viewRect = _mViewport.Rectangle; var e = new HeaderPaintEventArgs(graphics, clipRect, this, this.Font, this.HeaderFormat); if (PaintHeader != null) PaintHeader(this, e); // Draw header backgrounds var gradient = new System.Drawing.Drawing2D.LinearGradientBrush(info.H1Rect, e.Format.GradientLight, e.Format.GradientDark, System.Drawing.Drawing2D.LinearGradientMode.Vertical); graphics.FillRectangles(gradient, new RectangleF[] {info.H1Rect, info.H2Rect}); //graphics.DrawRectangles(e.Format.Border, new RectangleF[] { info.H1Rect, info.H2Rect }); // draw h2 labels string label = string.Empty; System.Globalization.GregorianCalendar cal = new System.Globalization.GregorianCalendar(System.Globalization.GregorianCalendarTypes.Localized); for (int i = 0; i < info.H2LabelRects.Count; i++) { var h2labelrect = info.H2LabelRects[i]; label = info.Dates_DT[i]; //var date = info.Dates[i]; //if (this.TimeScaleDisplay == GanttChart.TimeScaleDisplay.DayOfWeek) // label = Chart.ShortDays[date.DayOfWeek]; //else if (this.TimeScaleDisplay == GanttChart.TimeScaleDisplay.DayOfMonth) // label = date.Day.ToString(); //else if (this.TimeScaleDisplay == GanttChart.TimeScaleDisplay.WeekOfYear) // label = cal.GetWeekOfYear(date, System.Globalization.CalendarWeekRule.FirstFullWeek, DayOfWeek.Sunday).ToString(); //wangdequan 2020-05-31 var h2textrect = _TextAlignLeftMiddle(graphics, h2labelrect, label, e.Font); // h2textrect = _TextAlignCenterMiddle(graphics, h2labelrect, label, e.Font); graphics.DrawString(label, e.Font, e.Format.Color, h2textrect); // draw h1 label and rects wangdequan //__DrawHeaderOne(graphics, i, e, h2textrect); } //// draw "Now" line //float xf = (_mProject.Now + 0.5f) * BarWidth; //var pen = new Pen(e.Format.Border.Color); //pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash; //graphics.DrawLine(pen, new PointF(xf, _mViewport.Y), new PointF(xf, _mViewport.Rectangle.Bottom)); //_mProject.Now = 5; //float xf1 = _mProject.NowIndexXf; //pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Solid; //pen.Width = 2; //graphics.DrawLine(pen, new PointF(xf1, _mViewport.Y), new PointF(xf1, _mViewport.Rectangle.Bottom)); } private void _DrawColumns(Graphics graphics) { // draw column lines graphics.DrawRectangles(this.HeaderFormat.Border, _mHeaderInfo.Columns.ToArray()); // fill weekend columns //if (_mProject.TimeScale == TimeScale.Day) //{ // for (int i = 0; i < _mHeaderInfo.Dates.Count; i++) // { // var date = _mHeaderInfo.Dates[i]; // // highlight weekends for day time scale // if (date.DayOfWeek == DayOfWeek.Sunday || date.DayOfWeek == DayOfWeek.Saturday) // { // var pattern = new System.Drawing.Drawing2D.HatchBrush(System.Drawing.Drawing2D.HatchStyle.Percent20, this.HeaderFormat.Border.Color, Color.Transparent); // graphics.FillRectangle(pattern, _mHeaderInfo.Columns[i]); // } // } //} } private void __DrawHeaderOne(Graphics graphics, int columnIndex, HeaderPaintEventArgs e, RectangleF h2textrect) { var info = _mHeaderInfo; var h2labelrect = info.H2LabelRects[columnIndex]; var date = info.Dates[columnIndex]; var h1labelrect = RectangleF.Empty; // draw h1 labels for day of week if (this.TimeScaleDisplay == GanttChart.TimeScaleDisplay.DayOfWeek) { // put a date on every sunday if (date.DayOfWeek == DayOfWeek.Sunday) { var columnsinlabel = _mProject.TimeScale == TimeScale.Day ? 7 : 1; h1labelrect = new RectangleF(h2labelrect.X, _mViewport.Y, columnsinlabel * BarWidth, this.HeaderOneHeight); var h1textrect = _TextAlignLeftMiddle(graphics, h1labelrect, date.ToShortDateString(), e.Font, h2textrect.X - h2labelrect.X); graphics.DrawString(date.ToString(this.FullDateStringFormat), e.Font, e.Format.Color, h1textrect); __DrawMarker(graphics, (h2labelrect.Left + h2labelrect.Right) / 2f, _mHeaderInfo.H1Rect.Bottom - 5f); } } else if (this.TimeScaleDisplay == GanttChart.TimeScaleDisplay.DayOfMonth || this.TimeScaleDisplay == GanttChart.TimeScaleDisplay.WeekOfYear) { // put a month if (columnIndex == 0 || (date.Month != info.Dates[columnIndex - 1].Month)) { var h1label = _mProject.TimeScale == TimeScale.Day ? date.ToString("MMMM yyyy") : date.ToString("MMM yy"); var columnsinlabel = _mProject.TimeScale == TimeScale.Day ? DateTime.DaysInMonth(date.Year, date.Month) : Enumerable.Range(1, DateTime.DaysInMonth(date.Year, date.Month)) .Select(day => new DateTime(date.Year, date.Month, day)) .Count(d => d.DayOfWeek == DayOfWeek.Sunday); if (columnIndex == 0) { var leftcolshiftcount = _mProject.TimeScale == TimeScale.Day ? (date.Day - 1) : date.Day / 7; h1labelrect = new RectangleF(h2labelrect.X - leftcolshiftcount * BarWidth, _mViewport.Y, columnsinlabel * BarWidth, this.HeaderOneHeight); } else h1labelrect = new RectangleF(h2labelrect.X, _mViewport.Y, columnsinlabel * BarWidth, this.HeaderOneHeight); var h1textrect = _TextAlignCenterMiddle(graphics, h1labelrect, h1label, e.Font); graphics.DrawString(h1label, e.Font, e.Format.Color, h1textrect); graphics.DrawRectangle(e.Format.Border, h1labelrect); } } // **************************************** // Add the H1 Label rect to our header info // ***************************************** _mHeaderInfo.H1LabelRects.Add(h1labelrect); } private void __DrawMarker(Graphics graphics, float offsetX, float offsetY) { var marker = _Marker.Select(p => new PointF(p.X + offsetX, p.Y + offsetY)).ToArray(); graphics.FillPolygon(Brushes.LightGoldenrodYellow, marker); graphics.DrawPolygon(new Pen(SystemColors.ButtonShadow), marker); } private int _DrawTasks(Graphics graphics, Rectangle clipRect) { var viewRect = _mViewport.Rectangle; int row = 0; var crit_task_set = new HashSet(_mProject.CriticalPaths.SelectMany(x => x)); var pen = new Pen(Color.Gray); float labelMargin = this.BarWidth / 2.0f + 3.0f; pen.DashStyle = DashStyle.Dot; TaskPaintEventArgs e; foreach (var task in _mChartTaskRects.Keys) { // Get the taskrect var taskrect = _mChartTaskRects[task]; // Only begin drawing when the taskrect is to the left of the clipRect's right edge if (taskrect.Left <= viewRect.Right) { // Crtical Path bool critical = crit_task_set.Contains(task); if (critical) e = new TaskPaintEventArgs(graphics, clipRect, this, task, row, critical, this.Font, this.CriticalTaskFormat); else e = new TaskPaintEventArgs(graphics, clipRect, this, task, row, critical, this.Font, this.TaskFormat); if (PaintTask != null) PaintTask(this, e); if (viewRect.IntersectsWith(taskrect)) { if (_mProject.IsSplit(task)) { __DrawTaskParts(graphics, e, task, pen); } else { __DrawRegularTaskAndGroup(graphics, e, task, taskrect); } } // write text if (this.ShowTaskLabels && task.Name != string.Empty) { var name = task.Name; var txtrect = _TextAlignLeftMiddle(graphics, taskrect, name, e.Font, labelMargin); txtrect.Offset(taskrect.Width, 0); if (viewRect.IntersectsWith(txtrect)) { graphics.DrawString(name, e.Font, e.Format.Color, txtrect); } } // draw slack if (this.ShowSlack && task.Complete < 1.0f) { var slackrect = _mChartSlackRects[task]; if (viewRect.IntersectsWith(slackrect)) graphics.FillRectangle(e.Format.SlackFill, slackrect); } } row++; } return row; } private void _DrawPredecessorLines(Graphics graphics) { var viewRect = _mViewport.Rectangle; RectangleF clipRectF = new RectangleF(viewRect.X, viewRect.Y, viewRect.Width, viewRect.Height); foreach (var p in _mProject.Precedents) { var precedent = p; IEnumerable dependants = _mProject.DirectDependantsOf(precedent); // check with _mTaskRects that the precedent was drawn; this is needed to connect the lines if (_mChartTaskRects.ContainsKey(precedent)) { foreach (var d in dependants) { var dependant = d; // check with _mTaskRects that the dependant was drawn; this is needed to connect the lines if (_mChartTaskRects.ContainsKey(dependant)) { var prect = _mChartTaskRects[precedent]; var srect = _mChartTaskRects[dependant]; if (precedent.End <= dependant.Start) { /* var p1 = new PointF(prect.Right, prect.Top + prect.Height / 2.0f); var p2 = new PointF(srect.Left - BarWidth / 2, prect.Top + prect.Height / 2.0f); var p3 = new PointF(srect.Left - BarWidth / 2, srect.Top + srect.Height / 2.0f); var p4 = new PointF(srect.Left, srect.Top + srect.Height / 2.0f); var size = new SizeF(Math.Abs(p4.X - p1.X), Math.Abs(p4.Y - p1.Y)); var linerect = p1.Y < p4.Y ? new RectangleF(p1, size) : new RectangleF(new PointF(p1.X, p1.Y - size.Height), size); */ //var p1 = new PointF(prect.Right, prect.Top + prect.Height / 2.0f); //var p2 = new PointF(prect.Right + BarWidth / 2, prect.Top + prect.Height / 2.0f); //var p3 = new PointF(prect.Right + BarWidth / 2, srect.Top + srect.Height / 2.0f); //var p4 = new PointF(srect.Left, srect.Top + srect.Height / 2.0f); //wangdequan 20200602 var p1 = new PointF(prect.Right, prect.Top + prect.Height / 2.0f); var p2 = new PointF(srect.Left, prect.Top + prect.Height / 2.0f); var p3 = new PointF(srect.Left, srect.Top); var p4 = new PointF(srect.Left, srect.Top); //var p1 = new PointF(prect.Right, prect.Top + prect.Height / 2.0f); //var p2 = new PointF(prect.Right , prect.Top + prect.Height / 2.0f); //var p3 = new PointF(prect.Right, srect.Top ); //var p4 = new PointF(prect.Right, srect.Top); var size = new SizeF(Math.Abs(p4.X - p1.X), Math.Abs(p4.Y - p1.Y)); var linerect = p1.Y < p4.Y ? new RectangleF(p1, size) : new RectangleF(new PointF(p1.X, p1.Y - size.Height), size); if (clipRectF.IntersectsWith(linerect)) { graphics.DrawLines(Pens.Black, new PointF[] {p1, p2, p3, p4}); graphics.FillRectangle(Brushes.Black, p4.X - 1.5f, p4.Y - 1.5f, 3, 3); //graphics.FillRectangle(Brushes.Black, p3.X - 1.5f, p3.Y - 1.5f, 3, 3); } } } } } } } private void __DrawRegularTaskAndGroup(Graphics graphics, TaskPaintEventArgs e, Task task, RectangleF taskRect) { // select task if (_mSelectedTasks.Contains(task)) { var solidColor = new SolidBrush(Color.FromArgb(150, 255, 255, 255)); var penColor = new Pen(Color.FromArgb(255, 0, 128, 255), 5); if (_mProject.IsGroup(task)) { // check if this is a parent task / group task, then draw the bracket //if (_mProject.IsGroup(task)) var rod = new RectangleF(taskRect.Left, taskRect.Top, taskRect.Width, taskRect.Height / 2); graphics.FillRectangle(solidColor, rod); graphics.DrawRectangle(penColor, rod); if (!task.IsCollapsed) { // left bracket graphics.FillPolygon(Brushes.Black, new PointF[] { new PointF() {X = taskRect.Left, Y = taskRect.Top}, new PointF() {X = taskRect.Left, Y = taskRect.Top + BarHeight}, new PointF() {X = taskRect.Left + BarWidth, Y = taskRect.Top} }); // right bracket graphics.FillPolygon(Brushes.Black, new PointF[] { new PointF() {X = taskRect.Right, Y = taskRect.Top}, new PointF() {X = taskRect.Right, Y = taskRect.Top + BarHeight}, new PointF() {X = taskRect.Right - BarWidth, Y = taskRect.Top} }); //graphics.DrawRectangle(penColor, rod); var smallWidth = (float) BarWidth / 2.0f; GraphicsPath path = new GraphicsPath(); var lines = new PointF[] { new PointF() {X = taskRect.Left, Y = taskRect.Top}, new PointF() {X = taskRect.Right, Y = taskRect.Top}, new PointF() {X = taskRect.Right, Y = taskRect.Bottom}, new PointF() {X = taskRect.Right - smallWidth, Y = taskRect.Top + taskRect.Height / 2.0f}, new PointF() {X = taskRect.Left + smallWidth, Y = taskRect.Top + taskRect.Height / 2.0f}, new PointF() {X = taskRect.Left, Y = taskRect.Bottom}, new PointF() {X = taskRect.Left, Y = taskRect.Top} }; graphics.DrawLines(penColor, lines); } } else { graphics.FillRectangle(solidColor, taskRect); graphics.DrawRectangle(penColor, taskRect); } } //wangdequan var fill = taskRect; if (!_mProject.IsGroup(task)) { fill.Width = (int) (fill.Width * task.Complete); graphics.FillRectangle(task.BackFill, taskRect); //graphics.FillRectangle(e.Format.BackFill, taskRect); //graphics.FillRectangle(e.Format.ForeFill, fill); //graphics.DrawRectangle(e.Format.Border, taskRect); } else { // check if this is a parent task / group task, then draw the bracket //if (_mProject.IsGroup(task)) var rod = new RectangleF(taskRect.Left, taskRect.Top, taskRect.Width, taskRect.Height / 2); var solidColor = new SolidBrush(Color.FromArgb(255, 167, 194, 228)); graphics.FillRectangle(solidColor, rod); if (!task.IsCollapsed) { // left bracket graphics.FillPolygon(solidColor, new PointF[] { new PointF() {X = taskRect.Left, Y = taskRect.Top}, new PointF() {X = taskRect.Left, Y = taskRect.Top + BarHeight}, new PointF() {X = taskRect.Left + BarWidth, Y = taskRect.Top} }); // right bracket graphics.FillPolygon(solidColor, new PointF[] { new PointF() {X = taskRect.Right, Y = taskRect.Top}, new PointF() {X = taskRect.Right, Y = taskRect.Top + BarHeight}, new PointF() {X = taskRect.Right - BarWidth, Y = taskRect.Top} }); } } } private void __DrawTaskParts(Graphics graphics, TaskPaintEventArgs e, Task task, Pen pen) { var parts = _mChartTaskPartRects[task]; // Draw line indicator var firstRect = parts[0].Value; var lastRect = parts[parts.Count - 1].Value; var y_coord = (firstRect.Top + firstRect.Bottom) / 2.0f; var point1 = new PointF(firstRect.Right, y_coord); var point2 = new PointF(lastRect.Left, y_coord); graphics.DrawLine(pen, point1, point2); // Draw Part Rectangles var taskRects = parts.Select(x => x.Value).ToArray(); graphics.FillRectangles(e.Format.BackFill, taskRects); // Draw % complete indicators graphics.FillRectangles(e.Format.ForeFill, parts.Select(x => new RectangleF(x.Value.X, x.Value.Y, x.Value.Width * x.Key.Complete, x.Value.Height)) .ToArray()); // Draw border graphics.DrawRectangles(e.Format.Border, taskRects); } private RectangleF _TextAlignCenterMiddle(Graphics graphics, RectangleF rect, string text, Font font) { var size = graphics.MeasureString(text, font); return new RectangleF( new PointF(rect.Left + (rect.Width - size.Width) / 2, rect.Top + (rect.Height - size.Height) / 2), size); } private RectangleF _TextAlignLeftMiddle(Graphics graphics, RectangleF rect, string text, Font font, float leftMargin = 0.0f) { var size = graphics.MeasureString(text, font); return new RectangleF(new PointF(rect.Left + leftMargin, rect.Top + (rect.Height - size.Height) / 2), size); } #endregion Private Helper Methods #region Private Helper Variables /// /// Printing labels for header /// private static readonly SortedDictionary ShortDays = new SortedDictionary { {DayOfWeek.Sunday, "S"}, {DayOfWeek.Monday, "M"}, {DayOfWeek.Tuesday, "T"}, {DayOfWeek.Wednesday, "W"}, {DayOfWeek.Thursday, "T"}, {DayOfWeek.Friday, "F"}, {DayOfWeek.Saturday, "S"} }; /// /// Polygon points for Header markers /// private static readonly PointF[] _Marker = new PointF[] { new PointF(-4, 0), new PointF(4, 0), new PointF(4, 4), new PointF(0, 8), new PointF(-4f, 4) }; class HeaderInfo { public RectangleF H1Rect; public RectangleF H2Rect; public List H1LabelRects; public List H2LabelRects; public List Columns; public List Dates; public List Dates_DT; //public float scale; } public ProjectManager _mProject = null; // The project to be visualised / rendered as a Gantt Chart public IViewport _mViewport = null; Task _mDragSource = null; // The dragged source Task Point _mDragLastLocation = Point.Empty; // Record the dragging mouse offset Point _mDragStartLocation = Point.Empty; List _mSelectedTasks = new List(); // List of selected tasks Dictionary _mChartTaskHitRects = new Dictionary(); // list of hitareas for Task Rectangles public Dictionary _mChartTaskRects = new Dictionary(); Dictionary>> _mChartTaskPartRects = new Dictionary>>(); Dictionary _mChartSlackRects = new Dictionary(); HeaderInfo _mHeaderInfo = new HeaderInfo(); Task _mMouseEntered = null; // flag whether the mouse has entered a Task rectangle or not Dictionary _mTaskToolTip = new Dictionary(); #endregion Private Helper Variables } #region Chart Formatting /// /// Time scale display format /// public enum TimeScaleDisplay { /// /// Day of the week: S, M, T, W, T, F, S /// DayOfWeek, /// /// Day of month; 1 to 31 /// DayOfMonth, /// /// Week of the year: 1 to 52; or 53 in case of leap year /// WeekOfYear } /// /// Format for painting tasks /// public struct TaskFormat { /// /// Get or set Task outline color /// public Pen Border { get; set; } /// /// Get or set Task background color /// public Brush BackFill { get; set; } /// /// Get or set Task foreground color /// public Brush ForeFill { get; set; } /// /// Get or set Task font color /// public Brush Color { get; set; } /// /// Get or set the brush for slack bars /// public Brush SlackFill { get; set; } } /// /// Format for painting relations /// public struct RelationFormat { /// /// Get or set the line pen /// public Pen Line { get; set; } } /// /// Format for painting chart header /// public struct HeaderFormat { /// /// Font color /// public Brush Color { get; set; } /// /// Border and line colors /// public Pen Border { get; set; } /// /// Get or set the lighter color in the gradient /// public Color GradientLight { get; set; } /// /// Get or set the darker color in the gradient /// public Color GradientDark { get; set; } } #endregion Chart Formatting #region EventAgrs /// /// Provides data for TaskMouseEvent /// public class TaskMouseEventArgs : MouseEventArgs { /// /// Subject Task of the event /// public Task Task { get; private set; } /// /// Rectangle bounds of the Task /// public RectangleF Rectangle { get; private set; } /// /// Initialize a new instance of TaskMouseEventArgs with the MouseEventArgs parameters and the Task involved. /// public TaskMouseEventArgs(Task task, RectangleF rectangle, MouseButtons buttons, int clicks, int x, int y, int delta) : base(buttons, clicks, x, y, delta) { this.Task = task; this.Rectangle = rectangle; } } /// /// Provides data for TaskDragDropEvent /// public class TaskDragDropEventArgs : MouseEventArgs { /// /// Get the previous mouse location /// public Point PreviousLocation { get; private set; } /// /// Get the starting mouse location of this drag drop event /// public Point StartLocation { get; private set; } /// /// Get the source task that is being dragged /// public Task Source { get; private set; } /// /// Get the target task that is being dropped on /// public Task Target { get; private set; } /// /// Get the rectangle bounds of the source task in chart coordinates /// public RectangleF SourceRect { get; private set; } /// /// Get the rectangle bounds of the target task in chart coordinates /// public RectangleF TargetRect { get; private set; } /// /// Get the chart row number that the mouse is current at. /// public int Row { get; private set; } /// /// Initialize a new instance of TaskDragDropEventArgs with the MouseEventArgs parameters and the Task involved and the previous mouse location. /// public TaskDragDropEventArgs(Point startLocation, Point prevLocation, Task source, RectangleF sourceRect, Task target, RectangleF targetRect, int row, MouseButtons buttons, int clicks, int x, int y, int delta) : base(buttons, clicks, x, y, delta) { this.Source = source; this.SourceRect = sourceRect; this.Target = target; this.TargetRect = targetRect; this.PreviousLocation = prevLocation; this.StartLocation = startLocation; this.Row = row; } } /// /// Provides data for ChartPaintEvent /// public class ChartPaintEventArgs : PaintEventArgs { /// /// Get the chart that for this event /// public Chart Chart { get; private set; } /// /// Initialize a new instance of ChartPaintEventArgs with the PaintEventArgs graphics and clip rectangle, and the chart itself. /// /// /// /// public ChartPaintEventArgs(Graphics graphics, Rectangle clipRect, Chart chart) : base(graphics, clipRect) { this.Chart = chart; } } /// /// Provides data for ChartPaintEvent /// public class HeaderPaintEventArgs : ChartPaintEventArgs { /// /// Get or set the font to use for drawing the text on the header /// public Font Font { get; set; } /// /// Get or set the header formatting /// public HeaderFormat Format { get; set; } /// /// Initialize a new instance of HeaderPaintEventArgs with the editable default font and header format /// public HeaderPaintEventArgs(Graphics graphics, Rectangle clipRect, Chart chart, Font font, HeaderFormat format) : base(graphics, clipRect, chart) { this.Font = font; this.Format = format; } } /// /// Provides data for TaskPaintEvent /// public class TaskPaintEventArgs : ChartPaintEventArgs { /// /// Get the task to be painted /// public Task Task { get; private set; } /// /// Get the rectangle bounds of the task /// public RectangleF Rectangle { get { return new RectangleF(this.Task.Start * this.Chart.BarWidth, this.Row * this.Chart.BarSpacing + this.Chart.BarSpacing + this.Chart.HeaderOneHeight, this.Task.Duration * this.Chart.BarWidth, this.Chart.BarHeight); } } /// /// Get the row number of the task /// public int Row { get; private set; } /// /// Get or set the font to be used to draw the task label /// public Font Font { get; set; } /// /// Get or set the formatting of the task /// public TaskFormat Format { get; set; } /// /// Get whether the task is in a critical path /// public bool IsCritical { get; private set; } /// /// Initialize a new instance of TaskPaintEventArgs with the editable default font and task paint format /// public TaskPaintEventArgs(Graphics graphics, Rectangle clipRect, Chart chart, Task task, int row, bool critical, Font font, TaskFormat format) // need to create a paint event for each task for custom painting : base(graphics, clipRect, chart) { this.Task = task; this.Row = row; this.Font = font; this.Format = format; this.IsCritical = critical; } } /// /// Provides data for RelationPaintEvent /// public class RelationPaintEventArgs : ChartPaintEventArgs { /// /// Get the precedent task in the relation /// public Task Precedent { get; private set; } /// /// Get the dependant task in the relation /// public Task Dependant { get; private set; } /// /// Get or set the formatting to use for drawing the relation /// public RelationFormat Format { get; set; } /// /// Initialize a new instance of RelationPaintEventArgs with the editable default font and relation paint format /// public RelationPaintEventArgs(Graphics graphics, Rectangle clipRect, Chart chart, Task before, Task after, RelationFormat format) : base(graphics, clipRect, chart) { this.Precedent = before; this.Dependant = after; this.Format = format; } } #endregion EventArgs /// /// Provides information about the chart at a specific row and date/time. /// public struct ChartInfo { /// /// Get or set the chart row number /// public int Row { get; set; } /// /// Get or set the chart date/time /// public DateTime DateTime { get; set; } /// /// Get or set the task /// public Task Task { get; set; } /// /// Construct a passive data structure to hold chart information /// /// /// /// public ChartInfo(int row, DateTime dateTime, Task task) : this() { Row = row; DateTime = dateTime; Task = task; } } public class Row { public int Index { get; set; } public float Height { get; set; } } public class Column { public int Index { get; set; } public DateTime DateTime { get; set; } } }