spreadsheet : William Wakes TDD spreadsheet

Download spreadsheet.zip

Synopsis:

Formula_UTest.cs
References_UTest.cs
SheetTableModel_UTest.cs
SpreadSheet_UTest.cs
NonFormula_UTest.cs
BadFormula.cs
Calculator.cs
Cell.cs
CellMatrix.cs
CircularReference.cs
Form1.cs
InputString.cs
ListSelectionModel.cs
Operand.cs
OperandStack.cs
Operator.cs
OperatorStack.cs
Sheet.cs
SheetFrame.cs
SheetTableModel.cs
Table.cs
TableModel.cs


Formula_UTest.cs

Synopsis
using System;
using ut;

public class testFormula
{
  // Next - start on parsing expressions
  public void testConstantFormula() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=7");
    utx.assert("Formula", "=7", sheet.getLiteral("A1"));
    utx.assert("Value", "7", sheet.get("A1"));
  }

  // More formula tests. You may feel the need to make up 
  // additional intermediate test cases to drive your code
  // better. (For example, you might want to test "2*3" 
  // before "2*3*4".) That's fine, go ahead and create them.
  // Just keep moving one test at a time.

  // Order of tests - I'm familiar enough with parsing to think
  // it's probably better to do them in this order (highest
  // precedence to lowest). For extra credit, you might redo 
  // this part of the exercise with the tests in a different order 
  // to see what difference it makes.

  public void testParentheses() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=(7)");
    utx.assert("Parens", "7", sheet.get("A1"));
  }

  public void testDeepParentheses() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=((((10))))");
    utx.assert("Parends", "10", sheet.get("A1"));
  }

  public void testMultiply() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=2*3*4");
    utx.assert("Times", "24", sheet.get("A1"));
  }

  public void testAdd() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=71+2+3");
    utx.assert("Add", "76", sheet.get("A1"));
  }

  public void testPrecedenceA() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=2*3+5");
    utx.assert("Precedence", "11", sheet.get("A1"));
  }

  public void testPrecedenceB() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=2*(3+5)");
    utx.assert("Precedence", "16", sheet.get("A1"));
  }

  public void testPrecedenceC() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=2+3*5");
    utx.assert("Precedence", "17", sheet.get("A1"));
  }

  public void testPrecedenceD() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=(2+3)*5");
    utx.assert("Precedence", "25", sheet.get("A1"));
  }

  public void testPrecedence1() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=2*3+7");
    utx.assert("Precedence", "13", sheet.get("A1"));
  }

  public void testPrecedence2() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=7+2*3");
    utx.assert("Precedence", "13", sheet.get("A1"));
  }

  public void testPrecedence3() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=7*(2+3)");
    utx.assert("Expr", "35", sheet.get("A1"));
  }

  public void testPrecedence4() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=7*(2+3*4)");
    utx.assert("Expr", (7*(2+3*4)).ToString(), sheet.get("A1"));
  }

  public void testPrecedence5() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=7*((2+3)*4)");
    utx.assert("Expr", "140", sheet.get("A1"));
  }

  public void testPrecedence6() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=(7*2)+3");
    utx.assert("Expr", "17", sheet.get("A1"));
  }
  
  public void testFullExpression1() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=((((2+1))))");
    utx.assert("Expr", "3", sheet.get("A1"));
  }

  public void testFullExpression2() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=(2+3)*((((2+1))))");
    utx.assert("Expr", "15", sheet.get("A1"));
  }

  public void testFullExpression() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=7*(2+3)*((((2+1))))");
    utx.assert("Expr", "105", sheet.get("A1"));
  }

  //  // Then try your hand at a few test cases: Add "-" and "/"
  //  // with normal precedence. 
  public void testPrecedenceSD1() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=12/3-1");
    utx.assert("Precedence", "3", sheet.get("A1"));
  }

  public void testPrecedenceSD2() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=6-12/3");
    utx.assert("Precedence", "2", sheet.get("A1"));
  }

  public void testPrecedenceSD3() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=12/(3-1)");
    utx.assert("Expr", "6", sheet.get("A1"));
  }

  public void testPrecedenceSD4() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=(12-6)/3");
    utx.assert("Expr", "2", sheet.get("A1"));
  }

  public void testFullExpression3() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=8*(2+3)/((2+1-1))");
    utx.assert("Expr", (8*(2+3)/((2+1-1))).ToString(), sheet.get("A1"));
  }

  // Next, error handling.

  public void testSimpleFormulaError() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=7*");
    utx.assert("Error", "#Error", sheet.get("A1"));
  }

  public void testParenthesisError1() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=((7))))");
    utx.assert("Error", "#Error", sheet.get("A1"));  
  }

  public void testParenthesisError() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=(((((7))");
    utx.assert("Error", "#Error", sheet.get("A1"));  
  }

  // Add any more error cases you need. Numeric errors (e.g.,
  // divide by 0) can return #Error too.
  public void testMissingOperand() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=+");
    utx.assert("Error", "#Error", sheet.get("A1"));  
  }

  public void testMissingOperand2() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=5+");
    utx.assert("Error", "#Error", sheet.get("A1"));  
  }

  public void testMissingOperand3() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=+5+");
    utx.assert("Error", "#Error", sheet.get("A1"));  
  }

  public void testExtraOperand1() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=5+++5");
    utx.assert("Error", "#Error", sheet.get("A1"));  
  }

  public void testMiscError1() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=5+(");
    utx.assert("Error", "#Error", sheet.get("A1"));  
  }

  public void testMiscError2() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=(");
    utx.assert("Error", "#Error", sheet.get("A1"));  
  }

  public void testMiscError3() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=)");
    utx.assert("Error", "#Error", sheet.get("A1"));  
  }

  public void testMiscError4() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=7G");
    utx.assert("Error", "#Error", sheet.get("A1"));  
  }

  public void testMiscError5() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=7+)");
    utx.assert("Error", "#Error", sheet.get("A1"));  
  }

  public void testMiscError6() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=++");
    utx.assert("Error", "#Error", sheet.get("A1"));  
  }

  public void testMiscError7() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=5 7");
    utx.assert("Error", "#Error", sheet.get("A1"));  
  }

  public void testMiscError8() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=5(7");
    utx.assert("Error", "#Error", sheet.get("A1"));  
  }

  public void testMiscError9() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=5 (7");
    utx.assert("Error", "#Error", sheet.get("A1"));  
  }

  public void testMiscError10() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=7=");
    utx.assert("Error", "#Error", sheet.get("A1"));  
  }

  public void testMiscError11() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=7!");
    utx.assert("Error", "#Error", sheet.get("A1"));  
  }

  public void testDivision() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=5/2");
    utx.assert("integer dic", "2", sheet.get("A1"));  
  }

  public void testDivisionByZero() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=5/0");
    utx.assert("div by 0", "#Error", sheet.get("A1"));  
  }

  public void testBigNumber1() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=" + Int32.MaxValue.ToString());
    utx.assert("big", "2147483647", sheet.get("A1"));  
  }

  public void testBigNumber1A() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=" + Int32.MaxValue.ToString() + "+0");
    utx.assert("big", "2147483647", sheet.get("A1"));  
  }

  public void testBigNumber1B() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=" + Int32.MaxValue.ToString() + "+1");
    utx.assert("big", "#Error", sheet.get("A1"));  
  }

  public void testBigNumber2() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=2147483648");
    utx.assert("big", "#Error", sheet.get("A1"));  
  }

  public void testBigNumber2A() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=2147483648+0");
    utx.assert("big", "#Error", sheet.get("A1"));  
  }

  public void testBigNumber3() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=2147483649");
    utx.assert("big", "#Error", sheet.get("A1"));  
  }

  public void testBigNumber3A() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=2147483649+0");
    utx.assert("big", "#Error", sheet.get("A1"));  
  }

  public void testOverflow1() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=2147483646+1");
    utx.assert("overflow", "2147483647", sheet.get("A1"));  
  }

  public void testOverflow2() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=2147483647+0");
    utx.assert("overflow", "2147483647", sheet.get("A1"));  
  }

  public void testOverflow3() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=2147483647+1");
    utx.assert("overflow", "#Error", sheet.get("A1"));  
  }

  public void testOverflow4() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=2147483647+2");
    utx.assert("overflow", "#Error", sheet.get("A1"));  
  }

  public void testUnderflow1() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=0-2147483647-0");
    utx.assert("underflow", "-2147483647", sheet.get("A1"));  
  }

  public void testUnderflow2() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=0-2147483647-1");
    utx.assert("underflow", "-2147483648", sheet.get("A1"));  
  }

  public void testUnderflow3() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=0" + (Int32.MinValue + 1).ToString() + "-1");
    utx.assert("underflow", "-2147483648", sheet.get("A1"));  
  }

  public void testUnderflow4() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=0-2147483648-1");
    utx.assert("underflow", "#Error", sheet.get("A1"));  
  }

  public void testUnderflow5() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=0" + Int32.MinValue.ToString() + "-1");
    utx.assert("underflow", "#Error", sheet.get("A1"));  
  }

  public void testUnderflow6() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=" + Int32.MinValue.ToString() + "-2");
    utx.assert("underflow", "#Error", sheet.get("A1"));  
  }

  public void testEmbeddedBlanks1() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=5+ 3");
    utx.assert("blanks", "8", sheet.get("A1"));  
  }

  public void testEmbeddedBlanks2() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=5+ 3 ");
    utx.assert("blanks", "8", sheet.get("A1"));  
  }

  public void testEmbeddedBlanks3() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=5 + 3 ");
    utx.assert("blanks", "8", sheet.get("A1"));  
  }

  public void testEmbeddedBlanks4() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "= 5 + 3 ");
    utx.assert("blanks", "8", sheet.get("A1"));  
  }

  public void testLeadingBlanks() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=   5");
    utx.assert("blanks", "5", sheet.get("A1"));  
  }

  public void testTrailingBlanks() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=5   ");
    utx.assert("blanks", "5", sheet.get("A1"));  
  }
}

