Monday, July 28, 2008

Ruby cp_r caveat

The ruby fileutil function cp_r, copies a directory, all of its files and subdirectories to a target location.  This is very useful when you are trying to sync 2 directories.  However, by default it will copy the directory as a subdirectory of the supplied target directory.  So:

cp_r "c:\\fly", "c:\\soup"

would give you:

"c:\\soup\\fly"

Now you have a fly in your soup.  This is a serious problem.  What if you were trying to sync your stick and donkey folders?

To get around this, just stick a period at the end of the the original directory path.  Like so:

cr_r "c:\\fly\\.", "c:\\soup"

Friday, July 18, 2008

A tale of two documentations

From time to time I find myself needing to integrate our system with some external vendor, and that usually means writing an XSL transform.  Last week I had 2 completely different experiences dealing with documentation from a couple of vendors.  Let's compare, shall we?

Company #1:

Company #1 is a large internet video distribution company, which "empowers content owners... to reach their audiences directly through the internet."  And they are very good at what they do.  They have probably fed you a video through some web site and you don't even know it.  So when it came time to generate an xslt to integrate with their system, I sat down and read the 4 separate documents that describe how to generate the xml manifest.  For the next few days, I played email tag with their support team as I fixed one issue only to unveil another.  It turned out that their documentation had a few errors.  You know, little things like incorrect tag names, required fields that weren't specified as required, and the fact that their xml parser is case sensitive.   That last one really steamed me.  I have since used the documentation as firewood to fend off the chill that came over me after excluding a distribution region by using a tag named "allow" (no attributes on this tag).

Company #2:

On the opposite end of the spectrum is company #2.  Its a small, very specialized, service provider with a single developer currently dedicated to this particular project.  Instead of getting a word or pdf document describing the xml creation process, I was given a highly commented DTD document.  And, surprise, this integration worked the first time without errors. 

I've conversed with several of my colleagues about this and most of them fail to see the awesomeness of this approach.  So, for those of you who missed it, here's why it is so awesome.  Not only was the documentation completely correct, it couldn't have been incorrect.  By passing me their DTD, they were saying "Here is the code that we use to validate your response.  If it validates for you, it will validate for us as well."  In this instance the code itself was enough.  They didn't even have to send an example xml file.  XmlSpy generated one in less than a second. 

I also feel that the comments left directly in the code, in this case the DTD, are a much clearer reflection of the author's intent.  This is the author spilling out his thought process, not just some dud that's been told to document a process by Friday. 

This experience just validates my belief that large external documents are slowly going the way of the dodo.  I've felt this way for a while now.  I'm a big fan of less documentation that is more useful.  Think about it.  End user's don't want to read a manual anymore.  They've come to expect inline help and most are even trained to look for tooltips. 

Song of the day:
State of the Union - David Ford - I Sincerely Apologize for All the Trouble I've Caused

Thursday, July 17, 2008

Uppercase in XSLT

XPATH doesn't have a built in ToUpper() or ToLower() function.  However, you can use the translate function to switch cases.  Here's an example that changes the word "Upper" to all upper case.

<xsl:variable name="text" select="'Upper'" />
<xsl:value-of select="translate($text, 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'" />

If you want to go the other way and lowercase your string, just flip-flop the alphabet strings.

<xsl:variable name="text" select="'Upper'" />
<xsl:value-of select="translate($text, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'" />

Song of the day:
Twilight Zone(Live) - Golden Earring - The Naked Truth

Tuesday, July 15, 2008

Subversion Video

I came across this getting started with subversion video by Dan Wahlin. It's pretty good and would have saved me hours if I had know about it when I got started.  Dan does a good job explaining some of subversion's quirks and how to get around them.

http://weblogs.asp.net/dwahlin/archive/2007/08/13/video-how-to-getting-started-with-subversion-and-source-control.aspx

Song of the day:
Africa(Toto Cover) - Pyogenesis - Mono... or Will It Ever Be The Way It Used To Be

Monday, July 14, 2008

C# Succ

I have a project which requires me to generate multiple artifacts with sequential names.  No sweat, right?  Just stick a number on the end of the name and increment it.  Well, what if your users prefer letters to numbers? 

