10,000 Monkeys - Harnessing the Power of Typing Monkeys

America's 2,672,401st Most Read Blog by Kevin Marshall.
in

February 2006 - Posts

Displaying GridView When No Data Exists
One thing thats midly annoying to me about the new gridview control is that it doesn't display the header row when there is no data.  I would like to just have the header displayed like the original table with a single row saying something like "No records found".  The grid has a property for emptydata text which doesn't look that great in my opinion.  I have seen some people use the <EmptyDataTemplate> property to create a new table in there which isn't the cleanest solution.  One way to get the gridview to display how you want is to create a custom control that inherts from gidview.  Then you can override the CreateChildControls method to create a the gridview with the all the headers and a blank row with one cell and a text message.  Here's how to do it:


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Web.UI;
using System.Web.UI.WebControls;

public class EmptyGridView : GridView
{
    #region Properties

    /// <summary>
    /// Enable or Disable generating an empty table if no data rows in source
    /// 
</summary>
    [
    Description("Enable or disable generating an empty table with headers if no data rows in source"),
    Category("Misc"),
    DefaultValue("true"),
    ]
    public bool ShowEmptyTable
    {
        get
        {
            object o = ViewState["ShowEmptyTable"];
            return (o != null ? (bool)o : true);
        }
        set
        {
            ViewState["ShowEmptyTable"] = value;
        }
    }
    
    /// 
<summary>
    /// Get or Set Text to display in empty data row
    /// 
</summary>
    [
    Description("Text to display in empty data row"),
    Category("Misc"),
    DefaultValue(""),
    ]
    public string EmptyTableRowText
    {
        get
        {
            object o = ViewState["EmptyTableRowText"];
            return (o != null ? o.ToString() : "");
        }
        set
        {
            ViewState["EmptyTableRowText"] = value;
        }
    }

    #endregion


    protected override int CreateChildControls(System.Collections.IEnumerable dataSource, bool dataBinding)
    {
        int numRows = base.CreateChildControls(dataSource, dataBinding);

        //no data rows created, create empty table if enabled
        if (numRows == 0 && ShowEmptyTable)
        {
            //create table
            Table table = new Table();
            table.ID = this.ID;

            //create a new header row
            GridViewRow row = base.CreateRow(-1, -1, DataControlRowType.Header, DataControlRowState.Normal);

            //convert the exisiting columns into an array and initialize
            DataControlField[] fields = new DataControlField[this.Columns.Count];
            this.Columns.CopyTo(fields, 0);
            this.InitializeRow(row, fields);
            table.Rows.Add(row);

            //create the empty row
            row = new GridViewRow(-1, -1, DataControlRowType.DataRow, DataControlRowState.Normal);
            TableCell cell = new TableCell();
            cell.ColumnSpan = this.Columns.Count;
            cell.Width = Unit.Percentage(100);
            cell.Controls.Add(new LiteralControl(EmptyTableRowText));
            row.Cells.Add(cell);
            table.Rows.Add(row);

            this.Controls.Add(table);
        }

        return numRows;
    }
}

You can download the code here. Reflector was helpful in seeing how grids were created in the framework as was Fredrik Normen's blog which always has great asp.net info.  Everytime i search for asp.net problem, its usually covered on his site.

Posted: Feb 28 2006, 12:21 PM by kmarshall | with 7 comment(s)
Filed under:
Dynamically loading/saving images from a database in ASP.NET
The other day I was working on a web page and I needed to get images from the DB displayed on the web page.  Using a the file upload control, its pretty easy to save content:

Protected Sub btnSavePicture_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnSavePicture.Click

    Dim pictureTA As New MyTableAdapters.PictureTableAdapter()
    Dim imageStream As IO.Stream = picUpload.PostedFile.InputStream
    Dim imageData(picUpload.PostedFile.ContentLength) As Byte
    imageStream.Read(imageData, 0, picUpload.PostedFile.ContentLength)

    pictureTA.UpdatePicture(imageData, id)

End Sub

Then suppose you have another page that needs to display the picture.  You can set an asp Image control's ImageUrl to a another page which retrieves the image:  myPicure.ImageUrl = "ImageLoader.aspx"