References_UTest.cs

Synopsis
using System;
using ut;

public class testReferences
{
  //We're going to add dependencies now. This is one of those things that 
  //makes a spreadsheet a spreadsheet. 
  public void testThatCellReferenceWorks () 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "8");
    sheet.put("A2", "=A1");
    utx.assert("cell lookup", "8", sheet.get("A2"));
  }

  public void testThatCellChangesPropagate () 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "8");
    sheet.put("A2", "=A1");
    utx.assert("cell lookup", "8", sheet.get("A2"));

    sheet.put("A1", "9");
    utx.assert("cell change propagation", "9", sheet.get("A2"));
  }

  public void testThatFormulasKnowCellsAndRecalculate () 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "8");
    sheet.put("A2", "3");
    sheet.put("B1", "=A1*(A1-A2)+A2/3");
    utx.assert("calculation with cells", "41", sheet.get("B1"));

    sheet.put("A2", "6");
    utx.assert("re-calculation", "18", sheet.get("B1"));
  }

  public void testThatDeepPropagationWorks () 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "8");
    sheet.put("A2", "=A1");
    sheet.put("A3", "=A2");
    sheet.put("A4", "=A3");
    utx.assert("deep propagation", "8", sheet.get("A4"));

    sheet.put("A2", "6");
    utx.assert("deep re-calculation", "6", sheet.get("A4"));
  }

  // The following test is likely to pass already.
  public void testThatFormulaWorksWithManyCells () 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "10");
    sheet.put("A2", "=A1+B1");
    sheet.put("A3", "=A2+B2");
    sheet.put("A4", "=A3");
    sheet.put("B1", "7");
    sheet.put("B2", "=A2");
    sheet.put("B3", "=A3-A2");
    sheet.put("B4", "=A4+B3");

    utx.assert("multiple expressions - A4", "34", sheet.get("A4"));
    utx.assert("multiple expressions - B4", "51", sheet.get("B4"));
  }

  // Just like errors return a special value, it might be nice
  // if circular references did too. (See notes below).
  public void testThatCircularReferencesAdmitIt () 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=A1");
    utx.assert("Detect circularity", "#Circular", sheet.get("A1"));
  }

  public void testCircularReference() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=A2");
    sheet.put("A2", "=A3");
    sheet.put("A3", "=A1");
    utx.assert("Detect circularity", "#Circular", sheet.get("A1"));
  }
    
  public void testMultiReference() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=A2+A3");
    sheet.put("A2", "=A3+A3");
    sheet.put("A3", "=5");
    utx.assert("multiple references", "15", sheet.get("A1"));
  }

  public void testCircRef2()
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "=A2");
    sheet.put("A2", "=A1");
    utx.assert("#Circular", sheet.get("A2"));
  }

}

SheetTableModel_UTest.cs

Synopsis
using System;
using ut;

public class testSheetTableModel
{
  Sheet sheet;
  TableModel table;
  int LAST_COLUMN_INDEX = 49;
  int LAST_ROW_INDEX = 99;
  
  public void setup() 
  {
    sheet = new Sheet();
    table = new SheetTableModel(sheet);        
  }
  
  public void testTableModelRequiredOverrides() 
  {
    setup();
    utx.assert(table.getColumnCount() > LAST_COLUMN_INDEX);
    utx.assert(table.getRowCount() > LAST_ROW_INDEX);
    utx.assert("", table.getValueAt(10,10));
  }
  
  public void testColumnNames() 
  {
    setup();
    utx.assert ("", TableModel.getColumnName(0));
    utx.assert ("A", TableModel.getColumnName(1));
    utx.assert ("Z", TableModel.getColumnName(26));
    utx.assert ("AA", TableModel.getColumnName(27));
    utx.assert ("AW", TableModel.getColumnName(LAST_COLUMN_INDEX));
  }
  
  public void testThatColumn0ContainsIndex() 
  {
    setup();
    utx.assert ("1", table.getValueAt(0,0));
    utx.assert ("50", table.getValueAt(49, 0));
    utx.assert ("100", table.getValueAt(LAST_ROW_INDEX,0));
  }
  
  public void testThatMainColumnsHaveContents1() 
  {
    setup();
    sheet.put ("A1", "upper left");
    utx.assert ("upper left", table.getValueAt(0,1));
  }

