Some insight on the Telerik Weather Station application

by XAML Team | Comments 10

We have recently uploaded the Weather Station demo showing some of Telerik controls for Silverlight in a great rich context. Following Kalin’s blog post announcing the main features, this one is to provide some notes on the technical side of the matter.

weather-station
Here is a list of the main features:

  1. Find client’s location upon startup and retrieve weather information if available.
  2. Store current locations and favorite ones in the local storage for proper loading on next startup of the application.
  3. Use two formats for weather values - Celsius and Fahrenheit.
  4. Search for custom location.
  5. Display brief location information (on smaller zoom levels) as well as detailed view when zoomed-in to the largest level.

Find client’s location upon startup and retrieve weather information if available

It is based on the IP address of the client requesting the data. This is how to get it from the WCF service (WeatherService.svc.cs):

OperationContext context = OperationContext.Current;
MessageProperties messageProperties = context.IncomingMessageProperties;
RemoteEndpointMessageProperty endpointProperty = messageProperties[RemoteEndpointMessageProperty.Name] as RemoteEndpointMessageProperty;
String ipAddress = endpointProperty.Address;
 

Then, all that is needed is to query a the web service for the geo-location which corresponds to this IP address. Once you have it, you can make requests for weather, coordinates, etc.

Store current locations and favorite ones in the local storage for proper loading on next startup of the application

What we did is basically store the locations’ names in a text format (separated by ”;”) in the Silverlight’s IsolatedStorage. The code for accessing it resides in the IsolatedStorageHelper class, and its consumption is as simple as:

Get all locations and favorites upon initial load:
string defaultsString = IsolatedStorageHelper.GetDefaults();
string favouritesString = IsolatedStorageHelper.GetFavourites();

…and we call the following method as soon as we want to store the default and favorite locations:
internal void SaveDataState()
{
    string[] defaults = this.weatherInfoCache.Select(f => f.LocationInfo.FullName).ToArray<string>();
    string[] favs = this.weatherInfoCache.Where(wi=>wi.IsFavourite == true).Select(f => f.LocationInfo.FullName).ToArray<string>();
      
    IsolatedStorageHelper.SaveFavourites(string.Join(";", favs));
    IsolatedStorageHelper.SaveDefaults(string.Join(";", defaults));
}

Use two formats for weather values

Since our data object is of type WeatherConditions, we have 2 separate properties for Celsius and Fahrenheit (if temperature is concerned). This means that we need to have some kind of functionality that chooses between what property is currently been bound to the Content of the RadTransitionControl that is displaying it – just like a data template selector. To implement this, we have created the TemplateSelectorConverter (thank you for the idea, Rob) that, simply chooses between DataTemplates depending on a parameter that is passed (and this in our case is “US”, and “EU”, corresponding to imperial/metric values). Below is how a converter for temperature is declared:
 
<DataTemplate x:Key="ForecastTempCItemTemplate"
    <TextBlock FontFamily="/WeatherMonitor;component/Fonts/Fonts.zip#Segoe WP"
        <Run Text="{Binding LowC}"/><Run Text="°~"/><Run Text="{Binding HighC}"/><Run Text="°"/> 
    </TextBlock
</DataTemplate
<DataTemplate x:Key="ForecastTempFItemTemplate"
    <TextBlock FontFamily="/WeatherMonitor;component/Fonts/Fonts.zip#Segoe WP"
        <Run Text="{Binding LowF}"/><Run Text="°~"/><Run Text="{Binding HighF}"/><Run Text="°"/> 
    </TextBlock
</DataTemplate
<local:TemplateSelectorConverter x:Key="ForecastTemperatureItemTemplates"
    <local:TemplateMapEntry SourceType="US" DataTemplate="{StaticResource ForecastTempFItemTemplate}" /> 
    <local:TemplateMapEntry SourceType="EU" DataTemplate="{StaticResource ForecastTempCItemTemplate}" /> 
</local:TemplateSelectorConverter>
 
…and this is an example of how it is set to enable a RadTransitionControl animate when we toggle between different formats (we store the IsUsingUSValues value in the Tag property of the parent control):
<telerik:RadTransitionControl x:Name="temperatureContainer" UseLayoutRounding="False" Duration="00:00:00.5" Content="{Binding}"
        ContentTemplate="{Binding Tag, Converter={StaticResource ForecastTemperatureItemTemplates}, ElementName=forecastItemsControl}" HorizontalAlignment="Center" VerticalAlignment="Top" IsHitTestVisible="False" Width="64" Foreground="White" FontWeight="Bold" FontSize="16" HorizontalContentAlignment="Center" >
    <telerik:RadTransitionControl.Effect>
        <DropShadowEffect BlurRadius="3" ShadowDepth="2"/>
    </telerik:RadTransitionControl.Effect>
    <telerik:RadTransitionControl.Transition>
        <telerik:SlideAndZoomTransition SlideDirection="RightToLeft" MinZoom="1" />
    </telerik:RadTransitionControl.Transition>
</telerik:RadTransitionControl>
 

Search for custom location

The most convenient way to implement a kind of auto-complete textbox using a RadComboBox, so that while writing we can set its Items to the suggestions coming from the web service. 

<telerik:RadComboBox x:Name="searchCombo" Grid.Row="1" 
                     ……….
 
                     IsEditable="True" 
                     Text="{Binding SearchString, Mode=TwoWay}"
                     SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
                     ItemsSource="{Binding AllSearchRegions}"
                     ………>

What we use to trigger the search is the TwoWay binding to the SearchString property of our ViewModel – as soon as it is being set to a new value, the Search() method is called where a new BackgroundWorker is initialized and started, and an ID of the current(and last called) search operation is stored so that we can later get the latest asynchronous search result:

private void Search()
{
    latestSearchId = Guid.NewGuid();
    this.InitializeWorker();
    this.searchWorker.RunWorkerAsync();
}


And one of the reasons why we do this in a separate thread is that we want to sleep it for a while to add a delay in the search request. Here is what the worker does:

private void OnSearchWorkerDoWork(object sender, DoWorkEventArgs e)
{
    Guid thisId = latestSearchId;
 
    Thread.Sleep(400);
    Action<List<GeoLocationInformation>> onSearchComplete = (result) =>
    {
        if (thisId == latestSearchId) // this restricts setting the suggestion results only when they apply to the latest search
        {
            if (result != null)
            {
                this.AllSearchRegions.Clear();
 
                foreach (var locationInfo in result)
                {
                    if (locationInfo != null)
                    {
                        this.AllSearchRegions.Add(locationInfo);
                    }
                }
            }
        }
    };
 
    SearchHelper.SearchLocationWWO(this.SearchString, onSearchComplete);
}


Display brief location information (on smaller zoom levels) as well as detailed view when zoomed-in to the largest level

As we are displaying a number of items on the map, this number may vary on the user’s preferences. Also we need the items to be virtualized so that they do not consume system resources in vain, thus slowing the performance. This suggests the use of DynamicLayer on the map with our own implementation of a DynamicSource and an ItemTemplateSelector for switching the weather information between normal and details view:

 <telerik:RadMap x:Name="RadMap1" ...>
   
<telerik:DynamicLayer x:Name="dynamicLayer" DynamicSource="{Binding MapSource}"
        ItemTemplateSelector
="{StaticResource WeatherInfoItemTemplateSelector}" >
       
<telerik:ZoomGrid LatitudesCount="4" LongitudesCount="4" MinZoom="4" />
        <
telerik:ZoomGrid LatitudesCount="256" LongitudesCount="256" MinZoom="10" />
    </
telerik:DynamicLayer>
</telerik:RadMap>

 
Setting the different DataTemplates is as easy as:
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
    switch ((container.GetVisualParent<DynamicLayer>().MapControl).ZoomLevel)
    {
        case 10:
        case 11:
            return this.DetailedItemTemplate;
        default:
            return this.BasicItemTemplate;
    }
}
 

At several places in the solution we need to call asynchronous methods in a synchronous way, so that we can report progress and also continue the execution only after we receive certain results. To do this we use AutoResetEvent objects: 

private void LoadPredefinedGeoLocations()
{
    foreach (string location in this.predefinedSearchLocations)
    {
        // use AutoResetEvent to make the method synchronous, e.g. return to calling method after loading is finished
        AutoResetEvent autoResetEvent = new AutoResetEvent(false);
 
        // this timer is to apply a timeout of the get location & weather operation
        Timer t = new Timer(new TimerCallback((obj) =>
                                              {
                                                  // give a signal
                                                  autoResetEvent.Set();
                                              }));
        t.Change(10000, Timeout.Infinite);
 
        Action<List<GeoLocationInformation>> onSearchComplete = (result) => {
            this.RetrieveWeatherData(result.First(), new Action(() => {
                                                                    t.Dispose();
                                                                    // give a signal
                                                                    autoResetEvent.Set();
                                                                }));
        };
 
        SearchHelper.SearchLocationWWO(location, onSearchComplete);
 
        //wait for a signal before continuing
        autoResetEvent.WaitOne();
    }
}
 
 

Senior Software Developer,
xLabs Team

10 Comments

Eric
Great demo, thanks.

Would be nice if the format of the weather values are also stored.
Tim
I have downloaded the source code and when I try to compile I get a bunch of errors like this:

The type or namespace name 'RadComboBox' could not be found (are you missing a using directive or an assembly reference?) C:\Software\WeatherStation_2010_3_1110_TRIAL\WeatherMonitor\obj\Release\MainPage.g.i.cs 65
Tim
I have downloaded the source code and when I try to compile I get a bunch of errors like this:

The type or namespace name 'RadComboBox' could not be found (are you missing a using directive or an assembly reference?) C:\Software\WeatherStation_2010_3_1110_TRIAL\WeatherMonitor\obj\Release\MainPage.g.i.cs 65
Stefan Dobrev
@Tim

Sometimes errors like this are cause by VS 2010 designer which when opened caches the assemblies that are used in the opened XAML file. Can you try to close all files that you have opened and then perform Clean and Build operations? Also you can manually try to delete the obj and bin folders in the Silverlight application's directory.

Hope this helps.
Tim
@ Stefan

Thanks I got it to work. Just an fyi in the IsolatedStorageHelper

FileMode

 

 

.Open needs to be changed to FileMode.OpenOrCreate

Other than that it is working great!

 

Teodor Bozhikov
@ Eric
Thank you for the suggestion! Indeed, this could be useful and we may implement it for a next update of the example.
Just as a note in case you need to do it yourself - you can store a boolean value in the isolated storage (see IsolatedStorageHelper class) and initialize the IsUsingUsValues property with this value in the ViewModel's constructor. 
Nick
Downloaded the project but it's only in C#..... Do you have a VB.Net version for the other half of us, or are we left to our own devices to attempt to convert it?
Teodor Bozhikov
We have not included a VB.NET version of the application, unfortunately. For such a case, however, we have provided a converter that can be very useful to translate the project to VB. Find it at:
http://converter.telerik.com/

We will consider shipping VB versions of example applications in the future. 
dev
What weather service is used for the Weather API

Comments

  1.    
      
      
       
  2. (optional, emails won't be shown on public pages)
  3. (optional)
Read more articles by XAML Team - or - read latest articles in Developer Tools
Product Families