Ruby's string.succ function returns the successor to the string by incrementing characters starting from the rightmost alphanumeric character.  It's a shame that C# doesn't have this built in. 

Here's my stab at recreating the Ruby succ function in C#.  It's not as robust as the ruby version, as it only handles letters, but it is a start and it meets my needs for now.

public static string Succ(string value)
{
List<string> alphabet = new List<string>(" ,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z".Split(",".ToCharArray()));
char[] values = (" " + value).ToUpper().ToCharArray();
bool addone = true;

for(int i = values.Length - 1; i > -1; i--)
{
if (addone)
{
if (values[i] != 'Z')
{
values[i] = char.Parse(alphabet[alphabet.IndexOf(values[i].ToString()) + 1]);
addone = false;
}
else
values[i] = 'A';
}
}

return new string(values).Trim();
}


Here's some sample output:


  • Succ("R") => "S"

  • Succ("Z") => "AA"

  • Succ("ZAZ") => "ZBA"





Song of the day:

A Moment of Clarity - Therapy? - Infernal Love



Friday, May 30, 2008

A Million Lines of Code

I went to a product demo the other day for a CMS that is being developed in house.  I guess they want everybody to start using it.  The presenter went on an on about how flexible it was and how it could do anything.  It can scramble your eggs as long as you can pass in the eggs in a parseable format.  Apparently, they started development in back in 2000 and now it's ready for prime time.  That's right pasters, 8 years ago. 

Anyway, things were going along great, when something the presenter said hit me like a ton of bricks.  "This [application] has over a million lines of code."  Now, what would your reaction be if someone told you that their application had a million lines of code?  I had two.

1.  What does that even mean?

It seems silly to even mention this type of metric.  Are they still getting paid by the number of lines they produce?  I assume he was trying to impress the more non-technical folks in the room who now think that this thing is approaching the same level as the space shuttle.  (The avionics system, the code that flies the shuttle, has roughly 2 million lines of code.)  But to me it sounds like bloat, which lead me to my next reaction.

2. I bet I could write that in 999,999 lines of code.

You would think that in a million lines of code, there'd be a good refactor somewhere.  I would have expected them to find it by at least the sixth year.

The million lines of code also scream, "This is really complex and when it breaks, boy is it going to break."  Simplicity, that gets the job done, beats complexity any day.  Simplicity breeds confidence and attracts users.  I've added it to my developer's ethos.

Thursday, March 06, 2008

Replace Visual Studio Command Prompt with Powershell

I've been using sqlmetal a lot lately.  The easiest way to use it is through the Visual Studio Command Prompt.  In my case, I'm using the Visual Studio 2008(9.0) command prompt.  The PowerShell lover in me hated the fact that I had to go to another shell to do this.  After a brief inquiry, the all great Google led me to this excellent post by Robert Anderson which explains how to edit your PowerShell profile to correctly set all the environment variables used by the Visual Studio 2005 command prompt.  I've made a few tweaks for Visual Studio 2008 and am re-posting it for your enjoyment.

Just add the code below to your profile.

#Set environment variables for Visual Studio Command Prompt
pushd 'C:\Program Files\Microsoft Visual Studio 9.0\vc'
cmd /c “vcvarsall.bat&set” |
foreach {
if ($_ -match “=”) {
$v = $_.split(“=”); set-item -force -path "ENV:\$($v[0])" -value "$($v[1])"
}
}
popd
write-host "`nVisual Studio 2008 Command Prompt variables set." -ForegroundColor Yellow

Tweak Vista's Search Index

I'm not sure how I did anything on XP before I started using app launchers.  My first was Colibri, but then I found my true love, Launchy.  It's so liberating not to have to click the start menu(press ctrl+esc or win key), then find programs(press p) and then search through a gigantic list of stuff for what you want.  With Launchy, you just hit your short cut key and start typing in what you want. 

Vista's start menu comes with a similar search box that let's you type in what you want.  I think that they threw this in because actually going to Programs and looking for what you want is a hideous experience in Vista.  I keep all my applications that don't require an installation in a separate folder named "Programs".  Vista doesn't search this folder by default, so I had to modify the settings for the start menu... no, no... that's not it.  Maybe it's the user preferences... no, that's not it either.  Oh there it is, in the indexing service.