  public void testThatMainColumnsHaveContents2() 
  {
    setup();
    sheet.put ("A100", "lower left");
    utx.assert ("lower left", table.getValueAt(LAST_ROW_INDEX, 1));
  }

  public void testThatMainColumnsHaveContents3() 
  {
    setup();
    sheet.put ("AW1", "upper right");
    utx.assert ("upper right", table.getValueAt(0, LAST_COLUMN_INDEX));
  }

  public void testThatMainColumnsHaveContents4() 
  {
    setup();
    sheet.put ("AW100", "lower right");
    utx.assert ("lower right", table.getValueAt(LAST_ROW_INDEX, LAST_COLUMN_INDEX));
  }
  
  public void testThatStoresWorkThroughTableModel() 
  {
    setup();
    table.setValueAt("21", 0, 1);
    table.setValueAt("=A1", 1, 1);
  
    utx.assert("21", table.getValueAt(0,1));
    utx.assert("21", table.getValueAt(1,1));
  
    table.setValueAt("22", 0, 1);
    utx.assert("22", table.getValueAt(0,1));
    utx.assert("22", table.getValueAt(1,1));
  }
 
  // We've established that the table model can get and set values.
  // But JTable uses an event notification mechanism to find out
  // about the changes.
  
  // To test this, we'll introduce a test helper class. It's a very
  // simple listener, and will assure us that notifications are
  // sent when changes are made.
  
  // There's a couple of design decisions implicit here. One is that
  // we won't attempt to be specific about which cells change; we'll
  // just say that the table data has changed and let JTable refresh
  // its view of whichever cells it wants. (Because of cell 
  // dependencies, changes in one cell could potentially no others, 
  // all others, or anything in between.) We might revisit this 
  // decision during performance tuning, and try to issue 
  // finer-grained notifications.
  
  // The other decision is that we have no mechanism for our Sheet
  // to tell the table model about changes. So changes will either 
  // need to come in through the table model, or we'll have to 
  // add some notification mechanism to Sheet. For now, just 
  // make changes through the table model.
  
  public class TestTableModelListener: TableModelListener 
  {
    public bool wasNotified = false;
          
    public override void TableChanged(TableModelEvent e) 
    {
      wasNotified = true;
    }
  }
      
  public void testThatTableModelNotifies() 
  {
    setup();
    TestTableModelListener listener = new TestTableModelListener();
    table.addTableModelListener (listener);
    utx.assert (!listener.wasNotified);
  
    table.setValueAt("22", 0, 1);
    utx.assert (listener.wasNotified);
  }
  
  // Note the cast in our test here. Previous tests have been 
  // straight implementations of TableModel functions; now 
  // we're saying that our model has some extra functions. We'll 
  // face a small tradeoff later when we want access to the feature: 
  // if we get the model back from JTable, we'll have to cast it; 
  // if we don't want to cast it we'll have to track it somewhere.
  
  public void testThatSheetTableModelCanGetLiteral() 
  {
    setup();
    sheet.put("A1", "=7");
    String contents = ((SheetTableModel)table).getLiteralValueAt(0, 1);
  
    utx.assert("=7", contents);
  }
}

SpreadSheet_UTest.cs

Synopsis
using System;
using System.Windows.Forms;
using ut;

//--------------------------
class testSpreadSheet
{
  //--------------------------
  static void Main(string[] args)
  {
    Application.Run(new SheetFrame(new SheetTableModel(new Sheet())));
  }

  // One of the first XPlorations articles I wrote was to address
  // the question, "Can you do a GUI test-first?" My answer there
  // was, "Yes, but..." and this exercise might lead you to the
  // same opinion.
  //
  //  Today's goal is to make a simple GUI that looks sort of like this:
  //
  //  +--------+---------------+------+
  //  | Label  |  Text entry   |  OK  |
  //  +--------+---------------+------+
  //  |^                              |
  //  ||        Grid (JTable)         |
  //  |v                              |
  //  +-------------------------------+
  //
  //
  //  The idea is that if you click in the grid, the label tells
  //  which cell you're in, and the text entry field contains the
  //  literal value of that cell. You can edit it, and when you click
  //  "OK", it puts the value back in the cell. Then the whole
  //  spreadsheet updates accordingly. With this, you should have
  //  a minimalist but working spreadsheet.
  //
  //  Quick Design
  //  I'll put everything in a subclass of JFrame, using the Swing
  //  objects JLabel, JTextField, JButton, and JTable. In my frame,
  //  I'll have public variables for each of these components
  //  (exposed for testing purposes). (I won't bother exposing
  //  intermediate panels or other things not important outside
  //  the class.)
  //
  //  Most of the problem will be setup and hookup. I'll want to hook
  //  the table to the model, and set up listeners so that the
  //  label and textfield know when the selection has changed.
  //  I'll need a listener on the ok button to tell when to update
  //  the grid with the edited value. And all these objects have
  //  various configuration options to set up as well.
  //                                                                                      Challenge Tests
  //@@@@@@@@@@@@@@@@@@@@@@@@@ YOU ARE HERE
 
  Sheet sheet;
  TableModel table;
  SheetTableModel model; // New for part 5
  SheetFrame frame; // New for part 5

  public void setUp() 
  {
    sheet = new Sheet();
    table = new SheetTableModel (sheet);

    model = new SheetTableModel(sheet);
    frame = new SheetFrame(model);
  }

  public void testThatFrameHasRightParts () 
  {
    setUp();
    utx.assertnot(frame.table, null);
    utx.assertnot(frame.label, null);
    utx.assertnot(frame.editor, null);
    utx.assertnot(frame.okButton, null);
    utx.assert(model, frame.table.getModel());
  }

  public void testThatRowAndColumnSelectionAllowed() 
  {
    setUp();
    utx.assert(frame.table.getRowSelectionAllowed());
    utx.assert(frame.table.getColumnSelectionAllowed());
  }

  public class TestSelectionListener : ListSelectionListener 
  {
    public bool wasNotified = false;
    public ListSelectionEvent lastEvent;
  
    public void ValueChanged(ListSelectionEvent e) 
    {
      wasNotified = true;
      lastEvent = e;
    }
  }
   
  public void testThatSelectionsNotifyListeners() 
  {
    setUp();
    TestSelectionListener listener = new TestSelectionListener();
    frame.table.getSelectionModel().addListSelectionListener(listener);
   
    utx.assert(!listener.wasNotified);
   
    frame.table.changeSelection (3, 2, false, false);
    utx.assert(listener.wasNotified);
    utx.assert(3, listener.lastEvent.Row);
    utx.assert(2, listener.lastEvent.Column);
   
    listener.wasNotified = false;
    frame.table.changeSelection (1, 1, false, false);
    utx.assert (listener.wasNotified);
    utx.assert(1, listener.lastEvent.Row);
    utx.assert(1, listener.lastEvent.Column);
  }
   
  // If you need info on hooking up a selection listener, see
  //  http://java.sun.com/docs/books/tutorial/uiswing/components/table.html#selection
  public void testThatLabelIsUpdatedWhenSelectionChanges() 
  {
    setUp();
    utx.assert("", frame.label.Text);
   
    frame.table.changeSelection (0, 1, false, false);
    utx.assert("A1", frame.label.Text);
   
    frame.table.changeSelection (10, 10, false, false);
    utx.assert("J11", frame.label.Text);
  }
   
