Thursday, April 4, 2013

Design-time Data for Windows Store Apps with C#

One of my favorite features of XAML is the ability to provide design-time data. This feature is present in WPF, Silverlight, and of course Windows Store apps. The great thing about design-time data is that the developer can create it programmatically when needed or the designer can generate some through Blend. In this post I will share an example of generating design-time data through code. The example is a project I am working on for my upcoming book, and can be downloaded from the website (still in its very early stages) from CodePlex.

In Chapter 5 I am covering the various options for interacting with web services. A company called CDYNE Corporation provides a nice, free weather service that is useful for demonstrating how to connect to a SOAP service. Yes, I know everything today is about REST but trust me, the SOAP services are still around. I’m just glad that the committee that governs the standard was wise enough to declare it is no longer an acronym. In case you didn’t know, SOAP originally stood for “Simple Object Access Protocol” but there was really nothing simple about it. Fortunately Microsoft saw wisdom in WSDL and was kind enough to provide the ability from Visual Studio 2012 to generate a proxy for you.

The focus of this post is design-time data, not web services, so let’s focus on the problem at hand. The web service I am referring to provides two APIs I’ll be using. One will resolve a zip code to a city and provide a forecast, and the other takes various weather “types” and provides a corresponding URL so that you can show a friendly icon for “sunny” or “cloudy” etc.

The first step I take is to model the result. Although I could use the model generated by the proxy, I find it’s often better to create your own domain model with the pieces you want so you don’t have to take a dependency on the service itself. You can easily map the service model to a domain model. The main service returns a result that provides the city and state (if it was successfully resolved) and a collection of entries that represent the forecast for each day of the upcoming week.

A “forecast” generated by the service looks something like this (taken from the proxy itself):

public partial class Forecast : object, System.ComponentModel.INotifyPropertyChanged 
{
         private System.DateTime dateField;
         private short weatherIDField;
         private string desciptionField;
         private temp temperaturesField;
         private POP probabilityOfPrecipiationField;
}

I’ll model it a little differently. Some developers will cry out and gnash their teeth when they see me exposing a property to convert the date instead of using a value converter, but I say … cry away. Here is the entry:

public class ForecastEntry

{
     public DateTime Day { get; set; }
     public string DayText
     {
         get
         {
             return this.Day.Date.ToString("D");
         }
     }
     public int TypeId { get; set; }
     public string Description { get; set; }
     public string PrecipitationDay { get; set; }
     public string PrecipitationNight { get; set; }
     public string TemperatureLow { get; set; }
     public string TemperatureHigh { get; set; }
     public Uri ForecastUri { get; set; } }

And here is the extension method to convert from the service to an instance of my domain entry:

public static ForecastEntry AsForecastEntry(this Forecast forecast)

{
     return new ForecastEntry
     {
         Day = forecast.Date,
         Description = forecast.Desciption,
         PrecipitationDay = forecast.ProbabilityOfPrecipiation.Daytime,
         PrecipitationNight = forecast.ProbabilityOfPrecipiation.Nighttime,
         TemperatureLow = forecast.Temperatures.MorningLow,
         TemperatureHigh = forecast.Temperatures.DaytimeHigh,
         TypeId = forecast.WeatherID
     }; }

That was easy enough – and would have been even easier with a tool like AutoMapper. Now we can create a simple user control that displays a single entry. Here is the XAML (keep in mind I get paid mostly for development, not design):

<Border Width="230" Height="200" Background="DarkBlue" CornerRadius="20">

<
Grid d:DataContext="{Binding Source={d:DesignInstance Type=data:DesignForecastEntry, IsDesignTimeCreatable=True}}"        Margin="10"        Width="210">
     <Grid.RowDefinitions>
         <RowDefinition Height="40"/>
         <RowDefinition Height="60"/>
         <RowDefinition Height="Auto"/>
         <RowDefinition Height="Auto"/>
         <RowDefinition Height="Auto"/>
     </Grid.RowDefinitions>
     <TextBlock Text="{Binding DayText}"
                TextWrapping="Wrap"
                Style="{StaticResource BodyTextStyle}"
                HorizontalAlignment="Center"/>
     <Image Width="50" Height="50" Grid.Row="1">
         <Image.Source>
             <BitmapImage UriSource="{Binding ForecastUri}"/>
         </Image.Source>
     </Image>

     <TextBlock Text="{Binding Description}"
                Grid.Row="2"
                Style="{StaticResource CaptionTextStyle}"
                HorizontalAlignment="Center"
                Margin="5"/>
     <StackPanel Grid.Row="3"
                HorizontalAlignment="Center"
                Orientation="Horizontal">
         <TextBlock Text="Low:"/>
         <TextBlock Text="{Binding TemperatureLow}" Margin="10 0 0 0"/>
         <TextBlock Text=" / High:"/>
         <TextBlock Text="{Binding TemperatureHigh}" Margin="10 0 0 0"/>
     </StackPanel>
     <StackPanel Grid.Row="4" Orientation="Horizontal"
             HorizontalAlignment="Center">
         <TextBlock Text="Chance of Precipitation:"/>
         <TextBlock Text="{Binding PrecipitationDay}" Margin="10 0 0 0"/>
     </StackPanel> </Grid> </Border>