In the Vista search box type "index" and it should find "Indexing Options".  Inside Indexing Options click the Modify button to specify which folders to index.  I actually turned off a few that I didn't care about.  Things like other user's folders, and Outlook.  You can actually disable the service all together if you want to get a slight performance boost, but I like the search bar too much to kill it. 

For those of you who are keyboard bound like me, might I also suggest using SlimKeys in conjunction with the Vista search.  SlimKeys comes with an app launcher, but I disabled it.  I am using it's hotkey manager, window positioner/sizer, volume controller, screen grabber and it's format removing paster.  I highly recommend checking it out just for the window positioner/sizer.  It's great to be able to move a window to any corner, or center it or maximize it and even move it to my other monitor with a single keystroke.

Tuesday, March 04, 2008

Isilon Training: Day Two

Today we went through a lot of demos and labs.  The most impressive was the "Upgrade" demo.  We had a test cluster that was a version behind.  The instructor dropped a tar file on one of the nodes, ran an upgrade command from the command line and a couple of minutes later... BAM!  All three nodes in the cluster were updated with no down time.  The only instance where the nodes would have to be rebooted is when there is a change to the kernel.  They have a service that migrates the change to the other nodes so you don't have to. 

All in all it was really cool and I can't wait to get to play with ours.  If they give me access. :)

Isilon Training: Day One

I spent yesterday in a training class for the Isilon system that we use at work.  Isilon offers a clustered storage solution that is quite impressive both in its performance and its implementation.  I know that you can go the web site and look at all the specs, but I thought I would take some time and share the things that jumped out at me during the first day of training.

First off, I experienced a slight culture shock when they started talking about capacities.  Speaking from a developer's perspective, I'm used to talking Megabytes when referring to my applications and possibly Gigabytes when talking about data storage.  These guys talk Terabytes and Petabytes.  Imagine what you could do with a petabyte of code.  I think Skynet became self aware right around 1.3 petabytes.

Now as you can imagine its pretty hard to manage that much data.  The mainstream file systems just won't cut it, so they wrote their own.  They based their OneFS file system and OS on FreeBSD.  Each node in the cluster runs the OS and communicates with the other nodes so that each node knows what the other nodes have and are doing.  This way, there isn't a single controller unit.  Any node could go down completely and the system will continue to run without a hiccup.  It also makes it easier to add expansion nodes.  You can easily add several terabytes of new storage to the system in approximately 60 seconds.

One of the coolest decisions that they made was to stripe files across nodes and not across disks(most nodes have 12 disks).  This provides the highest level of data protection in the event of a failure. 

In today's class, we'll be looking at some troubleshooting exercises and hands on labs.  Should be fun.

Friday, February 29, 2008

Tweaking the SiteMap Navigation Menu

I have a site that uses a SiteMap provider and a Menu control for the site's navigation controls. Using the SiteMap provider is a simple and quick way to get dynamic menu for your site. Everything was going along smoothly until I was asked to have one of the menu items open it's link in a new window.

If this was just a link, I'd add a "target='_blank'" attribute and be done. However, a quick romp in the web.sitemap file showed that Visual Studio didn't have anything in the intellisence that resembled the target attribute. So what are we to do? Well, I'm glad you asked.

A little digging showed that the System.Web.UI.WebControls.MenuItem object actually had a target property. So all I had to do was set it. In the Web.Sitemap, I added a target attribute to the sitemap node.

<siteMapNode url="http://ewo/" title="EWO" target="_blank" />


Then I mapped the OnMenuItemDataBound event to a custom handler.



<asp:SiteMapDataSource ID="SiteMapDataSource1" runat="server" ShowStartingNode="false" />
<asp:Menu ID="Menu1" runat="server" DataSourceID="SiteMapDataSource1" Orientation="Horizontal"
OnMenuItemDataBound="Menu1_MenuItemDataBound">


The target attribute that I set in the SiteMapNode is translated into the dataobject and can easily be retrieved and used to set the MenuItem's target property.



protected void Menu1_MenuItemDataBound(object sender, MenuEventArgs e)
{
SiteMapNode node = (SiteMapNode)e.Item.DataItem;

if (node["target"] != null)
e.Item.Target = node["target"];
}


There are other great attributes that can be set this way, including the image url, text and tooltip properties. Have fun!

