Thursday, October 13, 2005

Trigger a page method from a User Control

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/trigger_a_page_method_from_a_user_control.htm]

[UPDATE 11/20/2006] - New post with code sample

User controls let you group UI-functionality together into a resuable control. For example, headers, footers, menus, and groups of controls like userId-passwords and addresses all make good user controls. Effective components need to both pass data in (i.e. be able to have the page set the values of the user control), and pass data out (be able to have the page retrieve the values from fields in the UserControl). Besides just passing data, we also want to be able to trigger events. For example you could click a button on a UserControl or Page, and that triggers a method from the UC. But how do you do it the other way - click a button on the user control and have it trigger a method on the page. There are several ways.

  • One way of using delegates, as mentioned in this article I wrote back in early 2004 for 4GuysFromRolla.

However, I have since seen better ways, and I would suggest either:

The last two links are from MSDN, and therefore have code samples in both VB.Net and C#.

Thursday, October 6, 2005

Rapid Development with the XmlSerializer

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/rapid_development_with_the_xmlserializer.htm]

Sometimes you'll want to save your business objects to an xml file. All the Xml parsing and manipulation can be tedious, but the .Net Framework provides a great class to save us from this: XmlSerializer. This class serializes objects (i.e. their internal state, like property values) to and from Xml. You could add methods to your objects like WriteToXml and ReadFromXml to encapsulate this logic. For example, the following employee class has a few methods, and the Xml Serialization methods:

    public class Employee
    {
        public Employee()
        {
        }

    #region Public Properties

        private string _strName;
        public string Name
        {
            get {return _strName;}
            set {_strName=value;}
        }

        private int _intId;
        public int EmployeeId
        {
            get {return _intId;}
            set {_intId=value;}
        }

    #endregion

    #region Public Methods

        public bool AddOrder()
        {
            return true;
        } //end of method

 
    #endregion

    #region Xml Serialization

    public void WriteToXml(string strPath)
    {
      System.Xml.Serialization.XmlSerializer x = new XmlSerializer(typeof(Employee));
      System.IO.StreamWriter sw = null;

      try
      {
        sw = new StreamWriter(strPath);
        x.Serialize(sw,this);
      }
      finally
      {
        if (sw != null)
          sw.Close();
      }
    }

    public static Employee ReadFromXml(string strPath)
    {
      System.Xml.Serialization.XmlSerializer x = new XmlSerializer(typeof(Employee));
      System.IO.StreamReader sr = null;

      try
      {
        sr = new StreamReader(strPath);
        Employee e = (Employee)x.Deserialize(sr);
        return e;
      }
      finally
      {
        if (sr != null)
          sr.Close();
      }
    }

    #endregion

    } //end of class

Both methods require a physical file path. The Write is an instance method that returns void (it writes the current state of the object). The Read is a static method that returns an instance of the type.

Serializing this to Xml produces the following file. Notice how the values of the properties are automatically handled. While this example is a direct mapping of object properties to xml nodes, XmlSerializer provide attributes for more advanced features.

xml version="1.0" encoding="utf-8"?>
<
Employee
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <Name>TimName>
  <EmployeeId>3EmployeeId>

Employee>

We can also verify the read-write integration with the following test. First we create an object, then we save it to Xml, then we read it into another object, and finally compare the properties.

    [Test] public void SaveToXml()
    {
      string strPath = @"C:\temp\emp.xml";

      Employee e= new Employee();
      e.EmployeeId = 3;
      e.Name = "Tim";

      //Save
      e.WriteToXml(strPath);

      //Get
      Employee e2 = Employee.ReadFromXml(strPath);

      Assert.AreEqual(e.EmployeeId,e2.EmployeeId);
      Assert.AreEqual(e.Name,e2.Name);

    }

XmlSerializer makes this easy, and is a useful concept to have.

Tuesday, October 4, 2005

CSS Tip: Scrolling inline tables

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/css_tip_scrolling_inline_tables.htm]

A common UI problem is displaying a large amount of information in only a little space. Different user requirements allow for different solutions. For example, perhaps the user has 50 items, but only needs to see 10 at a time. The entire list is still small enough that you can bring all the data to the client - don't need to page through it with a previous/next button doing server hits. Technically, this could be implemented by putting all 50 items in a sub-page, and then displaying that sub-page in the main page as an inline frame. This would fulfill the user requirements:

  • You could set the frame size to be only 10 items.
  • All the data is still at the client - so it's quick
  • The user can conveniently scroll through the list.

