Share via


AppNap user interface

The AppNap main page content is pretty much a grid with three elements - the search box, the list of search buttons, and an ad control. There's a picture of it in the first post in this series, and the Xaml looks like:

       <Grid x:Name="ContentPanel" Grid.Row="1">
         <Grid.RowDefinitions>
             <RowDefinition Height="Auto"/>
                <RowDefinition />
             <RowDefinition Height="Auto" />
           </Grid.RowDefinitions>

            <TextBox x:Name="TextQuery" Grid.Row="0" KeyUp="TextQuery_KeyUp" InputScope="Search" />

           <ScrollViewer Grid.Row="1" HorizontalScrollBarVisibility="Disabled" Background="Transparent">
             <ItemsControl x:Name="ListSearch" IsEnabled="False"
                        ItemTemplate="{StaticResource TemplateSearchShortcutPortrait}"
                          HorizontalContentAlignment="Stretch" HorizontalAlignment="Stretch">
                    <ItemsControl.ItemsPanel>
                     <ItemsPanelTemplate>
                          <toolkit:WrapPanel />
                     </ItemsPanelTemplate>
                 </ItemsControl.ItemsPanel>
                </ItemsControl>
           </ScrollViewer>

           <my:AdControl x:Name="AdControl" AdUnitId="Image480_80" ApplicationId="test_client"
                    Grid.Row="2" Height="80" Width="480" IsAutoRefreshEnabled="True" />
        </Grid>

There are a few things in that I want to point out:

First, I've set the text box input scope to Search to give a "search" button graphic for the "enter" key. Setting input scope is almost always worth doing, to tailor the soft keyboard to whatever semantics your textbox has. My search buttons are disabled until the user has typed something: my TextQuery_KeyUp method enables the buttons when the textbox is non-empty (it also behaves as if the first search button is clicked if the user hits "enter" - or "search" as explained earlier). I could have done this using something like MVVM, with the enabled property of the buttons being tied to a non-empty property of the model, but this application is so small that MVVM didn't really seem worth the time and effort.

Next, the buttons list is not a ListBox because I really don't want to select individual elements, I just want to act on them when they're pressed. I could have styled a ListBox such that selected elements didn't look any different to unselected ones, but I decided to use an ItemsControl instead. ItemsControls don't automatically sprout scroll bars when necessary, as ListBoxes do, so I have to wrap the ItemsControl in a ScrollViewer. I'll describe the ItemTemplate in a bit, but the final thing I want to mention here is the use of a WrapPanel from the Silverlight Toolkit: this allows me to have multiple buttons on one row of the list (if they're short enough - which is the case in landscape mode) very easily.

The final row uses the Microsoft advertising SDK - when you sign up for this on pubCenter, you get a unique ApplicationId, but I'm showing the test one here. The AdControl really is quite straightforward to use: add the control the Xaml and that's about it! Do make sure you download the latest SDK: an earlier version would occasionally crash and terminate the application.

Having mentioned the Silverlight Toolkit, although not shown here, I'm also using its transition services to give rather nice turnstile animations between pages in the application. Will Faught gives a good explanation of how to use them.

My data template for the list items is:

       <DataTemplate x:Key="TemplateSearchShortcutPortrait">
         <Button Margin="5" Width="470" Click="SearchButton_Click">
                <TextBlock Text="{Binding Name}" Margin="2" TextAlignment="Center" HorizontalAlignment="Stretch"
                        FontSize="{StaticResource PhoneFontSizeLarge}" />
         </Button>
     </DataTemplate>

They really are just buttons. I struggled for a while to get them to size automatically, but couldn't get this to work in conjunction with the WrapPanel, so I ended up hard coding a width. And, unfortunately, a width that looks good in portrait mode doesn't look as good in landscape, so I have a similarly named template for that mode: I switch between templates in a handler for the page's OrientationChanged event. There must be a better way to do this!

Moving on to the code-behind, the page Loaded event handler sets the list's ItemsSource to the Settings' Shortcuts list mentioned last time (again, could have been done via view model binding if I had gone to the effort of using MVVM). The search button click is the only interesting blob of code:

         private void SearchButton_Click(object sender, RoutedEventArgs e)
        {
            var btn = (FrameworkElement)sender;
            var ss = (SearchShortcut)btn.DataContext;
            var text = this.TextQuery.Text.Trim();
            try
            {
                if (shortcut.Extra.StartsWith("http:"))
                {
                    var uri = new Uri(shortcut.Extra + HttpUtility.UrlEncode(text), UriKind.Absolute);
                    var webTask = new WebBrowserTask { Uri = uri };
                    webTask.Show();
                }
                else
                {
                    var query = text + ' ' + shortcut.Extra;
                    var searchTask = new SearchTask { SearchQuery = query };
                    searchTask.Show();
                }
            }
            catch
            {
            } 
        }

If the shortcut is one that should be activated by hitting a web page (see the first post for an overview), construct a URL and navigate to it; otherwise, use the search task and append the extra text. The unconditional try..catch block is a tad embarrassing: it's been reported that sometimes these task invocations can throw exceptions - I've been too lazy to track down the details, and am not even sure if they occur on actual devices rather than being artefacts of the emulator, but I hedge my bets by silently swallowing anything nasty that results from running the tasks.

Talking about nasty things that result from using these tasks... My application does not make any use of the user's location at all (that is an option for the AdControl but I chose not to use it). However, web search can use location data and, the first time you run a search, it'll ask you if you want to let it access location. This added some frustrating delays to the application publication process because the testers maintained that my app was using location but not informing the user: it took a few extra attempts to persuade them that (a) it wasn't my app asking for location data and (b) I had no control over what the search app could or could not do with that data. Perhaps I should have rolled my own search (using, say, the Bing APIs) instead of relying on the SearchTask - but, as you can see from above, using the SearchTask is really really easy.