Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Building a Twitter client for Windows Phone - Building the core UI

DZone's Guide to

Building a Twitter client for Windows Phone - Building the core UI

· Mobile Zone
Free Resource

Download this comprehensive Mobile Testing Reference Guide to help prioritize which mobile devices and OSs to test against, brought to you in partnership with Sauce Labs.

It is now time to proceed with one of the most important parts of MangoTree - the core UI that will be used to show the following data one tap away:

  • Timeline
  • Mentions
  • DMs
  • Retweets
  • Searches

These are the five core capabilities that should be placed in five separate sections. The question is - what control to use? We could go the Metro way and use a Panorama or Pivot control. This would be fine, but in this case there will be redundant swipes (e.g. getting from Timeline to DMs will require at least two swipes). We could also create a list on the main page, something Twitter clients like Beezz are already doing. This is inconvenient because it will require the user to constantly go back and forth between different pages to get to the section they need.

At the end - I decided to use the TabControl. It is not officially supported on Windows Phone, but it works nonetheless since it is a core Silverlight component.

TabControl? But it's not Metro!

Use what's best for the current situation. Do you like Panorama or Pivot? Feel free to implement it that way. From a usability standpoint, TabControl is a better choice for this case. It really is not that big of a problem as other people make it look like.

Step 1: Add the proper namespace reference and the modified style

I hope that after reading the previous article in this series, you already familiarized yourself with the article I wrote on using the TabControl in a Windows Phone application. If not, please proceed there first.

I called my style MetroTabItem and placed it inside the App.xaml file. I then proceeded to create a TabControl as a part of the main working Grid in WorkPage.xaml.

Your TabControl declaration code should look like this:

<cc:TabControl Background="Transparent">
    <cc:TabItem Style="{StaticResource MetroTabItem}" Height="80" Width="89" Header="Tab 1" Foreground="Black"></cc:TabItem>
    <cc:TabItem Style="{StaticResource MetroTabItem}" Height="80" Width="89" Header="Tab 2" Foreground="Black"></cc:TabItem>
    <cc:TabItem Style="{StaticResource MetroTabItem}" Height="80" Width="89" Header="Tab 3" Foreground="Black"></cc:TabItem>
    <cc:TabItem Style="{StaticResource MetroTabItem}" Height="80" Width="89" Header="Tab 4" Foreground="Black"></cc:TabItem>
    <cc:TabItem Style="{StaticResource MetroTabItem}" Height="80" Width="89" Header="Tab 5" Foreground="Black"></cc:TabItem>
</cc:TabControl>

Make sure you set the Background property for the TabControl to Transparent, so we won't have to deal with a white background (set by default).

Now your main screen should closely resemble this:

I edited the default style, so it works according to the currently selected Windows Phone theme - if there is one thing I learned about applications that rely on fixed colors, is that sometimes they don't work quite well when the user decides to switch the theme from dark (default) to light, especially if in the application itself the developer mixed static colors with theme-dependent ones. 

Here is the modified XAML:

<Style x:Key="MetroTabItem" TargetType="cc:TabItem">
    <Setter Property="IsTabStop" Value="False"/>
    <Setter Property="Background" Value="{StaticResource PhoneBackgroundBrush}"/>
    <Setter Property="BorderBrush" Value="#FFA3AEB9"/>
    <Setter Property="BorderThickness" Value="1"/>
    <Setter Property="Padding" Value="6,2,6,2"/>
    <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
    <Setter Property="VerticalContentAlignment" Value="Stretch"/>
    <Setter Property="MinWidth" Value="5"/>
    <Setter Property="MinHeight" Value="5"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="cc:TabItem">
                <Grid x:Name="Root">
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="CommonStates">
                            <VisualStateGroup.Transitions>
                                <VisualTransition GeneratedDuration="0"/>
                                <VisualTransition GeneratedDuration="0:0:0.1" To="MouseOver"/>
                            </VisualStateGroup.Transitions>
                            <VisualState x:Name="Normal"/>
                            <VisualState x:Name="MouseOver">
                                <Storyboard>
                                    <DoubleAnimationUsingKeyFrames BeginTime="0" Duration="00:00:00.001" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="FocusVisualTop">
                                        <SplineDoubleKeyFrame KeyTime="0" Value="0"/>
                                    </DoubleAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Disabled">
                                <Storyboard>
                                    <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="DisabledVisualTopSelected">
                                        <SplineDoubleKeyFrame KeyTime="0" Value="1"/>
                                    </DoubleAnimationUsingKeyFrames>
                                    <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="DisabledVisualTopUnSelected">
                                        <SplineDoubleKeyFrame KeyTime="0" Value="1"/>
                                    </DoubleAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                        </VisualStateGroup>
                        <VisualStateGroup x:Name="SelectionStates">
                            <VisualState x:Name="Unselected"/>
                            <VisualState x:Name="Selected"/>
                        </VisualStateGroup>
                        <VisualStateGroup x:Name="FocusStates">
                            <VisualState x:Name="Focused">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetProperty="Visibility" Storyboard.TargetName="FocusVisualTop">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Unfocused">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetProperty="Visibility" Storyboard.TargetName="FocusVisualElement">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                    <Grid x:Name="TemplateTopSelected" Visibility="Collapsed" Canvas.ZIndex="1">
                        <Border BorderBrush="{StaticResource PhoneForegroundBrush}" BorderThickness="1,1,1,0" Background="{StaticResource PhoneForegroundBrush}" CornerRadius="3,3,0,0" Margin="-2,-2,-2,0">
                            <Border BorderBrush="{StaticResource PhoneForegroundBrush}" BorderThickness="1" CornerRadius="1,1,0,0">
                                <Grid>
                                    <Rectangle Fill="{StaticResource PhoneBackgroundBrush }" Margin="0,0,0,-2"/>
                                    <ContentControl x:Name="HeaderTopSelected" Cursor="{TemplateBinding Cursor}" Foreground="{StaticResource PhoneForegroundBrush}" FontSize="{TemplateBinding FontSize}" HorizontalAlignment="{TemplateBinding HorizontalAlignment}" IsTabStop="False" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalAlignment}"/>
                                </Grid>
                            </Border>
                        </Border>
                        <Border x:Name="FocusVisualTop" BorderBrush="{StaticResource PhoneForegroundBrush}" BorderThickness="1,1,1,0" CornerRadius="3,3,0,0" IsHitTestVisible="false" Margin="-2,-2,-2,0" Visibility="Collapsed"/>
                        <Border x:Name="DisabledVisualTopSelected" Background="{StaticResource PhoneDisabledBrush}" CornerRadius="3,3,0,0" IsHitTestVisible="false" Margin="-2,-2,-2,0" Opacity="0"/>
                    </Grid>
                    <Grid x:Name="TemplateTopUnselected" Visibility="Collapsed">
                        <Border x:Name="BorderTop" BorderBrush="{StaticResource PhoneForegroundBrush}" BorderThickness="1" Background="{StaticResource PhoneDisabledBrush}" CornerRadius="3,3,0,0">
                            <Grid>
                                <ContentControl x:Name="HeaderTopUnselected" Cursor="{TemplateBinding Cursor}" Foreground="{StaticResource PhoneForegroundBrush}" FontSize="{TemplateBinding FontSize}" HorizontalAlignment="{TemplateBinding HorizontalAlignment}" IsTabStop="False" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalAlignment}"/>
                            </Grid>
                        </Border>
                        <Border x:Name="DisabledVisualTopUnSelected" Background="{StaticResource PhoneDisabledBrush}" CornerRadius="3,3,0,0" IsHitTestVisible="false" Opacity="0"/>
                    </Grid>
                    <Border x:Name="FocusVisualElement" BorderBrush="White" BorderThickness="1" CornerRadius="3,3,0,0" IsHitTestVisible="false" Margin="-1" Visibility="Collapsed"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

If you need a reference for existing brushes and colors, please read this article. Now, you can easily compare what we have done:

Looking much better - there is still work to be done on it, but we're getting there.

Step 2: Set TabItem headers

The next step would be properly labeling the TabItem headers, so we know what's linked to each of them. Also, we will probably need some icons. I used the icons from Templarian, adding a white stroke for theme compatibility, and I ended up setting a header template for each TabItem:

<cc:TabControl Background="Transparent">
    <cc:TabItem Style="{StaticResource MetroTabItem}" Height="80" Width="89" Header="Tab 1">
        <cc:TabItem.HeaderTemplate>
            <DataTemplate>
                <Image Source="/Images/appbar.timeline.png"></Image>
            </DataTemplate>
        </cc:TabItem.HeaderTemplate>
    </cc:TabItem>
    <cc:TabItem Style="{StaticResource MetroTabItem}" Height="80" Width="89" Header="Tab 2">
        <cc:TabItem.HeaderTemplate>
            <DataTemplate>
                <Image Source="/Images/appbar.mentions.png"></Image>
            </DataTemplate>
        </cc:TabItem.HeaderTemplate>
    </cc:TabItem>
    <cc:TabItem Style="{StaticResource MetroTabItem}" Height="80" Width="89" Header="Tab 3">
        <cc:TabItem.HeaderTemplate>
            <DataTemplate>
                <Image Source="/Images/appbar.message.png"></Image>
            </DataTemplate>
        </cc:TabItem.HeaderTemplate>
    </cc:TabItem>
    <cc:TabItem Style="{StaticResource MetroTabItem}" Height="80" Width="89" Header="Tab 4">
        <cc:TabItem.HeaderTemplate>
            <DataTemplate>
                <Image Source="/Images/appbar.repeat.png"></Image>
            </DataTemplate>
        </cc:TabItem.HeaderTemplate>
    </cc:TabItem>
    <cc:TabItem Style="{StaticResource MetroTabItem}" Height="80" Width="89" Header="Tab 5">
        <cc:TabItem.HeaderTemplate>
            <DataTemplate>
                <Image Source="/Images/appbar.magnify.png"></Image>
            </DataTemplate>
        </cc:TabItem.HeaderTemplate>
    </cc:TabItem>