In the Page_Load event of ImagerLoader.aspx, just retrieve the content from the DB and stream the content type back to the client like so:

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

    Dim pictureTA As New MyTableAdapters.PictureTableAdapter()
    Dim pic As Image = pictureTA.GetPicture(id)
    If Not pic Is DBNull.Value Then
        Response.ContentType = "image/gif" 
        Response.BinaryWrite(pic)
    End If
   
End Sub

Even though I prefer developing in C#, VB.NET makes things so much simpler at times.  If I would have been using C# at the time I probably would have had a dozen cast statements litered throughout. Thanks to my 3 word per minute typing speed, those cast statements really add up.

Posted: Feb 27 2006, 11:49 AM by kmarshall | with no comments
Filed under:
Manage Your Comic Book Collection with CLibrary
As Seth Cohen said "Life's gritty enough. Comic books are for fun."

I'll admit it.  I have the taste of a 14yr old. I watch the O.C. and I'm starting to read comic books again. Or lets just call them graphic novels to preserve some dignity. Two problems though.  They get ruined easily and they're a pain to carry around. One way to get around this is too scan them and archive them in zip or rar files. There's a nice image viewing application called CDisplay to view the digital comics. I also found another great app for the mac called ComicBookLover.  Its a viewer/collection manager that lets you set metadata to help searching and displays the covers. I love mac apps.  It seems like you can always find a well designed app on the mac for whaterver you need. Sadly my iBook died over the weekend. Coincidently, 2 weeks after my warrenty exprired and I decided not to ante up for the extended AppleCare. At least its a good excuse to buy a new MacBook Pro<g>. Until then I needed a replacement for ComicBookLover. I couldn't find a windows equivalent, so as a good little software developer, I decided to build my own.  So thus was born the first *MonkeySource app produced by 10,000 Monkeys.  Actually, it was more like 1000 monkeys.  The other 9000 were busy watching the Illini dominate Iowa and working on the soon to be unveiled electronic foosball scoreboard app  (*MonkeySource software has no support and may or not be updated depending on my current motivation level, but hey, you can always modify the source yourself <g>) So inspired by the ComicBookLover layout, I developed CLibrary, named in honor of CDisplay:




I even used the dreaded MS Comic Sans font.  I mean if you can't use comic sans in a comic book manager, then when can you use it. The code is avaible here. Its written in C# with VS 2005 and is still pretty much a pre-pre-alpha application.  You can download the free express version of VS 2005 here if you want to build and improve this app which I know you do. Not too much in the way of error handling and features, but it does allow you to import a directory of comics, which takes a thumbnail screenshot of the cover page and loads it into a SQL Express DB.  The DB has a few fields to store the name, volume, and issue #.  The comic isn't modified in anyway, so I don't think it'll delete anything.  But remember, MonkeySource has no support <g>.  Oh and if you have CDisplay installed, you can double click on a row or on a cover and it'll open the comic in CDisplay. And you can search by name. I haven't had time to scan many comics from my collection, but it seems to perform relatively well when I loaded the same 4 comics a few hundred times.

So how was it done?  Obviously its pretty basic at this point.  Though here are some technical tidbits from the app:

Reading Comic Archives

I used #ziplib (for cbz zip files) and the UnRAR dll (for cbr rar files) to open comic archives and grab out a thumbnail of the cover. The cbz files are pretty quick to open and grab a thumb nail since it allows you to grab an entry from the zip and return it as a file stream like so:


private static Bitmap GetCoverFromZip(string file)
{
    ZipFile zipFile = new ZipFile(file);
      zipFile.GetInputStream(1);

      return new Bitmap(zipFile.GetInputStream(1));
}


RAR files a more of a pain to get a file from and its slow since you have to actually scan the whole archive to extract out the image. Maybe there is a better way, but I didn't spend alot of time on it.

private static Bitmap GetCoverFromRar(string file)
{
   //  Create new unrar class and open archive for listing files
    ArrayList files = new ArrayList();
    Unrar unrar = new Unrar();
    unrar.Open(file, Unrar.OpenMode.List);
    while (unrar.ReadHeader())
    {
        if (!unrar.CurrentFile.IsDirectory)
        {
            files.Add(unrar.CurrentFile.FileName);
        }
        unrar.Skip();
    }
    unrar.Close();

    files.Sort();

    unrar.Open(file, Unrar.OpenMode.Extract);
    while (unrar.ReadHeader())
    {
        if (unrar.CurrentFile.FileName == files[0].ToString())
        {
            unrar.Extract("temp.jpg");
            break;
        }
        unrar.Skip();
    }
    unrar.Close();

    return new Bitmap("temp.jpg");
}