While this functionally works, it's technically tedious. Having a separate sub-page means extra JavaScript to pass values between the main and sub page (such as when you save and you want to see the item selected). It also means one more page to keep track of. An alternative solution is to use CSS to inset the table within a div region:

  <body MS_POSITIONING="FlowLayout">
    <form id="Form1" method="post" runat="server">
      <P>This is an inline table test:P>
      <P>
      P>
      <table width="100%">
        <tbody>
        <tr>
          <td width="50%">
                <div style="BORDER-RIGHT: 1px inset; BORDER-TOP: 1px inset; Z-INDEX: 1; OVERFLOW: auto; BORDER-LEFT: 1px inset; BORDER-BOTTOM: 1px inset; HEIGHT: 100px">
        <TABLE id="Table1" cellSpacing="1" cellPadding="1" width="100%" border="1">
          <tbody>
          <TR>
            <TD>aaaaaTD>
            <TD>bbbTD>
          TR>
          <TR>
            <TD>bbbbbTD>
            <TD>bbbTD>
          TR>
          <TR>
            <TD>cccccTD>
            <TD>bbbTD>
          TR>
          <TR>
            <TD>dddddTD>
            <TD>bbbTD>
          TR>
          <TR>
            <TD>eeeeeeeeeeTD>
            <TD>bbbTD>
          TR>
          <TR>
            <TD>fffffffffTD>
            <TD>bbbTD>
          TR>
                    <TR>
            <TD>gggggggggTD>
            <TD>bbbTD>
          TR>
                    <TR>
            <TD>hhhhhhTD>
            <TD>bbbTD>
          TR>
                    <TR>
            <TD>iiiiiiiiTD>
            <TD>bbbTD>
          TR>
                    <TR>
            <TD>jjjjjjjTD>
            <TD>bbbTD>
          TR>
                    <TR>
            <TD>kkkkkkkkkTD>
            <TD>bbbTD>
          TR>
                    <TR>
            <TD>llllllllllllTD>
            <TD>bbbTD>
          TR>
        tbody>
        TABLE>
      div>
          td>
          <td>Other stufftd>
        tr>
      tbody>
      table>

      <P>P>
    form>
  body>

This technique is quick to develop, has the same user benefit, and is all around cleaner.

Sunday, October 2, 2005

Anchor onclick vs. Href

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/anchor_onclick_vs_href.htm]

ASPX, including all its "special" features like postbacks and validators, ultimately renders as standard Html. So it's good to understand how certain Html components, like anchors, work.

A simple anchor just has an target page in the Href, look like so:

AnchorLink

Clicking this anchor will always redirect to PageB.aspx. The Href property can also contain a method to call. For example, ASP.Net renders LinkButtons as anchors whose Href calls the postback method:

href="javascript:__doPostBack('LinkButton1','')">LinkButton

Anchors can also have an onclick event. You could have this method call a message box, set hidden fields, or do whatever else you needed. However, the Href will not be called if the onclick returns false. For example, you could create a "Do you want to save?" yes-no MessageBox like so:

AnchorLink

The JavaScript confirm() method displays a yes-no confirm box, and returns either true or false depending on what the user clicked. Therefore if the user clicks no, the messageBox returns false, and the href is never activated. This concept applies whether the Href contains a navigation page or JavaScript. For example, you could add the confirm box to the LinkButton, and block the postback if the user answered "No" to a certain question.

Knowing the relationship between the anchor onclick and href lets you develop a more interactive and rich UI.

Thursday, September 29, 2005

Creating an Excel Reader utility class

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/creating_an_excel_reader_utility_class.htm]

Yesterday I created a Cell data structure to help read and store a single cell from Excel. Now let's use that to build an ExcelReader utility class.

Let's first think about our goals:

  • The Reader should be able to get specific values from Excel given the path to the workbook, and worksheet index.
  • In Excel, cells can have formulas that produce results. For example, the cell formula may be "= 1 + 1", but its result - what you see if you print the sheet - would be '2'. We want to be able to get both the value (formula) and text (displayed to user).
  • Reading the sheet will be slow, so we want a way to bundle the retrieval of ranges into a single call that gets a 2D array of object values. For example, given the upperLeft cell A1, and the lowerRight cell C2, return a 2x3 object array, for a total of six values. Such a feature would let us quickly read large portions of the sheet.

Disclaimer: I have only used this for personal pet projects. I have not performance tested it for production.

Given that, here's the code for our ExcelReader:

