HP laid off a bunch of us August 25. C#/.NET is in demand here in the Boise area, and elsewhere. While I won't have enough C# experience to get a job where I live, I might be able to get a job somewhere else. I thought you might find what I have learned of interest.
I'm not completely done, but I would like to verify that the publish feature of Microsoft Visual Studio C# actually works. You can run the setup program from here. This should install the first significant C# application that I have written, which loads data from CSV files and plots the data. You can download some files of the correct format here, or here.
I solved the flicker problem! I just needed to call this.Invalidate() every time that I took steps that altered, or might alter, the plot.
Here's the source code.
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.IO; using System.Net; namespace WindowsFormsApplication1 { public partial class Form1 : Form { // The dimensions of the form brought up by this application. SizeF formSize; int menuBarHeight; // The configuration command dialog window (because we need to get to it from // the double click ListBox event handler. Form configDlg; public Form1() { InitializeComponent(); formSize = this.ClientSize; this.Size = new System.Drawing.Size((int)(formSize.Width - 50), (int)(formSize.Height - 50)); // I wish that I could get the ClientSize of the form (that is, minus the // menu bar), but that doesn't seem to be available. menuBarHeight = this.menuStrip1.Location.Y + this.menuStrip1.Size.Height; } // Set up a color palette to use for drawing lines. private ListcolorChoices = new List () {Color.Yellow, Color.Aqua, Color.Red, Color.Blue, Color.Beige, Color.Violet, Color.Coral, Color.CornflowerBlue, Color.Cornsilk, Color.Crimson, Color.Cyan, Color.DarkBlue}; // This is where the numeric data will go. private List numbersArray = new List (); // This is the largest count of numbers in any element of numbersArray int numbersArrayMaxCount = 0; // This is the largest value that we have to plot across all rows. float numbersArrayMax = 0.0F; // The legend information (contained in the first column) goes here. private List legendStrings = new List (); // And this is where the column header information goes (the years going // across, for the first sample. private ColHdr colHdr = new ColHdr(); // This contains a list of all the rows in the CSV file to plot. We'll default this to // every entry when we first load a CSV file. The user can use the Configure command to // be more selective. List listToShow = new List (); private float RecalculateMaxValue() { float maxValue = 0.0F; for (int i = 0; i < numbersArray.Count; i++) { NumbersToPlot numbers = numbersArray[i]; if (listToShow.Contains(i)) for (int j = 1; j < numbers.events.Count; j++) maxValue = Math.Max(maxValue, numbers.max); } return(maxValue); } private void openWebPageToolStripMenuItem_Click(object sender, EventArgs e) { Form dlgWebPage = new Form(); TextBox box = new TextBox(); box.Location = new Point(0, 0); box.Size = new System.Drawing.Size(300, 50); box.Text = "http://www.claytoncramer.com/java/bogusdata.csv"; dlgWebPage.Controls.Add(box); // Add a button. Button bOk = new System.Windows.Forms.Button(); bOk.Text = "OK"; bOk.Location = new Point(0, box.Size.Height); bOk.DialogResult = DialogResult.OK; dlgWebPage.Controls.Add(bOk); Button bCancel = new System.Windows.Forms.Button(); bCancel.Text = "Cancel"; bCancel.Location = new Point(bOk.Size.Width + 10, box.Size.Height); dlgWebPage.ClientSize = new System.Drawing.Size(box.Size.Width, bCancel.Size.Height + bCancel.Location.Y); bCancel.DialogResult = DialogResult.Cancel; this.AcceptButton = bOk; dlgWebPage.Controls.Add(bCancel); if (DialogResult.OK == dlgWebPage.ShowDialog()) { string url = box.Text; if (url.Length > 0) { // used to build entire input StringBuilder sb = new StringBuilder(); // used on each read operation byte[] buf = new byte[8192]; try { HttpWebRequest webpage = (HttpWebRequest)WebRequest.Create(url); HttpWebResponse resp = (HttpWebResponse)webpage.GetResponse(); Stream resStream = resp.GetResponseStream(); string tempString = null; int count = 0; do { // fill the buffer with data count = resStream.Read(buf, 0, buf.Length); // make sure we read some data if (count != 0) { // translate from bytes to ASCII text tempString = Encoding.ASCII.GetString(buf, 0, count); // continue building the string sb.Append(tempString); } } while (count > 0); // any more data to read? } catch ( System.Net.WebException ex) { MessageBox.Show("Couldn't open " + url, ex.ToString(), MessageBoxButtons.OK, MessageBoxIcon.Exclamation); return; } // Dispose of the old data set, if any if (numbersArray.Count > 0) { numbersArray.Clear(); numbersArrayMax = 0.0F; numbersArrayMaxCount = 0; legendStrings.Clear(); } string strLine = sb.ToString(); // Okay, time to break this into a series of lines, and feed them into the // Process1stCSVLine and ProcessCSVLine functions, as though this is a local // file. char[] charArray = new char[] { '\n' }; string[] strArray; if (strLine != null) { // Get rid of any \r characters that might be sitting in here. strLine = strLine.Replace("\r", ""); strArray = strLine.Split(charArray); strLine = strArray[0]; // Okay, feed in the first line. Process1stCSVLine(strLine); // Now process the rest of the lines, until we run out int dataRowNbr = 0; do { strLine = strArray[dataRowNbr]; if ((strArray[dataRowNbr + 1] != null) && (strArray[dataRowNbr + 1].Length > 0)) ProcessCSVLine(strArray[dataRowNbr + 1], dataRowNbr++); } while (strArray[dataRowNbr + 1] != null && strArray[dataRowNbr + 1].Length > 0); } } this.Invalidate(); } } // Open the data file and populate legend and numbersArray. private void openToolStripMenuItem_Click(object sender, EventArgs e) { OpenFileDialog openFileDialog1 = new OpenFileDialog(); openFileDialog1.Title = "Data Input File (CSV)"; openFileDialog1.DefaultExt = "*.csv"; openFileDialog1.Filter = "CSV files|*.csv"; if (openFileDialog1.ShowDialog() == DialogResult.OK) { // This is where we specify the CSV file to open. Stream myStream = new FileStream(openFileDialog1.FileName, FileMode.Open); if (myStream != null) { // Dispose of the old data set, if any if (numbersArray.Count > 0) { numbersArray.Clear(); numbersArrayMax = 0.0F; numbersArrayMaxCount = 0; legendStrings.Clear(); } StreamReader sr = new StreamReader(myStream); string strLine; // Pull in the first line--the column header line. strLine = sr.ReadLine(); // Process first line of this stream. Process1stCSVLine(strLine); // Now process the rest of the lines, until we run out int dataRowNbr = 0; do { strLine = sr.ReadLine(); if (strLine != null) ProcessCSVLine(strLine, dataRowNbr++); } while (strLine != null); // All done, close the stream myStream.Close(); } this.Invalidate(); } } void Process1stCSVLine (string strLine) { string[] strArray; char[] charArray = new char[] { ',' }; if (strLine != null) { strArray = strLine.Split(charArray); colHdr.colHdrStrings = new List (); for (int i = 2; i < strArray.Length; i++) colHdr.colHdrStrings.Add(strArray[i]); } } void ProcessCSVLine (string strLine, int dataRowNbr) { string[] strArray; char[] charArray = new char[] { ',' }; // Now pull in the data that we are going to plot. if (strLine != null) { System.Console.WriteLine("reading " + strLine); strArray = strLine.Split(charArray); NumbersToPlot numbers = new NumbersToPlot(); // Add the legend (the first column) to this list. legendStrings.Add(strArray[0]); listToShow.Add(dataRowNbr); numbers.category = strArray[1]; numbers.events = new List (); // We need to keep track of the smallest and largest values we see. numbers.min = numbers.max = 0; int lastNumber; for (int i = 2; i < strArray.Length; i++) { numbers.events.Add(float.Parse(strArray[i])); lastNumber = numbers.events.Count - 1; numbers.min = Math.Min(numbers.min, numbers.events[lastNumber]); numbers.max = Math.Max(numbers.max, numbers.events[lastNumber]); } numbersArray.Add(numbers); numbersArrayMax = Math.Max(numbersArrayMax, numbers.max); numbersArrayMaxCount = Math.Max(numbersArrayMaxCount, numbers.events.Count); } } // Configure which lines in the data set to display. private void configureToolStripMenuItem_Click(object sender, EventArgs e) { SizeF sizef; // This is where we specify which lines to plot. configDlg = new Form(); sizef = configDlg.ClientSize; // Add a ListBox ListBox box = new ListBox(); box.SelectionMode = SelectionMode.MultiExtended; box.DoubleClick += new EventHandler(this.listBoxDoubleClick); box.Size = new System.Drawing.Size(configDlg.ClientSize.Width, configDlg.ClientSize.Height); for (int i = 0; i < legendStrings.Count; i++) { box.Items.Add(legendStrings[i]); box.SetSelected(i, true); } configDlg.Controls.Add(box); box.Location = new Point(0, 0); // Add a button to the panel. Button bOk = new System.Windows.Forms.Button(); bOk.Text = "OK"; bOk.Location = new Point(0, box.Size.Height); bOk.DialogResult = DialogResult.OK; configDlg.Controls.Add(bOk); Button bCancel = new System.Windows.Forms.Button(); bCancel.Text = "Cancel"; bCancel.Location = new Point(bOk.Size.Width + 10, box.Size.Height); bCancel.DialogResult = DialogResult.Cancel; this.AcceptButton = bOk; configDlg.Controls.Add(bCancel); configDlg.ClientSize = new System.Drawing.Size(box.Width, bCancel.Location.Y + bCancel.Height); if (configDlg.ShowDialog(this) == DialogResult.OK) { System.Console.WriteLine("OK"); // Grab the selection list from the list box. ListBox.SelectedIndexCollection selectedItems = box.SelectedIndices; // Now go through and convert this into a list of ints. listToShow.Clear(); for (int i = 0; i < selectedItems.Count; i++) listToShow.Add(selectedItems[i]); // Recalculate the maximum value that we have to plot. numbersArrayMax = RecalculateMaxValue(); this.Invalidate(); } } private void listBoxDoubleClick(Object s, EventArgs e) { this.configDlg.DialogResult = DialogResult.OK; this.configDlg.Close(); } public SizeF CalcLeftMargin(Graphics g, Font legendFont, List legendStrings) { // Figure out the width of the legends on the left. These are the // first column of information in the CSV file. SizeF maxLegend = new SizeF(); int i; SizeF legendStrSize; for (maxLegend.Width = maxLegend.Height = 0, i = 0; i < numbersArray.Count; i++) { legendStrSize = g.MeasureString(legendStrings[i], legendFont); maxLegend.Width = Math.Max(maxLegend.Width, legendStrSize.Width); maxLegend.Height = Math.Max(maxLegend.Height, legendStrSize.Height); } maxLegend.Width *= 1.10F; return (maxLegend); } public float CalcTopMargin(Graphics g, Font colHdrFont, ColHdr colHdrStrings) { // Now figure out how much room we need at the top for the column headers. // Complicating this is that we need to adjust font size (perhaps) for too many // columns. float maxColHdrWidth = 0.0F; float maxColHdrHeight = 0.0F; SizeF colHdrStrSize; for (int i = 0; i < colHdr.colHdrStrings.Count; i++) { colHdrStrSize = g.MeasureString(colHdr.colHdrStrings[i], colHdrFont); maxColHdrWidth = Math.Max(maxColHdrWidth, colHdrStrSize.Width); maxColHdrHeight = Math.Max(maxColHdrHeight, colHdrStrSize.Height); } return (maxColHdrHeight); } public float CalcRightMargin(Graphics g, Font colHdrFont, ColHdr colHdr) { // We have to leave a little room on the right side of the last column because // we are centering the column headers under the center point for the lines. // So we need to know how much room that will be for the last column header. SizeF colHdrStrSize; colHdrStrSize = g.MeasureString(colHdr.colHdrStrings[colHdr.colHdrStrings.Count-1], colHdrFont); float rightMargin = (colHdrStrSize.Width / 2.0F) * 1.25F; return (rightMargin); } public void PlotLegends(Graphics g, int menuBarHeight, Font legendsFont, List legendStrings, float maxLegendsHeight, List listToShow) { // Now we know how big it is, we can draw the legend strings down the left side. Brush myBrush; for (int i = 0, legendLineIndex = 0; i < numbersArray.Count; i++) { if (listToShow.Contains(i)) { myBrush = new SolidBrush(colorChoices[legendLineIndex % 10]); g.DrawString(legendStrings[i], legendsFont, myBrush, 0, (maxLegendsHeight * legendLineIndex) + menuBarHeight); legendLineIndex++; } } } public void PlotColHdrs(Graphics g, int menuBarHeight, Font colHdrFont, ColHdr colHdr, float rightMargin, float leftMargin, float xScaling, float plotWidth) { SizeF colHdrStrSize; Brush myBrush = new SolidBrush(Color.White); float lastColHdrEndsAt = 0.0F; // not correct, but the first header must appear float colHdrStartsAt = 0.0F; for (int i = 0; i < colHdr.colHdrStrings.Count; i++) { // Get the size of the string so that we can move it over to be centered. colHdrStrSize = g.MeasureString(colHdr.colHdrStrings[i], colHdrFont); colHdrStartsAt = (i * xScaling) + leftMargin - colHdrStrSize.Width; // We don't want to step on the last column header. if (colHdrStartsAt > lastColHdrEndsAt + 5.0F) { g.DrawString(colHdr.colHdrStrings[i], colHdrFont, myBrush, (i * xScaling) + leftMargin - (colHdrStrSize.Width / 2), menuBarHeight); lastColHdrEndsAt = (i * xScaling) + leftMargin + (colHdrStrSize.Width / 2); } } } public void PlotLines(Graphics g, int menuBarHeight, float leftMargin, float topMargin, float bottomMargin, float rightMargin, float xScaling, ColHdr colHdr, float numbersArrayMax, int divisions, float yScaling, Font font) { Pen pen = new Pen(Color.White, 2); // Draw the left, right, top and bottom lines. g.DrawLine(pen, leftMargin, topMargin + menuBarHeight, leftMargin, bottomMargin + menuBarHeight); g.DrawLine(pen, rightMargin, topMargin + menuBarHeight, rightMargin, bottomMargin + menuBarHeight); g.DrawLine(pen, leftMargin, topMargin + menuBarHeight, rightMargin, topMargin + menuBarHeight); g.DrawLine(pen, leftMargin, bottomMargin + menuBarHeight, rightMargin, bottomMargin + menuBarHeight); // Draw the vertical grid lines. int xCoord = 0; for (int i = 0; i < colHdr.colHdrStrings.Count; i++) { xCoord = (int)((float)i * xScaling + leftMargin); g.DrawLine(pen, xCoord, topMargin + menuBarHeight, xCoord, bottomMargin + menuBarHeight); } // The brush for drawing the numbers left of the grid lines. Brush myBrush = new SolidBrush(Color.White); // Draw the horizontal grid lines. float perDivision = (yScaling * numbersArrayMax) / divisions; float perDivisionNumber = 0.0F; for (int i = 0; i < divisions + 1; i++) { g.DrawLine(pen, leftMargin, bottomMargin - (i * perDivision) + menuBarHeight, rightMargin, bottomMargin - (i * perDivision) + menuBarHeight); perDivisionNumber = i * numbersArrayMax / divisions; // Figure out how much we have to move the string to the left of the vertical grid // line, and how much we have to raise it up to center on the horizontal grid line. SizeF strSize = g.MeasureString(perDivisionNumber.ToString(), font); g.DrawString(perDivisionNumber.ToString(), font, myBrush, leftMargin - strSize.Width, (bottomMargin - (i * perDivision)) - strSize.Height / 2 + menuBarHeight); } } public void PlotData(Graphics g, int menuBarHeight, NumbersToPlot numbers, Pen pen, float leftMargin, float xScaling, float yScaling, float panelHeight) { for (int j = 1; j < numbers.events.Count; j++) { float value = (float)numbers.events[j]; float prevValue = (float)numbers.events[j - 1]; Point prevValueCoord = new Point((int)((j - 1) * xScaling + leftMargin), (int)(panelHeight - (prevValue * yScaling) + menuBarHeight)); Point curValueCoord = new Point((int)(j * xScaling + leftMargin), (int)(panelHeight - (value * yScaling) + menuBarHeight)); g.DrawLine(pen, prevValueCoord, curValueCoord); } } protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); Graphics g = e.Graphics; if (numbersArray.Count > 0) { // Clear the panel. g.Clear(Color.Black); // Create legend font. Font legendFont = new System.Drawing.Font("Times Roman", 10.0F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); // Calculate left margin from legend strings. SizeF maxLegendDimensions = CalcLeftMargin(g, legendFont, legendStrings); // Create column header font. Font colHdrFont = new System.Drawing.Font("Times Roman", 10.0F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); // Calculate top margin from column headers. float topMargin = CalcTopMargin(g, colHdrFont, colHdr) + 20.0F; // Calculate right margin from column headers. float rightMargin = CalcRightMargin(g, colHdrFont, colHdr); PlotLegends(g, menuBarHeight, legendFont, legendStrings, maxLegendDimensions.Height, listToShow); g.PageUnit = GraphicsUnit.Pixel; // Calculate the scaling required. float yScaling; SizeF sizef = g.VisibleClipBounds.Size; rightMargin = sizef.Width - rightMargin; float leftMargin = maxLegendDimensions.Width; // Leave some room at the bottom of the display. Using 2*menuBarHeight is // kind of cheating; it is actually menuBarHeight + half of the height of the // numbers going down the left side of the grid lines. float bottomMargin = sizef.Height - 2 * menuBarHeight; // Before we do the y-scaling, we want to round the maximum y-axis value up to // a nice even number. numbersArrayMax = (float)RoundUp(numbersArrayMax); yScaling = (float)((bottomMargin - topMargin) / numbersArrayMax); float xScaling = (float)(rightMargin - leftMargin) / (numbersArrayMaxCount - 1); // Write the column header strings. PlotColHdrs(g, menuBarHeight, colHdrFont, colHdr, rightMargin, leftMargin, xScaling, rightMargin - leftMargin); // Draw the grid lines. PlotLines(g, menuBarHeight, leftMargin, topMargin, bottomMargin, rightMargin, xScaling, colHdr, numbersArrayMax, 4, yScaling, colHdrFont); for (int i = 0, legendLineIndex = 0; i < numbersArray.Count; i++) { if (listToShow.Contains(i)) { Pen pen = new Pen(colorChoices[legendLineIndex % 10], 2); PlotData(g, menuBarHeight, numbersArray[i], pen, maxLegendDimensions.Width, xScaling, yScaling, bottomMargin); legendLineIndex++; } } } else g.Clear(Color.Black); // panel1.Invalidate(); } public static double RoundUp(double valueToRound) { return (Math.Floor(valueToRound + 0.5)); } private void exitToolStripMenuItem_Click(object sender, EventArgs e) { Application.Exit(); } private void Form1_ResizeEnd(object sender, EventArgs e) { // See what the new size is, so that we can resize the panel as well. formSize = this.ClientSize; this.Size = new System.Drawing.Size((int)(formSize.Width - 50), (int)(formSize.Height - 50)); menuBarHeight = this.menuStrip1.Location.Y + this.menuStrip1.Size.Height; this.Invalidate(); } } } public class ColHdr { public List colHdrStrings; } public class NumbersToPlot { public string category; public List events; public float min, max; public int maxNumbers; public NumbersToPlot() { } public NumbersToPlot(string Category) { category = Category; } };