Tuesday, December 21, 2010

How to Get Values From Form1 to Form2 (or any other forms, for that matter).

This question is asked practically every day on C# Forums: "how do I get a value from Form1 to Form2?" or the other way around. There is a shocking amount of bad advice out there on the internet about how to go about doing this. This tutorial is intended to show you more "correct" ways of passing data between forms.

The scope of this tutorial is limited to passing data between forms. We will look at both properties and custom events, but in a brief overview.

Friday, November 26, 2010

Silverlight RPN Calculator Tutorial.

Well I beat my old nemesis and finished an RPN calculator. It was much easier than I remember, but then again, I tried it like four years ago. I've learned so much in the time since, I can hardly believe how silly my old code is.

Have any of you ever used a Reverse Polish Notation calculator? I did in high school. It was easily the best calculator ever (the HP=32SII). RPN is great, because you don't have to use parenthesis. The stack and the order in which you key the operators maintains order of operations.

Today, I'll walk you through creating one. Note this tutorial is technically for Silverlight, but except for the marked sections, it can be applied wholesale to WinForms or WPF. Most if this is simple stack logic, and that exists on all .NET platforms.

Read more after the jump.

Tuesday, November 23, 2010

Postfix expression parser.

A question on DreamInCode lead me to my old nemesis, the Postfix expression parser. I'd fallen in love with a Reverse Polish Notation calculator in high school (the HP-32SII, they sadly don't make them anymore). When I tried to program a RPNC to use on my PC years ago, I failed miserably. But I've learned a lot since then, and I figured I could at least try a simple postfix parser.

It wasn't that bad. You don't have to check for a lot of things a calculator does. Here's the body of the code. I made it static, since you really don't need to instantiate it. If I ever make it into a calculator, I'd probably make it non-static, and make the stack a class-level field.

class PostfixParser
{
    const string Placeholder = "[x]";

    private static Dictionary<string, Func<double, double, double>> op;

    private static List<string> operators = new List<string>() { "+", "-", "*", "/" };

    static PostfixParser()
    {
        op = new Dictionary<string, Func<double, double, double>>();
        op.Add("+", (x, y) => x + y);
        op.Add("-", (x, y) => x - y);
        op.Add("*", (x, y) => x * y);
        op.Add("/", (x, y) => x / y);
        op.Add("^", (x, y) => Math.Pow(x, y));
    }

    public static double ParseExp(string exp, double? val)
    {
        string[] tokens = exp.Split(new string[] { " " } , StringSplitOptions.RemoveEmptyEntries);
        if (tokens.Contains(Placeholder) && val == null)
            throw new InvalidOperationException("Placeholder token detected in expression, but parameter \"val\" was null.");
        Stack<double> stack = new Stack<double>();
        foreach (string token in tokens)
        {
            if (operators.Contains(token))
            {
                if (stack.Count < 2)
                    throw new InvalidOperationException("Operator imbalance.");
                double y = stack.Pop();
                double x = stack.Pop();
                stack.Push(op[token](x, y));
            }
            else
            {
                double n;
                if (token == Placeholder)
                    n = val.Value;
                else if (!double.TryParse(token, out n))
                    throw new InvalidOperationException("Invalid token detected: " + token);
                stack.Push(n);
            }
        }

        if (stack.Count > 1)
            throw new InvalidOperationException("More than one result on the stack.");
        if (stack.Count < 1)
            throw new InvalidOperationException("No results on the stack.");
        return stack.Pop();
    }

    public static double ParseExp(string exp)
    {
        return ParseExp(exp, null);
    }
}

This can be used like this:
string fToCExp = "5 9 / [x] 32 - *";
string cToFExp = "9 5 / [x] * 32 + ";
Console.WriteLine("100C converted to F:");
Console.WriteLine(PostfixParser.ParseExp(cToFExp, 100));
Console.WriteLine("32F converted to C:");
Console.WriteLine(PostfixParser.ParseExp(fToCExp, 32));
Console.ReadKey();

