Recommended


Silverlight Hosting

Skip Navigation LinksHome / Articles / View Article

Silvester - A Silverlight Twitter Widget

+ Add to SilverlightShow Favorites
33 comments   /   posted by Emil Stoychev on Jun 25, 2008
(16 votes)
Categories: Controls , Tutorials , Resources , Samples

Are you a fan of Twitter? Personally I'm, but I'm also a fan of Silverlight. Twitter has a couple of Flash and HTML badges (a.k.a widgets) you can get and put on your blog to let your visitors know what you are up to in the moment. However, Twitter does not have a Silverlight widget. What negligence! :) If you are like me and want not a Flash, but a Silverlight widget on your blog go ahead and read on how you can build one by yourself or just copy the text below to use it.


Copy this HTML snippet, replace the twitterUser init parameter with your Twitter username and put it in your blog to let your visitors see your tweets.

UPDATE:
In this version you can also specify color scheme though the initParams parameter. The default values are as follows:
<param name="initParams" value="twitterUser=silverlightshow,
fontSize=9,
linkColor=#FFFFFF,
textColor1=#C5C5C5,
textColor2=#FFFFFF,
backgroundColor=#000000,
tweetBackgroundColor=#343434,
tweetSelectedBackgroundColor=#515151"
/>

Color scheme parameters: 

key description
fontSize font size
linkColor link color
textColor1 text color
textColor2 time gone, loading text, refresh button text
backgroundColor background color
tweetBackgroundColor background color of the status items
tweetSelectedBackgroundColor background color of the selected status

Download Source Code
 

Why Silvester?

Because it's funny. Remember the Warner Bros' Tweety right? Not sure if it has something to do with Twitter, but it does sounds the same way to me. So in the cartoon there is also a cat named Sylvester that want to eat the bird (Tweety). Thinking about Twitter, Tweety, Sylvester and Silverlight we came up with Silvester :)

Ok, enough laughing, let's start building the widget.

The UI

The Silvester's UI is pretty much simple. Basically what we need is a ListBox control that contains the tweets (user statuses).

 Tweet 
Structure of a tweet - user profile image, username, text, time gone

To arrange the elements I use a two StackPanels - one with Horizontal orientation to position the Image and the tweet details and one with Vertical orientation to position the username, the status and the time.

   1: "{StaticResource Status}" >
   2:     
   3:         Source="{Binding User.ProfileImageUrl}"
   4:         Style="{StaticResource UserProfileImage}"   />
   5:     "{StaticResource StatusData}">
   6:         
   7:             Content="{Binding User.Name}"
   8:             NavigateUri="{Binding User.Url}"
   9:             Style="{StaticResource UserName}"   />
  10:         
  11:             Text="{Binding Text}"
  12:             TextStyle="{StaticResource LinkLabelText}"
  13:             LinkStyle="{StaticResource LinkLabelLink}"
  14:             Style="{StaticResource StatusText}"   />
  15:         
  16:             Text="{Binding TimeGone}"
  17:             Style="{StaticResource StatusTimeGone}"   />
  18:     
  19: 

Fine, but what is this LinkLabel control? This is a custom control that represents a rich TextBlock with the ability to contain hyperlinks. For more information about it please read my previous article.

The UI is not complicated; however there are some elements, like the scrollbars, that need special attention. I won't get any deeper on this topic here, but we plan to write another article that will help you understand how you can easily customize elements using the States and Parts Model with VisualStateManager. If you are interested in the States and Parts Model you can read the great 4 parts tutorials by Karen Corby and Animating ListBoxItems - the VisualStateManager.

The Code

The main task of Silvester is to get the user timeline, i.e. the recent user posts. We add one more feature - refresh the timeline on a predefined interval of time.

Twitter service is available through a public API. However, to make calls from Silverlight to their API they have to explicitly add your domain to allow this (read more on the configuring a web service to enable Silverlight callers here). Of course, that's not an option for us - we want just to put the widget on our blog and have everything work, without having to ask Twitter to add our domain to their clientaccesspolicy.xml file (even if you ask them, like we did, they won't mind).

So what we do to get over this problem? We create a proxy service, hosted on our domain, which we will call in order to reach Twitter.

To achieve our goals we will follow several steps:

  1. Create a proxy service to get the user timeline from the Twitter API
  2. Reference the service from the Silverlight project
  3. Create a timer to update the timeline every minute

Proxy Service to Get the User Timeline

