This article is compatible with the latest version of Silverlight.
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 a 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).
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.
"{StaticResource Status}" >
Source="{Binding User.ProfileImageUrl}"
Style="{StaticResource UserProfileImage}" />
"{StaticResource StatusData}">
Content="{Binding User.Name}"
NavigateUri="{Binding User.Url}"
Style="{StaticResource UserName}" />
Text="{Binding Text}"
TextStyle="{StaticResource LinkLabelText}"
LinkStyle="{StaticResource LinkLabelLink}"
Style="{StaticResource StatusText}" />
Text="{Binding TimeGone}"
Style="{StaticResource StatusTimeGone}" />
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:
- Create a proxy service to get the user timeline from the Twitter API
- Reference the service from the Silverlight project
- 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.
"array">
Tue Jun 24 16:11:02 +0000 2008
842567566
Tip: Asynchronous Silverlight - Execute on the UI thread http://tinyurl.com/5o2rp9
web
false
false
14341499
silverlightshow
silverlightshow
Sofia, Bulgaria
SilverlightShow.net - Silverlight articles, tutorials, showcase, videos
http://s3.amazonaws.com/twitter_production/profile_images/52594162/estoychev_bigger_normal.png
http://www.silverlightshow.net/
<protected>falseprotected>
29
...
We display only a part of this information so the business objects will reflect only those elements that need to be displayed.
Status
[Serializable]
public class Status
{
public string CreatedAt
{
get;
set;
}
public string Text
{
get;
set;
}
public User User
{
get;
set;
}
public string TimeGone
{
get
{
string[] values = this.CreatedAt.Split( ' ' );
string timeValue = string.Format( "{0} {1}, {2} {3}", values[ 1 ], values[ 2 ], values[ 5 ], values[ 3 ] );
DateTime parsedDate = DateTime.Parse( timeValue );
DateTime relativeTo = DateTime.Now;
// time difference in seconds
double delta = relativeTo.Subtract( parsedDate ).TotalSeconds + DateTime.UtcNow.Subtract( relativeTo ).TotalSeconds;
if ( delta < 60 )
{
return "less than a minute ago";
}
else if ( delta < 120 )
{
return "about a minute ago";
}
else if ( delta < ( 60 * 60 ) )
{
return ( int )( delta / 60 ) + " minutes ago";
}
else if ( delta < ( 120 * 60 ) )
{
return "about an hour ago";
}
else if ( delta < ( 24 * 60 * 60 ) )
{
return string.Format( "about {0} hours ago", ( int )( delta / 3600 ) );
}
else if ( delta < ( 48 * 60 * 60 ) )
{
return "1 day ago";
}
else
{
return ( int )( delta / 86400 ) + " days ago";
}
}
set
{
}
}
}
User
[Serializable]
public class User
{
public string Name
{
get;
set;
}
public string ProfileImageUrl
{
get;
set;
}
public string Url
{
get;
set;
}
}
It's finally time to make the proxy service. Let's first see the code and then I'll give some explanations:
[System.Web.Script.Services.ScriptService]
public class TwitterWebService : System.Web.Services.WebService
{
private const string UserTimelineUri = "http://twitter.com/statuses/user_timeline/{0}.xml";
private const string StatusElementName = "status";
private const string CreatedAtElementName = "created_at";
private const string TextElementName = "text";
private const string ProfileImageUrlElementName = "profile_image_url";
private const string NameElementName = "name";
private const string UserElementName = "user";
private const string UserUrlElementName = "url";
public const int RequestRateLimit = 70;
[WebMethod]
public List GetUserTimeline( string twitterUser, string userName, string password )
{
if ( string.IsNullOrEmpty( twitterUser ) )
{
throw new ArgumentNullException( "twitterUser", "twitterUser parameter is mandatory" );
}
WebRequest rq = HttpWebRequest.Create( string.Format( UserTimelineUri, twitterUser ) );
if ( !string.IsNullOrEmpty( userName ) && !string.IsNullOrEmpty( password ) )
{
rq.Credentials = new NetworkCredential( userName, password );
}
HttpWebResponse res = rq.GetResponse() as HttpWebResponse;
// rate limit exceeded
if ( res.StatusCode == HttpStatusCode.BadRequest )
{
throw new ApplicationException( "Rate limit exceeded" );
}
XDocument xmlStatusData = XDocument.Load( XmlReader.Create( res.GetResponseStream() ) );
List data = ( from status in xmlStatusData.Descendants( StatusElementName )
select new Status
{
CreatedAt = status.Element( CreatedAtElementName ).Value.Trim(),
Text = status.Element( TextElementName ).Value.Trim(),
User = new User()
{
ProfileImageUrl = status.Element( UserElementName ).Element( ProfileImageUrlElementName ).Value.Trim(),
Name = status.Element( UserElementName ).Element( NameElementName ).Value.Trim(),
Url = status.Element( UserElementName ).Element( UserUrlElementName ).Value.Trim()
}
} ).ToList();
return data;
}
}
The web service contains only one method - GetUserTimeline. Basically it just creates a new WebRequest and loads the underlying response stream into an XDocument. Then a new generic list of type Status is created and initialized using LINQ to XML. Nothing special here - it is a normal web service you all have used/built. A couple of constants are defined just before the web method to define the API method address and to simplify the XML work.
Reference the Service
Add a reference to the service we've just created by using the Add Reference context menu item of the Silverlight project. Get the desired user's timeline:
private void RefreshTimeline( object sender, EventArgs e )
{
TwitterWebServiceSoapClient twitterClient = new TwitterWebServiceSoapClient();
twitterClient.GetUserTimelineCompleted +=
new EventHandler( this.GetUserTimelineCompleted );
twitterClient.GetUserTimelineAsync( this.TwitterUser, null, null );
}
On line 4 we pass a GetUserTimelineCompleted which is the target method to be called when the call completes. On line 5 this.TwitterUser is a property that specifies the username of the user whose timeline we want to get.
private void GetUserTimelineCompleted( object sender, GetUserTimelineCompletedEventArgs e )
{
StatusList.ItemsSource = e.Result;
}
In the callback just get the returned result and assign it to the ItemsSource property of the ListBox that contains the tweets.
Create a Timer to Update the Timeline
Twitter is obsessive. There are tweeters that updates their status every few minutes. To keep the widget up to date we need a mechanism to update the timeline every couple of minutes (by default every 3 minutes). To achieve this we can use a DispatcherTimer.
private DispatcherTimer timelineTimer;
private void RefreshTimeline()
{
// first refresh
if ( this.timelineTimer == null )
{
this.timelineTimer = new DispatcherTimer();
this.timelineTimer.Tick += new EventHandler( RefreshTimeline );
this.timelineTimer.Interval = new TimeSpan( 0, 1, 0 );
}
// manual refresh
else
{
this.timelineTimer.Stop();
}
this.RefreshTimeline( this, EventArgs.Empty );
}
A couple of changes are needed in the GetUserTimelineCompleted callback to make it work properly with the DispatcherTimer.
- If an error is encountered during the web service call the timer should be stopped.
- If the timeline is explicitly refreshed by the user the timer should be restarted.
private void GetUserTimelineCompleted( object sender, GetUserTimelineCompletedEventArgs e )
{
if ( e.Cancelled || e.Error != null )
{
this.timelineTimer.Stop();
// process the error
return;
}
StatusList.ItemsSource = e.Result;
// restart the timer if the user explicitly
// call the refresh action
if ( !this.timelineTimer.IsEnabled )
{
this.timelineTimer.Start();
}
}
I've hidden the error processing and other code for clarification.
Summary
Building a Twitter widget is a straight and relatively easy task - depends on what UI you want. You can download the full source code and examine it for yourself. If you have any questions or misunderstandings please comment below and I'll be glad to clarify the things for you.
Related Articles
Animating ListBoxItems - the VisualStateManager by Ivan Dragoev
Silverlight LinkLabel control by Emil Stoychev
Asynchronous Silverlight - Execute on the UI thread by Emil Stoychev
States & Parts Model with VisualStateManager by Karen Corby