To give an output of:

100C converted to F:
212
32F converted to C:
0

You could add more operators if you wished, like a 1/x or a SQRT, but since most other operators are unary rather than binary, you'd have to change your popping logic.

Note: I did it with a placeholder value in mind. I used "[x]" for whatever reason, but it could be anything that won't parse into a double by itself or match one of the operators. You could remove this altogether, if you cared to.

Tuesday, October 26, 2010

More extension methods: To and In. Also, a static class to make Sequences.

It's been quite a while since I posted. I've been quite busy with work. But without further ado, here's a static class I've been working on (code after the break):

Wednesday, September 1, 2010

Silverlight: Performing Multiple Asynchronous Calls

If you've spent any time with Silverlight, you've probably used it with a Web Service (ASMX or WCF, either one). And the first thing that jumps out at you is the fact that Silverlight does not and in fact cannot generate synchronous methods to call the web methods. In stead of a method called GetData that returns a value, you have a GetDataAsync method and a GetDataCompleted handler, to which the return value will be in the EventArg parameter.

This is not a bad thing. I don't particularly want a plugin blocking the browser's UI thread with a long-running synchronous call. In fact, I've read (somewhere, can't find the reference) that this is the reason that MS didn't allow Silverlight to do synchronous calls.

However, there are times when you need to do multiple calls, and then have something happen when they are all done. You can't trust that calls will finish in the order that you start them in, because these calls can run simultaneously. There are a few options to do this. I'll show you two examples. Article continues after the jump.

Monday, August 30, 2010

Trimming/rounding a date time. Floors and ceilings.

Well, first, credit where credit is due. I found this on StackOverflow and only slightly modified it. But it's too good not to share.

This code will let you "round" a date time to any given interval. You can get the floor or ceiling, or do a traditional round.

Here's the code:

public static class Extensions
{
    public static DateTime Floor(this DateTime date, TimeSpan span)
    {
        if (span == TimeSpan.FromMinutes(0))
            return date;
        long ticks = date.Ticks / span.Ticks;
        return new DateTime(ticks * span.Ticks);
    }

    public static DateTime Ceil(this DateTime date, TimeSpan span)
    {
        if (span == TimeSpan.FromMinutes(0))
            return date;
        long ticks = (date.Ticks + span.Ticks - 1) / span.Ticks;
        return new DateTime(ticks * span.Ticks);
    }

    public static DateTime Round(this DateTime date, TimeSpan span)
    {
        if (span == TimeSpan.FromMinutes(0))
            return date;
        long ticks = (date.Ticks + (span.Ticks / 2)) / span.Ticks;
        return new DateTime(ticks * span.Ticks);
    }
}


Here's a simple example using Floor:
DateTime dt = new DateTime(2010, 8, 29, 15, 26, 23);
Console.WriteLine("Floor:");
Console.WriteLine(dt.Floor(TimeSpan.FromSeconds(0)));
Console.WriteLine(dt.Floor(TimeSpan.FromMinutes(1)));
Console.WriteLine(dt.Floor(TimeSpan.FromMinutes(5)));
Console.WriteLine(dt.Floor(TimeSpan.FromMinutes(10)));
Console.WriteLine(dt.Floor(TimeSpan.FromMinutes(60)));
Console.WriteLine();

Console.WriteLine("Ceil:");
Console.WriteLine(dt.Ceil(TimeSpan.FromSeconds(0)));
Console.WriteLine(dt.Ceil(TimeSpan.FromMinutes(1)));
Console.WriteLine(dt.Ceil(TimeSpan.FromMinutes(5)));
Console.WriteLine(dt.Ceil(TimeSpan.FromMinutes(10)));
Console.WriteLine(dt.Ceil(TimeSpan.FromMinutes(60)));
Console.WriteLine();