  public void testThatEditorSeesLiteralValue() 
  {
    model.setValueAt("=7", 1, 1);
    frame.table.changeSelection(1,1,false,false);
    utx.assert("=7", frame.editor.Text);
  }
   
  // We would like to have a way to programmatically let the
  // text field click "Enter", but I don't see a mechanism.
  // So we'll use the okButton instead.
   
  public void testThatEditedValueGetsSaved() 
  {
    setUp();
    model.setValueAt("=7", 1, 1);
    frame.table.changeSelection(1,1,false,false);
   
    frame.editor.Text = "=8";
    frame.okButton.doClick();
    utx.assert("=8", frame.model.getLiteralValueAt(1,1));
    utx.assert("8", frame.model.getValueAt(1,1));
  }
   
   
  public void testThatValuePropagationWorks () 
  {
    setUp();
    frame.model.setValueAt("7", 0,1);
    frame.model.setValueAt("=A1+2", 2,2);
    utx.assert("9", frame.model.getValueAt(2,2));
    utx.assert("=A1+2", frame.model.getLiteralValueAt(2,2));
   
    frame.model.setValueAt("10", 0,1);
    utx.assert("12", frame.model.getValueAt(2,2));
  }
   
   
  // See discussion below on acceptance tests.
  public void testAcceptanceTest1() 
  {
    setUp();
    SheetTableModel model;
    SheetFrame frame;
   
    model = new SheetTableModel(new Sheet());
    frame = new SheetFrame(model);
   
    frame.table.changeSelection(0,1,false,false);   // A1
    frame.editor.Text = "8";
    frame.okButton.doClick();
   
    frame.table.changeSelection(1,1,false,false);   // A2
    frame.editor.Text = "=A1*A1+A1";
    frame.okButton.doClick();
   
    utx.assert("72", frame.model.getValueAt(1,1));
   
    frame.table.changeSelection(0,1,false,false);   // A1
    frame.editor.Text ="5";
    frame.okButton.doClick();
   
    utx.assert("30", frame.model.getValueAt(1,1));
  }

  //DISCUSSION
  //What I've found is that GUI testing works "ok" for some
  // things but there tends not to be an easy way to do
  // other things. For example, I didn't see an easy way to
  // programmatically click "Enter" in the edited box. (I know
  // you can set up a Robot and fool around with events,
  // but I wasn't willing to try that hard today.) Another example
  // is the difficulty of dealing with pop-up menus, tooltips,
  // dialogs, etc.
  //
  // The other problem I have is that I just don't think of everything
  // important without actually seeing the GUI grow. So even if I use test-
  // first, I'll mix it with running the application and seeing how it
  // looks. (For example, we didn't address column widths, or seriously
  // look at what happens when you select multiple cells, and my solution
  // doesn't leave the grid element selected after an edit.)
  //
  // I try to keep the GUI work limited to direct setup and hookup of
  // objects. It can be very tempting to say "oh, this is GUI so I don't
  // need to test it," but don't over-do that. Make sure that the GUI is
  // not making application-level or algorithmic decisions.
  //
  // I do sometimes end up creating programmatic acceptance tests,
  // like the last test. I try to minimize my need for these
  // because they're a fair bit of work to program, and the
  // customer can't really look at them and verify them. In
  // this case, the test checks the hookup, which is ok. But
  // for other spreadsheet tests, I might automate something

}

NonFormula_UTest.cs

Synopsis
using System;
using ut;

public class testNonFormula
{
  //--------------------------
  public void test1A_EmptyCells() 
  {
    Sheet sheet = new Sheet();
    utx.assert("", sheet.get("A1"));
    utx.assert("", sheet.get("ZX347"));
  }
 
  //--------------------------
  public void test1B_TextCells() 
  {
    Sheet sheet = new Sheet();
    String theCell = "A21";
   
    sheet.put(theCell, "A string");
    utx.assert("A string", sheet.get(theCell));
   
    sheet.put(theCell, "A different string");
    utx.assert("A different string", sheet.get(theCell));
   
    sheet.put(theCell, "");
    utx.assert("", sheet.get(theCell));
  }
   
  //--------------------------
  public void test1C_NumericCells() 
  {
    Sheet sheet = new Sheet();
    String theCell = "A21";
   
    sheet.put(theCell, "X99"); // "Obvious" string
    utx.assert("X99", sheet.get(theCell));
   
    sheet.put(theCell, "14"); // "Obvious" number
    utx.assert("14", sheet.get(theCell));
   
    sheet.put(theCell, " 99 X"); // Whole string must be numeric
    utx.assert(" 99 X", sheet.get(theCell));
   
    sheet.put(theCell, " 1234 "); // Blanks ignored
    utx.assert("1234", sheet.get(theCell));
   
    sheet.put(theCell, " "); // Just a blank
    utx.assert(" ", sheet.get(theCell));
  }
   
  public void test1D_LiteralValues() 
  {
    Sheet sheet = new Sheet();
    String theCell = "A21";
   
    sheet.put(theCell, "Some string"); 
    utx.assert("Some string", sheet.getLiteral(theCell));
   
    sheet.put(theCell, " 1234 "); 
    utx.assert(" 1234 ", sheet.getLiteral(theCell));
   
    sheet.put(theCell, "=7"); // Foreshadowing formulas:)
    utx.assert("=7", sheet.getLiteral(theCell));
  }

  public void testManyCells() 
  {
    Sheet sheet = new Sheet();
    sheet.put("A1", "8");
    sheet.put("X27", "10");
    sheet.put("ZX901", "4");

    utx.assert("A1", "8", sheet.get("A1"));
    utx.assert("X27", "10", sheet.get("X27"));
    utx.assert("ZX901", "4", sheet.get("ZX901"));

    sheet.put("A1", "12");
    utx.assert("A1 after", "12", sheet.get("A1"));
    utx.assert("X27 same", "10", sheet.get("X27"));
    utx.assert("ZX901 same", "4", sheet.get("ZX901"));
  }

  public void testFormulaSpec() 
  {
    Sheet sheet = new Sheet();
    sheet.put("B1", " =7");  // note leading space
    utx.assert("Not a formula", " =7", sheet.get("B1"));
    utx.assert("Unchanged", " =7", sheet.getLiteral("B1"));
  }
}

BadFormula.cs

Synopsis
using System;

//--------------------------
public class BadFormula : System.ApplicationException
{
  public BadFormula() {}  
} 

Calculator.cs

Synopsis
using System;
using System.Collections;
using System.Text.RegularExpressions;

//--------------------------
class Calculator
{
  private string myFormula;
  private OperatorStack myOperators = new OperatorStack();
  private OperandStack myOperands = new OperandStack();
  private CellMatrix myMatrix;
  private ArrayList myPrevReferences;

  //--------------------------
  public Calculator(string theFormula, CellMatrix theMatrix, ArrayList thePrevCells)
  {
    myFormula = theFormula;
    myMatrix = theMatrix;
    myPrevReferences = thePrevCells;
  }

