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 List colorChoices = 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;
}
};