Console.WriteLine("Round:");
Console.WriteLine(dt.Round(TimeSpan.FromSeconds(0)));
Console.WriteLine(dt.Round(TimeSpan.FromMinutes(1)));
Console.WriteLine(dt.Round(TimeSpan.FromMinutes(5)));
Console.WriteLine(dt.Round(TimeSpan.FromMinutes(10)));
Console.WriteLine(dt.Round(TimeSpan.FromMinutes(60)));
Console.ReadKey();

And here's the output:

Floor:
8/29/2010 3:26:23 PM
8/29/2010 3:26:00 PM
8/29/2010 3:25:00 PM
8/29/2010 3:20:00 PM
8/29/2010 3:00:00 PM

Ceil:
8/29/2010 3:26:23 PM
8/29/2010 3:27:00 PM
8/29/2010 3:30:00 PM
8/29/2010 3:30:00 PM
8/29/2010 4:00:00 PM

Round:
8/29/2010 3:26:23 PM
8/29/2010 3:26:00 PM
8/29/2010 3:25:00 PM
8/29/2010 3:30:00 PM
8/29/2010 3:00:00 PM

Tuesday, August 24, 2010

Finding the calling method.

I've been tasked with writing a logging component of a current application we're working on. My co-worker provided me with a snippet of code that has proven to be quite useful:

using System.Diagnostics;
using System.Reflection;
.
.
.
//dig through the stack trace to find the calling method
StackTrace stackTrace = new StackTrace();
//since this is two methods removed from the caller, go two deep
StackFrame stackFrame = stackTrace.GetFrame(2); 
MethodBase methodBase = stackFrame.GetMethod();
string caller = methodBase.ReflectedType + "." + methodBase.Name;

I'm getting the third frame, because the method is two deep in the logger code, but you can use whatever number necessary to get the right frame. This gives you the whole Fully.Qualified.Namespace.Class.MethodName, which can be quite useful.

Friday, August 13, 2010

System.IO.DirectoryInfo extension method: Recursively print directory structure

Yeah, lots of extension method posts today. I'm just going through my projects and posting the good ones.

Actually, this is one I did when I was looking at editing a .xlsx doc without Excel installed. It turns out that .xlsx docs are actually zip files. When decompressed, it's got several levels of directories. I wanted to show someone the directory structure on a forum, but I didn't feel like typing it all out, so I typed (more) code to do it for me. Amazing how we'll waste effort writing a program for something that was probably faster to do without it.

I also used it as an excuse to learn about default parameters. C# now supports them. You could call this method by providing just one parameter, because all the others are provided automatically. Also note that you can use any TextWriter, not just Console.Out. You could make a StreamWriter and output it to text.

Anyway, that was off topic. Here's the code.

using System.IO;

namespace DirectoryExtensions
{
    public static class DirectoryExtensions
    {
        public static void PrintDirectoryStructure(this DirectoryInfo directory, TextWriter writer, string prefix = ">", string spacer = "--")
        {
            writer.WriteLine(string.Format("{0}{1} (dir)", prefix, directory.Name));
            prefix = spacer + prefix;
            foreach (FileInfo file in directory.GetFiles())
                writer.WriteLine(string.Format("{0}{1} (file)", prefix, file.Name));
            DirectoryInfo[] subDirectories = directory.GetDirectories("*", SearchOption.TopDirectoryOnly);
            if (subDirectories.Length > 0)
                foreach (DirectoryInfo subDirectory in subDirectories)
                    PrintDirectoryStructure(subDirectory, writer, prefix, spacer);
        }
    }
}

Here's a use case:
DirectoryInfo directory = new DirectoryInfo(@"c:\dev\test");
directory.PrintDirectoryStructure(Console.Out);

Expected output:
>test (dir)
-->subdir1 (dir)
---->textfile.txt (file)
---->subdirA (dir)
---->subdirB (dir)
------>asdf.txt (file)
------>some text file.txt (file)
-->subdir2 (dir)
---->some text file.txt (file)
-->subdir3 (dir)

Extension method to export a Silverlight Datagrid to CSV