  //--------------------------
  public string parse()
  {
    InputString theInputString = new InputString(myFormula.Substring(1)); //strip off the '='
    for(theInputString.reset(); !theInputString.end(); theInputString.next())
    {
      if (theInputString.isValidOperand())
        handle(theInputString.theOperand(myMatrix, myPrevReferences));
      else
        handle(theInputString.theOperator());
    }

    evaluateUntilOperatorStackIsEmpty();

    if (myOperands.Count() != 1) throw new BadFormula();
    return myOperands.Pop().asString();
  }

  //--------------------------
  private void handle(Operand theOperand)
  {
    myOperands.Push(theOperand);
  }

  //--------------------------
  private void handle(Operator theOperator)
  {
    if (theOperator.isOpenParens())
      myOperators.Push(theOperator);
    else if (theOperator.isCloseParens())
      evaluateUntilMatchingParensFound();
    else
      evaluateUntilLowerPrecedenceOperatorFound(theOperator);
  }

  //--------------------------
  private void evaluateUntilMatchingParensFound()
  {
    while (myOperators.isNotEmpty())
    {
      if (myOperators.topIsOpenParens()) break;
      evaluate();
    }
    myOperators.Pop();
  }

  //--------------------------
  private void evaluateUntilLowerPrecedenceOperatorFound(Operator theOperator)
  {
    while (myOperators.isNotEmpty())
    {
      if (myOperators.precedenceIsLessThan(theOperator)) break;
      evaluate();
    }
    myOperators.Push(theOperator);
  }

  //--------------------------
  private void evaluateUntilOperatorStackIsEmpty()
  {
    while(myOperators.isNotEmpty())
      evaluate();
  }

  //--------------------------
  private void evaluate()
  {
    //must use temporaries to guarentee order of the Pops!
    Operand aRightOperand = myOperands.Pop();
    Operand aLeftOperand = myOperands.Pop();

    myOperands.Push(myOperators.Pop().evaluate(aLeftOperand, aRightOperand));
  }
}

Cell.cs

Synopsis
using System;
using System.Collections;

  //--------------------------
public abstract class Cell
{
  static public Cell Null = new StringCell("", "");

  protected string myAddress = "";
  protected string myLiteralValue = "";
  protected string myValue = "";

  static public Cell factory(string theAddress, string theValue, CellMatrix theMatrix)
  {
    if (isNumeric(theValue))
      return new NumericCell(theAddress, theValue);
    
    if (isFormula(theValue))
      return new FormulaCell(theAddress, theValue, theMatrix);

    return new StringCell(theAddress, theValue);
  }

  //--------------------------
  public abstract string displayValue();
  public abstract string calculatedValue(ArrayList thePrevCells);

  //--------------------------
  public string literalValue()
  {
    return myLiteralValue;
  }

  //--------------------------
  static private bool isFormula(string theValue)
  {
    return theValue.StartsWith("=");
  }

  //--------------------------
  static private bool isNumeric(string theValue)
  {
    bool rc = true;
    try
    {
      Int32.Parse(theValue);
    } 
    catch(FormatException /*ex*/)
    {
      rc = false;
    }
    return rc;
  }
  
  //--------------------------
  private  class FormulaCell : Cell
  {
    CellMatrix myMatrix;

    //--------------------------
    public FormulaCell(string theAddress, string theValue, CellMatrix theMatrix)
    {
      myValue = theValue;
      myLiteralValue = theValue;
      myMatrix = theMatrix;
      myAddress = theAddress;
    }

    //--------------------------
    public override string displayValue()
    {
      ArrayList aList = new ArrayList();
      string aResult;
      try 
      {
        aResult = evaluate(myValue, myMatrix, aList);
      } 
      catch(DivideByZeroException)
      {
        aResult = "#Error";
      }
      catch(OverflowException)
      {
        aResult = "#Error";
      }
      catch(BadFormula)
      {
        aResult = "#Error";
      }
      catch(CircularReference)
      {
        aResult = "#Circular";
      }

      return aResult;
    }

    //--------------------------
    public override string calculatedValue(ArrayList thePrevCells)
    {
      if (thePrevCells.Contains(myAddress)) throw new CircularReference();
      thePrevCells.Add(myAddress);

      //    {
      //      Console.WriteLine("myaddr=" + myAddress + " val='" + myValue + "' start-->");
      //      int count = 1;
      //      foreach (string addr in thePrevCells )
      //      {
      //        Console.WriteLine(count + ": " + addr );
      //        count++;
      //      }
      //      Console.WriteLine("<--end");
      //    }
      
      string s = evaluate(myValue, myMatrix, thePrevCells);
      //      Console.WriteLine("  val='"+myValue+"'  evals to:" + s);
      return s;
    }

    //--------------------------
    private string evaluate(string theFormula, CellMatrix theMatrix, ArrayList thePrevCells)
    {
      return new Calculator(theFormula, theMatrix, thePrevCells).parse();
    }
  }

  //--------------------------
  private class NumericCell : Cell
  {
    //--------------------------
    public NumericCell(string theAddress, string theValue)
    {
      myValue = theValue.Trim();
      myLiteralValue = theValue;
      myAddress = theAddress;
    }

    //--------------------------
    public override string displayValue()
    {
      return myValue;
    }

    public override string calculatedValue(ArrayList thePrevCells)
    {
      return myValue;
    }
  }

  //--------------------------
  private class StringCell : Cell
  {
    //--------------------------
    public StringCell(string theAddress, string theValue)
    {
      myValue = theValue;
      myLiteralValue = theValue;
      myAddress = theAddress;
    }

    //--------------------------
    public override string displayValue()
    {
      return myValue;
    }

    public override string calculatedValue(ArrayList thePrevCells )
    {
      return myValue;
    }
  }
}

CellMatrix.cs

Synopsis
using System;
using System.Collections;

//--------------------------
public class CellMatrix
{
  private Hashtable myHash = new Hashtable();

  public CellMatrix()
  {
  }

  //--------------------------
  public void put(string theAddress, string theValue)
  {
    myHash[theAddress] = Cell.factory(theAddress, theValue, this);
  }

  //--------------------------
  public Cell get(string theAddress)
  {
    return myHash.ContainsKey(theAddress) ? (Cell) myHash[theAddress] : Cell.Null;
  }

  //--------------------------
  public void dump()
  {
    int count = 1;
    for (IEnumerator e = myHash.Keys.GetEnumerator(); e.MoveNext(); )
    {
      string anAddr = (string) e.Current;
      Cell c = (Cell) myHash[anAddr];
      Console.WriteLine(count + ": " + anAddr + " : '" + c.literalValue().ToString() + "' -> '" + c.displayValue() + "'");
      count++;
    }
  }
}


CircularReference.cs

Synopsis
using System;

//--------------------------
public class CircularReference : System.ApplicationException
{
  public CircularReference() {}  
} 

Form1.cs