Then you can grab a thumbnail like so:

Image cover = GetCoverFromZip(file);
cover.GetThumbnailImage(65, 100, null, new IntPtr());

The rest is just a datagrid view and a list view for the covers.  I might write more about that later, but I've already set my new personal record for blog entry length.  I'd love to here any suggestions or even better, for someone to download the code and make some improvements.

Code Like a Champion Today

For all those Notre Dame fans out there, courtesy of some photoshop fun. Ironically, ND is the only college football team I actively root against.  My ND coworkers say its jealousy.  Jealous of what?  The fact that my alma matter U of Illinois won 1 football game in the four seasons that I was there?  No wonder native americans don't want to be used as our mascot.  If some cricket team with a 10% winning percentage in India had a computer nerd as a mascot, i'd protest too. At least there's college basketball.

Taming Session State

In an ASP.NET, its helpful to store a *few* commonly used items in session. But then you get lazy and you add a few more here and there. You try to add constants as the session key, but its too late. You added Session[EMAIL] and another developer adds Session[EMAILADDRESS]. Its mass hysteria or at the very least, a tad bit irritating and inefficient. One way to help clean this up is to make a session manager class and store that in session. Then you can add whatever strongly typed properties you need to this class. The implemenation is similar to a singleton pattern except that there is no static member variable to hold an instance of itself, rather it is storing itself in session. Did that last line even make sense?

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

/// <summary>
/// SessionManager provides strongly typed access to session information
/// 
</summary>
public class SessionManager
{
    private const string SESSIONMANAGER = "SessionManager";
    private int _userID;
    private string _email;

    /// 
<summary>
    /// ID of current user
    /// 
</summary>
    public int UserID
    {
        get { return _userID; }
        set { _userID = value; }
    }

    /// 
<summary>
    /// Email address of current user
    /// 
</summary>
    public string Email
    {
        get { return _email; }
        set { _email = value; }
    }

    /// 
<summary>
    /// Private constructor for singleton
    /// 
</summary>
    private SessionManager() {}

    /// 
<summary>
    /// Retrieves SessionManager object
    /// 
</summary>
    public static SessionManager GetInstance()
    {
        if (System.Web.HttpContext.Current.Session[SESSIONMANAGER] == null)
        {
            SessionManager sessionManager = new SessionManager();
            System.Web.HttpContext.Current.Session[SESSIONMANAGER] = sessionManager;
        }
        return (SessionManager)System.Web.HttpContext.Current.Session[SESSIONMANAGER];
    }

    /// 
<summary>
    /// Removes SessionManager from session
    /// 
</summary>
    public static void Abandon()
    {
        System.Web.HttpContext.Current.Session.Remove(SESSIONMANAGER);
    }
}
 
Hmmm, for some reason my nifty little code colorizer is not working. Actually, I'm color blind so maybe it is.  Oh well, you get the idea. I hope this helps you manage session a little better.  But remember kids, try to store only basic types like int, string, etc and avoid storing STA COM objects.  For the love of god, no STA COM objects. It affects performance and its just not cool.
Posted: Feb 23 2006, 07:45 PM by kmarshall | with no comments
Filed under:
Stylish CSS Gridviews (in IE)

I'm a huge fan a well designed GUIs and clean looking layouts, thats why I use a Mac <g>. While its easy to get the layout you want in a desktop application, its not always so easy in a web application. Even though IE frequently annoys me with its lack of standard CSS support, ocassionally it allows you to make some nice UI effects. One example is freezing headers in a grid view or table. In IE its pretty easy to do by wrapping the gridview in a div and setting a style on the header elements like so:

th 
{
    background-color: #006699; 
    color: white; 
    position: relative; 
    top: expression(this.scrollTop);
    z-index: 10; 
}
.tableContainer
{
    overflow-y: scroll; 
    height: 250px;
}
<div class="tableContainer">
    
<table class="table" cellpadding="0" cellspacing="0">
        
<tr>
            