Well, I couldn't figure out how to export it as an excel doc, but CSV is just as good...right? (Of course not, but it's good enough.)

This is an extension method, so you'd have to call it with dataGrid1.Export();

public static class DataGridExtensions
{
    public static void Export(this DataGrid dg)
    {
        SaveExportedGrid(ExportDataGrid(true, dg));
    }

    public static void Export(this DataGrid dg, bool withHeaders)
    {
        SaveExportedGrid(ExportDataGrid(withHeaders, dg));
    }

    private static void SaveExportedGrid(string data)
    {
        SaveFileDialog sfd = new SaveFileDialog() { DefaultExt = "csv", Filter = "CSV Files (*.csv)|*.csv|All Files (*.*)|*.*", FilterIndex = 1 };
        if (sfd.ShowDialog() ?? false)
        {
            using (StreamWriter sr = new StreamWriter(sfd.OpenFile()))
                sr.Write(data);
        }
    }

    private static string ExportDataGrid(bool withHeaders, DataGrid grid)
    {
        string colPath;
        System.Reflection.PropertyInfo propInfo;
        System.Windows.Data.Binding binding;
        System.Text.StringBuilder strBuilder = new System.Text.StringBuilder();
        System.Collections.IList source = (grid.ItemsSource as System.Collections.IList);
        if (source == null)
            return "";

        List<string> headers = new List<string>();
        grid.Columns.ToList().ForEach(col =>
        {
            if (col is DataGridBoundColumn)
            {
                headers.Add(FormatCSVField(col.Header.ToString()));
            }
        });
        strBuilder
        .Append(String.Join(",", headers.ToArray()))
        .Append("\r\n");

        foreach (Object data in source)
        {
            List<string> csvRow = new List<string>();
            foreach (DataGridColumn col in grid.Columns)
            {
                if (col is DataGridBoundColumn)
                {
                    binding = (col as DataGridBoundColumn).Binding;
                    colPath = binding.Path.Path;
                    propInfo = data.GetType().GetProperty(colPath);
                    if (propInfo != null)
                    {
                        csvRow.Add(FormatCSVField(propInfo.GetValue(data, null).ToString()));
                    }
                }
            }
            strBuilder
                .Append(String.Join(",", csvRow.ToArray()))
                .Append("\r\n");
        }

        return strBuilder.ToString();
    }

    private static string FormatCSVField(string data)
    {
        return String.Format("\"{0}\"",
            data.Replace("\"", "\"\"\"")
            .Replace("\n", "")
            .Replace("\r", "")
            );
    }
}

Convert Word and Excel to PDFs in C#

I had a very repetitive task I had to perform. Take a directory full of Word documents, open each, and do a Save As -> PDF on each of them. Not in this lifetime. So I wrote a quick program for it, and included Excel just to boot. I'll show the important methods.

You must have Office 2007 or greater installed on the machine the code will run on.

Add a reference to:
Microsoft Excel 12.0 Object Library (or greater)
Microsoft Word 12.0 Object Library (or greater)

Both can be found in the COM tab of the Add Reference dialog.

Here's the code:
using System;
using Word = Microsoft.Office.Interop.Word;
using Excel = Microsoft.Office.Interop.Excel;
...

void ExportExcel(string infile, string outfile)
{
    Excel.Application excelApp = null;
    try
    {
        excelApp = new Excel.Application();
        excelApp.Workbooks.Open(infile);
        excelApp.ActiveWorkbook.ExportAsFixedFormat(Excel.XlFixedFormatType.xlTypePDF, outfile);
    }
    finally
    {
        if (excelApp != null)
            excelApp.Quit();
    }
}

void ExportWord(string infile, string outfile)
{
    Word.Application wordApp = null;
    try
    {
        wordApp = new Word.Application();
        wordApp.Documents.Open(infile);
        wordApp.ActiveDocument.ExportAsFixedFormat(outfile, Word.WdExportFormat.wdExportFormatPDF);
    }
    finally
    {
        if (wordApp != null)
            wordApp.Quit();
    }
}


Thursday, August 12, 2010

Some new extension methods for System.String

So AdamSpeight2008 posted a small challenge for the VB.NET section, and it got me thinking. That would be a pretty useful extension method. I'd previously written a RemoveMany extension for strings, might as well add a ReplaceMany. Also, I'll throw in a ToTitleCase for free.

So, here they are:
public static class Extensions
{
    public static string ReplaceMany(this string dirty, Dictionary<char, char> replacer)
    {
        StringBuilder sb = new StringBuilder();
        dirty.ToList().ForEach(x => sb.Append(replacer.ContainsKey(x) 
            ? replacer[x] 
            : x));
        return sb.ToString();
    }

    public static string ReplaceMany(this string dirty, params Tuple<char, char>[] pairs)
    {
        StringBuilder sb = new StringBuilder();
        dirty.ToList().ForEach(x => sb.Append(pairs.Any(y => y.Item1 == x) 
            ? pairs.First(y => y.Item1 == x).Item2 
            : x));
        return sb.ToString();
    }

    public static string ReplaceMany(this string dirty, params Tuple<string, string>[] pairs)
    {
        pairs.ToList().ForEach(x => dirty = dirty.Replace(x.Item1, x.Item2));
        return dirty;
    }

    public static string RemoveMany(this string dirtyString, params string[] stringsToRemove)
    {
        return dirtyString.Split(stringsToRemove, StringSplitOptions.None)
            .Aggregate((sentence, next) => sentence + next);
    }

    public static string ToTitleCase(this string sentence)
    {
        return System.Threading.Thread.CurrentThread.CurrentCulture.TextInfo.ToTitleCase(sentence);
    }
}


Note that the ReplaceMany uses a Dictionary, which I'm not exactly thrilled about, but off the top of my head I can't think of any easy and elegant way to do things differently. Maybe tuples.

Definitely tuples. I've added an overload that takes tuples of chars, and tuples of strings.

Here's some code to test them and show usage, if you're interested.
Dictionary<char, char> dict = 
    new Dictionary<char, char>() { { 'D', 'a' }, { 'a', 'D' }, { ' ', 'X' } };
Console.WriteLine("Dream In Code".ReplaceMany(dict));
Console.WriteLine("Bytes.com".ReplaceMany(Tuple.Create('B', 'c'),
    Tuple.Create('c', 'B'), Tuple.Create('.', 'U')));
Console.WriteLine("Bob is not your old uncle".ReplaceMany(Tuple.Create("not", "surely"),
    Tuple.Create("old", "young")));
Console.WriteLine("Texstying".RemoveMany("x", "y"));
Console.WriteLine("this sHould be in title caSE".ToTitleCase());
Console.ReadKey();

Ridiculously overambitious projects

I see this all the time on forums.  Here's an example:
http://www.dreamincode.net/forums/topic/185703-face-recognition-with-c%23/

As you can see, my answer was less than complimentary.  But I see this stuff all the time.  People asking "how to create antivirus software" or "how to make 3d game."

I have no idea where they get these ideas. Is some insane professor assigning them super hard homework? Do they think that just because they wrote their first Hello World program and it compiled with no errors that they're ready for the big leagues?

More likely, they have no idea how complicated the topics they want to try are.  But that doesn't stop me from saying WTF! every time I see these stupid questions questions from stupid people.

Silverlight ChildWindows and Focus

ChildWindow is an awesome control.  No doubt about that.  It saves me a lot of time and effort creating my own.  But it's not without it's quirks.

I can't figure out exactly when, but a ChildWindow will somehow .Focus() itself, after the constructor and after the ChildWindow_Loaded event. This was driving me crazy. I'd focus my TextBox in the Loaded event, see the cursor blink once, and then vanish.