Thursday, February 28, 2008

PowerShell Convert CSV to SQL Insert Script

Recently, I've been handed a lot of Excel spreadsheets (and various other formats) and told to move the data into a SQL table.  You could do this with DTS if you have that kind of access to the server, but suppose you didn't.  Here's a little PowerShell script that crunches through the rows in a CSV file and generates insert statements in a SQL script.  The script requires that you pass a path to the CSV file and also accepts an optional parameter for the name of the table to insert into.  If you don't pass this in, the filename is used as the table name.  The script is intentionally lengthy and properly commented to make it easier to read and understand. Here you go...

# Verify that a file argument was passed in.
if ($args[0]) { $file = $args[0] }
else { write-warning "You must supply a path to the csv."; break }

# Verify that the file exists.
if(![System.IO.File]::Exists($file)) { write-warning "Can not find '$file'."; break }

# Verify that the file is a csv file.
if(!$file.ToLower().EndsWith("csv")) { write-warning "The file specified is not a CSV file."; break }

# If no table name was provided, we'll use the file name instead.
if($args[1]) { $tableName = $args[1] }
else { ls $file | % { $tableName = $_.Name.ToLower().TrimEnd(".csv") } }

# Generate an informative header for the sql file.
# Note that the out-file command will overwrite any existing file.

$output = $file.ToLower().TrimEnd(".csv") + ".sql"
"/************************************************************`n" +
"** Script generated by CsvToBatchInsert.ps1 `n" +
"** Date: " + [System.DateTime]::Now.ToString("MM/dd/yyyy hh:mm:ss") + " `n" +
"** From file: $file `n" +
"************************************************************/`n" | out-file -filepath $output

# Loop through the rows in the csv file.
Import-Csv $file | % {
# The insert variable is used to build a single insert statement.
$insert = "INSERT INTO $tableName ("

# We only care about the noteproperties, no use dealing with methods and the such.
$properties = $_ | Get-Member | where { $_.MemberType -eq "NoteProperty" }

# Create a comma delimited string of all the property names to use in the insert statement.
# You should make sure that the column headings in the CSV file match the field names in
# your table before you run the script.

$properties | % { $insert += $_.Name + ", " }

$insert = $insert.TrimEnd(", ") + ") VALUES ("

# Couldn't figure out how to access the value directly. So here I'm forced to use
# substring to get it. The Definition looks like "System.String PropertyName=PropertyValue".
# Since the value will be enclosed in single quotes, you will run into trouble if the value
# contains a single quote. To escape the single quote in T-SQL, just put another single quote
# directly in front of it.

$properties | % {
$value = $_.Definition.SubString($_.Definition.IndexOf("=") + 1)
$insert += "'" + $value.Replace("'", "''") + "', "
}

$insert = $insert.TrimEnd(", ") + ")"

# Append the insert statement to the end of the output file.
$insert | out-file -filepath $output -append
}


You can easily tweak this script to generate update statements, or delete statements, or anything else for that matter.  As always, I welcome any comments, especially ones that show a better way of doing this.  Enjoy!!!

Tuesday, February 26, 2008

Dynamically create Powershell objects

I wrote a sweet powershell script today that dynamically generated a custom object.  To create an object in Powershell, you basically edit an existing object by adding new methods and properties.  In my case, I used System.Object and added some properties. 

Suppose you are in the middle of a powershell script and you realize that you need a cat class.  Cat's have claws, fur and multiple output devices, but let's limit ourselves to just one.

$myCat = New-Object -TypeName System.Object
$myCat | Add-Member -MemberType noteProperty -Name "Fur" -Value "Soft"
$myCat | Add-Member -MemberType noteProperty -Name "Claws" -Value "Sharp"
$myCat | Add-Member -MemberType ScriptMethod GenerateHairBall { $hairball = "Hmmph, hmmph, ack, aaaaaack! There you go!"; $hairball }

And there you go.  If you have more than one cat, you can store them in an array.  Like this:

$cardboardBox += $myCat
$cardboardBox += $SarahsCat

etc... 

Monday, February 25, 2008

Javascript getElementByRegexId Function

One of the most frustrating aspects of working with ASP.NET is the way it rewrites the id's for all the controls on the page.  When you want to access that control via javascript, you're screwed.  Sure you could have ASP.NET render the control's ClientID, like...