using System;
using Microsoft.Office.Core;
using System.Reflection;

namespace ExcelUtilities
{
    ///


    /// Summary description for ExcelReader.
    ///

    public class ExcelReader : IDisposable
    {
        public ExcelReader(string strWorkBookPath)
        {
            _app = new Excel.ApplicationClass();
            _book = null;
            _sheet = null;

            //speed up performance
            _app.Visible = false;
            _app.ScreenUpdating = false;
            _app.DisplayAlerts = false;

      if (!System.IO.Path.IsPathRooted(strWorkBookPath))
      {
        //root off current directory
        string strRoot = Assembly.GetExecutingAssembly().Location;
        strRoot = System.IO.Path.GetDirectoryName(strRoot);
        strWorkBookPath = strRoot + @"\" + strWorkBookPath;
      }

      if (!System.IO.File.Exists(strWorkBookPath))
        throw new System.ArgumentException("
The file path for the Excel book did not exist: '" + strWorkBookPath + "'.");

            _book = _app.Workbooks.Open(strWorkBookPath,
                Missing.Value, Missing.Value, Missing.Value,
                Missing.Value, Missing.Value, Missing.Value, Missing.Value,
                Missing.Value, Missing.Value, Missing.Value, Missing.Value,
                Missing.Value, Missing.Value, Missing.Value);
        }

        private Excel.ApplicationClass _app;
        private Excel.Workbook _book;   
        private Excel.Worksheet _sheet;
        private Excel.Range _range;

        private int _intCurrentSheet = 1;

        public int CurrentSheet
        {
            get
            {
                return _intCurrentSheet;
            }
            set
            {
                if (_intCurrentSheet < 1)
                    throw new ArgumentOutOfRangeException("
Sheet must be a positive number.");
                _intCurrentSheet = value;
            }
        }

        ///

        ///        Where cell is of the form "
A1"
        ///

        ///
        ///
        public object GetCell(Cell c)
        {
            return GetRange(c,c)[0,0];
        }

    public string GetCellText(Cell c)
    {
      _sheet = (Excel.Worksheet)_book.Worksheets[this.CurrentSheet];

      //get values
      _range = _sheet.get_Range(c.A1Reference,c.A1Reference);

      object o =  _range.Text;
     
      if (o == null)
        return String.Empty;
      else
        return o.ToString().Trim();
    }

        public object[,] GetRange(Cell cUpperLeft, Cell cLowerRight)
        {
            _sheet = (Excel.Worksheet)_book.Worksheets[this.CurrentSheet];

            //get values
            _range = _sheet.get_Range(cUpperLeft.A1Reference,cLowerRight.A1Reference);

            object[,] aaobj = null;
            if (_range.Count == 1)
            {
                aaobj = new object[1,1];
                aaobj[0,0] = _range.Value2;
            }
            else
                aaobj = (object[,])_range.Value2;

            return aaobj;
        }

        #region IDisposable Members

        public void Dispose()
        {
            //cleanup
            _range = null;
            _sheet = null;

            if (_book != null)
                _book.Close(false, Missing.Value, Missing.Value);
            _book = null;
            if (_app != null)
                _app.Quit();
            _app = null;
        }

        #endregion
    }
}

The constructor takes the necessary parameters to initialize the "connection" to Excel. Instance members then use this info to retrieve data.

Note that due to the heavy resources associated to read Excel, we want to implement IDisposable, such that callers can run it in a "using" context like so:

        [Test] public void GetCell_Empty()
        {
            using (ExcelReader er = new ExcelReader(_strPath))
            {
                er.CurrentSheet = 2;
                object o = er.GetCell(new Cell("X",7));
                Assert.IsNull(o);
            }
        }

We use our Cell object from the previous post to ensure that we're only requesting valid cells. If a user directly accessed the sheet, requesting cell '-1a$', it would be an error. Our Cell object can't even be created with these bad inputs, and hence relieves the ExcelReader from this validation burden. It's also quicker for the program to catch the bad input when trying to instantiate an in-memory Cell class than requesting an erroneous value from the Excel workbook.

One big question: How to test this? You could create a real Excel Workbook, include it as an embedded resource, and then have your tests hit that workbook. This would let you test all the boundary conditions, like:

  • Making sure that our methods distinguish between value (like the formula '=1 + 1') and text (its result, like '2')
  • The GetCell methods handle ranges of input
  • The ExcelReader handles both absolute and relative paths.

This series has provided us several tips to use .Net to enhance Excel functionality. We can call a .Net console app from Excel, passing in whatever command-line values we need, and then have that .Net console app read the Excel book to do complex functionality, such as creating an Html Report, sending data to the database, transforming the data to an Xml file, sending the data over an Xml Web Service, or anything else the .Net Framework lets us do.

Wednesday, September 28, 2005

Reading Excel from .Net: Creating a Cell structure

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/reading_excel_from_net_creating_a_cell_structure.htm]

I've been blogging a series on enhancing Excel with .Net. One way to do this is to have some process in Excel kick off a .Net Console app, and than have the .Net app do all the difficult programming (where you have the support of the .Net Framework) as opposed to VBA. In order to do this, we need to be able to read an Excel sheet from .Net.

Previous posts in this series were:

This post will start showing how to read Excel from .Net. This is pretty standard. If you google it you'll see a lot of variations. However, many "solutions" are not very reusable. We'd like to create a reusable ExcelReader utility class. This will be much easier if we first build a "Cell" class. This Class should have:

  • Constructors to take a row and column in both RC (int-int) and A1 (letter-int)  notation.
  • Properties for the row and column (even if you use the A1 notation). So if you enter the column 'C', it will return 3.
  • A Property to always return the A1Reference. So if you enter (2,2), it should return "B2".

This post will show how to build and test this cell. The next post will apply it to our ExcelReader.
 

Source Code:

using System;

namespace ExcelUtilities
{
    public struct Cell 
    {   
        #region Constructors

        public Cell(int intRow, int intCol)
        {
            if (intRow < 1)
                throw new ArgumentException("Row must be greater than 0");
            if (intCol < 1)
                throw new ArgumentException("Column must be greater than 0");
            _intRow = intRow;
            _intCol = intCol;
            _strA1Reference = ConvertIntToStringColumn(intCol) + intRow;
        }

        public Cell(string strCol, int intRow)
        {
      if (strCol == null)
        throw new System.ArgumentNullException("Column can't be null");
      if (intRow < 1)
        throw new ArgumentException("Row must be greater than 0");

            _intRow = intRow;
            _intCol = ConvertStringToIntColumn(strCol);
            if (_intCol > 256)
                throw new ArgumentException("Column cannot be greater than IV (256). Value was '" + strCol + "'.");

            _strA1Reference = strCol.ToUpper() + intRow.ToString();
        }  
       
        #endregion

        #region Implementation

        private static int ConvertStringToIntColumn(string strCol)
        {
            strCol = strCol.ToUpper();
            //A --> 1, BD --> (26+4), IV     
            //Can only have max of two characters.     
            char[] ach = strCol.ToCharArray();
            if (ach.Length == 0)
                throw new ArgumentException("Column cannot have 0 length. Must be A - IV. Was: '" + strCol + "'.");
            else if (ach.Length == 1)
                return GetNumberValue(ach[0]);
            else       
                return 26*GetNumberValue(ach[0]) + GetNumberValue(ach[1]);
        }

        private static string ConvertIntToStringColumn(int intCol)
        {
            if (intCol < 1)
                throw new ArgumentOutOfRangeException("intCol",intCol,"Column must be between 1 and 256.");
            if (intCol > 256)
                throw new ArgumentOutOfRangeException("intCol",intCol,"Column must be between 1 and 256.");

            //single digit, just convert it.
            if (intCol <= 26)   
                return GetCharacterValue(intCol).ToString();

            //multiple char
            int intDigit1 = intCol / 26;
            int intDigit2 = intCol % 26;

            string s = GetCharacterValue(intDigit1).ToString() + GetCharacterValue(intDigit2).ToString();

            return s;
        }

        private static char GetCharacterValue(int i)
        {
            return (char)(i + 64);
        }

        private static int GetNumberValue(char c)
        {
            return (int)c - 64;
        }

   
        #endregion

        #region Public Properties

        private int _intRow, _intCol;
        private string _strA1Reference;

        public int Row
        {
            get
            {
                return this._intRow;
            }
        }

        public int Column
        {
            get
            {
                return this._intCol;
            }
        }

        public string A1Reference
        {
            get
            {
                return _strA1Reference;
            }
        }

        #endregion

        #region Test Wrappers

#if DEBUG

        public static string TestConvertIntToStringColumn(int intCol)
        {
            return ConvertIntToStringColumn(intCol);
        }

#endif