So here's a clunky hack to get around it:
private bool firstFocus = true;
//...
private void ChildWindow_GotFocus(object sender, RoutedEventArgs e)
{
    if (firstFocus)
    {
        textBox1.Focus();
        firstFocus = false;
    }
}

Credit where credit is due.

Hmm...feature creep? In MY project?

Ugh. I've dealt with this so many times it shouldn't be a surprise. Right near the end of a project, someone comes up with a brilliant idea that should be implemented. It's a good idea, but it'll take extra work, and we're already near turning the damn thing in. Of course, this person that thinks it up is in a position that he can make it reality.

Also, changes to the damn spec. We have a set XML schema that we've used and revised over and over again until we thought it was perfect. Then we built an application around it. Then someone "discovers" that we need to change an important part of it. This project is never going to end.

Tuesday, August 10, 2010

On the importance of logging...

Today I fought with an issue for hours. I have a Silverlight frontend that calls a WCF service to edit a file. Everything worked in debug mode, but once deployed, it only worked for about half the people that tried it, with no obvious correlation between them.

I kept thinking that the issue was with the service.  After all, I'd seen errors similar to this. Maybe it's some permissions thing.  Maybe the binding or behaviors aren't set properly.  Maybe the client config isn't set up right.

And maybe I was chasing my tail.  I was about ready to quit when a colleague suggested actually using the logging service we set up.  So I put debug logging messages in at each point in the flow.  Guess what?  The service was never even being called.  The issue was happening before the service call.

It was a stupid, simple issue that should have never been there in the first place.
contactList.Single(x => x.Address == userEmailAddress).Timestamp = DateTime.Now;
Should have been
contactList.Single(x => x.Address.ToLower() == userEmailAddress.ToLower())
.Timestamp = DateTime.Now;

side note: How silly is it that some people in the same company have email addresses with a different case?  We tested this on six people.  Three of the people had lower case domain names in their email (like @gmail.com).  The other three had capital letters (@GMAIL.com).  Ugh.

Simple little things like this can ruin your day. My colleague called this an "object lesson." Use logging. Set it up so that you can put debug messages everywhere, and just flip a bit in the config file to stop logging them, but continue to log errors.

How forum experts know you're not worth our time.

Most of the people who answer questions on forums are professionals. We do this whole "writing code thing" for a living. Most of us have put up with enough shit just from work that we won't hesitate for a second to ignore your question if you give us an excuse. That's not to say we're jerks, looking for reasons to ignore you; it means that we're not going to work really hard to help you when you make it hard for us.

There are many obvious indicators, and I'll go through a few of them now. Hopefully you don't make any of these mistakes.
  1. No source code. I'm not sure how many hundreds of times I've had to type this in one way or another, but we aren't going to write your project for you. Homework or actual work; it doesn't matter. None of us are going to do your job for you. Most of us love helping. The greatest reward for a forum expert is to see that someone comprehended the answer you posted and applied it to solve their problems.

    Handing you the code is not teaching. It's cheating. And it's also working for free. Screw that.

  2. Spelling. Look, I understand English may not be your first language. That's fine, I don't expect you to spell everything perfectly. But for those of you that do speak English, I expect you to type it properly as well. Every modern browser has a built-in spell checker, and IE has addons that provide that capability. Get one. It honestly can make the difference. It lets us know that you respect us enough to treat us like the professionals we are.

    On that note, if you type "plz," especially with numerous extra "z"s, I close the browser tab and ignore your question. It's a personal peeve, but one I've seen reflected in many of the other experts on the forums I frequent. Just don't do it.

    And it's "code," not "codes" or "codez."

  3. Overstating urgency. I'm a volunteer. You're not paying me. Because of that, my time is mine, not yours. Just because you "urgently need codes plzzzz send quick" doesn't mean that I'm going to go out of my way to help. Your poor planing does not create an emergency on my part. I'll do my best to help, but don't start getting frustrated or impatient or I'll walk away and never look back.

    If your problem is urgent, clearly state so. A real timeline helps: "This is actually pretty urgent. The project is due in 48 hours." If I can help you in that timeframe, I'll do my best to. But that's different than just freaking out and begging. Which leads me to...

  4. Begging. The entire point of this (and any) programming forum is for experts to help you. You don't need to beg us. If we can help, we will. Saying "plz plzzzz help im clueless help plz" isn't incentive for us to help. It's actually the opposite.

    The best thing you can do to get help is clearly state your problems, what you've tried to resolve them, the code where it's happening, and any error/exception messages. Which is a segue into:

  5. Lack of effort/clarity. This is the big one. This is probably the most common way that we decide you're not worth our time. If it's not worth your time to clearly post what your problem is, then it's not worth our time to help you fix it.