javascript:alert('<%= txtName.ClientID %>');

But, IMO, this is highly unattractive.  It also breaks the separation of content, design, and function by requiring javascript to be on the page.

We are all pretty reliant on the getElementById function.  It's a shame that there isn't a 'getElementByPartialId' function.  I can not find a way to get javascript's default functions to return my "txtName" control when it is rendered as the "ctl00_GridView1_WhoApprovedThisNamingConvention?_ctl05_txtName" control.  ASP messes with the "name" attribute as well.  It just uses dollar signs instead of underscores. 

Note to Microsoft:  Next time you revamp ASP.NET.  Create a new attribute on your base web control object called "aspid" and hack away.  Just leave "id" alone.

Knowing that Javascript is incredibly powerful and versatile, I put together a little function to help out.  You just pass in the id that you know and what kind of control you expect to be rendered.  Passing in "*" for the tag name should work as well, it just won't be quite as fast.

function getElementByRegexId(id, tagName)
{
    var rx = new RegExp("^.*" + id + "$");
    var controls = document.getElementsByTagName(tagName);
    var result;
    for(var i = 0; i < controls.length; i++)
    {
        if(rx.test(controls[i].id))
        {
            result = controls[i];
            break;
        }
    }
    return result;
}

I've gone through this exercise before, but I can't remember where.  Can anyone think of a cooler way to accomplish this? 

Tuesday, February 19, 2008

ASP.NET Membership Password Requirements

I've been meaning to post about this for a while.  I experienced an issue with the ASP.NET Membership provider and it's default password requirements.  By default the provider requires that a user's password contains at least one non-alphanumeric character.  After blindly stumbling around Google for a while I smacked my head on this post by Derek Hatchard.  Turns out that there is an attribute in the membership provider section called "minRequiredNonalphanumericCharacters".  Once I set this to "0", my issue was resolved.

There are some other cool attributes as well.  "MinRequiredPasswordLength" sets how long the password has to be.  You can even validate the password against a regular expression with the "passwordStrengthRegularExpression" attibute.

Thursday, February 07, 2008

Resharper 3.1 Upgrade Issues

I upgraded from Resharper 3.0 to 3.1 today and immediately had some issues. 

Issue #1 - Intellisense was disabled. 

My intellisense was disabled on the Visual Studio level.  To re-enable it, I went to Tools -> Options -> Text Editor -> C#.  In the Statement Completion control grouping, I checked "Auto list members"  to turn it back on.

Issue #2 - Alt+Enter stopped working.

To fix this, just go to Resharper -> Options -> General.  Then select the "Visual Studio" radio button under "Restore Resharper keyboard shortcuts". Then click "Apply"

Here's another handy tip.  Since Resharper won't play nice with the new .NET 3.0+ features until version 4.0, you can temporarily disable/enable it on any open file with the Ctrl+8 keyboard shortcut.

Tuesday, January 15, 2008

CSS Lessons

I just finished re-skinning a website and came across a couple of CSS stumbling blocks that gave me a little trouble.  Hopefully these my save you some time in your future projects.

My first issue has to do with the position style.  Absolute positioning will place an element at the exact pixel location on the page.  However, if the parent element has a position style of "relative", then the absolutely positioned element will use the parent element to determine position, instead of the page itself.

My other issue revolved around the anchor pseudo classes.  If you assign them directly to the "a" element, they will be used by all the anchors on the page, whether you like it or not.  Consider the following CSS styles:

a:link { color: red; }
a:active { color: red; }
a:visited { color: red; }
a:hover { color: green; }

#mydiv a:link { color: blue; }
#mydiv a:active { color: blue; }
#mydiv a:visited { color: blue; }
#mydiv a:hover { color: yellow; }

In this scenario, all the links in mydiv will be red.  To get around this, you need to put identifiers in front of all the "a" styles.

I experienced this behavior in both IE and Firefox. 

Thursday, January 10, 2008

Microsoft AJAX UpdateProgress Control

Today, I learned that the coconut crab is the largest land-living arhropod in the world, Clinophobia is a fear of going to bed, and you can easily cause an animated gif to display while your ASP.NET AJAX Update panel is busy doing a call back. 