</cc:TabControl>

Here is what you ultimately get:

The application finally gets the shape it needs. Time to design the custom TextBox that will be used to post the user's update.

Step 3: Prepare the "status updater" input control

It will be placed in the space currently taken by the page header. It will be a large control, with two additional buttons on it - Upload Picture and Post Update. Obviously, I will not carry labels but rather pictures that represent those.

I am using a regular TextBox control with a custom style attached to it.

<TextBox MaxLength="140" TextWrapping="Wrap" Height="140" Style="{StaticResource StatusTextBox}"></TextBox>

There is no character counter, so it might seem like the user can involuntarily go over the 140 character limit per tweet. To avoid this, I am manually setting the MaxLength property for the TextBox.

The style is defined like this:

<Style x:Key="StatusTextBox" TargetType="TextBox">
<Setter Property="FontFamily" Value="{StaticResource PhoneFontFamilyNormal}"/>
<Setter Property="FontSize" Value="{StaticResource PhoneFontSizeMediumLarge}"/>
<Setter Property="Background" Value="{StaticResource PhoneTextBoxBrush}"/>
<Setter Property="Foreground" Value="{StaticResource PhoneTextBoxForegroundBrush}"/>
<Setter Property="BorderBrush" Value="{StaticResource PhoneTextBoxBrush}"/>
<Setter Property="SelectionBackground" Value="{StaticResource PhoneAccentBrush}"/>
<Setter Property="SelectionForeground" Value="{StaticResource PhoneTextBoxSelectionForegroundBrush}"/>
<Setter Property="BorderThickness" Value="{StaticResource PhoneBorderThickness}"/>
<Setter Property="Padding" Value="2"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TextBox">
<Grid Background="Transparent">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="MouseOver"/>
<VisualState x:Name="Disabled">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="EnabledBorder">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="DisabledOrReadonlyBorder">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="ReadOnly">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="EnabledBorder">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="DisabledOrReadonlyBorder">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="DisabledOrReadonlyBorder">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PhoneTextBoxBrush}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="DisabledOrReadonlyBorder">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PhoneTextBoxBrush}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="DisabledOrReadonlyContent">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PhoneTextBoxReadOnlyBrush}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="FocusStates">
<VisualState x:Name="Focused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="EnabledBorder">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PhoneTextBoxEditBackgroundBrush}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="EnabledBorder">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PhoneTextBoxEditBorderBrush}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Unfocused"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border x:Name="EnabledBorder" Background="{TemplateBinding Background}" Margin="{StaticResource PhoneTouchTargetOverhang}">
<Grid>
<ContentControl x:Name="ContentElement" BorderThickness="0" HorizontalContentAlignment="Stretch" Margin="8,2,68,2" Padding="{TemplateBinding Padding}" VerticalContentAlignment="Stretch">
</ContentControl>
<Button x:Name="StatusImage" Opacity=".6" Height="64" HorizontalAlignment="Right" VerticalAlignment="Top" Padding="0" BorderThickness="0">
<Image Height="64" Source="Images/appbar.right.png" Stretch="Fill" Width="64" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Button>
<Button x:Name="PhotoImage" Opacity=".6" Height="64" HorizontalAlignment="Right" VerticalAlignment="Bottom" Padding="0" BorderThickness="0">
<Image Height="64" Source="Images/appbar.camera.png" Stretch="Fill" Width="64" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Button>
</Grid>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

Notice that at the end I have two Button instances declared, both with images inside them. These buttons have no borders and are highlighted when tapped. 

Your UI should now closely resemble this:

Congratulations! You now have the user interface generally ready for the main work screen.

Analysts agree that a mix of emulators/simulators and real devices are necessary to optimize your mobile app testing - learn more in this white paper, brought to you in partnership with Sauce Labs.

Topics:

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}