protected override void OnRender(DrawingContext drawingContext) { try { using (IsGraphDrawnScope isGraphDrawnScope = new IsGraphDrawnScope()) { if (RenderSize.Width < 10.0 || RenderSize.Height < 10.0) { return; } if (PBMCommand.Instance == null) { return; } DTE2 dte = (DTE2)PBMCommand.Instance.ServiceProvider.GetService(typeof(DTE)); if (dte == null) { return; } if (DataModel.Instance.AllProjectsCount == 0) { return; } int linesCount = DataModel.CurrentBuilds.Count + DataModel.FinishedBuilds.Count + 1 + 1 + 1 + 1; // 1 for header, 1 for status, 1 for CPU, 1 for HDD double totalHeight = rowHeight * linesCount; Height = totalHeight + penThickness; long tickStep = 100000000; long maxTick = tickStep; long nowTick = DateTime.Now.Ticks; long t = nowTick - DataModel.StartTime.Ticks; if (DataModel.CurrentBuilds.Count > 0) { if (t > maxTick) { maxTick = t; } } int ii; bool atLeastOneError = false; uint maxBuildOrderNbr = 1; double projectNameMaxLen = 10; for (ii = 0; ii < DataModel.FinishedBuilds.Count; ii++) { FormattedText iname = new FormattedText(DataModel.FinishedBuilds[ii].ProjectName, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, fontFace, FontSize, blackBrush); double ll = iname.Width; t = DataModel.FinishedBuilds[ii].end; atLeastOneError = atLeastOneError || !DataModel.FinishedBuilds[ii].success; if (t > maxTick) { maxTick = t; } if (ll > projectNameMaxLen) { projectNameMaxLen = ll; } if (DataModel.FinishedBuilds[ii].ProjectBuildOrderNumber > maxBuildOrderNbr) { maxBuildOrderNbr = DataModel.FinishedBuilds[ii].ProjectBuildOrderNumber; } } foreach (KeyValuePair <string, Tuple <uint, long> > item in DataModel.CurrentBuilds) { FormattedText iname = new FormattedText(DataModel.GetHumanReadableProjectName(item.Key), CultureInfo.CurrentCulture, FlowDirection.LeftToRight, fontFace, FontSize, blackBrush); double ll = iname.Width; if (ll > projectNameMaxLen) { projectNameMaxLen = ll; } if (item.Value.Item1 > maxBuildOrderNbr) { maxBuildOrderNbr = item.Value.Item1; } } if (isBuilding) { maxTick = (maxTick / tickStep + 1) * tickStep; } Spacings spacings = new Spacings(); { //setting spacings string pattern = "> "; int len = maxBuildOrderNbr.ToString().Length + pattern.Length; pattern = pattern.PadLeft(len, '8'); // let's assume that 8 is the widest char FormattedText bn = new FormattedText(pattern, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, fontFace, FontSize, blackBrush); spacings.lProjName = Spacings.lOrder + bn.Width + 3; // let's add 3 pix just in case 8 is not the widest spacings.lGanttC = spacings.lProjName + projectNameMaxLen + penThickness + 3; // let's add 3 pix just in case } int rowNbr = 0; //first row has number 0 { // Draw header DrawSeparator(drawingContext, rowNbr); // draw backgroud things first due to anty-aliasing string headerText = DataModel.GetSolutionNameWithMachineInfo(" | ", false /*WithBuildStartedStr*/); // Draw text FormattedText itext = new FormattedText(headerText, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, fontFace, FontSize, blackBrush); { // Cut text when it is too long for window. Probably correct solution is to add horizontal scrollbar to window. // Set a maximum width and height. If the text overflows these values, an ellipsis "..." appears. itext.MaxTextWidth = RenderSize.Width - Spacings.lOrder - Spacings.rGanttC; itext.MaxTextHeight = rowHeight; } drawingContext.DrawText(itext, new Point(Spacings.lOrder, rowNbr * rowHeight)); rowNbr++; } foreach (BuildInfo item in DataModel.FinishedBuilds) { DrawSeparator(drawingContext, rowNbr); // draw backgroud things first due to anty-aliasing DrawOrderAndProjectNameText(drawingContext, rowNbr, item.ProjectBuildOrderNumber, item.ProjectName, spacings, (item.success ? greenSolidBrush : redSolidBrush)); DrawBar(drawingContext, rowNbr, item.begin, item.end, maxTick, spacings, (item.success ? greenGradientBrush : redGradientBrush), DataModel.CriticalPath.Contains(item)); rowNbr++; } foreach (KeyValuePair <string, Tuple <uint, long> > item in DataModel.CurrentBuilds) { DrawSeparator(drawingContext, rowNbr); // draw backgroud things first due to anty-aliasing DrawOrderAndProjectNameText(drawingContext, rowNbr, item.Value.Item1, DataModel.GetHumanReadableProjectName(item.Key), spacings, blueSolidBrush); DrawBar(drawingContext, rowNbr, item.Value.Item2, (nowTick - DataModel.StartTime.Ticks), maxTick, spacings, blueGradientBrush, false /*markAsCriticalPath*/); rowNbr++; } DrawSeparator(drawingContext, rowNbr); // draw backgroud things first due to anty-aliasing DrawGraph("CPU usage", drawingContext, DataModel.CpuUsage, cpuPen, cpuSoftPen, rowNbr, RenderSize, rowHeight, spacings, maxTick, nowTick, fontFace); rowNbr++; DrawSeparator(drawingContext, rowNbr); // draw backgroud things first due to anty-aliasing DrawGraph("HDD usage", drawingContext, DataModel.HddUsage, hddPen, hddSoftPen, rowNbr, RenderSize, rowHeight, spacings, maxTick, nowTick, fontFace); rowNbr++; if (DataModel.CurrentBuilds.Count > 0 || DataModel.FinishedBuilds.Count > 0) { string status = (isBuilding ? "Building..." : "Done"); if (DataModel.MaxParallelBuilds > 0) { status += " (" + DataModel.PercentageProcessorUse().ToString() + "% of " + DataModel.MaxParallelBuilds.ToString() + " CPUs)"; } DrawText(drawingContext, status, rowNbr, Spacings.lOrder, blackBrush); } DrawVerticalSeparator(drawingContext, spacings.lGanttC - penThickness, rowNbr, false /*wholeSize*/); drawingContext.DrawLine(new Pen(atLeastOneError ? redSolidBrush : greenSolidBrush, 1), new Point(0, rowNbr * rowHeight), new Point(RenderSize.Width, rowNbr * rowHeight)); DateTime dt = new DateTime(maxTick); string s = Utils.SecondsToString(dt.Ticks); FormattedText maxTime = new FormattedText(s, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, fontFace, FontSize, blackBrush); double m = maxTime.Width; drawingContext.DrawText(maxTime, new Point(RenderSize.Width - m - Spacings.rGanttC, rowNbr * rowHeight)); { // Draw frame around - it looks better in window and definitelly in saved .png. Draw frame as last to be clearly visible DrawSeparator(drawingContext, -1); // Top DrawSeparator(drawingContext, rowNbr); // Bottom DrawVerticalSeparator(drawingContext, 0, rowNbr, true /*wholeSize*/); // Left DrawVerticalSeparator(drawingContext, RenderSize.Width - penThickness, rowNbr, true /*wholeSize*/); // Right } isGraphDrawnScope.IsDrawn = true; } //End of IsGraphDrawnScope } catch (Exception) { // Keep this try{} catch{}! // Actually code in try{} bail from time to time on windows resize. // I guess because there is division by (x - y), while x equals y, but it might be not the only case. Debug.Assert(false, "Gantt chart not refreshed! Exception thrown while drawing Gantt chart."); } }
protected override void OnRender(DrawingContext drawingContext) { try { using (IsGraphDrawnScope isGraphDrawnScope = new IsGraphDrawnScope()) { if (RenderSize.Width < 10.0 || RenderSize.Height < 10.0) { return; } if (IsEmptyBuilds()) { // Case when no single build was started yet - display some info to ensure user that everything is OK FormattedText captionFT = new FormattedText(emptyGanttMsg, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, fontFace, FontSize, blackBrush, VisualTreeHelper.GetDpi(this).PixelsPerDip); drawingContext.DrawText(captionFT, new Point(50.0, rowNbrNoProjectsFiller / 2 * rowHeight)); return; } int linesCount = DataModel.CurrentBuilds.Count + DataModel.FinishedBuilds.Count + 1 + 1 + 1 + 1; // 1 for header, 1 for status, 1 for CPU, 1 for HDD double totalHeight = rowHeight * linesCount; Height = totalHeight + penThickness; long tickStep = 100000000; long maxTick = tickStep; long nowTick = ((nowTickForTest > 0) ? nowTickForTest : DateTime.Now.Ticks); long t = nowTick - DataModel.StartTime.Ticks; if (DataModel.CurrentBuilds.Count > 0) { if (t > maxTick) { maxTick = t; } } int ii; bool atLeastOneError = false; uint maxBuildOrderNbr = 1; double projectNameMaxLen = 10; for (ii = 0; ii < DataModel.FinishedBuilds.Count; ii++) { FormattedText iname = new FormattedText(DataModel.FinishedBuilds[ii].ProjectName, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, fontFace, FontSize, blackBrush, VisualTreeHelper.GetDpi(this).PixelsPerDip); double ll = iname.Width; t = DataModel.FinishedBuilds[ii].end; atLeastOneError = atLeastOneError || !DataModel.FinishedBuilds[ii].success; if (t > maxTick) { maxTick = t; } if (ll > projectNameMaxLen) { projectNameMaxLen = ll; } if (DataModel.FinishedBuilds[ii].ProjectBuildOrderNumber > maxBuildOrderNbr) { maxBuildOrderNbr = DataModel.FinishedBuilds[ii].ProjectBuildOrderNumber; } } foreach (KeyValuePair <string, Tuple <uint, long> > item in DataModel.CurrentBuilds) { FormattedText iname = new FormattedText(DataModel.GetHumanReadableProjectName(item.Key), CultureInfo.CurrentCulture, FlowDirection.LeftToRight, fontFace, FontSize, blackBrush, VisualTreeHelper.GetDpi(this).PixelsPerDip); double ll = iname.Width; if (ll > projectNameMaxLen) { projectNameMaxLen = ll; } if (item.Value.Item1 > maxBuildOrderNbr) { maxBuildOrderNbr = item.Value.Item1; } } if (DataModel.IsBuilding) { maxTick = (maxTick / tickStep + 1) * tickStep; } Spacings spacings = new Spacings(); { //setting spacings string pattern = "> "; int len = maxBuildOrderNbr.ToString().Length + pattern.Length; pattern = pattern.PadLeft(len, '8'); // let's assume that 8 is the widest char FormattedText bn = new FormattedText(pattern, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, fontFace, FontSize, blackBrush, VisualTreeHelper.GetDpi(this).PixelsPerDip); spacings.lProjName = Spacings.lOrder + bn.Width + 3; // let's add 3 pix just in case 8 is not the widest spacings.lGanttC = spacings.lProjName + projectNameMaxLen + penThickness + 3; // let's add 3 pix just in case } // check if usage text is longer than the longest project name, and yes, values greater than 1000% are possible double usageTextLen = new FormattedText("HDD Usage (Avg. 1000%)", CultureInfo.CurrentCulture, FlowDirection.LeftToRight, fontFace, FontSize, blackBrush, VisualTreeHelper.GetDpi(this).PixelsPerDip).Width; spacings.lGanttC = Math.Max(spacings.lGanttC, Spacings.lOrder + usageTextLen + penThickness); int rowNbr = 0; //first row has number 0 { // Draw header DrawSeparator(drawingContext, rowNbr); // draw backgroud things first due to anty-aliasing DrawMachineInfo(drawingContext, rowNbr); rowNbr++; } foreach (BuildInfo item in DataModel.FinishedBuilds) { DrawSeparator(drawingContext, rowNbr); // draw backgroud things first due to anty-aliasing DrawOrderAndProjectNameText(drawingContext, rowNbr, item.ProjectBuildOrderNumber, item.ProjectName, spacings, (item.success ? greenSolidBrush : redSolidBrush)); DrawBar(drawingContext, rowNbr, item.begin, item.end, maxTick, spacings, (item.success ? greenGradientBrush : redGradientBrush), DataModel.CriticalPath.Contains(item)); rowNbr++; } foreach (KeyValuePair <string, Tuple <uint, long> > item in DataModel.CurrentBuilds) { DrawSeparator(drawingContext, rowNbr); // draw backgroud things first due to anty-aliasing DrawOrderAndProjectNameText(drawingContext, rowNbr, item.Value.Item1, DataModel.GetHumanReadableProjectName(item.Key), spacings, blueSolidBrush); DrawBar(drawingContext, rowNbr, item.Value.Item2, (nowTick - DataModel.StartTime.Ticks), maxTick, spacings, blueGradientBrush, false /*markAsCriticalPath*/); rowNbr++; } DrawSeparator(drawingContext, rowNbr); // draw backgroud things first due to anty-aliasing DrawGraph("CPU usage", drawingContext, DataModel.CpuUsage, cpuPen, cpuSoftPen, rowNbr, RenderSize, rowHeight, spacings, maxTick, nowTick, fontFace); rowNbr++; DrawSeparator(drawingContext, rowNbr); // draw backgroud things first due to anty-aliasing DrawGraph("HDD usage", drawingContext, DataModel.HddUsage, hddPen, hddSoftPen, rowNbr, RenderSize, rowHeight, spacings, maxTick, nowTick, fontFace); rowNbr++; if (DataModel.CurrentBuilds.Count > 0 || DataModel.FinishedBuilds.Count > 0) { string status = (DataModel.IsBuilding ? "Building..." : "Done."); if (DataModel.MaxParallelBuilds > 0) { status += " Max. no. of parallel projects: " + DataModel.MaxParallelBuilds.ToString() + " (Avg. " + Math.Round(DataModel.MaxParallelBuilds / 100.0 * DataModel.PercentageProcessorUse(), 1).ToString(format: "0.0") + ")"; } DrawText(drawingContext, status, rowNbr, Spacings.lOrder, blackBrush); } DrawVerticalSeparator(drawingContext, spacings.lGanttC - penThickness, rowNbr, false /*wholeSize*/); drawingContext.DrawLine(new Pen(atLeastOneError ? redSolidBrush : greenSolidBrush, 1), new Point(0, rowNbr * rowHeight), new Point(RenderSize.Width, rowNbr * rowHeight)); DateTime dt = new DateTime(maxTick); string s = Utils.SecondsToString(dt.Ticks); FormattedText maxTime = new FormattedText(s, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, fontFace, FontSize, blackBrush, VisualTreeHelper.GetDpi(this).PixelsPerDip); double m = maxTime.Width; drawingContext.DrawText(maxTime, new Point(RenderSize.Width - m - Spacings.rGanttC, rowNbr * rowHeight)); { // Draw frame around - it looks better in window and definitelly in saved .png. Draw frame as last to be clearly visible DrawSeparator(drawingContext, -1); // Top DrawSeparator(drawingContext, rowNbr); // Bottom DrawVerticalSeparator(drawingContext, 0, rowNbr, true /*wholeSize*/); // Left DrawVerticalSeparator(drawingContext, RenderSize.Width - penThickness, rowNbr, true /*wholeSize*/); // Right } isGraphDrawnScope.IsDrawn = true; } //End of IsGraphDrawnScope } catch (Exception) { Debug.Assert(false, "Gantt chart not refreshed! Exception thrown while drawing Gantt chart."); } }