There is no way I would be clever enough to design that simply by tapping out XAML and I’m far too lazy to put an entry and then run the app and repeat. In fact, I like to build from the bottom up – in other words, I worked on the forecast entry first, then the entire forecast, then the page to request the forecast, so if I didn’t have design-time data it would be impossible for me to preview the app before I wired everything in! If you can eyeball that XAML and tell me what it looks like … wow. More power to you! I, on the other hand, just created a simple class:

public class DesignForecastEntry : ForecastEntry

{
     public DesignForecastEntry()
         {
             this.Day = DateTime.Now;
             this.ForecastUri = new Uri(

http://ws.cdyne.com/WeatherWS/Images/mostlycloudy.gif,
UriKind.Absolute);
             this.Description = "Sample day for weather";
             this.PrecipitationDay = "50";
             this.PrecipitationNight = "20";
             this.TemperatureLow = "25";
             this.TemperatureHigh = "49";
             this.TypeId = 1;
         } }

Now the reference in the XAML makes sense. Specifically, you can specify a d:DataContext that is a data context only used at design time. You pass the type and inform the designer that it can be instantiated, and then in design-time you get something beautiful like this:

weatherentry

The best part? Even with my aesthetically challenged design skills, I can tweak the XAML and see the changes reflected immediately. I’m not complaining about the end result, are you? Now there is the whole forecast that I modeled like this:

public class WeatherForecast

{
     public WeatherForecast()
     {
         this.Forecast = new List<ForecastEntry>();
     }
     public string City { get; set; }
    public string State { get; set; }
    public string Result { get; set; }
    public List<ForecastEntry> Forecast { get; private set; } }

And the XAML for the whole forecast:

<Grid d:DataContext="{Binding Source={d:DesignInstance Type=data:DesignForecast, IsDesignTimeCreatable=True}}">
     <Grid.RowDefinitions>
         <RowDefinition Height="Auto"/>
         <RowDefinition Height="Auto"/>
         <RowDefinition Height="*"/>
     </Grid.RowDefinitions>
     <StackPanel Orientation="Horizontal" Margin="10">
         <TextBlock Text="{Binding City}" Style="{StaticResource HeaderTextStyle}"/>
         <TextBlock Text="," Style="{StaticResource HeaderTextStyle}"/>
         <TextBlock Text="{Binding State}" Style="{StaticResource HeaderTextStyle}" Margin="20 0 0 0"/>
     </StackPanel>
     <TextBlock Grid.Row="1"
                 Text="{Binding Result}"
                 Style="{StaticResource PageSubheaderTextStyle}"
                Margin="10"/>
     <GridView Grid.Row="2" ItemsSource="{Binding Forecast}">
         <GridView.ItemTemplate>
             <DataTemplate>
                 <local:ForecastEntry/>
             </DataTemplate>
         </GridView.ItemTemplate>
         <GridView.ItemsPanel>
             <ItemsPanelTemplate>
                 <WrapGrid MaximumRowsOrColumns="1"/>
             </ItemsPanelTemplate>
         </GridView.ItemsPanel>
     </GridView>
 </Grid>

The design-time model for the forecast is kind enough to offer some variety and generate several days of entries.

public class DesignForecast : WeatherForecast

{
     private readonly Uri[] testUris = new[]
      {
new Uri("http://ws.cdyne.com/WeatherWS/Images/mostlycloudy.gif",
                   UriKind.Absolute),
new Uri(http://ws.cdyne.com/WeatherWS/Images/sunny.gif,
UriKind.Absolute)
        };
     public DesignForecast()
     {
         this.City = "Woodstock";
         this.State = "GA";
         this.Result = "Design-mode data";
for (var x = 0; x < 7; x++)
         {
             var offset = 7 - x;
             var entry = new ForecastEntry
             {
                 Day = DateTime.Now.AddDays(-1 * offset),
                 ForecastUri = this.testUris[x % 2],
                 Description = string.Format("Rainy {0}", x),
                 PrecipitationDay = "50",
                 PrecipitationNight = "20",
                 TemperatureLow = "25",
                 TemperatureHigh = "49",
                 TypeId = x
             };
             this.Forecast.Add(entry);
         }
     } }

And this is what I see in the designer:

weatherforecast

How cool is that? Now I have what I need. The view model exposes a zip code for the user to enter and an instance of the forecast. The view model I instantiate directly but in the constructor detect design mode and set the forecast property to a design version:

if (Windows.ApplicationModel.DesignMode.DesignModeEnabled)

{
     this.currentForecast = new DesignForecast();
     this.zipCode = "30189";
     return; }
(Here’s a tip – I usually set the field values in design mode so the property change notification isn’t fired – when it’s done in the constructor it will be there in time for binding to the design view).

Now I have the whole app assembled, and even with a “real” instance of the view model in the designer, I’m able to see what the app will look like.

weatherdesigner 

The individual design instances let me compose the UI with design-time data, while the view model can then instantiate a design instance or “go live” and actually call the service to provide a “real” weather forecast. Here’s what the runtime looks like … not too much different from the design-time view!

runtime

Thank goodness for XAML and the ability to easily create design-time data for our apps.

No comments:

Post a Comment