Monday, September 20, 2010

Building a ‘real’ Windows Phone 7 Twitter App Part 3 – Sending a new Tweet

In parts one and two I showed you how to create a new Windows Phone 7 Panorama Package.  In part two we added oAuth to authenticate the user with Twitter. This is where the fun stuff happens. I’ll show you how to allow a new user to send a tweet.
We’ll be adding a new xaml page with a textBox for the tweet and an application bar with a save and cancel button.
Right click on the “Views” folder in the solution. Select “Add” then “New Item”.  Pick the “Windows Phone Portrait Page” template and give the page a name, I’ll name the page “TweetPage.xaml”.  Hit the “Add” button when done.
image
In the TweetPage.xaml file, change the “ApplicationTitle” to “Twitt” and the “PageTitle to “new tweet”.  Also remove the applicatoin bar code that is at the bottom.  I”m going to show you how to add it via code.  Your file should look like this:
<phone:PhoneApplicationPage
    x:Class="Twitt.Views.TweetPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    mc:Ignorable="d" d:DesignHeight="768" d:DesignWidth="480"
    shell:SystemTray.IsVisible="True">     <!--LayoutRoot is the root grid where all page content is placed-->
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>         <!--TitlePanel contains the name of the application and page title-->
        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock x:Name="ApplicationTitle" Text="Twitt" Style="{StaticResource PhoneTextNormalStyle}"/>
            <TextBlock x:Name="PageTitle" Text="new tweet" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>         <!--ContentPanel - place additional content here-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"></Grid>
    </Grid>
</phone:PhoneApplicationPage>



Let’s now add a button on our MainPage.xaml to open up this new page.  Open “MainPage.xaml” and find the third panorama item that we added in part 2.  It should look like this:
<!--Panorama item three-->
<controls:PanoramaItem Header="settings">
    <StackPanel>
        <Button Content="Account Settings" Click="Button_Click"/>
    </StackPanel>
</controls:PanoramaItem>
Add a new button and I’ll add names to each button and change the Click EventHandler names to match:
<!--Panorama item three-->
<controls:PanoramaItem Header="settings">
    <StackPanel>
        <Button x:Name="SettingsButton" Content="Account Settings" Click="SettingsButtonClick"/>
        <Button x:Name="TweetButton" Content="New Tweet" Click="TweetButtonClick"/>
    </StackPanel>
</controls:PanoramaItem>
We’ll need to create the new event handler for our new button in the MainPage.xaml.cs file:
private void SettingsButtonClick(object sender, RoutedEventArgs e)
{
    NavigationService.Navigate(new Uri("/Views/TwitterAuthPage.xaml", UriKind.Relative));
} private void TweetButtonClick(object sender, RoutedEventArgs e)
{
    NavigationService.Navigate(new Uri("/Views/TweetPage.xaml", UriKind.Relative));
}

Compile and run the app to ensure that pressing the new button opens our “new tweet” page:. image  image Let’s now add the TextBox and a couple TextBlock labels to our new “TweetPage.xaml” where the user will enter their tweet.  We’ll add the new code inside the “ContentPanel” “Grid” node.
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
    <StackPanel>
        <TextBlock x:Name="TitleTextBlock" HorizontalAlignment="Center" FontSize="{StaticResource PhoneFontSizeLarge}" Text="Twitter Message (tap to edit)" TextWrapping="Wrap"/>
        <TextBlock x:Name="CharactersCountTextBlock" HorizontalAlignment="Center" Text="140 characters remaining"/>
        <TextBox x:Name="TweetTextBox" Height="250" HorizontalAlignment="Stretch" TextWrapping="Wrap" TextChanged="TweetTextBoxTextChanged" KeyUp="MessageTextBoxKeyUp" IsTabStop="true" InputScope="Text"/>
    </StackPanel>
</Grid>
I’ve added one TextBlock for a title above the control to tell the user to “tap to edit”.  I’ve also added a seond TextBlock to update the number of characters remaining, since tweets can only hold a maximum of 140 characters.
I’ve set the “InputScope” property to “Text” on the TextBox control. Setting to “Text” will show the user suggested words as they type them on top of the keypad.  I’ve also added the “MessageTextboxKeyUp” event handler.  I want to dismiss the keypad if the user hit the “Enter” key.  For this to work you also need to set “IsTabStop” property to “true”.
I’ve also hooked up the “TextChanged” Event Handler so that as users type I can update the characters left TextBlock that I added.  Here’s what the code behind looks like:
namespace Twitt.Views
{
    public partial class TweetPage : PhoneApplicationPage
    {
        public TweetPage()
        {
            InitializeComponent();             UpdateRemainingCharacters();
        }         private void UpdateRemainingCharacters()
        {
            CharactersCountTextBlock.Text = String.Format("{0} characters remaining", 140 - TweetTextBox.Text.Length);
        }         private void TweetTextBoxTextChanged(object sender, TextChangedEventArgs e)
        {
            UpdateRemainingCharacters();
        }         private void MessageTextBoxKeyUp(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
            {
                Focus();
            }
        }
    }
}




I’ve made a helper method called “UpdateRemainingCharacters” which I also call from the constructor to set the text as well as being called from “TweetTextBoxTextChanged”.
Running the app, the “new tweet” page should now look like this:
image
If you select the TextBox and start typing you should see the keypad and suggested words I previously mentioned:
image
Next, we’ll add the application bar to the bottom for our “tweet” and cancel buttons.  In the TweetPage.xaml.cs, add the following new “CreateApplicationBar” method.  I enclosed it in “#region” to keep my code cleaner.
       public TweetPage()
       {
           InitializeComponent();
           CreateApplicationBar();            UpdateRemainingCharacters();
       }        #region ApplicationBar        private void CreateApplicationBar()
       {
           ApplicationBar = new ApplicationBar { IsMenuEnabled = true, IsVisible = true, Opacity = .9 };            var save = new ApplicationBarIconButton(new Uri("/Resources/Images/check.png", UriKind.Relative));
           save.Text = "tweet";
           save.Click += SaveClick;
           save.IsEnabled = false;            var cancel = new ApplicationBarIconButton(new Uri("/Resources/Images/cancel.png", UriKind.Relative));
           cancel.Text = "cancel";
           cancel.Click += CancelClick;            ApplicationBar.Buttons.Add(save);
           ApplicationBar.Buttons.Add(cancel);
       }        private void CancelClick(object sender, EventArgs e)
       {
           if (NavigationService.CanGoBack)
               NavigationService.GoBack();
       }
       private void SaveClick(object sender, EventArgs e)
       {
       }               #endregion In “CreateApplicaitonBar” I add two buttons, the first one is for sending the tweet to Twitter.  I set the “IsEnabled” property to “false.”  We need to check if the user has validated their account before we enable this button.  Notice I’m using two images “Resources/Images/check.png” and also “Resources/Images/cancel.png”.  We’re going to need to go get these two images.