It's easy, just use the UpdateProgress Control.  Point it at the update panel and give it something to display and voila!  Here's a code example:

<asp:UpdateProgress ID="updateProgress1" runat="server" AssociatedUpdatePanelID="UpdatePanel1" DisplayAfter="100" DynamicLayout="true">
<ProgressTemplate>
<div class="Update"><img src="MyAnimated.gif" /></div>
</ProgressTemplate>
</asp:UpdateProgress>

Wednesday, January 09, 2008

Use .NET 3.5 in ASP.NET 2.0

Today, I was able to get an ASP.NET 2.0 application to successfully reference and use a .NET 3.5 dll.  I don't know why this surprised me, but it did.  The dll uses Linq to do some XML parsing among other things.  All I had to do was install the framework on the server.  Sweet!  I may have to rename my site to something like "Frankenstein".  :)

Monday, January 07, 2008

Count things with Linq

I had a situation where I needed to determine how many objects in a generic list were missing a value in a property.  That count was very important to the user.  Basically, I was looking for all the objects in the list that didn't have a value in the path property.  Normally, I would have created a count integer and a loop of some sort and just counted. 

int count = 0;
foreach(Profile p in Profiles)
{
if(String.IsNullOrEmpty(p.Path))
{
count++;
}
}



I could have also used an anonymous delegate, but I was playing with Linq and found this little short cut.



int count = (from p in profiles where String.IsNullOrEmpty(p.Path) select p).Count();



I think that this is much cleaner and easier to read.   Happy Linq'n!

Friday, January 04, 2008

Multiple Using Statements

Using statements are used to define a scope on an object that implements the iDisposable interface.  It automatically calls the Dispose method on the object when it goes out of scope.

Here's a neat little trick to deal with nested using statements.  Consider the following code snippet that has a couple of them:

using (FileStream fs = File.Create("File.txt"))
{
using (TextWriter tw = new StreamWriter(fs))
{
tw.WriteLine("The content of my file!");
}
}



Here's a cleaner, and in my opinion easier, way to write these.  Notice that the FileStream object is useable when creating the TextWriter object.



using (FileStream fs = File.Create("File.txt"))
using (TextWriter tw = new StreamWriter(fs))
{
tw.WriteLine("The content of my file!");
}



If you are instantiating multiple objects of the same type, you could put them all on one line.  It is harder to read this way, but maybe that's just me. ;)



using (StreamWriter tw1 = File.CreateText("W1"), tw2 = File.CreateText("W2"))
{
tw1.WriteLine("The content of my first file!");
tw2.WriteLine("The content of my second file!");
}

Thursday, January 03, 2008

Databinding a Generic List to a WPF ListView

Inevitably there will come a time when you will want to start using the WPF framework for your business apps, and that means that you'll need a grid.  The WPF grid of choice is the ListView control. 

The ListView control is extremely powerful, flexible and customizable.  It's also way complicated!  On the Microsoft campus, in the basement, past the closet where they hide the Steve Balmer bobble-heads and the unused copies of Microsoft BOB, from a darkened cubicle, a developer is laughing at us.

Anyway, there are not a lot of examples of how to programmatically bind a generic list to a ListView control.  So, here's mine.

First let's take a look at the class that we want to add to the collection:

namespace MyNameSpace
{
public class Item
{
public string Description { get; set; }
public string Name { get; set; }

public Item(string name, string description)
{
Name = name;
Description = description;
}
}
}