Synopsis
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;

	/// <summary>
	/// Summary description for Form1.
	/// </summary>
	public class Form1 : System.Windows.Forms.Form
	{
		/// <summary>
		/// Required designer variable.
		/// </summary>
		private System.ComponentModel.Container components = null;

		public Form1()
		{
			//
			// Required for Windows Form Designer support
			//
			InitializeComponent();

			//
			// TODO: Add any constructor code after InitializeComponent call
			//
		}

		/// <summary>
		/// Clean up any resources being used.
		/// </summary>
		protected override void Dispose( bool disposing )
		{
			if( disposing )
			{
				if(components != null)
				{
					components.Dispose();
				}
			}
			base.Dispose( disposing );
		}

		#region Windows Form Designer generated code
		/// <summary>
		/// Required method for Designer support - do not modify
		/// the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent()
		{
      // 
      // Form1
      // 
      this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
      this.ClientSize = new System.Drawing.Size(292, 273);
      this.Name = "Form1";
      this.Text = "Form1";
      this.Load += new System.EventHandler(this.Form1_Load);

    }
		#endregion

    private void Form1_Load(object sender, System.EventArgs e)
    {

    }
	}

InputString.cs

Synopsis
using System;
using System.Collections;
using System.Text.RegularExpressions;

//--------------------------
class InputString
{
  private string myString;
  private int myIndex;

  //--------------------------
  public InputString(string theString)
  {
    myString = theString;
    reset();
  }

  //--------------------------
  public Operator theOperator()
  {
    return new Operator(myString[myIndex]);
  }

  //--------------------------
  public bool isValidOperand()
  {
    string aValue = thePossibleOperand();
    return aValue.Length > 0;
  }

  //--------------------------
  public Operand theOperand(CellMatrix theMatrix, ArrayList thePrevRefs)
  {
    string aValue = thePossibleOperand();
    skip(aValue.Length - 1);
    return Operand.factory(aValue, theMatrix, thePrevRefs);
  }

  //--------------------------
  public void reset()
  {
    myIndex = 0;
  }

  //--------------------------
  public bool end()
  {
    return myIndex >= myString.Length;
  }

  //--------------------------
  public void next()
  {
    skip(1);
  }

  //--------------------------
  private void skip(int theCount)
  {
    myIndex += theCount;
  }

  //--------------------------
  private string thePossibleOperand()
  {
    Regex re = new Regex("^\\s*([A-Z]*[0-9]+)\\s*");
    return re.Match(myString.Substring(myIndex)).Value;
  }
}

ListSelectionModel.cs

Synopsis
using System;
using System.Collections;

//------------------------------------------------
public class ListSelectionEvent
{
  private int myRow = 0;
  private int myCol = 0;
  public ListSelectionEvent(int row, int col)
  {
    myRow = row;
    myCol = col;
  }

  public int Row 
  {
    get 
    {
      return myRow;
    }
  }
  public int Column
  {
    get 
    {
      return myCol;
    }
  }
}

//------------------------------------------------
public interface ListSelectionListener 
{
  void ValueChanged(ListSelectionEvent e);
} 

//------------------------------------------------
public class ListSelectionModel
{
  private int myRow = 0;
  private int myCol = 0;

  private ArrayList myListeners = new ArrayList();
  public void addListSelectionListener(ListSelectionListener l)
  {
    myListeners.Add(l);
  }

  public void changeSelection(int row, int col)
  {
    myRow = row;
    myCol = col;
    Notify();
  }

  public void Notify()
  {
    foreach (ListSelectionListener listener in myListeners)
    {
      ListSelectionEvent e = new ListSelectionEvent(myRow, myCol);
      ((ListSelectionListener)listener).ValueChanged(e);
    }
  }
}

Operand.cs

Synopsis
using System;
using System.Collections;
using System.Text.RegularExpressions;

//--------------------------
public abstract class Operand
{
  //--------------------------
  public static Operand factory(string theString, CellMatrix theMatrix, ArrayList thePrevRefs)
  {
    if (isReference(theString))
      return new ReferenceOperand(theString, theMatrix, thePrevRefs);

    return new NumericOperand(Int32.Parse(theString));
  }

  //--------------------------
  public static Operand factory(int theValue)
  {
    return new NumericOperand(theValue);
  }

  //--------------------------
  private static bool isReference(string theString)
  {
    Regex re = new Regex("^([A-Z]+)");
    return re.Match(theString).Value.Length > 0;
  }

  //--------------------------
  public Operand multipliedBy(Operand theOther)
  {
    return Operand.factory(asInt() * theOther.asInt());
  }

  //--------------------------
  public Operand dividedBy(Operand theOther)
  {
    return Operand.factory(asInt() / theOther.asInt());
  }

  //--------------------------
  public Operand addedTo(Operand theOther)
  {
    int aValue = asInt() + theOther.asInt();
    if (asInt() > 0 && theOther.asInt() > 0 && aValue < 0) throw new System.OverflowException();
    return Operand.factory(aValue);
  }

  //--------------------------
  public Operand subtractedFrom(Operand theOther)
  {
    return Operand.factory(asInt() - theOther.asInt());
  }
  
  //--------------------------
  protected abstract int asInt();
  public abstract string asString();

  //--------------------------
  private class NumericOperand : Operand
  {
    private int myOperand;

    public NumericOperand(int theValue)
    {
      myOperand = theValue;
    }

    //--------------------------
    protected override int asInt()
    {
      return myOperand;
    }

    //--------------------------
    public override string asString()
    {
      return myOperand.ToString();
    }
  }

  //--------------------------
  private class ReferenceOperand : Operand
  {
    private string myStringValue;
    private string myRefAddress;
    private CellMatrix myMatrix;
    ArrayList myPrevReferences;

    //--------------------------
    public ReferenceOperand(string theAddress, CellMatrix theMatrix, ArrayList thePrevRefs)
    {
      myRefAddress = theAddress;
      myStringValue = theAddress;
      myMatrix = theMatrix;
      myPrevReferences = thePrevRefs;
    }
    
    //--------------------------
    protected override int asInt()
    {
      return Int32.Parse(asString());
    }

    //--------------------------
    public override string asString()
    {
      return myMatrix.get(myRefAddress).calculatedValue(new ArrayList(myPrevReferences));
    }
  }
}

OperandStack.cs

Synopsis
using System;
using System.Collections;

//--------------------------
class OperandStack
{
  private Stack myStack = new Stack();

  //--------------------------
  public OperandStack()
  {
  }

  //--------------------------
  public void Push(Operand theOperand)
  {
    myStack.Push(theOperand);
  }

  //--------------------------
  public Operand Pop()
  {
    if (isEmpty()) throw new BadFormula();
    return (Operand) myStack.Pop();
  }

  //--------------------------
  bool isEmpty()
  {
    return Count() == 0;
  }

  //--------------------------
  public int Count()
  {
    return myStack.Count;
  }
}

Operator.cs

Synopsis
using System;
using System.Text.RegularExpressions;

//--------------------------
class Operator
{
  private char myOperator;
    
  //--------------------------
  public Operator(char c)
  {
    myOperator = c;
  }

  //--------------------------
  public bool isOpenParens()
  {
    return myOperator == '(';
  }

  //--------------------------
  public bool isCloseParens()
  {
    return myOperator == ')';
  }