        #endregion

    }
}

Unit Tests:

This kind of class - purely an in-memory data structure with no external dependencies, is easily unit tested. This codes uses the NUnit 2.2 Framework.

using System;using NUnit.Framework;using ExcelUtilities;namespace UnitTests{    ///     /// Summary description for TestCell.    ///     [TestFixture] public class TestCell    {        public TestCell()        {        }        #region A1 Constructor        [Test] public void GetRowAndColumn_1()         {            Cell c = new Cell("B",3);            Assert.AreEqual(3,c.Row);            Assert.AreEqual(2,c.Column);        }        [Test] public void GetRowAndColumn_IgnoreCase()         {            Cell c = new Cell("b",3);            Assert.AreEqual(3,c.Row);            Assert.AreEqual(2,c.Column);        }        [Test] [ExpectedException(typeof(ArgumentException))]         public void GetRowAndColumn_InvalidCol()         {            Cell c = new Cell("ZZ",3);        }        [Test] [ExpectedException(typeof(ArgumentException))]         public void GetRowAndColumn_InvalidRow()         {            Cell c = new Cell("A",-1);        }    [Test] [ExpectedException(typeof(System.ArgumentNullException))]     public void GetRowAndColumn_InvalidColumn0()     {      Cell c = new Cell(null,-1);    }    [Test] [ExpectedException(typeof(System.ArgumentException))]     public void GetRowAndColumn_InvalidColumn1()     {      Cell c = new Cell("",-1);    }    [Test] [ExpectedException(typeof(System.ArgumentException))]     public void GetRowAndColumn_InvalidColumn2()     {      Cell c = new Cell("#$%",-1);    }        #endregion        #region RC constructor        [Test] public void RC_1()         {            Cell c = new Cell(2,3);            Assert.AreEqual(2,c.Row);            Assert.AreEqual(3,c.Column);        }        [Test] [ExpectedException(typeof(ArgumentException))]         public void RC_2()         {            Cell c = new Cell(-1,3);        }        [Test] [ExpectedException(typeof(ArgumentException))]         public void RC_3()         {            Cell c = new Cell(1,-1);        }        #endregion        #region ConvertIntToStringColumn        #if DEBUG        [Test] [ExpectedException(typeof(System.ArgumentOutOfRangeException))]         public void ConvertIntToStringColumn_1()         {            Cell.TestConvertIntToStringColumn(-1);        }        [Test] [ExpectedException(typeof(System.ArgumentOutOfRangeException))]        public void ConvertIntToStringColumn_2a()         {            Cell.TestConvertIntToStringColumn(0);        }        [Test] [ExpectedException(typeof(System.ArgumentOutOfRangeException))]        public void ConvertIntToStringColumn_2()         {            Cell.TestConvertIntToStringColumn(257);        }        [Test] public void ConvertIntToStringColumn_3()         {            Assert.AreEqual("A",Cell.TestConvertIntToStringColumn(1));        }        [Test] public void ConvertIntToStringColumn_4()         {            Assert.AreEqual("IV",Cell.TestConvertIntToStringColumn(256));        }        [Test] public void ConvertIntToStringColumn_5()         {            Assert.AreEqual("Z",Cell.TestConvertIntToStringColumn(26));        }#endif        #endregion        #region A1Reference        [Test] public void A1Reference_1a()         {            Cell c = new Cell("B",3);            Assert.AreEqual("B3",c.A1Reference);        }    [Test] public void A1Reference_CaseInsensitive()     {      Cell c = new Cell("b",3);      Assert.AreEqual("B3",c.A1Reference);    }        [Test] public void A1Reference_2()         {            Cell c = new Cell(3,2);            Assert.AreEqual("B3",c.A1Reference);        }        #endregion    }}

There's not much to explain about this code. One note about using the #if DEBUG to enclose a test-wrapper. We could use reflection to avoid this, as explained in this Code Project article, but I was just being quick and informal. Next we'll do the more interesting task of using this in an ExcelReader utility class.

Thursday, September 22, 2005

C# 3.0 LINQ - querying data

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/c_30_linq__querying_data.htm]

This looks so cool:

[Quote from MSDN]

The LINQ Project is a codename for a set of extensions to the .NET Framework that encompass language-integrated query, set, and transform operations. It extends C# and Visual Basic with native language syntax for queries and provides class libraries to take advantage of these capabilities. 

[End Quote]

It looks like you'd have the similar query abilities that you'd have in SQL or XPath, and it beats writing tons of custom code. See 101 examples here: http://www.msdn.microsoft.com/vcsharp/future/linqsamples/