It's just a simple class with a couple of public accessors.  Now lets turn our attention to the xaml file:




   1:  <Window x:Class="DMG.AnyStream.ProfileEditor.Main"


   2:      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"


   3:      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"


   4:      xmlns:tools="clr-namespace:MyNamespace;assembly=MyAssemblyName"


   5:      Title="Sample Application" Height="600" Width="800">


   6:      <Window.Resources>


   7:          <DataTemplate x:Key="NameHeader">


   8:              <StackPanel Orientation="Horizontal">


   9:                  <TextBlock Margin="10,0,0,0" Text="Name" VerticalAlignment="Center" />


  10:              </StackPanel>


  11:          </DataTemplate>


  12:          <DataTemplate x:Key="NameCell" DataType="Profile">


  13:              <StackPanel Orientation="Horizontal">


  14:                  <TextBlock>


  15:                      <TextBlock.Text>


  16:                          <Binding Path="Name" />


  17:                      </TextBlock.Text>


  18:                  </TextBlock>


  19:              </StackPanel>


  20:          </DataTemplate>


  21:          <DataTemplate x:Key="DescriptionHeader">


  22:              <StackPanel Orientation="Horizontal">


  23:                  <TextBlock Margin="10,0,0,0" Text="Description" VerticalAlignment="Center" />


  24:              </StackPanel>


  25:          </DataTemplate>


  26:          <DataTemplate x:Key="DescriptionCell" DataType="Profile">


  27:              <StackPanel Orientation="Horizontal">


  28:                  <TextBlock>


  29:                      <TextBlock.Text>


  30:                          <Binding Path="Description" />


  31:                      </TextBlock.Text>


  32:                  </TextBlock>


  33:              </StackPanel>


  34:          </DataTemplate>


  35:      </Window.Resources>


  36:      <Grid>


  37:          <ListView Margin="0,22,0,0" Name="lvItems" ItemsSource="{Binding}" 


  38:                    IsSynchronizedWithCurrentItem="True" 


  39:                    SelectionMode="Single" >


  40:              <ListView.View>


  41:                  <GridView>


  42:                      <GridViewColumn Header="Name" HeaderTemplate="{StaticResource NameHeader}" CellTemplate="{DynamicResource NameCell}" Width="200px"  />


  43:                      <GridViewColumn Header="WatchFolderPath" HeaderTemplate="{StaticResource DescriptionHeader}" CellTemplate="{DynamicResource DescriptionCell}" Width="400px" />


  44:                  </GridView>


  45:              </ListView.View>


  46:          </ListView>


  47:      </Grid>


  48:  </Window>


Take a look at line 4. If your class lives in an assembly outside of your WPF project, like mine, then you will need to add this line to declare it to the xaml.



The Windows resources section starts at line six. Here, I'm just defining some templates that will tell the ListView how to present my information.  Without these, the control will show the results from typeof() for each item.  Not good.  Inside the "NameCell" template, I'm just creating a TextBlock control.  There's nothing to stop you from inserting images, checkboxes, radio buttons, etc. Inside the TextBlock, I create a new binding object and give it the name of the public accessor that I want to bind to this column.



The actual ListView control starts down on line 37. Notice the GridView section starting at line 41.  Here is where we define our columns. All we have to do is point to the templates that we created in the Resources section.  You could define them here, but I think that this is cleaner.>



Finally, we need to bind a list to control.  I do that in the code behind for the form:



public partial class Main : Window
{
public Main()
{
List<Item> items = new List<Items>();
items.add(new Item("Item1", "Item1 Description"));
items.add(new Item("Item2", "Item2 Description"));
items.add(new Item("Item3", "Item3 Description"));

lvItems.ItemsSource = profiles;
}
}



It took me forever to figure this out, so I hope that it saves someone some headache and/or chest pains. I know what you are saying, "I only work on web apps."  I understand that this doesn't seem that important right now, but in a recent interview with Redmond Developer News, Scott Guthrie is quoted with:



"When you look at the next public preview of Silverlight that we're doing, all of those features that are core to WPF are built-in, and there's more than just the XAML in common. You can actually take your core UI code, working with controls and databinding and things like that, and use that seamlessly in the Windows app and in the Office app and in the RIA [rich Internet application] app."



How's that for cool?




Wednesday, January 02, 2008

New Year's Resolutions

Happy New Year Kiddies!  Its that time again.  Its time to make a bunch of promises to ourselves in an attempt to justify our shortcomings from the previous year.  That's right, its time to lay down some New Year's Resolutions.  Here are some of mine.  Enjoy!

  1. Gain weight - At least I'm realistic.
  2. Eat at least 2 new animals that I haven't eaten before.
  3. Watch more TV - That one's actually a job requirement.
  4. Learn 2 new programming languages.  Does Linq count?
  5. Build 2 pieces of furniture from scratch.
  6. Project 365 - Take a picture a day for the next year. I set up a Flickr account just for this.
  7. Blog more - Thanks Jeff Atwood.

What empty promises are you making yourself this year?