Before creating the service let's see what kind of business object we need. In the UI we display a few fields - user profile image, username (with a hyperlink to the Twitter account), status and time gone. To reflect the XML structure of the user timeline that we receive from the Twitter API we create 2 business objects - Status and User. Let's first take a look at the XML structure.

   1: "array">
   2:     
   3:         Tue Jun 24 16:11:02 +0000 2008
   4:         842567566
   5:         Tip: Asynchronous Silverlight - Execute on the UI thread http://tinyurl.com/5o2rp9
   6:         web
   7:         false
   8:         
   9:         
  10:         false
  11:         
  12:             14341499
  13:             silverlightshow
  14:             silverlightshow
  15:             Sofia, Bulgaria
  16:             SilverlightShow.net - Silverlight articles, tutorials, showcase, videos
  17:             http://s3.amazonaws.com/twitter_production/profile_images/52594162/estoychev_bigger_normal.png
  18:             http://www.silverlightshow.net/
  19:             <protected>falseprotected>
  20:             29
  21:         
  22:     
  23: ...

We display only a part of this information so the business objects will reflect only those elements that need to be displayed.

Status

   1: [Serializable]
   2: public class Status
   3: {
   4:     public string CreatedAt
   5:     {
   6:         get;
   7:         set;
   8:     }
   9: 
  10:     public string Text
  11:     {
  12:         get;
  13:         set;
  14:     }
  15: 
  16:     public User User
  17:     {
  18:         get;
  19:         set;
  20:     }
  21: 
  22:     public string TimeGone
  23:     {
  24:         get
  25:         {
  26:             string[] values = this.CreatedAt.Split( ' ' );
  27:             string timeValue = string.Format( "{0} {1}, {2} {3}", values[ 1 ], values[ 2 ], values[ 5 ], values[ 3 ] );
  28:             DateTime parsedDate = DateTime.Parse( timeValue );
  29: 
  30:             DateTime relativeTo = DateTime.Now;
  31: 
  32:                 // time difference in seconds
  33:             double delta = relativeTo.Subtract( parsedDate ).TotalSeconds + DateTime.UtcNow.Subtract( relativeTo ).TotalSeconds;
  34: 
  35:             if ( delta < 60 )
  36:             {
  37:                 return "less than a minute ago";
  38:             }
  39:             else if ( delta < 120 )
  40:             {
  41:                 return "about a minute ago";
  42:             }
  43:             else if ( delta < ( 60 * 60 ) )
  44:             {
  45:                 return ( int )( delta / 60 ) + " minutes ago";
  46:             }
  47:             else if ( delta < ( 120 * 60 ) )
  48:             {
  49:                 return "about an hour ago";
  50:             }
  51:             else if ( delta < ( 24 * 60 * 60 ) )
  52:             {
  53:                 return string.Format( "about {0} hours ago", ( int )( delta / 3600 ) );
  54:             }
  55:             else if ( delta < ( 48 * 60 * 60 ) )
  56:             {
  57:                 return "1 day ago";
  58:             }
  59:             else
  60:             {
  61:                 return ( int )( delta / 86400 ) + " days ago";
  62:             }
  63:         }
  64: 
  65:         set
  66:         {
  67: 
  68:         }
  69:     }
  70: }

User

   1: [Serializable]
   2: public class User
   3: {
   4:     public string Name
   5:     {
   6:         get;
   7:         set;
   8:     }
   9: 
  10:     public string ProfileImageUrl
  11:     {
  12:         get;
  13:         set;
  14:     }
  15:
  16:     public string Url
  17:     {
  18:         get;
  19:         set;
  20:     }
  21: }

It's finally time to make the proxy service. Let's first see the code and then I'll give some explanations:

   1: [System.Web.Script.Services.ScriptService]
   2: public class TwitterWebService : System.Web.Services.WebService
   3: {
   4:     private const string UserTimelineUri = "http://twitter.com/statuses/user_timeline/{0}.xml";
   5:     private const string StatusElementName = "status";
   6:     private const string CreatedAtElementName = "created_at";
   7:     private const string TextElementName = "text";
   8:     private const string ProfileImageUrlElementName = "profile_image_url";
   9:     private const string NameElementName = "name";
  10:     private const string UserElementName = "user";
  11:     private const string UserUrlElementName = "url";
  12:     public const int RequestRateLimit = 70;
  13: 
  14:     [WebMethod]
  15:     public List GetUserTimeline( string twitterUser, string userName, string password )
  16:     {
  17:         if ( string.IsNullOrEmpty( twitterUser ) )
  18:         {
  19:             throw new ArgumentNullException( "twitterUser", "twitterUser parameter is mandatory" );
  20:         }
  21: 
  22:         WebRequest rq = HttpWebRequest.Create( string.Format( UserTimelineUri, twitterUser ) );
  23: 
  24:         if ( !string.IsNullOrEmpty( userName ) && !string.IsNullOrEmpty( password ) )
  25:         {
  26:             rq.Credentials = new NetworkCredential( userName, password );
  27:         }
  28: 
  29:         HttpWebResponse res = rq.GetResponse() as HttpWebResponse;
  30: 
  31:         // rate limit exceeded
  32:         if ( res.StatusCode == HttpStatusCode.BadRequest )
  33:         {
  34:             throw new ApplicationException( "Rate limit exceeded" );
  35:         }