  //--------------------------
  public int precedence()
  {
    switch(myOperator)
    {
      case '*' :
      case '/' : return 3;
      case '+' :
      case '-' : return 2;
      case '(' : return 1;
      default:   return 0;
    }
  }

  //--------------------------
  public Operand evaluate(Operand theLeftOperand, Operand theRightOperand)
  {
    switch(myOperator)
    {
      case '*' :  return  theLeftOperand.multipliedBy(theRightOperand);
      case '/' :  return  theLeftOperand.dividedBy(theRightOperand);
      case '+' :  return  theLeftOperand.addedTo(theRightOperand);
      case '-' :  return  theLeftOperand.subtractedFrom(theRightOperand);
      default  :  throw new BadFormula();
    }
  }
}

OperatorStack.cs

Synopsis
using System;
using System.Collections;

//--------------------------
class OperatorStack
{
  private Stack myStack = new Stack();

  //--------------------------
  public OperatorStack()
  {
  }

  //--------------------------
  public bool isNotEmpty()
  {
    return !isEmpty();
  }

  //--------------------------
  bool isEmpty()
  {
    return myStack.Count == 0;
  }

  //--------------------------
  public Operator Pop()
  {
    if (isEmpty()) throw new BadFormula();
    return (Operator) myStack.Pop();
  }

  //--------------------------
  public void Push(Operator theOperator)
  {
    myStack.Push(theOperator);
  }

  //--------------------------
  public bool topIsOpenParens()
  {
    return ((Operator) myStack.Peek()).isOpenParens();
  }

  //--------------------------
  public bool precedenceIsLessThan(Operator theOther)
  {
    return ((Operator) myStack.Peek()).precedence() < theOther.precedence();
  }
}


Sheet.cs

Synopsis
using System;

//--------------------------
public class Sheet
{
  private CellMatrix mySheet = new CellMatrix();

  //--------------------------
  public Sheet()
  {
  }

  //--------------------------
  public void dump()
  {
    mySheet.dump();
  }

  //--------------------------
  public string get(string theAddress)
  {
    return mySheet.get(theAddress).displayValue();
  }

  //--------------------------
  public string getLiteral(string theAddress)
  {
    return mySheet.get(theAddress).literalValue();
  }

  //--------------------------
  public void put(string theAddress, string theValue)
  {
    mySheet.put(theAddress,theValue);
  }
}


SheetFrame.cs

Synopsis
using System;
using System.Drawing;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;

public class MyButton : Button
{
  public void doClick()
  {
    this.OnClick(new System.EventArgs());
  }
}

public class SheetFrame : Form, ListSelectionListener
{
  private SheetTableModel myTableModel;
  private MyButton myOK;
  private Label myLabel;
  private TextBox myEditor;
  private Table myTable;

  public SheetFrame(SheetTableModel m)
  {
    myTableModel = m;
    myOK = new MyButton();
    myLabel = new Label();
    myEditor = new TextBox();
    myTable = new Table(myTableModel);
    ((System.ComponentModel.ISupportInitialize)(myTable)).BeginInit();
   
    SuspendLayout();

    myLabel.Text = "";
    myLabel.Name = "myLabel";
    myLabel.Size = new System.Drawing.Size(72, 24);
    myLabel.TabIndex = 0;
    Controls.Add(myLabel);

    myEditor.Location = new System.Drawing.Point(72, 0);
    myEditor.Name = "myEditor";
    myEditor.Size = new System.Drawing.Size(168, 20);
    myEditor.TabIndex = 1;
    myEditor.Text = "";
    Controls.Add(myEditor);

    myOK.Location = new System.Drawing.Point(248, 0);
    myOK.Name = "myOK";
    myOK.TabIndex = 4;
    myOK.Text = "OK";
    //myOK.DialogResult = DialogResult.OK;
    myOK.Click += new EventHandler(myOK_Click);
    Controls.Add(myOK);


    myTable.Bind();

    myTable.HeaderForeColor = System.Drawing.SystemColors.ControlText;
    myTable.Location = new System.Drawing.Point(0, 24);
    myTable.Name = "SpreadSheet";
    myTable.Size = new System.Drawing.Size(590, 264);
    myTable.TabIndex = 3;
    myTable.getSelectionModel().addListSelectionListener(this);
    myTable.CurrentCellChanged += new EventHandler(myTable_CurCellChange);
    Controls.Add(myTable);
      
    AutoScaleBaseSize = new System.Drawing.Size(5, 13);
    ClientSize = new System.Drawing.Size(600, 300);
    Name = "SheetFrame";
    Text = "SpreadSheet";
    Load += new System.EventHandler(Form1_Load);
      
    ((System.ComponentModel.ISupportInitialize)(myTable)).EndInit();
    ResumeLayout(false);
  }

  private void Form1_Load(object sender, System.EventArgs e)
  {

  }

  private void myOK_Click(object sender, System.EventArgs e)
  {
    myTableModel.setValueAt(myEditor.Text, myLabel.Text);
  }

  private void myTable_CurCellChange(object sender, System.EventArgs e)
  {
    myTable.changeSelection();
  }

  public void ValueChanged(ListSelectionEvent e)
  {
    string addr = TableModel.ToAddress(e.Row, e.Column);
    myLabel.Text = addr;
    myEditor.Text = myTableModel.getLiteralValueAt(e.Row, e.Column);
  }

  public MyButton okButton
  {
    get
    {
      return myOK;
    }
  }

  public Label label  
  {
    get
    {
      return myLabel;
    }
  }

  public TextBox editor  
  {
    get
    {
      return myEditor;
    }
  }

  public Table table  
  {
    get
    {
      return myTable;
    }
  }

  public SheetTableModel model  
  {
    get
    {
      return myTableModel;
    }
  }

#region Windows Form Designer generated code
  /// <summary>
  /// Required method for Designer support - do not modify
  /// the contents of this method with the code editor.
  /// </summary>
  private void InitializeComponent()
  {
    this.myLabel = new System.Windows.Forms.Label();
    this.myEditor = new System.Windows.Forms.TextBox();
    //this.myOK = new System.Windows.Forms.Button();
    ((System.ComponentModel.ISupportInitialize)(this.myTable)).BeginInit();
    this.SuspendLayout();
    // 
    // myLabel
    // 
    this.myLabel.Name = "myLabel";
    this.myLabel.Size = new System.Drawing.Size(72, 24);
    this.myLabel.TabIndex = 0;
    // 
    // myTable
    // 
    this.myTable.DataMember = "";
    this.myTable.HeaderForeColor = System.Drawing.SystemColors.ControlText;
    this.myTable.Location = new System.Drawing.Point(0, 24);
    this.myTable.Name = "myTable";
    this.myTable.Size = new System.Drawing.Size(344, 264);
    this.myTable.TabIndex = 2;
    // 
    // myEditor
    // 
    this.myEditor.Location = new System.Drawing.Point(72, 0);
    this.myEditor.Name = "myEditor";
    this.myEditor.Size = new System.Drawing.Size(168, 20);
    this.myEditor.TabIndex = 3;
    this.myEditor.Text = "";
    // 
    // myOK
    // 
    this.myOK.Location = new System.Drawing.Point(248, 0);
    this.myOK.Name = "myOK";
    this.myOK.TabIndex = 4;
    this.myOK.Text = "OK";
    // 
    // SheetFrame
    // 
    this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
    this.ClientSize = new System.Drawing.Size(336, 273);
    this.Controls.AddRange(new System.Windows.Forms.Control[] {
                                                                this.myOK,
                                                                this.myEditor,
                                                                this.myTable,
                                                                this.myLabel});
    this.Name = "SheetFrame";
    this.Text = "SpreadSheet";
    this.Load += new System.EventHandler(this.Form1_Load);
    ((System.ComponentModel.ISupportInitialize)(this.myTable)).EndInit();
    this.ResumeLayout(false);

  }
		#endregion

}