<th>Name</th>    
        
</tr>
        ..........
    
</table>
</div>

This creates a scrollbar on the side of the table and keeps the headers in place as you scroll. Another nice effect which compliments the frozen headers is the rounded corners css style at Nifty Corners. You can check out the javascript/css code there which adds rounded edges to elements without images. By wrapping the tableContainer div in an extra roundCorners div, you can get something like this:

I posted a sample of the code used to create this screenshot here. Again this looks way cooler in IE 6+. It works fine in firefox, the headers just don't stay in place. Perhaps as CSS standards progress it'll be way easier to do all of this. Oh and all the browsers have to actually support CSS standards. IE, I'm looking in your direction.

Posted: Feb 23 2006, 08:15 AM by kmarshall | with 1 comment(s)
Filed under:
CSS and Forms aka Forms Without Tables

One thing I've noticed while doing web development is that most people use tables when laying out forms.  In my never-ending quest to eliminate tables in web design for layout, I thought I'd demonstrate how to have a form with aligned columns without a table.  Not that this is innovative or anything, it just seems like many people are unaware of how to do it.  So instead of this:

<table>
<tr>
    
<td>Name:</td><td><input type="text" name="name"></td>
</tr>
<tr>
    
<td>Email:</td><td><input type="text" name="email"></td>
</tr>
<tr>
    
<td>Phone:</td><td><input type="text" name="phone"></td>
</tr>
<tr>
    
<td>Address:</td><td><input type="text" name="address"></td>
</tr>
<tr>
    
<td>City:</td><td><input type="text" name="city"></td>
</tr>
</table>

You can use CSS to style the lablel elements like so:

<style type="text/css">
    fieldset
    
{
        width
: 16em;
    
}
    label
    
{
        font-size
: 1em;
        width
: 5em;
        float
: left;
        margin-right
: 0.5em;
        display
: block;
    
}
    
#rightAligned label
    
{
        text-align
: right;
    
}        
</
style>
<fieldset id="rightAligned">
    
<legend>Right Aligned</legend>
    
<p><label for="name">Name:</label><input type="text" name="name"></p>
    
<p><label for="email">Email:</label><input type="text" name="email"></p>
    
<p><label for="phone">Phone:</label><input type="text" name="phone"></p>
    
<p><label for="address">Address:</label><input type="text" name="address"></p>
    
<p><label for="city">City:</label><input type="text" name="city"></p>
    
<input type="button" class="btn" value="Save">
</fieldset>

Which looks like this:

Posted: Feb 22 2006, 10:18 AM by kmarshall | with 1 comment(s)
Filed under:
ASP.NET 2005 Annoyances #00001 - Master Pages & Javascript Files

This is my first post in an ongoing series of things that annoy me about ASP.NET in VS2005.  Issue #00001, when using a MasterPage, any content pages not in the same directory as the master pages cannot use any javascript linked like so:

<script language="javascript" type="text/javascript" src="/Scripts/myscript.js"></script>
 
Normally when using a masterpage, you can make a reference to something using "~/subdir/file" on a server control in a content page and it will convert the url based on the content page's location in the folder structure. Since <script> tags cannot be set to runat="server", the reference to a file is not converted and the content page won't be able to access the javascript. I tried a couple of different ways of trying to change the reference dynamically on the script tags, but the easiest fix seemed to be to make my own control to write out html <script> tag.  Something like this will mimic the attributes of a normal <script> tag, but it will adjust the source on any conent pages that aren't in the root.
 
 public class ServerScript : Control
    {
        
private string _src = string.Empty;
        private string 
_language "javascript";
        private string 
_type "text/javascript";
        private const string 
HTML_CODE "<script language=\"{0}\" src=\"{1}\" type=\"{2}\"></script>";

        public string 
src
        {
            
get return _src}
            
set { _src = value; }
        }

        
public string language
        {
            
get return _language}
            
set { _language = value; }
        }

        
public string type
        {
            
get return _language}
            
set { _language = value; }
        }

        
public override void RenderControl(HtmlTextWriter writer)
        {
            writer.Write(
string.Format(HTML_CODE, _language, ResolveClientUrl(_src), _type));
        
}
}

Posted: Feb 17 2006, 10:56 AM by kmarshall | with no comments
Filed under: