Skip Navigation Links

Posts for: Dec 2006

Code Camp Oz 2007 Countdown Sidebar Gadget

Mitch has put out a Code Camp Oz sidebar gadget: 

Link to Code Camp Oz 2007 Sidebar Gadget « notgartner

It's a simple countdown gadget based off of the codebase of the Vista countdown gadget.  I'm hoping that we'll also see much more detailed gadgets for Code Camp.  Some ideas include a "Session roster" gadget and an "Attendee interviews" gadget. 

I think that it would also be cool to have a "fun and geeky" gadget that could get (virtually) passed around at Code Camp.   I'm thinking of a gadget that allows attendees to "collaborate" in some way - it would be an interesting use of gadgets to "connect" people during a major event.

posted on 1/1/2007 9:27:21 AM ( 0 Comments )


How to build a Vista Sidebar Gadget with a Flyout Window

This article discusses how to create a Vista Sidebar Gadget using a technique called "screen scraping" to fetch data from an external web page.  I'm not sure what the actual protocols are for using data from another site but at the very least I think that you should first gain the permission of the owner of the web page before doing so.  If you would like to learn more about screen scraping then this Wikipedia article is a great place to start: Screen Scraping article on Wikipedia.

 

The code that accompanies this article can be downloaded from here.

 

Getting Started

To get things started create a folder named ScraperGadget and add the following things into it:

  1. An HTML file named ScraperGadget.html
  2. An HTML file named Flyout.html
  3. An XML manifest file named Gadget.xml
  4. A JavaScript file named Main.js
  5. A CSS file named Main.css
  6. A CSS file named Flyout.css
  7. Create a folder named "Images" and add an icon for yourself and one for the gadget. 

In my images folder I have the following images for my icons:

Scraper Gadget images

Adding a Manifest

Open the Gadget.xml file and add the following content for your manifest definition:

<?xml version="1.0" encoding="utf-8"?>
<gadget>
    <name>Dilbert Homepage Scraper Gadget</name>
    <namespace>MarkItUp.Gadgets</namespace>
    <version>1.0</version>
    <author name="Darren Neimke">
        <info url="http://MarkItUp.com" />
        <logo src="images/MarkItUpIcon.jpg"/>
    </author>
    <copyright>MarkItUp.com 2006</copyright>
    <description>
        A Gadget allows a user to display content from the Dilbert home page
        which is located here: http://dilbertblog.typepad.com/.
    </description>
    <icons>
        <icon height="48" width="48" src="images/scraper.jpg"/>
    </icons>
    <hosts>
        <host name="sidebar">
            <base type="HTML" apiVersion="1.0.0" src="scrapergadget.html"/>
            <permissions>Full</permissions>
            <platform minPlatformVersion="1.0"/>
            <defaultImage src="images/scraper.jpg"/>
        </host>
    </hosts>
</gadget>

 To learn more about the makeup of a Gadget manifest file, view the following MSDN article:

http://msdn2.microsoft.com/en-us/library/aa965879.aspx

Defining the HTML User Interface

Finally, add some HTML into the ScraperGadget.html file to get things started:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
    <head>
        <title>Dilbert Homepage Scraper Gadget - Main</title>
        <script src="main.js" language="javascript" type="text/javascript"></script>
        <link href="main.css" rel="stylesheet" type="text/css" />
    </head>
    <body onload="initializeMain();">
        <ul id="myList"></ul>
        <div id="errorView"></div>
    </body>
</html>

That's all that we need at this stage for the main view of our gadget which will ultimately display a list of the titles of the posts on the main page of the Dilbert website.  You can see how the gadget has a reference to both the main.js file and also the main.css file and if you take a look at the body tag, you can see that we call a function named initializeMain() to get things rolling.

A pinch of CSS

To make our gadget look a little nice we can add some CSS code to spice up the UL and LI elements and alter the default font sizes to get more text into our small viewing space.  Here is the code for Main.css:

body {
    margin: 4px;
    width: 130px;
    height: 245px;
    font: 9pt Calibri;
    color: #000;
    background-color:#cccccc;
}

a {
    color: #ffffff;
    text-decoration: none;
    text-overflow:ellipsis;
}

a:hover {
    color: red;
    text-decoration: underline;
}

ul {
    width: 90%;
    border: 1px solid #000;
    background-color: #8aa;
    padding: 0px 5px ;
    cursor: default;
    margin-left: 0px;
}

ul li {
    list-style-type: none;
    margin: 0px;
    position: relative;
}

ul li:hover {
background-color: #ffa;
color: #000;
}

 

The Javascript behavior code

The initializeMain function is responsible for grabbing the source of the page that we are scraping, breaking it down into an object model, and then displaying elements of that object model onto the page.  Here is the content of the initializeMain function:

 

function initializeMain() {
    try {
        var scraper = new WebScraper() ;
        gPageString = scraper.Scrape(gURL) ;
        parseDocument() ;
        renderDocument() ;
    } catch ( ex ) {
        displayError( ex.message ) ;
    }
}

There's some error handling and other stuff in there but it's mostly just fetching data and formatting it on the page.  You can learn about the underlying screen scraping code by reading the following article and by downloading the source files for this article and going through them:

http://markitup.com/Posts/Post.aspx?postId=96d2941f-591f-4363-b5a5-0535ef710f1b

One interesting thing about the parsing of the HTML is that we break it down into an object model before we display it - this makes the data much simpler to work with when the gadget is running.  Here's the code for the Article data class that we store each article's data in:

function Article(title, body, index) {
    this.Title = title ;
    this.Body = body ;
    this.Index = index ;
}

Each instance of this class will have a Title, Body, and Index property that we can access when we want to use the data.  In the parseDocument function we actually grab each article from the page, create an Article object for each one, and store the whole lot of them in a globally-scoped Array so that we can use and access the data on-demand quite easily.  The code for the parseDocument function is shown here:

function parseDocument() {
    var titlePattern = "<h3 class=\"entry-header\">(([^<]|.)+?)</h3>" ;
    var bodyPattern = "<div class=\"entry-body\">(([^<]|.)+?)</div>" ;
    var matches = new RegexHelper().Matches( gPageString, titlePattern ) ;

    var titles = new Array() ;
    for( i=0; i<matches.length; i++ ) {
        titles[i] = matches[i].Groups[0] ;
    }

    matches = new RegexHelper().Matches( gPageString, bodyPattern ) ;
    for( i=0; i<matches.length; i++ ) {
        var body = matches[i].Groups[0] ;
        var article = new Article(titles[i], body, i) ;
        gArticles[i] = article ;
    }
}

Once we have our array (gArticles) of Article objects, all that remains is to render a list of their titles in our gadget - this task is performed by the renderDocument function:

function renderDocument() {

    for( i=0; i<gArticles.length; i++ ) {
        var article = gArticles[i] ;
        var text = document.createTextNode(article.Title);
        var e = document.createElement("li");
        var a = document.createElement("a");
        a.dataIndex = article.Index; // custom prop

        a.attachEvent('onclick', handleClick);

        a.appendChild(text);
        e.appendChild(a);
        myList.appendChild(e);
    }
}

It's important to highlight that the way that this code wires up the handleClick event hander is not by any means cross-browser compatible, but it's running in the gadget runtime which is the same as IE so that's not important.  The handleClick event handler is a simple function that will be invoked whenever the user clicks on a title and will display the content in a gadget Flyout window.  For now simply add the following code for handleClick function in the main.js file and we'll test our gadget:

function handleClick(event){
    var selectedIndex = event.srcElement.dataIndex ;
    errorView.innerHTML = "You clicked on article " + selectedIndex ;
}

As you can see, this code will simply display the index of the article that was clicked on in our errorView DIV element.

An Initial Test of our Gadget

The simplest way to test the gadget is to open the ScraperGadget.html file in a browser to check that everything runs OK.  Running the page and clicking on an article heading show give you a result similar to this:

Scraper Gadget in browser

Once you've got it working in the browser it's time to package up the gadget and get it running in the Vista Sidebar.  Open the ScraperGadget folder and create a .zip file of all the contents.  When the .zip package is created, rename it to ScraperGadget.gadget (NOTE the .gadget extension!).  Upon renaming to a .gadget file, the icon should change to indicate that the package is now a gadget package.  Double-click on this file to begin the installation.

ScraperGadget gadget file

Double-click on the gadget and press 'Install' when asked.  After doing that the gadget should now appear in the Sidebar like so:

ScraperGadget gadget file

The Dilbert screen scraping gadget is displayed at the head of the list here - just above my custom Cricket Gadgetsmile_regular  If you open the Sidebar gallery (by pressing the + icon at the top of the Sidebar, you will see how the gadget has used the data in the manifest file to display information about itself:

ScraperGadget in gadget gallery

 

Adding the Flyout code

Now we've tested out gadget and can see that the important bits are all working as they should be.  We've rendered the UI for the default view and have our object model stored nicely in memory.  It's time to open that Flyout.html file and get started with creating the flyout behavior.  When we've finished we'll have a flyout that will display the stories behind those headlines, here's a peek at the finished product:

ScraperGadget with flyout

 The behaviour here is similar to the RSS Gadget that comes as a standard gadget with Windows Vista.  In the picture you can see that the user has clicked on the first article in the list and this has then displayed the entire post content in the flyout window.  The title of article is displayed at the top of the flyout and the body of the article is displayed in a scrollable DIV element.  If the user clicks on the same article title in the list, the flyout will close but if they click on another title then its content will be displayed in the flyout.

Just as with the main window, the HTML for the flyout is dirt simple:

 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
    <head>
        <title>Dilbert Homepage Scraper Gadget - Main</title>
        <script src="main.js" language="javascript" type="text/javascript"></script>
        <link href="flyout.css" rel="stylesheet" type="text/css" />
    </head>
    <body>
        <div id="flyoutBackground">
            <h4 id="title"></h4>
            <div id="bodyContent"></div>
        </div>
    </body>
</html>

You can find the CSS for those elements in the download for this article.

Loading the Flyout Window

It's now time to revisit that handleClick function which receives the clicks from the links in the list and add the code for displaying the Flyout.

var gSelectedIndex = -1 ;

function handleClick(event) {
    var idx = event.srcElement.dataIndex ;

    if( idx == gSelectedIndex ) {
        gSelectedIndex = -1 ;
        System.Gadget.Flyout.show = false ;
    }else{
        gSelectedIndex = idx ;
        if(System.Gadget.Flyout.show) {
            addContentToFlyout();
        } else {
            System.Gadget.Flyout.show = true;
            System.Gadget.Flyout.onShow = function() {
                addContentToFlyout();
            }

            System.Gadget.Flyout.onHide = function() {
                gSelectedIndex = -1 ;
            }
        }
    }
}

Here we can see that the handleClick function gets the index of the link that was clicked and compares it against the previously clicked item in the list; if it's the same as the previous selection then we reset the selected index and hide the flyout.  If the index is different to the previously selected one we check to see whether the flyout window is open and then load the content into it with our addContentToFlyout function.  You can also see that whenever the flyout is shown we wire up event handlers for the onShow and onHide events.

Finally, the code for adding content to the flyout is dirt simple:

function addContentToFlyout() {
    try {
        if(System.Gadget.Flyout.show) {
            var flyoutDoc = System.Gadget.Flyout.document;
            var article = gArticles[gSelectedIndex] ;
            flyoutDoc.getElementById("title").innerHTML = article.Title ;
            flyoutDoc.getElementById("bodyContent").innerHTML = article.Body ;
        }
    } catch ( ex ) {
        displayError( ex.message ) ;
    }
}

We simply get a handle to the document in the Flyout window, grab the current article object from our list of articles and then bind the Title and Body properties of that object to some HTML elements in the window.

I encourage you to download the code that accompanies this article and to play around with customizing it for your own purposes.

posted on 12/30/2006 4:33:00 PM ( 24 Comments )


Share Your [WEI] Score

Mitch linked to this great site that allows you to view performance data for WEI scores.

Here's mine:

1

 

Ouch! smile_omg  Time to upgrade this old clunker?

posted on 12/30/2006 10:40:16 AM ( 1 Comments )


RegexHelper - a javascript wrapper for regex usage

Now that I've started to do a lot more work with javascript again - to create Vista Sidebar Gadgets - I need to revert to some of my old helper libraries of useful functions.  One that will probably be very useful is this one that I wrote to assist me when working  with javascript regexen:

Link to ShowUsYour : RegexHelper - a javascript wrapper for regex usage

Here's an example of how you would use that code to help enumerate the headings on the home page of the Dilbert blog and displays them in a list in my Gadget:

window.onload = function() {
    try {
        var url = "http://dilbertblog.typepad.com/" ;
        var titlePattern = "<h3 class=\"entry-header\">(([^<]|.)+?)</h3>" ;
        var scraper = new WebScraper() ;
        var pageString = scraper.Scrape(url) ;
        var matches = new RegexHelper().Matches( pageString, titlePattern ) ;

        for( i=0; i<matches.length; i++ ) {
            var match = matches[i] ;
            var text = document.createTextNode(match.Groups[0]);
            var e = document.createElement("li");
            e.appendChild(text);
            myList.appendChild(e);
         }
    } catch ( ex ) {
        alert( ex.message ) ;
    }
}

That function also uses the WebScraper class that I've been using to grab web pages from within my Gadgets:

 

/////////////////////////////////////////////////////////////////////////////
//
// WEBSCRAPER MODULE
// Author: Darren Neimke
//
/////////////////////////////////////////////////////////////////////////////

// WebScraper Class
function WebScraper() {
    var xmlHttp = null;
   
    this.Scrape = function( url ) {
        var html = "" ;
        initializeProxy() ;
       
        try {
            if (xmlHttp != null) {
                xmlHttp.open("GET", url) ;
                xmlHttp.send(null) ;
                html = xmlHttp.responseText;
            }
        } catch(ex) {
            throw new ErrorObject( ex.message ) ;
        }
    return html ;
}


function initializeProxy() {
    xmlHttp = null ;
    if (window.XMLHttpRequest) {
        xmlHttp = new XMLHttpRequest()
    } else if (window.ActiveXObject) {
        xmlHttp = new ActiveXObject("Microsoft.XMLHTTP")
    }
}
}

posted on 12/30/2006 9:41:01 AM ( 1 Comments )


Web Parts in Action Book Review on DotNetSlackers

I just noticed that DotNetSlackers have posted a review of my book.  It's available to read here:

Link to Book Review: ASP.NET 2.0 Web Parts in Action

I'm very happy with the review and it appears that the reviewer liked the style of the book. 

posted on 12/30/2006 7:46:45 AM ( 0 Comments )


My little Cricket Gadget

I got to work on the little cricket Gadget that I wrote about here yesterday.  I implemented a summary view for the normal state of the Gadget.  In the following image you can see the details of the current South Africa versus India test being shown:

CricketGadget.PNG

 

You can also use the settings window to choose from any other current or recent match:

 

GadgetSettings.PNG

 

Here I have selected to view the 4th Test in the current Ashes series between Australia and England.  Notice that the current score panel is replaced by a "Game Completed on ..." label because this game has finished.

 

CricketGadget.PNG

 

I love the way that my Gadget appears in the Gallery...

 

GadgetSettings.PNG

 

... and you can also expand the details panel in the Gallery to get more information about the gadget...

 

GadgetSettings.PNG

 

Cool eh? smile_teeth

posted on 12/29/2006 10:30:44 PM ( 13 Comments )


Google Trends: C#, VB.net

On the forum for my book today a reader linked to this trend graph of Google searches for C# (Blue) against VB.NET (Red):

C# versus VB.NET

I'm not sure exactly what that means but I thought that it was interesting data. Here's a similar graph for C# (Blue) versus Java (Red).

C$ versus Java

Finally, I also read the following interesting article that was linked to from the Google Trends page: C#: Is the party over?

posted on 12/29/2006 4:12:06 PM ( 1 Comments )


UX Podcast - What is UX?

In this episode James and I take a look at the topic of "What is UX?". We also pose the question of whether developers can create a good UX.

Right click to download and listen to the pilot episode of the UX Podcast: UX Podcast - What is UX?

 

Links

Wikipedia entry for UX

 

Note: the podcast dropped out about half way through but hopefully I did a good job of stitching them together. smile_regular

posted on 12/29/2006 11:09:22 AM ( 0 Comments )


Another cool BlogML testimony :-)

Link to protected virtual void jaysonBlog { : My Last Holdout Switches To Community Server

posted on 12/29/2006 8:32:10 AM ( 0 Comments )


Polar bears face meltdown - USATODAY.com

Today I read an article about how Polar bears may soon be declared a "threatened species":

Link to Polar bears face meltdown - USATODAY.com

One thing in the article that really caught my attention was this graph:

Graph of carbon dioxide in the atmosphere

I find it hard to believe that we'll actually reduce our carbon emissions over the next century which means that my great grandchildren will read about the year 2006 and not believe how clean our atmosphere was.  Kinda scary huh?

posted on 12/28/2006 3:35:58 PM ( 0 Comments )


Letter to the ABC - please give us a Cricket Gadget

I sent the following email to the ABC this morning:

To ABC Sports Online, my name is Darren Neimke and I work for an Australian IT company called Readify. I’m an avid follower of your cricket broadcasts and regularly visit your cricket scorecard pages to get score updates. I’ve seen that you have a Konfabulator cricket desktop widget for getting the scores directly on your desktop but I wondered whether you have plans to create a Vista Sidebar Gadget? Sidebar Gadgets are similar to Konfabulator widgets except for the important distinction that Vista Sidebar comes as a standard component of Windows Vista – meaning that *everyone* who has Windows Vista will have it installed. This will mean that the reach of Vista Sidebar Gadgets will be significantly greater than Konfabulator widgets.

Here’s a brief overview of how Vista Sidebar Gadgets work. When you add a Vista Sidebar Gadget to the Windows Vista Sidebar, it appears in its normal state. Here’s a picture of my desktop with 3 gadgets displayed in their default docked state – a Notes Gadget, a Flickr Calendar Gadget, and a Virtual Earth mapping Gadget:

GadgetsStandard.PNG

When you hover over a Gadget, a settings selector appears which allows you to display the Settings window for the gadget. Here the settings window for my Notes Gadget is displayed allowing me to change the colour and the font settings of my notes:

GadgetSettings.PNG

You can also add a Flyout feature for displaying detailed information from your Gadget. Here the detailed Flyout is displayed for my Virtual Earth gadget:

GadgetFlyout.PNG

I was thinking that we could do something similar for a cricket gadget and use the Settings view to allow the user to select different cricket games to view and use the Flyout for displaying detailed scorecard views. Here is a conceptual view of how I would see an ABC Cricket Vista Sidebar Gadget:

CricketGadget.PNG

If you would like some advice about creating Vista Sidebar Gadgets I would love to help out – heck, I’d even create it for you free of charge smile_regular

I look forward to hearing from you in the near future.

Regards,

Darren Neimke
Readify - Resource Manager 

Suite 206 Nolan Tower | 29 Rakaia Way | Docklands | VIC 3008 | Australia
M: +61 439 855 046 | E: darren.neimke@readify.net | C: darren.neimke@readify.net | W: www.readify.net

posted on 12/28/2006 9:33:29 AM ( 2 Comments )


BlogML now on Wikipedia

Keyvan Nayyeri has done it again.  BlogML now has an entry on Wikipedia

Link to BlogML on Wikipedia

That's awesome! thumbs_upsmile_nerd

posted on 12/27/2006 5:23:14 PM ( 0 Comments )


BlogML used by closing community

This is not great news, Ryan has announced the impending closure of the GeekDojo blogs:

Link to Ryan Rinaldi's Blog : A bit of bad news

In announcing the closure Ryan did mention that each blogger in the community will be able to get a copy of their content as BlogML.  That's thousands of posts, comments, and trackbacks that will be able to be ported to another platform.  Hopefully those bloggers will read Mitch's post titled "Healing the Web" after they've made their decision about relocation.

posted on 12/27/2006 11:35:02 AM ( 2 Comments )


ASP.NET 2.0 Web Parts - Creating an Editor Zone Dialog

Some modern portal sites such as http://my.msn.com/ allow the editing of web parts to be carried out in a dialog window. This article shows the mechanics that are required to allow this to be done.

In my book I showed how to create a Catalog Zone dialog which allowed the web parts catalog zone to be displayed in a separate dialog window above the main page.  On the forums for the book, a user recently asked whether it would be possible to create an Editor Zone dialog - a question that I've also seen on the ASP.NET forums a few times as well.

 

Dialog Editor Zone

 

While creating a catalog zone dialog is a little tricky there's really only one really difficult task - to pass the Type name of the selected web part from the dialog window back to the parent window that opened it.  From there you simply force a postback (__doPostback) and use the IPostbackEventHandler interface to intercept it.  In the handing code you create an instance of the Type that was passed back and add it to a zone; simple! 

Creating an editor zone dialog is much trickier because we need to refer to a single instance of a web part across multiple pages.  The reason that this is so difficult is that the WebPartManager knows what web parts exist of a page and which web parts belong to which page and it does not allow a web part that belongs on one page to be added to another page - you can't even programatically change the page that a web part is connected to.  Luckily the portal framework has a mechanism that allows for web parts to be moved around via a form of serialization known as Importing and Exporting.  Through this mechanism, a web part can be exported from one page into an XML format and then, imported onto a second page using the ImportWebPart method of the WebPartManager.  The following diagrams highlight the steps that I took to get a dialog editor zone solution up and running.

 

Dialog Editor Zone

To launch the dialog I added a special edit verb to each web part which called a client-side javascript function that launched the dialog zone.  To do that I had to create a special base class which added the verb and have all of my web parts inherit from that base class.  The code for adding the special verb looked like this:

 

public override WebPartVerbCollection Verbs {
    get {
        Collection<WebPartVerb> verbs = new Collection<WebPartVerb>();
        HttpContext ctx = HttpContext.Current;

        if (ctx.Request.IsAuthenticated) {

            ClientScriptManager cs = this.Page.ClientScript;
            string postbackReference = cs.GetPostBackEventReference(this, "whatever");

            string js = string.Format("DisplayDialog(\"DialogEditor.aspx?path={0}&wpid={1}&postbackReference={2}\");",
                this.Page.Request.Path,
                this.ID,
                postbackReference
            );

            WebPartVerb editVerb = new WebPartVerb("MyEditVerb", js);
            editVerb.Text = "Edit WebPart"
            verbs.Add(editVerb);
        }

        return new WebPartVerbCollection(verbs);
    }
}

Here you can see that the new edit verb will call a client-side function named DisplayDialog when it is clicked and will pass through the following information:

  1. The path to the dialog editor
  2. The id of the web part to edit
  3. a postback reference that can be used to invoke a postback

The postback reference is special in that we can now implement the IPostbackEventHandler interface on the web part to receive the event notification when the postbackReference is invoked. 

 

Dialog Editor Zone

 

Now that the editor has been launched we need to silently invoke the calling page to get the current web part instance that we'll be editing.  Silently invoking the main page is done using the same technique that I discussed in the post about fixing broken web parts

When the dialog is loaded we first check to see whether a web part ID is passed through and if so, we remove any existing web parts and add the new one that is being edited.  We also set the page to Edit mode:

 

protected override void OnLoad(EventArgs e) {
    base.OnLoad(e);

    if (!IsPostBack) {
        if (!string.IsNullOrEmpty(this.WebPartID)) {
            RemoveAllWebParts();
            AddWebPart();
            WebPartManager1.DisplayMode = WebPartManager.EditDisplayMode;
            WebPartManager1.BeginWebPartEditing(WebPartZone1.WebParts[0]);
        }
    }
}

The code for the RemoveAllWebParts and AddWebPart methods look like so:

 

void RemoveAllWebParts() {
    int countOfParts = WebPartManager1.WebParts.Count;
    for (int i = 0; i < countOfParts; i++) {
        this.WebPartManager1.DeleteWebPart(WebPartManager1.WebParts[i]);
    }
}

private void AddWebPart() {
    string webPartXML = WebPartHelper.ExportWebPart(this.WebPartID, this.PathToEdit, this.Context);
    WebPart wp = WebPartHelper.ImportWebPart(webPartXML, this);
    wp.ID = this.WebPartID;
    WebPartManager1.AddWebPart(wp, WebPartZone1, 0);
    WebPartManager1.SetDirty();
}

The REALLY IMPORTANT thing to note here is the call to SetDirty on the WebPartManager.  If you check the documentation for the WebPartManager you will not see such a method because mine is a custom WebPartManager class.  The SetDirty method simply calls through to the protected SetPersonalizationDirty method of the WebPartManager:

 

public class CustomWebPartManager : WebPartManager {
    public CustomWebPartManager() { }

    public void SetDirty() {
        this.SetPersonalizationDirty();
    }
}

I added the call to SetDirty because generally Personalization changes will only be automatically saved on POST requests and not on GET's.  Because we got to the dialog editor via a GET request we need to manually mark the personalization data as dirty for our changes to get saved.  Failure to do this will mean that our web part is not properly added to the page and will therefore not be available when we attempt to re-access our changes from the main page later on.

To close the dialog editor I've just stuck a close button on the page which invokes the following javascript:

 

protected void btnClose_Click(object sender, EventArgs e) {
    ClientScriptManager cs = this.ClientScript;
    string script = "<script>CloseEditorDialog(\"" + PostbackReference + "\") ;</script>"
    cs.RegisterStartupScript(this.GetType(), "whatever", script);
}

As you can see, closing the dialog will call a CloseEditorDialog function passing in the original PostbackReference string that was created from the verb on the calling web part.  The CloseEditorDialog function will close the editor dialog and invoke the postback by eval'ing the PostBackReference string:

 

function CloseEditorDialog( postbackReference ) {
    if( window.opener ) {
        window.opener.DoEditorPostBack( postbackReference ) ;
    }
    window.close() ;
}

function DoEditorPostBack( postbackReference ) {
    eval(postbackReference) ;
}

 

 

Dialog Editor Zone

 

When the postback is invoked our RaisePostBackEvent method will get called and we will grab the web part from the dialog window and return it as XML: 

public void RaisePostBackEvent(string eventArgument) {
    if (eventArgument == "whatever") {
        string xml = WebPartHelper.ExportWebPart(this.EditorPath, this.Context);
        if (!string.IsNullOrEmpty(xml)) {
            WebPartHelper.CopyWebPartValues(xml, this);
        }
    }
}

The XML is retrieved from the dialog window using the custom ExportWebPart method which, again, silently invokes the editor dialog to get the instance that was just edited:

 

public static string ExportWebPart(string path, HttpContext context) {

    StringBuilder sb = new StringBuilder();
    Page page = (Page)BuildManager.CreateInstanceFromVirtualPath(path, typeof(Page));

    page.Load += delegate {
        WebPartManager wm = WebPartManager.GetCurrentWebPartManager(page);

        if (wm.WebParts.Count > 0) {
            WebPart part = wm.WebParts[0];
            if (part.ExportMode != WebPartExportMode.None) {
                using (StringWriter sw = new StringWriter(sb))
                using (XmlTextWriter xw = new XmlTextWriter(sw)) {
                    wm.ExportWebPart(part, xw);
                }
            }
        }
    };

    ExecutePage(page, path, context);
    return sb.ToString();
}

private static void ExecutePage(Page page, string path, HttpContext context) {
    string originalPath = context.Request.Path;
    context.RewritePath(path);

    try {
        context.Server.Execute(page, TextWriter.Null, false);
    } catch { }

    context.RewritePath(originalPath);
}

 

Dialog Editor Zone

 

For now I have a very dumb method in my WebPartHelper class to copy the values from the XML that came back from the editor dialog and onto the instance.  The method is basically just a huge switch statement which knows how to grab a string value and convert it to several underlying instance types.  I'm sure that this could be done much better with more reflection code and some type converters but I won't take it any further for now.  The method looks like this:

 

/// <summary>
/// Copies the Browsable property values from one web part to another
/// </summary>
/// <param name="webPartXML">The web part values to copy from</param>
/// <param name="copyTo">The web part to copy the values to</param>

public static void CopyWebPartValues( string webPartXML, WebPart copyTo ) {

    XmlDocument doc = new XmlDocument();
    doc.LoadXml(webPartXML);
    XmlNamespaceManager mgr = new XmlNamespaceManager(doc.NameTable);
    mgr.AddNamespace("wp", "http://schemas.microsoft.com/WebPart/v3");

    foreach (XmlNode node in doc.SelectNodes(@"//wp:data/wp:properties/wp:property", mgr)) {
        string propertyName = node.Attributes["name"].Value;
        string propertyValue = (node.FirstChild == null) ? "" : node.FirstChild.Value;

        PropertyInfo prop = copyTo.GetType().GetProperty(propertyName);

        switch (prop.PropertyType.Name) {
            case "Boolean":
                prop.SetValue(copyTo, Convert.ToBoolean(propertyValue), null);
                break;
            case "Integer":
                int b = 0;
                if( int.TryParse(propertyValue, out b )) {
                    prop.SetValue(copyTo, Convert.ToInt32(propertyValue), null);
                }
                break;
            case "DateTime":
                DateTime d = DateTime.MaxValue;
                if( DateTime.TryParse(propertyValue, out d ) ) {
                    prop.SetValue(copyTo, d, null);
                }
                break;
            case "Unit":
                Unit u = new Unit(propertyValue);
                prop.SetValue(copyTo, u, null);
                break;
            case "Color":
                prop.SetValue(copyTo, Color.FromName(propertyValue), null);
                break;
            case "String":
                prop.SetValue(copyTo, propertyValue, null);
                break;
            default:
                // TODO: Handle these
                // System.Web.UI.WebControls.WebParts.WebPartExportMode ExportMode
                // System.Web.UI.WebControls.WebParts.PartChromeType ChromeType
                // System.Web.UI.WebControls.ContentDirection Direction
                // System.Web.UI.WebControls.WebParts.PartChromeState ChromeState
                // System.Web.UI.WebControls.WebParts.WebPartHelpMode HelpMode
                Debug.WriteLine(prop);
                break;
        }
    }
}

posted on 12/27/2006 10:50:41 AM ( 28 Comments )


I Heard the Bells on Christmas Day

 

I heard the bells on Christmas day
Their old familiar carols play,
And wild and sweet the words repeat
Of peace on earth, good will to men.
And thought how, as the day had come,
The belfries of all Christendom
Had rolled along the unbroken song
Of peace on earth, good will to men.
Till ringing, singing on its way
The world revolved from night to day,
A voice, a chime, a chant sublime
Of peace on earth, good will to men.
And in despair I bowed my head
“There is no peace on earth,” I said,
“For hate is strong and mocks the song
Of peace on earth, good will to men.”
Then pealed the bells more loud and deep:
“God is not dead, nor doth He sleep;
The wrong shall fail, the right prevail
With peace on earth, good will to men.”

Historical Note: This hymn was written during the American civil war, as reflected by the sense of despair in the next to last stanza of the current, common presentation (above). The original stanzas 5 and 6 (below) speak of the battle, and are usually omit­ted from hymnals:

Then from each black, accursed mouth
The cannon thundered in the South,
And with the sound the carols drowned
Of peace on earth, good will to men.
It was as if an earthquake rent
The hearth-stones of a continent,
And made forlorn, the households born
Of peace on earth, good will to men.

Retrieved from "http://en.wikipedia.org/wiki/I_Heard_the_Bells_on_Christmas_Day"

posted on 12/24/2006 8:19:25 PM ( 1 Comments )


UX Podcast - The Pilot Episode

I've joined forces with a James Chapman-Smith - a good friend and ex-work colleague - to produce this first podcast in a series about 'user experience' (UX).  In this pilot episode we do some intro's and touch on the topic of developers and XAML - what sort of XAML will developers write?

Right click to download and listen to the pilot episode of the UX Podcast: UX Podcast - The Pilot Episode

posted on 12/22/2006 7:42:02 PM ( 0 Comments )


Creating interesting Gadgets - Getting data from a web service

If you are building a Sidebar Gadget you will nearly always need to get your hands on data of some type.  Some of the common data types that you will want to access are:

  • RSS Feeds from the web
  • RSS Feedstore
  • Web Services
  • Mail and Calendar data
  • Messenger Contacts
  • File and Folder information
  • Other LOB data

In this article I'm going to show how to get data from a web service.  Here's a web part that accesses the ProjectDistributor web services and uses the ListProjectsByLatest method to return a list of recently added projects:

PD Recent Projects Gadget

 

When accessing a web service you can either use a GET method or a POST method to make the web service call.  The simpler of these is a GET as you can simply use a URL with the arguments contained in the querystring to call the web service, here's the GET URL for the ListProjectsByLatest web method:

http://projectdistributor.net/WebServices/ProjectDistributor.asmx/ListProjectsByLatest?countOfItems=5

You can see that this method takes an argument named countOfItems which it uses to determine how many items to return - in this case I am returning 5 items.  By default, ASP.NET web services are configured to deny GET access and so if you are planning to make a GET call, you will need to add the following configuration entry in the web.config file for the applicaiton that is hosting the web service:

<webServices>
    <protocols>
        <add name="HttpGet"/>
    </protocols>
</webServices>

The next thing to do is to write some Javascript code that will call the web service and get the XML contained in the web service response so that we can begin working with that data.

function loadData() {
    gXMLHttp=null ;
    if (window.XMLHttpRequest) {
        gXMLHttp = new XMLHttpRequest()
    } else if (window.ActiveXObject) {
        gXMLHttp = new ActiveXObject("Microsoft.XMLHTTP")
    }

    if (gXMLHttp!=null) {
        gXMLHttp.onreadystatechange = state_Change
        gXMLHttp.open("GET", gWsUrl, true)
        gXMLHttp.send(null)
    } else {
        alert("Could not get an XMLHTTP instance.")
    }
}

function state_Change() {
    var xmlDoc = null ;
    if (gXMLHttp.readyState==4 && gXMLHttp.status==200) {
        xmlDoc = new ActiveXObject("Microsoft.XMLDom");
        xmlDoc.async="false"
        xmlDoc.loadXML(gXMLHttp.responseText);
        processData(xmlDoc);
    }
    xmlDoc = null ;
}

Here we see that an XMLHTTP instance is created and a event handler method named state_Change is wired up to the onreadystatechange event of that object.  When the web service call returns the state_Change method will be called and the readyState for XMLHTTP will be 4 and the status will be 200 (OK).  Once we have that return call we create an XML document instance and load the responseText of the XMLHTTP response into it using hte loadXML method of the XMLDom instance.  Finally, once we have our XML document we pass that instance off to a method that will create our UI - in this case the method is named processData.

The processData method will use XML DOM methods to grab the data from the XML document and then use string concatenation to embed the values into some user interface elements.  The following code displays the processData logic which loops through the nodes in the XML document and renders Project elements as a list of HTML DIV elements with a clickable project name:

function processData(doc) {
    var projects = doc.documentElement.selectNodes("//Project");
    projectItemHolder.innerHTML = "" ;
    var node = null ;
    gBuilder = new Array();

    for (var i=0; i<Math.min(projects.length, 5); i++) {
        buildProjectDiv(projects[i]) ;
    }

    projectItemHolder.innerHTML = gBuilder.join("");
    node = null ;
    projects = null ;
    gBuilder = null ;
}

function buildProjectDiv(node) {
    var idNode = node.selectSingleNode("Id") ;
    var displayNameNode = node.selectSingleNode("DisplayName") ;
    var purposeNode = node.selectSingleNode("PrimaryPurpose") ;
    var dateCreatedNode = node.selectSingleNode("DateCreated") ;

    var id = idNode.childNodes[0].nodeValue ;
    var projectName = displayNameNode.childNodes[0].nodeValue ;
    var purpose = purposeNode.childNodes[0].nodeValue ;
    var dateCreated = dateCreatedNode.childNodes[0].nodeValue.substr(0, 10) ;

    gBuilder.push("<div id=\"") ;
    gBuilder.push(id) ;
    gBuilder.push("\" class=\"ProjectItem\">") ;
    gBuilder.push(" <a class=\"ProjectItemLink\" href=\"http://projectdistributor.net/Projects/Project.aspx?projectId=") ;
    gBuilder.push(id) ;
    gBuilder.push("\" title=\"") ;
    gBuilder.push(projectName) ;
    gBuilder.push("\" onmouseover=\"toggleBack(this.parentElement, true);\" onmouseout=\"toggleBack(this.parentElement, false);\">") ;
    gBuilder.push(projectName) ;
    gBuilder.push("</a><div class=\"ProjectItemName\" style=\"float:left;text-align:left;\">") ;
    gBuilder.push(purpose) ;
    gBuilder.push("</div><div class=\"ProjectItemDate\" style=\"float:right;\">") ;
    gBuilder.push(dateCreated) ;
    gBuilder.push("</div></div>") ;
}

 As you can see, the processData method calls out to a helper method named buildProjectDiv which extracts the values from each XMLNode and places those values into a UI display.

posted on 12/20/2006 2:48:46 PM ( 21 Comments )


Daniel Lehenbauer and Kurt Berglund: Interactive 2D controls on WPF 3D Surfaces

I was chatting with Mitch yesterday about how users might interact with rich media types (audio, video, pictures, etc) in a WPF world and then this morning I came across this great new Channel 9 video: 

Link to Daniel Lehenbauer and Kurt Berglund: Interactive 2D controls on WPF 3D Surfaces

In the video, members of the WPF team are showing off a feature that they've added to allow much richer interactions with 3D objects.

Interacting with 2D objects on a 3D surface.

 

This image is taken from the video and shows a context menu attached to a 3D Virtual Earth globe - which itself sits above a set of photos which are databound to a 3D carousel.  This video has to be seen to be believed!  Just for starters, imagine the implications of this technology on something like learning software.

posted on 12/20/2006 10:05:46 AM ( 0 Comments )


Creating interesting Gadgets - User Interface Elements

Vista Sidebar Gadgets are changing the way that we consume data.  Over the next few weeks I will be looking at some of the interesting behaviors that you can add to your gadgets to make them interesting and I'll be using different Gadgets that I find on MicrosoftGadgets.com to highlight various features along the way.  To get things started I'd like to talk about the various UI elements that you can use to present information to your users.

The default view

Below is an image of one of the core Windows Vista Gadgets (a gadget that ships with the core Vista operating system and therefore is located in your C:\Program Files\Windows Sidebar\Gadgets folder), the Notes Gadget.  Here we see the Notes Gadget in its standard docked state in the Sidebar:

Notes Gadget docked in Sidebar

 

This UI is a simple HTML file which displays an image as its default background that gives it an appearance of a stack of notes.  The default view (HTML file) for a Gadget is a configuration setting in the XML manifest file of the Gadget:

 

<gadget>
    <hosts>
        <host name="sidebar">
            <base type="HTML" apiVersion="1.0.0" src="TestGadget.html" />
        </host>
    </hosts>
</gadget>

 

Customizable Gadgets 

The Notes Gadget provides the user with the option of creating new notes or deleting an existing notes and they perform those interactions by mouse'ing over the Gadget and having additional UI elements displayed.  Here we see the additional UI elements that appear when we mouseover the Notes Gadget:

Mouseover the Notes Gadget displays additional UI options

The elements on the upper-left side of the Gadget are added by the Vista Gadget system and allow the user to perform different actions.  The [x] icon is standard for all Gadgets and allows the user to remove the Gadget from the Sidebar.  Underneath that we have a spanner icon that allows the user to manage the settings of the Gadget, this icon is only present when an HTML file has been associated with the System.Gadget.settingsUI property.  Setting this is done at runtime like so:

System.Gadget.settingsUI = "settings.html"

With the settingsUI property assigned, the Gadget will display the spanner and when the user clicks on that, the contents of the settings.html file will be displayed to allow the user to make configuration changes to their Gadget.

Settings UI displayed for the Notes Gadget

 

Here the settings UI for the Notes Gadget allows the user to choose their note color and change their font settings.  When the user has made their choices and pressed OK, the Gadget would use the Gadget.Settings API to persist that configuration information.  Reading and writing configuration settings is as simple as this:

 

var settingValue = System.Gadget.Settings.readString("SettingName");   // reading

System.Gadget.Settings.writeString("SettingName", mytextbox.value);  // writing

It's important to note that the Settings.html file in the above image did not contain the Title bar with the text "Notes" and nor did it contain the OK or Cancel buttons as these were all provided by the Gadget runtime.  The Title bar text was read by the runtime from the title that is supplied in the Gadget's manifest file.

Providing a detailed view

Another useful UI feature of Gadgets are Flyout's.  Flyout's are used when your Gadget contains some summary information and you want to allow the user to click on that summary data to see a more detailed view of that data.  One of the standard Gadget's which makes use of Flyout's is the Feed Headlines Gadget (another of the standard Vista Sidebar Gadgets).  By default the Feed Headlines Gadget displays a summary of recent feed items in a list.

The Feeds Summary Gadget

 

The user can then click on an item in the list to display a detailed view of that item.  Clicking on the item displays the following Flyout with the detailed content for the RSS item displayed:

 

The Feeds Summary Gadget with Flyout information displayed

 

Again, the Flyout is simply another HTML page which you set on the file property Flyout object.   When it comes time to display the Flyout you simply set the value of the show property of the Flyout to 'true':

System.Gadget.Flyout.file = "flyout.html"

System.Gadget.Flyout.show = true;

As with the Configuration Settings view, the Title bar and surrounding chrome for the Flyout view are provided by the Gadget infrastructure.  To populate data into the Flyout window the Feed Headlines Gadget makes use of the onShow event of the Flyout object to determine when the Flyout is available and then the document property of the Flyout to gain access to the DOM of the HTML document for the flyout view:

System.Gadget.Flyout.onShow = function() {
    addContentToFlyout();
}

function addContentToFlyout() {
    if(System.Gadget.Flyout.show) {
        var flyoutDiv = System.Gadget.Flyout.document;
        flyoutDiv.getElementById("flyoutTitleLink").innerHTML = tempTitle;
        flyoutDiv.getElementById("flyoutTitleLink").href = g_feedURL;

        ....
    }
}

posted on 12/19/2006 11:08:21 PM ( 0 Comments )


Some useful links for evaluating product opportunities

Link to How To Evaluate A Product Opportunity

Four VC's on Evaluating Opportunities

posted on 12/19/2006 7:12:28 PM ( 0 Comments )


Some useful entrepreneurial links

Link to Allen's Blog: Ten Commandments for Entrepreneurs

Link to Julie King's: Creating a Powerful Elevator Pitch

Link to David Beisel's: Seven Common Tactical Mistakes Entrepreneurs Make in their Initial VC Pitch which are Simple to Fix

posted on 12/19/2006 6:51:55 PM ( 0 Comments )


The 20 smartest companies to start now

Want to build something but need good ideas, here's a place to start: 

Link to The 20 smartest companies to start now - September 1, 2006

posted on 12/19/2006 10:56:45 AM ( 0 Comments )


Getting started with developing WPF/E applications

Some links to good resources for getting started with WPF/E:

Link to David Boschmans Weblog : Getting started with developing WPF/E applications

posted on 12/18/2006 9:34:34 PM ( 0 Comments )


audio: Professional Development (PD)

I'm sure that we all have PD programs right?  But what are the best ones?  And how do you really recognize and evaluate the value of PD within an organization?

Right click to download and listen to my thoughts about PD here: audio blog 20061218 - Professional Development

 

Note: audio icon blatantly stolen from Hanselminutes site smile_regular

posted on 12/18/2006 9:31:58 PM ( 1 Comments )


TIME.com: Person of the Year: You

I'm not sure whether it was my book or my blog that did it, but it's great to finally get some real recognition smile_regular

Link to TIME.com: Person of the Year: You -- Dec. 25, 2006 -- Page 1

posted on 12/17/2006 9:01:07 PM ( 0 Comments )


audio: Getting the information that you need

In this podcast (yes Grant, I'm no longer calling them "audio blogs" smile_regular) I briefly discuss my thoughts on books how I've turned away from books being my primary source of information and what my current strategy is for trawling through technical information. 

Right click to download and listen to my thoughts about books here: audio blog 20061215 - Getting the information that you need

 

Note: audio icon blatantly stolen from Hanselminutes site smile_regular

posted on 12/15/2006 12:17:32 PM ( 0 Comments )


Subtext 1.9.3 "Repair Job" Edition Released!

Steve announces the latest release of Subtext: 

Link to Subtext 1.9.3 "Repair Job" Edition Released!

 

In that post he mentions that this latest version now supports version 2.0 of BlogML - well done guys!

posted on 12/14/2006 8:46:02 PM ( 0 Comments )


Things we know

Donald Rumsfield is an intruiging character and one that I would not for a moment presume to pass judgement on - I'll be interested to see how history remembers him though. 

Donald Rumsfield

 

Whether you love him or hate him you have to love the energy that he displays and just how animated the guy is.  My favourite Donald Rumsfield quote can easily be applied to the estimation process for software development:

"There are things that we know we know.  There are known unknowns... things that we now know we don't know.  But there are also unknown unknowns.  There are things we don't know we don't know."

Rumsfield got a caning from the media when he uttered that truth - and you'll get a caning from any customer that you try it on too.  It appears that sometimes people just hate to hear the truth smile_regular

posted on 12/12/2006 1:10:49 PM ( 0 Comments )


Pan's Labyrinth | Official Movie Site | Picturehouse

This looks quite interesting: 

Link to Pan's Labyrinth | Official Movie Site | Picturehouse

 

It premiered at Electric Shadows cinema here in Canberra last week but does not start showing here in Canberra until January.

posted on 12/11/2006 9:52:27 PM ( 0 Comments )


audio: What are Coding Standards?

Another ramble from me, this time I'm talking about what I've been up to during the week and what conversations have captured my thoughts. The one that has me thinking the most was one that I had with Udhay about Coding Standards.

Right click to download and listen to what I don't understand about coding standards: audio blog 20061208 - Coding Standards

 

Note: audio icon blatantly stolen from Hanselminutes site smile_regular

posted on 12/8/2006 8:54:10 PM ( 1 Comments )


Moving from design to reality - building HedKandi.com in Microsoft Office SharePoint Server 2007

 I haven't fully digested this yet but Matty Cosier posted it on the internal Tech List today:

Link to Enterprise Content Management (ECM) Team Blog : Moving from design to reality - building HedKandi.com in Microsoft Office SharePoint Server 2007

 

Looks very interesting.  I'm really hoping that WCM is as simple as it should be with Themes, Master Pages, and Custom Server controls.   I'd really love for these guys to produce a video of how to create a WCM site like this.

posted on 12/5/2006 2:25:57 PM ( 0 Comments )


YouTube - Chasing Cars - Snow Patrol

 

Source: YouTube - Chasing Cars - Snow Patrol

posted on 12/5/2006 11:57:19 AM ( 0 Comments )


audio: The Intro

This is my first attempt at blogging in an audio format.

Right click to download: audio blog 20061201

 

Note: audio icon blatantly stolen from Hanselminutes site smile_regular

posted on 12/1/2006 9:38:46 PM ( 1 Comments )