SheetTableModel.cs

Synopsis
using System;

public class SheetTableModel : TableModel
{
  Sheet mySheet;
  public SheetTableModel(Sheet theSheet)
  {
    mySheet = theSheet;
  }

  public override string  getValueAt(int row, int col)
  {
    if (col == 0) return (row+1).ToString();
    return mySheet.get(ToAddress(row, col));
  }

  public override void  setValueAt(string value, int row, int col)
  {
    if (col == 0) throw new ArgumentException();
    setValueAt(value, ToAddress(row, col));
  }

  public void  setValueAt(string value, string address)
  {
    mySheet.put(address, value);
    Notify();
  }

  public string getLiteralValueAt(int row, int col)
  {
    if (col == 0) return (row+1).ToString();
    return mySheet.getLiteral(ToAddress(row, col));
  }
}

Table.cs

Synopsis
using System;
using System.Collections;
using System.Windows.Forms;

//------------------------------------------------
public class Table : DataGrid
{
  private SheetTableModel myModel;
  private ListSelectionModel mySelectionModel = new ListSelectionModel();

  public Table(SheetTableModel m)
  {
    myModel = m;
  }

  public void Bind()
  {
    myModel.BindTo(this);
  }

  public SheetTableModel getModel()
  {
    return myModel;
  }

  public bool getRowSelectionAllowed() {return true;}
  public bool getColumnSelectionAllowed() {return true;}

  public ListSelectionModel getSelectionModel()
  {
    return mySelectionModel;
  }

  public void changeSelection (int row, int col,  bool b1, bool b2)
  {
    mySelectionModel.changeSelection(row, col);
  }
   
  public void changeSelection ()
  {
    mySelectionModel.changeSelection(this.CurrentCell.RowNumber, this.CurrentCell.ColumnNumber);
  }
}

TableModel.cs

Synopsis
using System;
using System.Data;
using System.Collections;
using System.Windows.Forms;

//------------------------------------------------
public class TableModelEvent
{
}

//------------------------------------------------
public abstract class TableModelListener 
{
  public abstract void TableChanged(TableModelEvent e);
} 

//------------------------------------------------
public abstract class TableModel
{
  private DataSet myDataSet;
  private ArrayList myListeners = new ArrayList();

  static public string ToAddress(int row, int col)
  {
    return getColumnName(col) + (row+1).ToString();
  }
   
  static public string getColumnName(int col)
  {
    //col is one-based
    string name = "";
    col--; //make it zero based
    while (col >= 0) 
    {
      char c = (char) (col % 26 + (int) 'A');
      name = c + name;
      col = (col / 26) - 1;
    } 
    return name;
  }

  public int getColumnCount()
  {
    return 100;
  }

  public int  getRowCount()
  {
    return 100;
  }

  public abstract string  getValueAt(int row, int col);
  public abstract void  setValueAt(string value, int row, int col);

  public void Notify()
  {
    foreach (TableModelListener listener in myListeners)
    {
      TableModelEvent e = new TableModelEvent();
      ((TableModelListener)listener).TableChanged(e);
    }
  }

  public void addTableModelListener(TableModelListener listener)
  {
    myListeners.Add(listener);
  }

  public void BindTo(Table dg)
  {
    int maxcols = 10;//27 * 26 + 10;
    int maxrows = 9;
    DataTable aTable = new DataTable("ParentTable");
    
    myDataSet = new DataSet();
    myDataSet.Tables.Add(aTable);
    dg.SetDataBinding(myDataSet,"ParentTable");
    DataGridTableStyle aTableStyle = new DataGridTableStyle();
    aTableStyle.MappingName = "ParentTable";
    aTableStyle.ColumnHeadersVisible = true;
    aTableStyle.RowHeadersVisible = false;
    aTableStyle.AllowSorting = false; 
    //    aTableStyle.SelectionBackColor = System.Drawing.Color.LightGray;
    //    aTableStyle.SelectionForeColor = System.Drawing.Color.AliceBlue;
    MakeColumns(aTableStyle, aTable, maxcols); 
    dg.TableStyles.Add(aTableStyle);
    
    for(int row = 0; row < maxrows; ++row)
    {
      DataRow myDataRow = aTable.NewRow();
      myDataRow["-"] = (row+1).ToString();
      for (int col = 1; col < maxcols; col++)
      {
        //myDataRow[getColumnName(col)] = "cell " + getColumnName(col) + (row+1);
        myDataRow[getColumnName(col)] = getValueAt(row, col);
      }
      aTable.Rows.Add(myDataRow);
    }
  }

  private void MakeColumns(DataGridTableStyle theTableStyle, DataTable theTable, int theNumCols)
  {
    MakeColumn0(theTableStyle, theTable);
    for(int i = 1; i < theNumCols; ++i)
    {
      MakeColumn(theTableStyle, theTable, getColumnName(i)); 
    }
  }
         
  private void MakeColumn(DataGridTableStyle theTableStyle, DataTable theTable, string theColName)
  {
    DataColumn aDataColumn = new DataColumn();
    aDataColumn.DataType = System.Type.GetType("System.String");
    aDataColumn.ColumnName = theColName;
    aDataColumn.AutoIncrement = false;
    aDataColumn.ReadOnly = false;
    aDataColumn.Unique = false;
    theTable.Columns.Add(aDataColumn);

    DataGridColumnStyle aColumnStyle = new DataGridTextBoxColumn();
    aColumnStyle.MappingName = theColName;
    aColumnStyle.HeaderText = theColName;
    theTableStyle.GridColumnStyles.Add(aColumnStyle);
  }
   
  private void MakeColumn0(DataGridTableStyle theTableStyle, DataTable theTable)
  {
    DataColumn aDataColumn = new DataColumn();
    aDataColumn.DataType = System.Type.GetType("System.Int32");
    aDataColumn.ColumnName = "-";
    aDataColumn.AutoIncrement = false;
    aDataColumn.ReadOnly = true;
    aDataColumn.Unique = true;
    theTable.Columns.Add(aDataColumn);

    DataGridColumnStyle aColumnStyle = new DataGridTextBoxColumn();
    aColumnStyle.MappingName = "-";
    aColumnStyle.ReadOnly = true;
    aColumnStyle.Width = 20;
    theTableStyle.GridColumnStyles.Add(aColumnStyle);
  }
}






Contact me about content on this page using john_web-at-arrizza-dot-com
For Web Master or site problems contact: webadmin-at-arrizza-dot-com
Copyright John Arrizza (c) 2001-2010