Scott Hanselman

A vCard Preview Handler using the Coding4Fun DevKit

August 13, 2007 Comment on this post [5] Posted in Coding4Fun | Programming
Sponsored By

ExplorerForm I sat down with the Coding4Fun Developer Kit and immediately noticed the Preview Handler stuff. I got addicted to Preview Handlers back when Tim wrote one for PDFs. They are darned useful and it seems to me that any application that adds a new file type should add a preview handler for it. They are used in the Vista Explorer, in Outlook 2007 and in Windows Desktop Search. If you or your company makes an explorer replacement, you can also host a Preview Control and add Preview functionality to your own File Explorer application.

I wanted to make a vCard Preview Handler so I could see what's inside a vCard on systems that don't have Outlook. Here's the process to create your own Preview Handler in no time (keeping in mind that the kit is BETA).

  • After installing the C4F Developer Kit, make new Class Library project and add a reference to the PreviewHandlerFramework.
  • Create a new class like below and add the [PreviewHandler] attribute with a name for your project, the extension or extensions (like .foo;.bar), and another Guid for the COM Stuff.
    • You can get new GUIDs either via GuidGen.exe included with the Windows SDK or online at http://www.guidgen.com/ or in PowerShell via [Guid]::NewGuid().ToString()
  • Also, include a ProgId for your new class. I just used the namespace.classname.
  • Notice that I derived from FileBasedPreviewHandler. You'll need to override CreatePreviewHandlerControl and return a new instance of your own Control that is derived from FileBasedPreviewHandlerControl. The boilerplate is below. It's inside Load() where you create whatever WinForms controls you need to and add them to the this.Controls collection.
using C4F.DevKit.PreviewHandler.PreviewHandlerFramework;

namespace C4F.DevKit.PreviewHandler.PreviewHandlers
{
    [PreviewHandler("Hanselman Silly vCard Preview Handler", ".vcf", "{42810C0B-FEA8-4dbf-A711-5634DFBA9F3B}")]
    [ProgId("C4F.DevKit.PreviewHandler.PreviewHandlers.vCardPreviewHandler")]
    [Guid("D193B258-AC07-4139-B334-C20F18F4FC7C")]
    [ClassInterface(ClassInterfaceType.None)]
    [ComVisible(true)]
    public sealed class vCardPreviewHandler : FileBasedPreviewHandler
    {
        protected override PreviewHandlerControl CreatePreviewHandlerControl()
        {
            return new vCardPreviewHandlerControl();
        }

        private sealed class vCardPreviewHandlerControl : FileBasedPreviewHandlerControl
        {
            public override void Load(FileInfo file)
            {
//ADD STUFF HERE } } } }

vCards are funky things, and there's multiple versions of the format. The general format is like this:

BEGIN:VCARD
VERSION:2.1
N;LANGUAGE=en-us:Hanselman;Scott
FN:Scott Hanselman
ORG:Microsoft
TITLE:Senior Program Manager
TEL;WORK;VOICE:+1 (503) 766-2048
TEL;HOME;VOICE:+1 (503) 766-2048
TEL;CELL;VOICE:+1 (503) 766-2048
ADR;WORK;PREF:;;One Microsoft Way;Redmond;WA;11111;United States of America
LABEL;WORK;PREF;ENCODING=QUOTED-PRINTABLE:One Microsoft Way=0D=0A=
Redmond, WA  11111
ADR;HOME:;;5 Main Street;Main Town;OR,;12345;United States of America
LABEL;HOME;ENCODING=QUOTED-PRINTABLE:5 Main Street=0D=0A=
Main Town, OR, 12345
URL;WORK:
http://www.hanselman.com
EMAIL;PREF;INTERNET:firstname@lastname.com
REV:20070810T050105Z
END:VCARD

But there's a million extensions to this format and things can get very complex very fast. I set myself a goal of getting something passable working in a few hours, so I decided to preview the vCard in a DataGrid. That made Load() look like this:

public override void Load(FileInfo file)
{
    DataGridView grid = new DataGridView();
    grid.DataSource = ConvertVCardToDataTable(file);
    grid.ReadOnly = true;
    grid.Dock = DockStyle.Fill;
    grid.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
    Controls.Add(grid);
}

Next, I took the FileInfo and spun through it, breaking up each line and sticking it into a static DataTable, the format most friendly to the DataGridView.

private static DataTable ConvertVCardToDataTable(FileInfo file)
{
    DataTable table = new DataTable();
    table.Locale = System.Threading.Thread.CurrentThread.CurrentCulture;
    table.TableName = file.Name;

    using (StreamReader sr = file.OpenText())
    {
        table.Columns.Add("Data");
        table.Columns.Add("Value");

        string line;
        while ((line = sr.ReadLine()) != null)
        {
            if (line.Length > 3)
            {
                string[] parts = ProcessVCardLine(line);
                if (parts != null && parts.Length == 2)
                {
                    table.Rows.Add(parts);
                }
            }
        }
    }
    return table;
}

ProcessVCardLine just returns a string array of "name,value" given a single vCard line. Again, I never said it was pretty, I just said it worked.

private static string[] ProcessVCardLine(string line)
{
    //This is by no means a complete or even passable parsing of the fairly complex vCard format.
    List<string> nameValue = new List<string>();

    if (line.StartsWith("BEGIN:VCARD")) return null;
    if (line.StartsWith("VERSION:")) return null;
    string[] parts = line.Split(':');
    if (parts.Length == 2)
    {
        AddVCardLine(parts, ref nameValue, "TZ", "TimeZone");
        AddVCardLine(parts, ref nameValue, "NICKNAME", "Nickname");
        AddVCardLine(parts, ref nameValue, "N", "Name");
        AddVCardLine(parts, ref nameValue, "FN", "Friendly Name");
        AddVCardLine(parts, ref nameValue, "ORG", "Organization");
        AddVCardLine(parts, ref nameValue, "TITLE", "Title");
        AddVCardLine(parts, ref nameValue, "TEL", "Phone");
        AddVCardLine(parts, ref nameValue, "ADR", "Address");
        AddVCardLine(parts, ref nameValue, "URL", "Website");
        AddVCardLine(parts, ref nameValue, "EMAIL", "Email");
        AddVCardLine(parts, ref nameValue, "X-MS-IMADDRESS", "IM");
    }

    return nameValue.ToArray();
}

private static void AddVCardLine(string[] parts, ref List<string> nameValue, string name, string friendlyName)
{
    if (parts[0].StartsWith(name) && parts[1] != null)
    {
        nameValue.Add(friendlyName);
        nameValue.Add(parts[1].Replace(";", ",").Trim().Trim(','));
    }
}

Registry Editor Because this is a .NET assembly that will be called by an app expecting a COM dll, you'll need to put it in the GAC and run Regasm on it.

I made this easier during development by adding these two lines to the Post-build event command line. You may need to search your system to find where Gacutil.exe and Regasm.exe are on your system, or you can download the .NET Framework 2.0 SDK.

"C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\Gacutil.exe" /i "$(TargetPath)"
"C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\Regasm.exe" /codebase "$(TargetPath)"

Once registered, the new vCard Preview Handlers is available all over Windows to any app that cares to use it.

I've looked at this code a couple of times, not just because it's poopy, but because I felt there must be at least a few clean ways I could have made the code cleaner/terser using some of the new C# 3.0 features like Anonymous Types, and yield. Any ideas?

Here's the real tragedy. After I wrote this very sad little "just barely good enough" vCard parser, I discovered that the Coding4Fun DevKit already included a very complete vCard parsing implementation.

Curse my metal body! The vCard sample is in the Contacts project within C4FDevKit and it's scrumptious. Well, live and learn. Anyway, I had fun and it took less than an hour to get a useful (to me) PreviewHandler working. The C4FDevKit includes samples and compiled PreviewHandlers for CSV, generic binary, Icons, XML files via IE, MSIs, PDFs, Resx and Resources, SNK (keys) and Zip Files. Sweet.

You can more easily test your Preview Handlers using the PreviewHandlerHost control on a simple WinForms app, or even easier by using the already-written PreviewHandlerHost at Coding4Fun\C4FDevKit2008v1\Preview Handlers\Samples\VB\PreviewHandlerHostSample as seen in the screenshot above.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Hosting By
Hosted in an Azure App Service
August 13, 2007 8:27
Wow, cool, thanks for linking to my guidgen.com website :-)!
Uwe
August 13, 2007 16:22
Your plug-in will load the CLR inside the explorer process, that's a big no, no.
August 13, 2007 18:57
Another cool control in the kit, related to Preview Handlers, is Ryan Power's Preview Host control. With it, you can preview ANY files with a registered handler in your own .NET application.
August 13, 2007 20:28
I can't wait until windows gets a preview like apples.

Ben
August 13, 2007 22:35
ShayEr,

CLR in the shell is a big no-no indeed, but assuming the framework Scott uses is based on the one from the MSDN mag, then the previewers are actualy hosted out-of-proc on Vista (and that is the reason these do not work on XP, because Outlook can only host them in-proc on XP) - so there is no issue.

Cheers
Daniel

Comments are closed.

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.