I'll expand on effort and clarity, because it deserves its own section. Here are some dos and don'ts.

Dont...

  • ...only post one sentence. If your question is simple enough to be fit into a single sentence, it's probably simple enough to use google to answer.
  • ...post no code. We need to see your code to solve your problem. Conversely, 
  • ...post ALL your code. We don't need to see 4000 lines of auto-generated garbage to solve a problem in one method.
  • ...just say "It doesn't work." That's the most useless statement ever. What doesn't work? Does anything happen? Nothing? Is your code being executed? Does it throw an exception? These are the important things we need to know.
Do...

  • ...clearly post your entire problem.
  • ...post relevant source code.
  • ...post any exceptions, and indicate which line they're thrown at.
  • ...post the steps you've tried to resolve your own problem.
  • ...respect the professionals trying to help you. This one's important!
Following these simple rules makes it much more likely that someone is going to help you. Number one rule: respect us, and we'll respect you.

Syntax Highlighting

Lets test the syntax highlighting:

using System;

namespace HelloWorld
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
            Console.ReadKey();
        }
    }
}

Now one with line level highlighting:

using System;

namespace HelloWorld
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
            Console.ReadKey();
        }
    }
}

Nice.

Happened to find the instructions for syntax highlighting on Luka Marinko's blog These are good instructions for using Prettify, but I like SyntaxHighligher better.

Anyway, here's how I enabled this.  Paste this code, and any other brushes that you'd like directly above the </head> tag in your template.
<link href="http://alexgorbatchev.com/pub/sh/current/styles/shCore.css" rel="stylesheet" type="text/css"></link>  
<link href="http://alexgorbatchev.com/pub/sh/current/styles/shThemeDefault.css" rel="stylesheet" type="text/css"></link>  
<script src="http://alexgorbatchev.com/pub/sh/current/scripts/shCore.js" type="text/javascript"></script>  
<script src="http://alexgorbatchev.com/pub/sh/current/scripts/shBrushCss.js" type="text/javascript"></script>  
<script src="http://alexgorbatchev.com/pub/sh/current/scripts/shBrushJScript.js" type="text/javascript"></script>  
<script src="http://alexgorbatchev.com/pub/sh/current/scripts/shBrushXml.js" type="text/javascript"></script>  
<script src="http://alexgorbatchev.com/pub/sh/current/scripts/shBrushCSharp.js" type="text/javascript"></script> 
<script src="http://alexgorbatchev.com/pub/sh/current/scripts/shBrushVb.js" type="text/javascript"></script>
<script src="http://alexgorbatchev.com/pub/sh/current/scripts/shBrushSql.js" type="text/javascript"></script>
<script language='javascript'> 
//<![CDATA[  
SyntaxHighlighter.config.bloggerMode = true;
SyntaxHighlighter.defaults['toolbar'] = false;
SyntaxHighlighter.config.clipboardSwf = 'http://alexgorbatchev.com/pub/sh/current/scripts/clipboard.swf';
SyntaxHighlighter.all();
//]]>  
</script>

Lots and lots of props to Alex Gorbatchev for creating SyntaxHighlighter, and hosting the .js files for those of us that can't host them ourselves.

Hello World!

Well, this is my first blog post on my new blog.  Hopefully I can stick with it and keep it updated frequently.