2313 lines
86 KiB
C#
2313 lines
86 KiB
C#
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gantt Chart control
|
|
/// </summary>
|
|
public partial class Chart : UserControl
|
|
{
|
|
/// <summary>
|
|
/// Construct a gantt chart
|
|
/// </summary>
|
|
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
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Delegate method for creating a new Task. Creates Task by default.
|
|
/// </summary>
|
|
public Func<Task> CreateTaskDelegate = delegate() { return new Task(); };
|
|
|
|
/// <summary>
|
|
/// Get the selected tasks
|
|
/// </summary>
|
|
public IEnumerable<Task> SelectedTasks
|
|
{
|
|
get { return _mSelectedTasks.ToArray(); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the latest selected task
|
|
/// </summary>
|
|
public Task SelectedTask
|
|
{
|
|
get { return _mSelectedTasks.LastOrDefault(); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get or set header1 pixel height
|
|
/// </summary>
|
|
[DefaultValue(32)]
|
|
public int HeaderOneHeight { get; set; }
|
|
|
|
/// <summary>
|
|
/// Get or set the DateTime string format. Default value is D/M/YYYY
|
|
/// </summary>
|
|
[DefaultValue("D/M/YYYY")]
|
|
public string FullDateStringFormat { get; set; }
|
|
|
|
/// <summary>
|
|
/// Get or set header2 pixel height
|
|
/// </summary>
|
|
[DefaultValue(20)]
|
|
public int HeaderTwoHeight { get; set; }
|
|
|
|
/// <summary>
|
|
/// Get or set pixel distance from top of each Task to the next
|
|
/// </summary>
|
|
[DefaultValue(26)]
|
|
public int BarSpacing { get; set; }
|
|
|
|
/// <summary>
|
|
/// Get or set pixel height of each Task
|
|
/// </summary>
|
|
[DefaultValue(20)]
|
|
public int BarHeight { get; set; }
|
|
|
|
/// <summary>
|
|
/// Get or set pixel width of each unit of period
|
|
/// </summary>
|
|
[DefaultValue(20)]
|
|
public float BarWidth { get; set; }
|
|
|
|
/// <summary>
|
|
/// Get or set format for Tasks
|
|
/// </summary>
|
|
public TaskFormat TaskFormat { get; set; }
|
|
|
|
/// <summary>
|
|
/// Get or set format for critical Tasks
|
|
/// </summary>
|
|
public TaskFormat CriticalTaskFormat { get; set; }
|
|
|
|
/// <summary>
|
|
/// Get or set format for headers
|
|
/// </summary>
|
|
public HeaderFormat HeaderFormat { get; set; }
|
|
|
|
/// <summary>
|
|
/// Get or set format for relations
|
|
/// </summary>
|
|
public RelationFormat RelationFormat { get; set; }
|
|
|
|
/// <summary>
|
|
/// Get or set whether dragging of Tasks is allowed. Set to false when not dragging to skip drag(drop) tracking.
|
|
/// </summary>
|
|
[DefaultValue(true)]
|
|
public bool AllowTaskDragDrop { get; set; }
|
|
|
|
/// <summary>
|
|
/// Get or set whether to show relations
|
|
/// </summary>
|
|
[DefaultValue(true)]
|
|
public bool ShowRelations { get; set; }
|
|
|
|
/// <summary>
|
|
/// Get or set format for headers wangdequan
|
|
/// </summary>
|
|
[DefaultValue(1f)]
|
|
public float HeaderScale { get; set; }
|
|
|
|
/// <summary>
|
|
/// Get or set format for headers wangdequan Max = 10;Min=0
|
|
/// </summary>
|
|
[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};
|
|
|
|
/// <summary>
|
|
/// Get or set whether to show task labels
|
|
/// </summary>
|
|
[DefaultValue(true)]
|
|
public bool ShowTaskLabels { get; set; }
|
|
|
|
/// <summary>
|
|
/// 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)
|
|
/// </summary>
|
|
[DefaultValue(false)]
|
|
public bool AccumulateRelationsOnGroup { get; set; }
|
|
|
|
/// <summary>
|
|
/// Get or set whether to show slack
|
|
/// </summary>
|
|
[DefaultValue(false)]
|
|
public bool ShowSlack { get; set; }
|
|
|
|
/// <summary>
|
|
/// Get or set the time scale display format
|
|
/// </summary>
|
|
[DefaultValue(TimeScaleDisplay.DayOfMonth)]
|
|
public TimeScaleDisplay TimeScaleDisplay { get; set; }
|
|
|
|
/// <summary>
|
|
/// Occurs when the mouse is moving over a Task
|
|
/// </summary>
|
|
public event EventHandler<TaskMouseEventArgs> TaskMouseOver = null;
|
|
|
|
/// <summary>
|
|
/// Occurs when the mouse leaves a Task
|
|
/// </summary>
|
|
public event EventHandler<TaskMouseEventArgs> TaskMouseOut = null;
|
|
|
|
/// <summary>
|
|
/// Occurs when a Task is clicked
|
|
/// </summary>
|
|
public event EventHandler<TaskMouseEventArgs> TaskMouseClick = null;
|
|
|
|
/// <summary>
|
|
/// TaskMouseDown
|
|
/// </summary>
|
|
public event EventHandler<TaskMouseEventArgs> TaskMouseDown = null;
|
|
|
|
/// <summary>
|
|
/// Occurs when a Task is double clicked by the mouse
|
|
/// </summary>
|
|
public event EventHandler<TaskMouseEventArgs> TaskMouseDoubleClick = null;
|
|
|
|
/// <summary>
|
|
/// Occurs when a Task is being dragged by the mouse
|
|
/// </summary>
|
|
public event EventHandler<TaskDragDropEventArgs> TaskMouseDrag = null;
|
|
|
|
/// <summary>
|
|
/// Occurs when a dragged Task is being dropped by releasing any previously pressed mouse button.
|
|
/// </summary>
|
|
public event EventHandler<TaskDragDropEventArgs> TaskMouseDrop = null;
|
|
|
|
/// <summary>
|
|
/// Occurs when a task is selected.
|
|
/// </summary>
|
|
public event EventHandler<TaskMouseEventArgs> TaskSelected = null;
|
|
|
|
/// <summary>
|
|
/// Occurs before one or more tasks are being deselected. All Task in Chart.SelectedTasks will be deselected.
|
|
/// </summary>
|
|
public event EventHandler<TaskMouseEventArgs> TaskDeselecting = null;
|
|
|
|
/// <summary>
|
|
/// Occurs before a Task gets painted
|
|
/// </summary>
|
|
public event EventHandler<TaskPaintEventArgs> PaintTask = null;
|
|
|
|
/// <summary>
|
|
/// Occurs before overlays get painted
|
|
/// </summary>
|
|
public event EventHandler<ChartPaintEventArgs> PaintOverlay = null;
|
|
|
|
/// <summary>
|
|
/// Occurs before the header gets painted
|
|
/// </summary>
|
|
public event EventHandler<HeaderPaintEventArgs> PaintHeader = null;
|
|
|
|
/// <summary>
|
|
/// Get the line number of the specified task
|
|
/// </summary>
|
|
/// <param name="task"></param>
|
|
/// <param name="row"></param>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the task at the specified line number
|
|
/// </summary>
|
|
/// <param name="row"></param>
|
|
/// <param name="task"></param>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initialize this Chart with a Project
|
|
/// </summary>
|
|
/// <param name="project"></param>
|
|
public void Init(ProjectManager<Task, object> project)
|
|
{
|
|
_mProject = project;
|
|
HeaderScale = 1.0f;
|
|
_GenerateModels();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Print the Chart to the specified PrintDocument.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Print the Chart to the specified Image
|
|
/// </summary>
|
|
/// <param name="scale">Scale to print the image at.</param>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get information about the chart area at the mouse coordinate of the chart
|
|
/// </summary>
|
|
/// <param name="mouse"></param>
|
|
/// <returns></returns>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set tool tip for the specified task
|
|
/// </summary>
|
|
/// <param name="task"></param>
|
|
/// <param name="text"></param>
|
|
public void SetToolTip(Task task, string text)
|
|
{
|
|
if (task != null && text != string.Empty)
|
|
_mTaskToolTip[task] = text;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get tool tip currently set for the specified task
|
|
/// </summary>
|
|
/// <param name="task"></param>
|
|
/// <param name="text"></param>
|
|
/// <returns></returns>
|
|
public string GetToolTip(Task task)
|
|
{
|
|
if (task != null)
|
|
return _mTaskToolTip[task];
|
|
else
|
|
return string.Empty;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clear tool tip for the specified task
|
|
/// </summary>
|
|
/// <param name="task"></param>
|
|
public void ClearToolTip(Task task)
|
|
{
|
|
if (task != null)
|
|
_mTaskToolTip.Remove(task);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clear all tool tips
|
|
/// </summary>
|
|
public void ClearToolTips()
|
|
{
|
|
_mTaskToolTip.Clear();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Scroll to the specified DateTime
|
|
/// </summary>
|
|
/// <param name="datetime"></param>
|
|
public void ScrollTo(DateTime datetime)
|
|
{
|
|
TimeSpan span = datetime - _mProject.Start;
|
|
_mViewport.X = span.Days * this.BarWidth;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Scroll to the specified task
|
|
/// </summary>
|
|
/// <param name="task"></param>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Begin billboard mode. Graphics must orginate from Chart and be same as that used in EndBillboardMode.
|
|
/// </summary>
|
|
/// <param name="graphics"></param>
|
|
public void BeginBillboardMode(Graphics graphics)
|
|
{
|
|
graphics.Transform = ControlViewport.Identity;
|
|
}
|
|
|
|
/// <summary>
|
|
/// End billboard mode. Graphics must orginate from Chart and be same as that used in BeginBillboardMode.
|
|
/// </summary>
|
|
/// <param name="graphics"></param>
|
|
public void EndBillboardMode(Graphics graphics)
|
|
{
|
|
graphics.Transform = _mViewport.Projection;
|
|
}
|
|
|
|
#region UserControl Events
|
|
|
|
/// <summary>
|
|
/// Raises the System.Windows.Forms.Control.Paint event
|
|
/// </summary>
|
|
/// <param name="e"></param>
|
|
protected override void OnPaint(PaintEventArgs e)
|
|
{
|
|
base.OnPaint(e);
|
|
|
|
if (!this.DesignMode)
|
|
this._Draw(e.Graphics, e.ClipRectangle);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raises the System.Windows.Forms.Control.MouseMove event
|
|
/// </summary>
|
|
/// <param name="e"></param>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raises the System.Windows.Forms.Control.MouseClick event
|
|
/// </summary>
|
|
/// <param name="e"></param>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raises the System.Windows.Forms.Control.MouseDown event
|
|
/// </summary>
|
|
/// <param name="e"></param>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raises the System.Windows.Forms.Control.MouseUp event
|
|
/// </summary>
|
|
/// <param name="e"></param>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raises the System.Windows.Forms.Control.MouseDoubleClick event
|
|
/// </summary>
|
|
/// <param name="e"></param>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Raises the TaskMouseOver event
|
|
/// </summary>
|
|
/// <param name="e"></param>
|
|
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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raises the TaskMouseOver event
|
|
/// </summary>
|
|
/// <param name="e"></param>
|
|
protected virtual void OnTaskMouseOut(TaskMouseEventArgs e)
|
|
{
|
|
if (TaskMouseOut != null)
|
|
TaskMouseOut(this, e);
|
|
|
|
this.Cursor = Cursors.Default;
|
|
|
|
_mOverlay.HideToolTip();
|
|
this.Invalidate();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raises the TaskMouseDrag( event
|
|
/// </summary>
|
|
/// <param name="e"></param>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raises the TaskMouseDrop event
|
|
/// </summary>
|
|
/// <param name="e"></param>
|
|
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();
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Raises the TaskMouseClick event
|
|
/// </summary>
|
|
/// <param name="e"></param>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raises the TaskMouseDoubleClick event
|
|
/// </summary>
|
|
/// <param name="e"></param>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raises the TaskSelected event
|
|
/// </summary>
|
|
/// <param name="e"></param>
|
|
protected virtual void OnTaskSelected(TaskMouseEventArgs e)
|
|
{
|
|
if (TaskSelected != null)
|
|
TaskSelected(this, e);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raises the TaskDeselecting event
|
|
/// </summary>
|
|
/// <param name="e"></param>
|
|
protected virtual void OnTaskDeselecting(TaskMouseEventArgs e)
|
|
{
|
|
if (TaskDeselecting != null)
|
|
TaskDeselecting(this, e);
|
|
|
|
// deselect all tasks
|
|
_mSelectedTasks.Clear();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raises the PaintOverlay event
|
|
/// </summary>
|
|
/// <param name="e"></param>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert view Y coordinate to zero based row number
|
|
/// </summary>
|
|
/// <param name="y"></param>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert world Y coordinate to zero-based row number
|
|
/// </summary>
|
|
/// <param name="y"></param>
|
|
/// <returns></returns>
|
|
private int _ChartCoordToChartRow(float y)
|
|
{
|
|
var row = (int) ((y - this.HeaderTwoHeight - this.HeaderOneHeight) / this.BarSpacing);
|
|
return row < 0 ? 0 : row;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert zero based row number to client Y coordinates
|
|
/// </summary>
|
|
/// <param name="row"></param>
|
|
/// <returns></returns>
|
|
private float _ChartRowToChartCoord(int row)
|
|
{
|
|
return row * this.BarSpacing + this.HeaderTwoHeight + this.HeaderOneHeight;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generate the task models and resize the world accordingly
|
|
/// </summary>
|
|
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<KeyValuePair<Task, RectangleF>>();
|
|
_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<Task, RectangleF>(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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generate the task models and resize the world accordingly wangdequan 20200531
|
|
/// </summary>
|
|
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<KeyValuePair<Task, RectangleF>>();
|
|
_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<Task, RectangleF>(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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generate Header rectangles and dates wangdequan
|
|
/// </summary>
|
|
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<RectangleF>();
|
|
var h2LabelRects = new List<RectangleF>();
|
|
var columns = new List<RectangleF>();
|
|
var h1labels = new List<string>();
|
|
var h2labels = new List<string>();
|
|
var dates = new List<DateTime>();
|
|
var dates_DT = new List<string>();
|
|
|
|
// 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<RectangleF>();
|
|
var h2LabelRects = new List<RectangleF>();
|
|
var columns = new List<RectangleF>();
|
|
var h1labels = new List<string>();
|
|
var h2labels = new List<string>();
|
|
var dates = new List<DateTime>();
|
|
var dates_DT = new List<string>();
|
|
|
|
// 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generate Header rectangles and dates
|
|
/// </summary>
|
|
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<RectangleF>();
|
|
var h2LabelRects = new List<RectangleF>();
|
|
var columns = new List<RectangleF>();
|
|
var h1labels = new List<string>();
|
|
var h2labels = new List<string>();
|
|
var dates = new List<DateTime>();
|
|
|
|
// 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;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Calculate the date in the first visible column in the viewport
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Integrates the current DateTime by one column of TimeScale
|
|
/// </summary>
|
|
/// <param name="current"></param>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draw the Chart using the specified graphics
|
|
/// </summary>
|
|
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<Task>(_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<Task> 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
|
|
|
|
/// <summary>
|
|
/// Printing labels for header
|
|
/// </summary>
|
|
private static readonly SortedDictionary<DayOfWeek, string> ShortDays = new SortedDictionary<DayOfWeek, string>
|
|
{
|
|
{DayOfWeek.Sunday, "S"},
|
|
{DayOfWeek.Monday, "M"},
|
|
{DayOfWeek.Tuesday, "T"},
|
|
{DayOfWeek.Wednesday, "W"},
|
|
{DayOfWeek.Thursday, "T"},
|
|
{DayOfWeek.Friday, "F"},
|
|
{DayOfWeek.Saturday, "S"}
|
|
};
|
|
|
|
/// <summary>
|
|
/// Polygon points for Header markers
|
|
/// </summary>
|
|
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<RectangleF> H1LabelRects;
|
|
public List<RectangleF> H2LabelRects;
|
|
public List<RectangleF> Columns;
|
|
public List<DateTime> Dates;
|
|
|
|
public List<string> Dates_DT;
|
|
//public float scale;
|
|
}
|
|
|
|
|
|
public ProjectManager<Task, object>
|
|
_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<Task> _mSelectedTasks = new List<Task>(); // List of selected tasks
|
|
|
|
Dictionary<Task, RectangleF>
|
|
_mChartTaskHitRects = new Dictionary<Task, RectangleF>(); // list of hitareas for Task Rectangles
|
|
|
|
public Dictionary<Task, RectangleF> _mChartTaskRects = new Dictionary<Task, RectangleF>();
|
|
|
|
Dictionary<Task, List<KeyValuePair<Task, RectangleF>>> _mChartTaskPartRects =
|
|
new Dictionary<Task, List<KeyValuePair<Task, RectangleF>>>();
|
|
|
|
Dictionary<Task, RectangleF> _mChartSlackRects = new Dictionary<Task, RectangleF>();
|
|
HeaderInfo _mHeaderInfo = new HeaderInfo();
|
|
Task _mMouseEntered = null; // flag whether the mouse has entered a Task rectangle or not
|
|
Dictionary<Task, string> _mTaskToolTip = new Dictionary<Task, string>();
|
|
|
|
#endregion Private Helper Variables
|
|
}
|
|
|
|
#region Chart Formatting
|
|
|
|
/// <summary>
|
|
/// Time scale display format
|
|
/// </summary>
|
|
public enum TimeScaleDisplay
|
|
{
|
|
/// <summary>
|
|
/// Day of the week: S, M, T, W, T, F, S
|
|
/// </summary>
|
|
DayOfWeek,
|
|
|
|
/// <summary>
|
|
/// Day of month; 1 to 31
|
|
/// </summary>
|
|
DayOfMonth,
|
|
|
|
/// <summary>
|
|
/// Week of the year: 1 to 52; or 53 in case of leap year
|
|
/// </summary>
|
|
WeekOfYear
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Format for painting tasks
|
|
/// </summary>
|
|
public struct TaskFormat
|
|
{
|
|
/// <summary>
|
|
/// Get or set Task outline color
|
|
/// </summary>
|
|
public Pen Border { get; set; }
|
|
|
|
/// <summary>
|
|
/// Get or set Task background color
|
|
/// </summary>
|
|
public Brush BackFill { get; set; }
|
|
|
|
/// <summary>
|
|
/// Get or set Task foreground color
|
|
/// </summary>
|
|
public Brush ForeFill { get; set; }
|
|
|
|
/// <summary>
|
|
/// Get or set Task font color
|
|
/// </summary>
|
|
public Brush Color { get; set; }
|
|
|
|
/// <summary>
|
|
/// Get or set the brush for slack bars
|
|
/// </summary>
|
|
public Brush SlackFill { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Format for painting relations
|
|
/// </summary>
|
|
public struct RelationFormat
|
|
{
|
|
/// <summary>
|
|
/// Get or set the line pen
|
|
/// </summary>
|
|
public Pen Line { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Format for painting chart header
|
|
/// </summary>
|
|
public struct HeaderFormat
|
|
{
|
|
/// <summary>
|
|
/// Font color
|
|
/// </summary>
|
|
public Brush Color { get; set; }
|
|
|
|
/// <summary>
|
|
/// Border and line colors
|
|
/// </summary>
|
|
public Pen Border { get; set; }
|
|
|
|
/// <summary>
|
|
/// Get or set the lighter color in the gradient
|
|
/// </summary>
|
|
public Color GradientLight { get; set; }
|
|
|
|
/// <summary>
|
|
/// Get or set the darker color in the gradient
|
|
/// </summary>
|
|
public Color GradientDark { get; set; }
|
|
}
|
|
|
|
#endregion Chart Formatting
|
|
|
|
#region EventAgrs
|
|
|
|
/// <summary>
|
|
/// Provides data for TaskMouseEvent
|
|
/// </summary>
|
|
public class TaskMouseEventArgs : MouseEventArgs
|
|
{
|
|
/// <summary>
|
|
/// Subject Task of the event
|
|
/// </summary>
|
|
public Task Task { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Rectangle bounds of the Task
|
|
/// </summary>
|
|
public RectangleF Rectangle { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Initialize a new instance of TaskMouseEventArgs with the MouseEventArgs parameters and the Task involved.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Provides data for TaskDragDropEvent
|
|
/// </summary>
|
|
public class TaskDragDropEventArgs : MouseEventArgs
|
|
{
|
|
/// <summary>
|
|
/// Get the previous mouse location
|
|
/// </summary>
|
|
public Point PreviousLocation { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Get the starting mouse location of this drag drop event
|
|
/// </summary>
|
|
public Point StartLocation { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Get the source task that is being dragged
|
|
/// </summary>
|
|
public Task Source { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Get the target task that is being dropped on
|
|
/// </summary>
|
|
public Task Target { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Get the rectangle bounds of the source task in chart coordinates
|
|
/// </summary>
|
|
public RectangleF SourceRect { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Get the rectangle bounds of the target task in chart coordinates
|
|
/// </summary>
|
|
public RectangleF TargetRect { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Get the chart row number that the mouse is current at.
|
|
/// </summary>
|
|
public int Row { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Initialize a new instance of TaskDragDropEventArgs with the MouseEventArgs parameters and the Task involved and the previous mouse location.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Provides data for ChartPaintEvent
|
|
/// </summary>
|
|
public class ChartPaintEventArgs : PaintEventArgs
|
|
{
|
|
/// <summary>
|
|
/// Get the chart that for this event
|
|
/// </summary>
|
|
public Chart Chart { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Initialize a new instance of ChartPaintEventArgs with the PaintEventArgs graphics and clip rectangle, and the chart itself.
|
|
/// </summary>
|
|
/// <param name="graphics"></param>
|
|
/// <param name="clipRect"></param>
|
|
/// <param name="chart"></param>
|
|
public ChartPaintEventArgs(Graphics graphics, Rectangle clipRect, Chart chart)
|
|
: base(graphics, clipRect)
|
|
{
|
|
this.Chart = chart;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Provides data for ChartPaintEvent
|
|
/// </summary>
|
|
public class HeaderPaintEventArgs : ChartPaintEventArgs
|
|
{
|
|
/// <summary>
|
|
/// Get or set the font to use for drawing the text on the header
|
|
/// </summary>
|
|
public Font Font { get; set; }
|
|
|
|
/// <summary>
|
|
/// Get or set the header formatting
|
|
/// </summary>
|
|
public HeaderFormat Format { get; set; }
|
|
|
|
/// <summary>
|
|
/// Initialize a new instance of HeaderPaintEventArgs with the editable default font and header format
|
|
/// </summary>
|
|
public HeaderPaintEventArgs(Graphics graphics, Rectangle clipRect, Chart chart, Font font, HeaderFormat format)
|
|
: base(graphics, clipRect, chart)
|
|
{
|
|
this.Font = font;
|
|
this.Format = format;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Provides data for TaskPaintEvent
|
|
/// </summary>
|
|
public class TaskPaintEventArgs : ChartPaintEventArgs
|
|
{
|
|
/// <summary>
|
|
/// Get the task to be painted
|
|
/// </summary>
|
|
public Task Task { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Get the rectangle bounds of the task
|
|
/// </summary>
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the row number of the task
|
|
/// </summary>
|
|
public int Row { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Get or set the font to be used to draw the task label
|
|
/// </summary>
|
|
public Font Font { get; set; }
|
|
|
|
/// <summary>
|
|
/// Get or set the formatting of the task
|
|
/// </summary>
|
|
public TaskFormat Format { get; set; }
|
|
|
|
/// <summary>
|
|
/// Get whether the task is in a critical path
|
|
/// </summary>
|
|
public bool IsCritical { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Initialize a new instance of TaskPaintEventArgs with the editable default font and task paint format
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Provides data for RelationPaintEvent
|
|
/// </summary>
|
|
public class RelationPaintEventArgs : ChartPaintEventArgs
|
|
{
|
|
/// <summary>
|
|
/// Get the precedent task in the relation
|
|
/// </summary>
|
|
public Task Precedent { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Get the dependant task in the relation
|
|
/// </summary>
|
|
public Task Dependant { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Get or set the formatting to use for drawing the relation
|
|
/// </summary>
|
|
public RelationFormat Format { get; set; }
|
|
|
|
/// <summary>
|
|
/// Initialize a new instance of RelationPaintEventArgs with the editable default font and relation paint format
|
|
/// </summary>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Provides information about the chart at a specific row and date/time.
|
|
/// </summary>
|
|
public struct ChartInfo
|
|
{
|
|
/// <summary>
|
|
/// Get or set the chart row number
|
|
/// </summary>
|
|
public int Row { get; set; }
|
|
|
|
/// <summary>
|
|
/// Get or set the chart date/time
|
|
/// </summary>
|
|
public DateTime DateTime { get; set; }
|
|
|
|
/// <summary>
|
|
/// Get or set the task
|
|
/// </summary>
|
|
public Task Task { get; set; }
|
|
|
|
/// <summary>
|
|
/// Construct a passive data structure to hold chart information
|
|
/// </summary>
|
|
/// <param name="row"></param>
|
|
/// <param name="dateTime"></param>
|
|
/// <param name="task"></param>
|
|
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; }
|
|
}
|
|
} |