Application icons are installed with the development tools and located here: C:\Program Files (x86)\Microsoft SDKs\Windows Phone\v7.0\Icons The two icons we need are located at \Icons\dark\appbar.cancel.rest.png and \Icons\dark\appbar.check.rest.png.  We need to add these two icons to our “Resources/Images” folder.  Right click on “Images”, select “Add” and then “Existing Items” and add the two files.  Let’s rename them to “check.png” and “cancel.png”.  Right click on each one and select “Rename”. Try running the app now, and openeing the “new tweet” page, you should see this: image The icons are not showing up.  You need to set each icon as a “content” type instead of the default “resource”.  To do this, right click on each image file and select “Properties” and set the “Build Action” to “Content”. image
Now run the app and the page should now look like this:
image
Try the cancel button.  It should navigate back.
We now need to check if the user has been authenticated with Twitter and if so, enable the “Tweet” icon on the application bar.  I’m going to load the saved settings and check if the “AccessToken” and “AccessTokenSecret” exist, if so enable the first button:
public partial class TweetPage : PhoneApplicationPage
{
    private TwitterAccess _twitterSettings;     public TweetPage()
    {
        InitializeComponent();
        CreateApplicationBar();         UpdateRemainingCharacters();
    }     protected override void OnNavigatedTo(NavigationEventArgs e)
    {
        _twitterSettings = Helper.LoadSetting<TwitterAccess>(Constants.TwitterAccess);
        if (_twitterSettings == null) return;         ((ApplicationBarIconButton)ApplicationBar.Buttons[0]).IsEnabled = !String.IsNullOrEmpty(_twitterSettings.AccessToken) && !String.IsNullOrEmpty(_twitterSettings.AccessTokenSecret);
    }





Now run the app.  If the “Tweet” button is still disabled  on the “new tweet” page, go back to the main page and ensure you hit “Account Settings” to authenticate your Twitter account.  Note:  every time you close the emulator down the Isolated Storage is wiped out.  You’ll have to re-authenticate every time you reload the emulator.  However, if you just exit the app and don’t close the emulator you won’t have to do this. 
Once you’re authenticated you should get this screen.  The “Tweet” button however still won’t do anything, we’ll tackle that next.
image
When we hit the “Tweet” button, we’re going to want a progress bar to show something is going on. In the TweetPage.xaml above the “TitlePanel” I’ve added a ProgressBar.  I’ve set the “Visibility” property to “Collapsed” so it is off by default.  For performance reasons you should also set “IsIndeterminate” to “False” when it’s not visible,
<!--TitlePanel contains the name of the application and page title-->
<ProgressBar x:Name="ProgressBar" VerticalAlignment="Top" IsIndeterminate="False" Visibility="Collapsed"/>
<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
    <TextBlock x:Name="ApplicationTitle" Text="Twitt" Style="{StaticResource PhoneTextNormalStyle}"/>
    <TextBlock x:Name="PageTitle" Text="new tweet" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
</StackPanel>
I’m now going to create a “TwitterHelper” class to wrap all of our calls to Twitter.  Under the “Common” folder create a new class (Right click folder, “Add” –> “Class”:
image
Here’s the code for our “TwitterHelper” class:
using System;
using System.Net;
using System.Windows;
using Hammock;
using Hammock.Authentication.OAuth;
using Hammock.Web; namespace Twitt.Common
{
    public class TwitterHelper
    {
        private readonly TwitterAccess _twitterSettings;
        private readonly bool _authorized;
        private readonly OAuthCredentials _credentials;
        private readonly RestClient _client;
        public event EventHandler TweetCompletedEvent;
        public event EventHandler ErrorEvent;         public TwitterHelper(TwitterAccess settings)
        {
            _twitterSettings = settings;             if (_twitterSettings == null || String.IsNullOrEmpty(_twitterSettings.AccessToken) ||
               String.IsNullOrEmpty(_twitterSettings.AccessTokenSecret))
            {
                return;
            }             _authorized = true;             _credentials = new OAuthCredentials
            {
                Type = OAuthType.ProtectedResource,
                SignatureMethod = OAuthSignatureMethod.HmacSha1,
                ParameterHandling = OAuthParameterHandling.HttpAuthorizationHeader,
                ConsumerKey = TwitterSettings.ConsumerKey,
                ConsumerSecret = TwitterSettings.ConsumerKeySecret,
                Token = _twitterSettings.AccessToken,
                TokenSecret = _twitterSettings.AccessTokenSecret,
                Version = TwitterSettings.OAuthVersion,
            };             _client = new RestClient
            {
                Authority = "http://api.twitter.com",
                HasElevatedPermissions = true
            };
        }         public void NewTweet(string tweetText)
        {
            if (!_authorized)
            {
                if (ErrorEvent != null)
                    ErrorEvent(this, EventArgs.Empty);
                return;
            }             var request = new RestRequest
            {
                Credentials = _credentials,
                Path = "/statuses/update.json",
                Method = WebMethod.Post
            };             request.AddParameter("status", tweetText);             _client.BeginRequest(request, new RestCallback(NewTweetCompleted));
        }         private void NewTweetCompleted(RestRequest request, RestResponse response, object userstate)
        {
            // We want to ensure we are running on our thread UI
            Deployment.Current.Dispatcher.BeginInvoke(() =>
                {
                    if (response.StatusCode == HttpStatusCode.OK)
                    {
                        if (TweetCompletedEvent != null)
                            TweetCompletedEvent(this, EventArgs.Empty);
                    }
                    else
                    {
                        if (ErrorEvent != null)
                            ErrorEvent(this, EventArgs.Empty);
                    }
                });
        }
    }
} I’ll walk through this code now: In the constructor, we take in the TwitterAccess object.  This was already loading in our “TweetPage” so we will pass it through.  This object contains the User Tokens.  If the tokens were passed in successfully we set an “_authorized” member variable to true.  This will be used to ensure we’ll  have stored credentials when we make calls to the class methods.  The constructor also sets up the “_credentials” and “_client” objects as they are needed for all authorized calls to Twitter. Next is the “NewTweet” method.  This is the method we’ll call when the user presses the “Tweet” button.  It will first check that we are authorized and then make the call.  Last in this file, is the “NewTweetCompleted” Callback method, which is called when the Twitter call is completed.  We need to check if the call was successful or not.  I set up two different Events to fire back, one for a successful call and one for a failed call. All we have left is the “TweetPage.xaml.cs” code that makes this call and hooks up the two event handlers.  Here is the code:
  private void SaveClick(object sender, EventArgs e)
{
    PostTweet();
} … private void PostTweet()
{
    if (String.IsNullOrEmpty(TweetTextBox.Text))
        return;     ProgressBar.Visibility = Visibility.Visible;
    ProgressBar.IsIndeterminate = true;     var twitter = new TwitterHelper(_twitterSettings);     // Successful event handler, navigate back if successful
    twitter.TweetCompletedEvent += (sender, e) =>
        {
            ProgressBar.Visibility = Visibility.Collapsed;
            ProgressBar.IsIndeterminate = false;
            if (NavigationService.CanGoBack)
                NavigationService.GoBack();
        };     // Failed event handler, show error
    twitter.ErrorEvent += (sender, e) =>
        {
            ProgressBar.Visibility = Visibility.Collapsed;
            ProgressBar.IsIndeterminate = false;
            MessageBox.Show("There was an error connecting to Twitter");
        };     twitter.NewTweet(TweetTextBox.Text);
}








The first thing this method does is turns on the ProgressBar by setting the “Visibility” property to “Visible” and also turns on the “IsIndeterminate” property to “true”.  We then setup the two callback event handlers (one for success and one for failed calls).  Both will turn the progressbar off.  Failed calls will also display an error message.  Successful calls will navigate back to the previous page. That’s it!  We can now tweet from the app. Next Part of this series I’ll show how to get friend statuses, mentions, direct messages and favorites. You can find the source code for part 3 here: http://twitt.codeplex.com/  Sam






































Saturday, September 18, 2010

Building a ‘real’ Windows Phone 7 Twitter App Part 2 - oAuth

Next I’m going to cover oAuth.  This is probably the trickiest part to this app, but with some help from an OpenSource library called “Hammock,” it won’t be too bad.  I want to credit a blog post I found on the subject,  but I found it wasn’t very detailed and struggled to figure out a few things on my own,
Here’s the link: http://byatool.com/c/connect-your-web-app-to-twitter-using-hammock-csharp/comment-page-1/#comment-9955

Step 1: Add Hammock Library to our project

Update 5/18/2012: Note: Hammoc is no longer hosted on Codeplex, the best way to add it is using NuGet in Visual Studio.

Hammock is an open source REST library for .NET that simplifies calling RESTful services.  All we have to do is download the file from Codeplex and add it to our solution.  The project is located at: http://hammock.codeplex.com/ .
Download the latest binaries.  There is a link on the right side of the page:
image
I just downloaded the zip file to my desktop.  Unzip the file, then browse to find the Windows Phone 7 dll.  At the time of this post, there was only a CTP build but will still work with the RTM version of the WP7 tools.
image 
Next step is to copy “Hammock.WindowsPhone.dll” and “Hammock.WindowsPhone.pdb” into our solution.  In my case, I created a new directory called “Hammock” under my Twitt project and copied the two files over:
image
There’s one additional step you need to do since these files were downloaded off the web and Windows doesn’t trust them.
Right click on the file and select “Properties”.  Notice the “Unblock” button at the bottom right.  Click it then hit “OK”.  Do the same for the second file.
image
Next step is to add the dll to our solution.  Load your “Twitt” solution that you created in Part 1.  In the Solution Explorer locate the “References” folder, right click followed by “Add Reference”, Hit the “Browse” tab and navigate to the location you put the Hammock dll. 
image
Select it and hit “OK”.  You should now see Hammock.WindowsPhone under the Refereces folder:
image
You can try rebuilding the solution to make sure the compiler doesn’t complain.

Step 2: Setup a twitter dev application

You now need to register your app with Twitter.  Go to http://dev.twitter.com/apps .  If you don’t have an existing Twitter account create one and you should be able to register a new app:
image
You’ll get a screen like this next:
image
Give your app a name & description.  If you have a website, provide one.  I just put a fake URL in there for now.  Ensure “Application Type”’ is “Browser”.  Make “Callback URL” something that you’ll remember as we will need this in our app.  I chose http://www.bing.com.  Set Access Type to “Read & Write” so you can post tweets.  I also specified the application icon I created earlier but you can leave that as the default icon.
Click “Register Application” at the bottom and if all was successful you’ll get a new Consumer Key  and Consumer Secret.  You do need to keep these secret and if they are compromised you can regenerate new ones.  I’ll show the ones the issues my app and then change them :-)
image
That’s it!  We’re now done with Twitter.  A coding we will go…
Notice my Consumer key was: gOsH5beAiw138xyj29A5AA
and my Consumer Secret was: 8NZNamErHj8YkpLUQZQVipC1p4KOu3AYYDN3pWQ
We’re going to need these later, so keep them handy.  You can log back into Twitter at any time to get these.

 

Step 3: Setting up oAuth

We’re going to need to add a new page to our application and a button to get to this new page.  So let’s start there.
Open up the MainPage.xaml and examine the xaml a bit for the Panorama control.  Notice there are two Panorama items:
<!--Panorama item one-->
<controls:PanoramaItem Header="first item">
    <!--Double line list with text wrapping-->
    <ListBox Margin="0,0,-12,0" ItemsSource="{Binding Items}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <StackPanel Margin="0,0,0,17" Width="432">
                    <TextBlock Text="{Binding LineOne}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
                    <TextBlock Text="{Binding LineTwo}" TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
                </StackPanel>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
</controls:PanoramaItem>
<!--Panorama item two-->
<!--Use 'Orientation="Horizontal"' to enable a panel that lays out horizontally-->
<controls:PanoramaItem Header="second item">
    <!--Double line list with image placeholder and text wrapping-->
    <ListBox Margin="0,0,-12,0" ItemsSource="{Binding Items}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal" Margin="0,0,0,17">
                    <!--Replace rectangle with image-->
                    <Rectangle Height="100" Width="100" Fill="#FFE5001b" Margin="12,0,9,0"/>
                    <StackPanel Width="311">                                   
                        <TextBlock Text="{Binding LineOne}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
                        <TextBlock Text="{Binding LineTwo}" TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
                    </StackPanel>
                </StackPanel>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
</controls:PanoramaItem>

Let’s add a third item right underneath item 2 for our settings:

<!--Panorama item three-->
<controls:PanoramaItem Header="settings">
    <StackPanel>
        <Button Content="Account Settings" Click="Button_Click"/>
    </StackPanel>
</controls:PanoramaItem>
I put it in a StackPanel because we’re going to add more stuff here later.
I also added a Click event and when I typed it, it automatically created my Code behind in the MainPage.xaml.cs file that looks like this:
private void Button_Click(object sender, RoutedEventArgs e)
{
}
Add the following Code to navigate to the new page that we will create
private void Button_Click(object sender, RoutedEventArgs e)
{
    NavigationService.Navigate(new Uri("/Views/TwitterAuthPage.xaml", UriKind.Relative));
}
Now we should go create that page under the “Views” folder.  Right Click on it and select “Add” –> “New Item”.  Select “Windows Phone Portrait Page” and give it the same name we have above “TwitterAuthPage.xaml” then hit the “Add” button.
 image
Run the app now and you should now have a third Panorama item:
image
Select the new “Account Settings” button and you should see our new page:
image
Next ,we will add the Web Browser Control to the page.  The way oAuth is going to work is our app will pass our client key’s to Twitter and Twitter will return a web page where the user will be prompted for their user name, password and to accept granting access to this app.  This way the user never enters in credentials into the app, where a malicious app developer could hijack those credentials or the app could get compromised.  Once Twitter is successful they will redirect to a web page of ours with a token in the query string parameter.  (Remember we used http://www.bing.com for our callback).  We then need to extract this token and make a final call to Twitter to get the set of client key’s which our app will then store and use for all authorization calls to Twitter.  That made perfect sense right?  OK, well what about that return forward to bing.com?  This is the trick I had to figure out.  We need to trap the navigation event to www.bing.com , extract the token, then cancel it and continue on in our process.
Hopefully you can follow better in code.  I’m going to dump a bunch of code here and won’t explain every detail of the Hammock call but it should be easy to figure out.
First we need to add the browser control to our new xaml page we created.  I’m also going to add a progressbar and default it to “Collapsed”.
Here’s what the changes look like in TwitterAuthPage.xaml.  Notice I’ve added two event handlers one for Navigated and another for Navigating:
<!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="Transparent">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <ProgressBar x:Name="ProgressBar" VerticalAlignment="Top" IsIndeterminate="False" Visibility="Collapsed"/>
    <!--TitlePanel contains the name of the application and page title-->
    <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
        <TextBlock x:Name="ApplicationTitle" Text="MY APPLICATION" Style="{StaticResource PhoneTextNormalStyle}"/>
        <TextBlock x:Name="PageTitle" Text="page name" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
    </StackPanel>
    <!--ContentPanel - place additional content here-->
    <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
        <phone:WebBrowser x:Name="BrowserControl" Navigated="BrowserControl_Navigated" Navigating="BrowserControl_Navigating" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
    </Grid>
</Grid>
We’re going to need a helper class for some settings.  Under the project add a new folder called “Common,”  right click that and “Add” a new “Class” called TwitterSettings.cs
image
image
Make this class look like this.  Don’t forget to substitute your own Consumer Key and Secret:
namespace Twitt.Common
{
    public class TwitterSettings
    {
        public static string ConsumerKey = "gOsH5beAiw138xyj29A5AA";
        public static string ConsumerKeySecret = "8NZNamErHj8YkpLUQZQVipC1p4KOu3AYYDN3pWQ";
        public static string RequestTokenUri = "https://api.twitter.com/oauth/request_token";
        public static string OAuthVersion = "1.0";
        public static string CallbackUri = "http://www.bing.com";
        public static string AuthorizeUri = "https://api.twitter.com/oauth/authorize";
        public static string AccessTokenUri = "https://api.twitter.com/oauth/access_token";
    }
    public class TwitterAccess
    {
        public string AccessToken { get; set; }
        public string AccessTokenSecret { get; set; }
        public string UserId { get; set; }
        public string ScreenName { get; set; }
    }
}
Now in our TwitterAuthPage.xaml.cs file copy all of this code:
using System;
using System.Windows;
using System.Windows.Navigation;
using Hammock;
using Hammock.Authentication.OAuth;
using Microsoft.Phone.Controls;
using Twitt.Common;
namespace Twitt.Views
{
    public partial class TwitterAuthPage
    {
        private string _oAuthTokenSecret;
        private string _oAuthToken;
        public TwitterAuthPage()
        {
            InitializeComponent();
        }
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            GetTwitterToken();
            ProgressBar.Visibility = Visibility.Visible;
            ProgressBar.IsIndeterminate = true;
        }
        private void GetTwitterToken()
        {
            var credentials = new OAuthCredentials
            {
                Type = OAuthType.RequestToken,
                SignatureMethod = OAuthSignatureMethod.HmacSha1,
                ParameterHandling = OAuthParameterHandling.HttpAuthorizationHeader,
                ConsumerKey = TwitterSettings.ConsumerKey,
                ConsumerSecret = TwitterSettings.ConsumerKeySecret,
                Version = TwitterSettings.OAuthVersion,
                CallbackUrl = TwitterSettings.CallbackUri
            };
            var client = new RestClient
            {
                Authority = "https://api.twitter.com/oauth",
                Credentials = credentials,
                HasElevatedPermissions = true,
                          SilverlightAcceptEncodingHeader = "gizp",
                          DecompressionMethods = DecompressionMethods.GZip,
             }; 
            var request = new RestRequest
            {
                Path = "/request_token"
            };
            client.BeginRequest(request, new RestCallback(TwitterRequestTokenCompleted));
        }
        private void TwitterRequestTokenCompleted(RestRequest request, RestResponse response, object userstate)
        {
            _oAuthToken = GetQueryParameter(response.Content, "oauth_token");
            _oAuthTokenSecret = GetQueryParameter(response.Content, "oauth_token_secret");
            var authorizeUrl = TwitterSettings.AuthorizeUri + "?oauth_token=" + _oAuthToken;
            if (String.IsNullOrEmpty(_oAuthToken) || String.IsNullOrEmpty(_oAuthTokenSecret))
            {
                Dispatcher.BeginInvoke(() => MessageBox.Show("error calling twitter"));
                return;
            }
            Dispatcher.BeginInvoke(() => BrowserControl.Navigate(new Uri(authorizeUrl)));
        }
        private static string GetQueryParameter(string input, string parameterName)
        {
            foreach (string item in input.Split('&'))
            {
                var parts = item.Split('=');
                if (parts[0] == parameterName)
                {
                    return parts[1];
                }
            }
            return String.Empty;
        }
        private void BrowserControl_Navigated(object sender, NavigationEventArgs e)
        {
            ProgressBar.IsIndeterminate = false;
            ProgressBar.Visibility = Visibility.Collapsed;
        }
        private void BrowserControl_Navigating(object sender, NavigatingEventArgs e)
        {
            ProgressBar.IsIndeterminate = true;
            ProgressBar.Visibility = Visibility.Visible;
            if (e.Uri.AbsoluteUri.CompareTo("https://api.twitter.com/oauth/authorize") == 0)
            {
                ProgressBar.IsIndeterminate = true;
                ProgressBar.Visibility = Visibility.Visible;
            }
            if (!e.Uri.AbsoluteUri.Contains(TwitterSettings.CallbackUri))
                return;
            e.Cancel = true;
            var arguments = e.Uri.AbsoluteUri.Split('?');
            if (arguments.Length < 1)
                return;
            GetAccessToken(arguments[1]);
        }
        private void GetAccessToken(string uri)
        {
            var requestToken = GetQueryParameter(uri, "oauth_token");
            if (requestToken != _oAuthToken)
            {
                MessageBox.Show("Twitter auth tokens don't match");
            }
            var requestVerifier = GetQueryParameter(uri, "oauth_verifier");
            var credentials = new OAuthCredentials
            {
                Type = OAuthType.AccessToken,
                SignatureMethod = OAuthSignatureMethod.HmacSha1,
                ParameterHandling = OAuthParameterHandling.HttpAuthorizationHeader,
                ConsumerKey = TwitterSettings.ConsumerKey,
                ConsumerSecret = TwitterSettings.ConsumerKeySecret,
                Token = _oAuthToken,
                TokenSecret = _oAuthTokenSecret,
                Verifier = requestVerifier
            };
            var client = new RestClient
            {
                Authority = "https://api.twitter.com/oauth",
                Credentials = credentials,
                HasElevatedPermissions = true
            };
            var request = new RestRequest
            {
                Path = "/access_token"
            };
            client.BeginRequest(request, new RestCallback(RequestAccessTokenCompleted));
        }
        private void RequestAccessTokenCompleted(RestRequest request, RestResponse response, object userstate)
        {
            var twitteruser = new TwitterAccess
            {
                AccessToken = GetQueryParameter(response.Content, "oauth_token"),
                AccessTokenSecret = GetQueryParameter(response.Content, "oauth_token_secret"),
                UserId = GetQueryParameter(response.Content, "user_id"),
                ScreenName = GetQueryParameter(response.Content, "screen_name")
            };
            if (String.IsNullOrEmpty(twitteruser.AccessToken) || String.IsNullOrEmpty(twitteruser.AccessTokenSecret))
            {
                Dispatcher.BeginInvoke(() => MessageBox.Show(response.Content));
                return;
            }
            Helper.SaveSetting(Constants.TwitterAccess, twitteruser);
            Dispatcher.BeginInvoke(() =>
            {
                if (NavigationService.CanGoBack)
                {
                    NavigationService.GoBack();
                }
            });
        }
    }
}
There are a few things to note above.  The OnNavigatedTo method is invoked whenever this xaml page is Navigated to.  This method calls GetTwitterToken. 
You can then see the magic of Hammock in wrapping the REST calls to Twitter.  Notice the request object sets the path to “/request_token”.  It’s requesting a token from Twitter.  Once completed, the TwitterRequestTokenCompleted method is invoked where we extract the auth token and auth secret.  The return method will then navigate to a new Twitter Authorize URL in the browser control with these tokens attached.  If all is good it will redirect us back to http://www.bing.com with a new token attached as a query string parameter.
In BrowserControl_Navigating we check to see if we are navigating to our callback URI if so we cancel the navigation, grab our token and make a final call to Twitter for the users Access Token.
Finally in RequestAccessTokenCompleted we grab the Access Token and Secret and store them in a new object called TwitterAccess.  This was also defined in the TwitterSettings.cs file, followed by saving this object to Isolated Storage.
There’s still one Helper class we need to create that will help load and store our Isolated Storage files.  We need to store our user tokens in Isolated Storage so that we don’t have to get them again.
Create another new class under the “Common” folder called Helper.cs
image
This class is going to use “DataContractSerializer” which requires us to add a new reference to “Systm.Runtime.Serialization
image
Here’s the code for Helper.cs file:
using System;
using System.IO;
using System.IO.IsolatedStorage;
using System.Runtime.Serialization;
using System.Windows;
namespace Twitt.Common
{
    public static class Helper
    {
        private static Object _thisLock = new Object();
        public static T LoadSetting<T>(string fileName)
        {
            using (var store = IsolatedStorageFile.GetUserStoreForApplication())
            {
                if (!store.FileExists(fileName))
                    return default(T);
                lock (_thisLock)
                {
                    try
                    {
                        using (var stream = store.OpenFile(fileName, FileMode.Open, FileAccess.Read))
                        {
                            var serializer = new DataContractSerializer(typeof(T));
                            return (T)serializer.ReadObject(stream);
                        }
                    }
                    catch (SerializationException se)
                    {
                        Deployment.Current.Dispatcher.BeginInvoke(
                            () => MessageBox.Show(String.Format("Serialize file error {0}:{1}", se.Message, fileName)));
                        return default(T);
                    }
                    catch (Exception e)
                    {
                        Deployment.Current.Dispatcher.BeginInvoke(
                            () => MessageBox.Show(String.Format("Load file error {0}:{1}", e.Message, fileName)));
                        return default(T);
                    }
                }
            }
        }
        public static void SaveSetting<T>(string fileName, T dataToSave)
        {
            using (var store = IsolatedStorageFile.GetUserStoreForApplication())
            {
                lock (_thisLock)
                {
                    try
                    {
                        using (var stream = store.CreateFile(fileName))
                        {
                            var serializer = new DataContractSerializer(typeof(T));
                            serializer.WriteObject(stream, dataToSave);
                        }
                    }
                    catch (Exception e)
                    {
                        MessageBox.Show(String.Format("Save file error {0}:{1}", e.Message, fileName));
                        return;
                    }
                }
            }
        }
        public static void DeleteFile(string fileName)
        {
            using (var store = IsolatedStorageFile.GetUserStoreForApplication())
            {
                if (store.FileExists(fileName))
                    store.DeleteFile(fileName);
            }
        }
        public static DateTime ParseDateTime(string date)
        {
            var dayOfWeek = date.Substring(0, 3).Trim();
            var month = date.Substring(4, 3).Trim();
            var dayInMonth = date.Substring(8, 2).Trim();
            var time = date.Substring(11, 9).Trim();
            var offset = date.Substring(20, 5).Trim();
            var year = date.Substring(25, 5).Trim();
            var dateTime = string.Format("{0}-{1}-{2} {3}", dayInMonth, month, year, time);
            var ret = DateTime.Parse(dateTime).ToLocalTime();
            return ret;
        }
        public static void ShowMessage(string message)
        {
            Deployment.Current.Dispatcher.BeginInvoke(() => MessageBox.Show(message));
        }
    }
}
There are a couple other helper methods in here we’ll be using later, like DeleteFile, LoadSetting and ParseDateTime.  One other note: you may notice I have a thread lock around reading and writing my files.  I needed thread safety and just threw a quick lock around them.  I will change this to a reader-writer lock in the future, but this should do for now.
We’ll need one more Constants class to make this all work.  Create another new class under the “Common” folder called “Constants.cs” and add the following:
namespace Twitt.Common
{
    public class Constants
    {
        public static string TwitterAccess = "TwitterAccess";
    }
}
This will be used for our file names.
You should be able to compile and run now.  Then navigate to our new page, you should get a Twitter web page in the browser control:
image
Enter in some valid credentials, then hit the “Allow” link.  If the call was successful you’ll see this screen:
image
You don’t want to hit the back button here since we’re not done.  When authentication is complete the code will go back for us to our settings panorama item.

image
We are now authorized and can go ahead and start reading lists and sending Tweets.

Part 3 will show how to send a Tweet.
Download the source code for part 2 here: http://twitt.codeplex.com/

Sam

Friday, September 17, 2010

Building a ‘real’ Windows Phone 7 Twitter App Part 1

(Update – Added source code link to bottom)

In this series, I’m going to show how to build a ‘real’ twitter app.  One that uses oAuth to authenticate with twitter, can get your friend statuses, mentions and direct messages.  It will also have the ability to Tweet, Re-Tweet and view links from tweets.  I’ll show you how to localize it into the 5 languages that Windows Phone 7 Marketplace supports and how to support tombstoning (ability to restore state when the app gets interrupted by a phone call for example or the user hitting the start button then back again).  I’ll also show you how to work with Isolated Storage and the MVVM pattern.

If you saw my first post, I mentioned I was new to both Windows Phone Development and Silverlight development.  I’ve learned a great deal along the way and hope to share what I’ve learned.  I also have a lot to learn and hope that some of you will provide input on things I can improve.

I plan to make this blog very detailed so it could even beginners.  I’ll just assume you know some c#.  Hopefully more advanced folks can just skip some of these details.

Let’s jump right in.

Part 1: Solution Setup & House Cleaning

Step 1: Install the tools. 

Hopefully you’ve already done that and I won’t give any details except you can get the latest free RTM tools from: http://developer.windowsphone.com 

Step 2: Create a new project.

We’re going to make this a “Panorama” app, You could choose Pivot App instead.   The main difference is that the Panorama has a large ‘panorama’ background image.  I think the design looks great, however one draw back is the default layout gives you less room for your content than a pivot control.  The title area takes up much more space.  But I’ll leave it up to you to pick one.

You’ll want to hit the typical “File” –> “New” –> “Project” and give your app a name as well as a Location to store it.  I picked “Twitt” as my app name.

image

Hit “OK”

Wait a few seconds for the magic to happen, then build the solution.  Before you run it, there’s an important drop down at the top of Visual Studio where you can pick the target of where you want it to run.  It has two options, the emulator or a phone device.  Make sure it’s set to emulator:

image

Now you can run the app.  The emulator will launch; this takes a few seconds for the emulator to start if it’s the first time before your app is deployed.  You don’t need to shut down the emulator when you’re done.  In fact, just hit the “back” button on the bottom of the emulator and the app will exit or you can just stop debugging from within Visual Studio.  This will speed up your next launch if the emulator is kept running.  When you run it, you should see something like this:

image 

Try swiping on the screen to move to the next panoramic item:

image

Did you notice those funky looking numbers along the right edge?  Those are frame rate counters.  Open up the App.xaml.cs file and look at the constructor:

       public App()
       {
           // Global handler for uncaught exceptions.
           UnhandledException += Application_UnhandledException;

           // Show graphics profiling information while debugging.
           if (System.Diagnostics.Debugger.IsAttached)
           {
               // Display the current frame rate counters.
               Application.Current.Host.Settings.EnableFrameRateCounter = true;

               // Show the areas of the app that are being redrawn in each frame.
               //Application.Current.Host.Settings.EnableRedrawRegions = true;

               // Enable non-production analysis visualization mode,
               // which shows areas of a page that are being GPU accelerated with a colored overlay.
               //Application.Current.Host.Settings.EnableCacheVisualization = true;
           }

           // Standard Silverlight initialization
           InitializeComponent();

           // Phone-specific initialization
           InitializePhoneApplication();
       }

Notice the “EnableFrameRateCounter” property is set to true during debugging.  If you don’t care about this, you can set it to false or just ignore,.  It won’t appear when you’re not debugging the app.

Here’s a look at all the files that were created when you created the solution:

image

Under “Properties” you’ll see a few interesting files.  The AppManifest.xml doesn’t contain very much and in fact I’ve never had to touch it.  Assembly info contains information about the assembly that will be created such as the app title, version, etc…

Last in “Properties” is the WMAppManifest.xml file.  It looks like this:

<?xml version="1.0" encoding="utf-8"?>

<Deployment xmlns="http://schemas.microsoft.com/windowsphone/2009/deployment" AppPlatformVersion="7.0">
  <App xmlns="" ProductID="{f0b6633a-b659-4a59-a587-027322b5e096}" Title="Twitt" RuntimeType="Silverlight" Version="1.0.0.0" Genre="apps.normal"  Author="Twitt author" Description="Sample description" Publisher="Twitt">
    <IconPath IsRelative="true" IsResource="false">ApplicationIcon.png</IconPath>
    <Capabilities>
      <Capability Name="ID_CAP_GAMERSERVICES"/>
      <Capability Name="ID_CAP_IDENTITY_DEVICE"/>
      <Capability Name="ID_CAP_IDENTITY_USER"/>
      <Capability Name="ID_CAP_LOCATION"/>
      <Capability Name="ID_CAP_MEDIALIB"/>
      <Capability Name="ID_CAP_MICROPHONE"/>
      <Capability Name="ID_CAP_NETWORKING"/>
      <Capability Name="ID_CAP_PHONEDIALER"/>
      <Capability Name="ID_CAP_PUSH_NOTIFICATION"/>
      <Capability Name="ID_CAP_SENSORS"/>
      <Capability Name="ID_CAP_WEBBROWSERCOMPONENT"/>
    </Capabilities>
    <Tasks>
      <DefaultTask  Name ="_default" NavigationPage="MainPage.xaml"/>
    </Tasks>
    <Tokens>
      <PrimaryToken TokenID="TwittToken" TaskName="_default">
        <TemplateType5>
          <BackgroundImageURI IsRelative="true" IsResource="false">Background.png</BackgroundImageURI>
          <Count>0</Count>
          <Title>Twitt</Title>
        </TemplateType5>
      </PrimaryToken>
    </Tokens>
  </App>
</Deployment>

The <App> node contains some app specific information that you can change if you like.

The <IconPath> node points to the icon that will appear for the installed app.  This icon is a 62x62 pixel icon and you can see the default “ApplicationIcon.png” file exists at the root of the project. 

The <Capabilities> section is important, it tells the OS what capabilities of the phone your application will be using.  We won’t be using most of the ones listed except for the web browser control and networking layer.  So I removed the rest of them:

<Deployment xmlns="http://schemas.microsoft.com/windowsphone/2009/deployment" AppPlatformVersion="7.0">
  <App xmlns="" ProductID="{f0b6633a-b659-4a59-a587-027322b5e096}" Title="Twitt" RuntimeType="Silverlight" Version="1.0.0.0" Genre="apps.normal"  Author="Twitt author" Description="Sample description" Publisher="Twitt">
    <IconPath IsRelative="true" IsResource="false">ApplicationIcon.png</IconPath>
    <Capabilities>
      <Capability Name="ID_CAP_NETWORKING"/>
      <Capability Name="ID_CAP_WEBBROWSERCOMPONENT"/>
    </Capabilities>
    <Tasks>
      <DefaultTask  Name ="_default" NavigationPage="MainPage.xaml"/>
    </Tasks>
    <Tokens>
      <PrimaryToken TokenID="TwittToken" TaskName="_default">
        <TemplateType5>
          <BackgroundImageURI IsRelative="true" IsResource="false">Background.png</BackgroundImageURI>
          <Count>0</Count>
          <Title>Twitt</Title>
        </TemplateType5>
      </PrimaryToken>
    </Tokens>
  </App>
</Deployment>

Notice the <DefaultTask> node.  It points to Mainpage.xaml.  This is the first xaml page that will load when the app launches.  I personally like to put all the view xaml files in a folder called “Views”.  Right click on the project and add a folder called “Views”.  Then move the MainPage.xaml and MainPage.xaml.cs into this folder. It should look like this:

image

Now we have a “Views” directory and also a “ViewModels” directory that will contain the viewModels that go with each view (more about that in a future post).

Since we made this change we also have to change <DefaultTask> node we saw above to point to this new MainPage.xaml location, otherwise it won’t find it and will throw an exception when you run it.  Try running it if you don’t believe me!

Here’s the change:

<?xml version="1.0" encoding="utf-8"?>

<Deployment xmlns="http://schemas.microsoft.com/windowsphone/2009/deployment" AppPlatformVersion="7.0">
  <App xmlns="" ProductID="{f0b6633a-b659-4a59-a587-027322b5e096}" Title="Twitt" RuntimeType="Silverlight" Version="1.0.0.0" Genre="apps.normal"  Author="Twitt author" Description="Sample description" Publisher="Twitt">
    <IconPath IsRelative="true" IsResource="false">ApplicationIcon.png</IconPath>
    <Capabilities>
      <Capability Name="ID_CAP_NETWORKING"/>
      <Capability Name="ID_CAP_WEBBROWSERCOMPONENT"/>
    </Capabilities>
    <Tasks>
      <DefaultTask  Name ="_default" NavigationPage="Views/MainPage.xaml"/>
    </Tasks>
    <Tokens>
      <PrimaryToken TokenID="TwittToken" TaskName="_default">
        <TemplateType5>
          <BackgroundImageURI IsRelative="true" IsResource="false">Background.png</BackgroundImageURI>
          <Count>0</Count>
          <Title>Twitt</Title>
        </TemplateType5>
      </PrimaryToken>
    </Tokens>
  </App>
</Deployment>

Try running it now!  Notice anything strange?  I got this:

image

EEK, what happened to the background panorama image?  Don’t panic, we’ll fix it.  Open up the MainPage.xaml file and locate where the PanoramaBackground.png is set in the <ImageBrush> node:

<!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="Transparent">
    <!--Panorama control-->
    <controls:Panorama Title="my application">
        <controls:Panorama.Background>
            <ImageBrush ImageSource="PanoramaBackground.png"/>
        </controls:Panorama.Background>

Since we moved the MainPage.xaml out of the root and into a new folder and since the system is using relative paths, we’ll need to update the path for this image.  Here’s an addition I usually like to do with most apps.  Create a “Resources” folder for images and any other resources, such as localized resource files (more on those in a future post).

So once again right click on the project and “Add” –> “New Folder” and call it “Resources”.  Then add a child folder to this one called “Images”.  Now move the PanoramaBackground.png to the “Resources/Images” folder.  Your structure should now look like this:

image

We could also move the other images there but I’ll leave them at the root for now and leave the App.xaml there.

Now we can change the MainPage.xaml code to point to this new location:

<!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="Transparent">
    <!--Panorama control-->
    <controls:Panorama Title="my application">
        <controls:Panorama.Background>
            <ImageBrush ImageSource="..\Resources\Images\PanoramaBackground.png"/>
        </controls:Panorama.Background>

You should notice the background image comes back if you run the app.

Let’s go back for one more look at the WMAppManifest.xml file,.  There’s a “Tokens” node at the bottom, that looks like this:

<Tokens>
  <PrimaryToken TokenID="TwittToken" TaskName="_default">
    <TemplateType5>
      <BackgroundImageURI IsRelative="true" IsResource="false">Background.png</BackgroundImageURI>
      <Count>0</Count>
      <Title>Twitt</Title>
    </TemplateType5>
  </PrimaryToken>
</Tokens>

You may be wondering what the Background.png image is for.  This is a 173x173 pixel image that shows up on the Windows Phone start screen if the user “pins” the app.  To check this out, go back to the emulator.  As long as you didn’t close it, the app will still be on the emulator and you can run it from there.  You should see this screen:

image

Hit that top right arrow and as long as you’ve run the app at least once without shutting down the emulator you should get this:

image

There’s our “Twitt” app and that icon you see is the “ApplicationIcon.png” we talked about.  Now hold down the mouse button on “Twitt” and you will get a pop-up menu:

image

Select “pin to start” and it’ll take you back to the start page:

image

And voila, there’s the “Background.png” image.  Notice the text title that says “Twitt”.  You may want to remove the text and have an icon that has the name in the image.  To remove the name (or change it for that matter), go back to the WMAppManifest.xml and locate the title node:

<Tokens>
     <PrimaryToken TokenID="TwittToken" TaskName="_default">
       <TemplateType5>
         <BackgroundImageURI IsRelative="true" IsResource="false">Background.png</BackgroundImageURI>
         <Count>0</Count>
         <Title>Twitt</Title>
       </TemplateType5>
     </PrimaryToken>
   </Tokens>

You can clear it:

<Tokens>
     <PrimaryToken TokenID="TwittToken" TaskName="_default">
       <TemplateType5>
         <BackgroundImageURI IsRelative="true" IsResource="false">Background.png</BackgroundImageURI>
         <Count>0</Count>
         <Title></Title>
       </TemplateType5>
     </PrimaryToken>
   </Tokens>

Now re-run the app to deploy the xap file again to the emulator. 

By the way, when you compile a Windows Phone 7 app (or Silverlight app) you get a “xap” file (pronounced zap) that is really just a zipped file.  Go look in the output directory of our solution and if you change the extention to .zip  you can then unzip it and see what’s inside.  This one single file gets deployed to the phone.

Once you’ve re-run the app, exit the app.  Notice the text is still there?  It doesn’t update the pinned item for some reason when you redeploy, but if you “unpin” it then “pin” it again it will update and you’ll see the title is now gone:

image

Another neat thing about this image is its supported transparency.  If you make an icon with a transparent background it will pick up the user’s “accent” color and apply that to the background.  In this case it would be blue and match the Internet Explorer icon shown in the image.

The last image to look at is the SplashScreenImage.jpg.  This is the image that comes up briefly while the app is starting.  Notice it’s a jpg image and not a png image.  This is for performance reasons. JPG’s are lighter weight and you don’t want heavy processing going on like transparency while starting up the app.  In fact, if your app takes too long to start, it’s possible the OS will kill it before it completes. 

The default SplashScreen looks like this:

image

Right now our 3 images look pretty plain (AppliationIcon.png, Background.png and SplashScreenImage.jpg).  I’ll leave this as a side exercise but what I did is open each file up in Paint.Net .  You can use your favorite image editor instead or even Microsoft Paint. 

I changed my graphics to look like these:

image   image   image

Most people probably leave these details until the end, but sometimes it’s nice to get these out of the way early on.  I also think it looks better as you start showing people, since it gives it a real app feel.  You might also want to change the panoramic background but I decided to leave mine as is.

Let’s do one last thing, which is to set our app name in the MainPage.xaml instead of the default “my application”.

In the MainPage.xaml change the “Title” property to your app name:

<!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="Transparent">
    <!--Panorama control-->
    <controls:Panorama Title="Twitt">

image

In summary, we have created a brand new Panorama based application, did a little house cleaning of the files, modified our icons and learned a little about the various project files.

Next up will be setting up Twitter Authentication which uses oAuth (Open Authentication). 

Two blogs that have been very helpful for me have been Jeff Wilcox's and Peter Torr.  These are both great resources to check out.

You can download the source code here: http://twitt.codeplex.com/

Sam