(X) Hide this
    • Login
    • Join
      • Generate New Image
        By clicking 'Register' you accept the terms of use .

Custom controls: Weather control: Part 3 - Templates and web service

(0 votes)
Denislav Savkov
>
Denislav Savkov
Joined Feb 11, 2008
Articles:   14
Comments:   6
More Articles
2 comments   /   posted on Aug 15, 2008
Categories:   Data Access , General , Controls , Design

This article is compatible with the latest version of Silverlight.

See also part 1, part 2 and the update.

Introduction

In the last part of our article series about the weather control we take a look at the templates and the web service we use.

Making the templates

Common resources

We try to make the control change its look easy and without retemplating everything. That is why we define text styles and brushes and we use them throughout the templates we create.

<Color x:Name="DarkColor">DodgerBlueColor>
<Color x:Name="MediumColor">LightSkyBlueColor>
<Color x:Name="LightColor">WhiteSmokeColor> 
<SolidColorBrush x:Name="DarkBrush" Color="{StaticResource DarkColor}"/>
<SolidColorBrush x:Name="MediumBrush" Color="{StaticResource MediumColor}"/>
<SolidColorBrush x:Name="LightBrush" Color="{StaticResource LightColor}"/>
<Style x:Name="TextStyle" TargetType="TextBlock">
     <Setter Property="HorizontalAlignment" Value="Center"/>
     <Setter Property="FontSize" Value="12"/>
     <Setter Property="Foreground" Value="{StaticResource DarkBrush}"/>
 </Style>
 <Style x:Name="TextHeading" TargetType="TextBlock">
     <Setter Property="HorizontalAlignment" Value="Center"/>
     <Setter Property="FontSize" Value="14"/>
     <Setter Property="Foreground" Value="{StaticResource LightBrush}"/>
     <Setter Property="FontWeight" Value="Bold"/>
 </Style>

Thus by editing those few styles and brushes we can very easily change the look of the control, even though not that dramatically.

ItemTemplate

To change the look of the buttons and the text box we use Expression Blend and the brushes we have already defined. For the weather data we have min,max temperature, city and image data. Creating the template for each item is very simple.

<c:AnimatedSlider.ItemTemplate>
    <DataTemplate>
        <Border BorderBrush="{StaticResource DarkBrush}" BorderThickness="1">
            <StackPanel Background="{TemplateBinding Background}"  >
                <Grid Background="{StaticResource DarkBrush}" Height="20">
                    <TextBlock Text="{Binding City}" Style="{StaticResource TextHeading}"
                           FontWeight="ExtraBold" />
                </Grid>
                <Image Source="{Binding ConditionBitmap}" Stretch="None" />
                <TextBlock Text="{Binding High}"  Style="{StaticResource TextStyle}" FontWeight="Bold"/>
                <TextBlock Text="{Binding Low}" Style="{StaticResource TextStyle}" />
            </StackPanel>
            </Border>
    </DataTemplate>
</c:AnimatedSlider.ItemTemplate>

ContainerStyle

The containers of the items will always fill the height of the control. To have space between the items we make the height smaller then that of the control. The sizes of a container and an item are always proportional. That means the width of a container in this case is 128,(3)

<c:AnimatedSlider x:Name="Slider" Height="140"     Width="500" 
                                  ItemHeight="120" ItemWidth="110"
...    
/>

The template of the container has a  rectangle to indicate the selection. For the background we define separate brush because we use it in the animation of the selected item.

<Rectangle x:Name="Indicator" Fill="{StaticResource LightBrush}" Height="0" />
<Grid x:Name="ContentContainer" RenderTransformOrigin="0,0" >
    <Grid.Background>
        <SolidColorBrush Color="{StaticResource LightColor}"/>
    <Grid.Background>
    <ContentControl x:Name="ContentPresenter">
        <ContentPresenter />
    <ContentControl>
</Grid>

We have states for the selection and mouse events. For the MouseOver state we make the rectangle, indicating the selection, stretches. And for the selection we change the background of the content to opaque.

Note: The Storyboards for the states can't be set as static resources, because the VisualStateManager throws an error with message "Element is already the child of another element.", so you have define them inside the VisualSate tag.

<vsm:VisualStateManager.VisualStateGroups>
    <vsm:VisualStateGroup x:Name="CommonStates">
        <vsm:VisualState x:Name="MouseOver">
            <Storyboard x:Name="MouseOverStoryboard" >
                <DoubleAnimation Duration="0:0:.2"
                     Storyboard.TargetName="Indicator"
                     Storyboard.TargetProperty="Height"
                     To="160">
                </DoubleAnimation>
            </Storyboard>
        </vsm:VisualState>
        <vsm:VisualState x:Name="Normal">
            <Storyboard x:Name="NormalStoryboard" >
                <DoubleAnimation Duration="0:0:.3"
                     Storyboard.TargetName="Indicator"
                     Storyboard.TargetProperty="Height"
                     To="0">
                </DoubleAnimation>
            </Storyboard>
        </vsm:VisualState>
        <vsm:VisualState x:Name="Pressed">
            <Storyboard x:Name="PressedStoryboard" >
                <DoubleAnimation Duration="0:0:.2"
                     Storyboard.TargetName="Indicator"
                     Storyboard.TargetProperty="Height"
                     To="153">
                </DoubleAnimation>
            </Storyboard>
        </vsm:VisualState>
        <vsm:VisualStateGroup.Transitions>
            <vsm:VisualTransition GeneratedDuration="0:0:.1"/>
            <vsm:VisualTransition From="Normal" To="MouseOver" GeneratedDuration="0:0:.2"/>
            <vsm:VisualTransition From="MouseOver" To="Pressed" GeneratedDuration="0:0:.025"/>
        </vsm:VisualStateGroup.Transitions>
    </vsm:VisualStateGroup>
    <vsm:VisualStateGroup x:Name="SelectionStates">
        <vsm:VisualState x:Name="Selected">
            <Storyboard x:Name="SelectedStoryboard">
                <ColorAnimation Duration="0:0:.02"
                     Storyboard.TargetName="BackgroundGrid"
                     Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)"
                     To="{StaticResource LightColor}"/>
                <DoubleAnimation Duration="0:0:.02"
                     Storyboard.TargetName="Indicator"
                     Storyboard.TargetProperty="Height"
                     To="153"/>
            </Storyboard>
        </vsm:VisualState>
        <vsm:VisualState x:Name="Deselected">
            <Storyboard x:Name="DeselectedStoryboard">
                <ColorAnimation Duration="0:0:.02"
                     Storyboard.TargetName="BackgroundGrid"
                     Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)"
                     To="{StaticResource SemiTransparentColor}"/>
                <DoubleAnimation Duration="0:0:.02"
                     Storyboard.TargetName="Indicator"
                     Storyboard.TargetProperty="Height"
                     To="0">
                </DoubleAnimation>
            </Storyboard>
        </vsm:VisualState>
    </vsm:VisualStateGroup>
</vsm:VisualStateManager.VisualStateGroups>
 

Weather Forecast Web Service

Creating adapter service

We use the service provided by WebseviceX.NET. More information for it you can find at http://www.webservicex.net/WS/WSDetails.aspx?WSID=68&CATID=12. Silverlight doesn't support cross domain web service references so we have to make an adapter web service in our web application and link it to the Silverlight app. Martin Mihaylov has an article on consuming web service in Silverlight.
First we add reference to the online web service.
 
 
 
We name our adapter service WeatherService.
 
It only transmits data from the real web service.
 
[WebMethod]
public net.webservicex.www.WeatherForecasts GetForecastByPlaceName( string PlaceName )
{
    net.webservicex.www.WeatherForecast client = new WeatherForecastWeb.net.webservicex.www.WeatherForecast();
    net.webservicex.www.WeatherForecasts results = client.GetWeatherByPlaceName( PlaceName);
    return results;
}

We add reference to our web service in the Silverlight application.


Using the adapter service

We want to edit the data from the weather service we use so we create a class to accept the data - WeatherDataPresentation. We create the client for the service.

private WeatherForecast.WeatherService.WeatherServiceSoapClient client;

When initializing we send requests for the weather forecast for some cities.

client.GetForecastByPlaceNameCompleted += new EventHandler( client_GetForecastByPlaceNameCompleted );
for ( int i = 0 ; i < CityNames.Length ; ++i )
{
    client.GetForecastByPlaceNameAsync( CityNames[ i ] );
}

When the data is recieved we put it in an ObservableCollection - citiesForecastData - that has the week forecast for all the cities.

void client_GetForecastByPlaceNameCompleted( object sender, GetForecastByPlaceNameCompletedEventArgs e )
{
    WeatherForecasts results = e.Result;
    if ( e.Cancelled == false && results != null && results.PlaceName != null )
    {
        WeatherService.WeatherData data = results.Details[ 0 ];
 
        WeatherDataPresentation item = new WeatherDataPresentation();
        item.City = results.PlaceName;
        item.Day = data.Day.Split( ',' )[ 0 ];
        item.High = "Max:" + data.MaxTemperatureC;
        item.Low = "Min:" + data.MinTemperatureC;
        if ( data.WeatherImage != null )
            item.ConditionBitmap = new BitmapImage( new Uri( data.WeatherImage ) );
        citiesTodayForecast.Add( item );
        citiesForecastData.Add( results );
    }
}

The today's weather data for each city is bound to the slider.

Slider.ItemsSource = citiesTodayForecast;
And when the selection is changed we put the data for the selected city in the collection bound to the displaying ItemsControl.
private void Slider_SelectionChange( object sender, ControlsLibrary.SelectionChangedEventArgs e )
{
    City.Text = citiesTodayForecast[ e.selectedItemIndex ].City;
    cityWeekForecast.Clear();
    foreach ( WeatherService.WeatherData data in citiesForecastData[ e.selectedItemIndex ].Details )
    {
        if ( data.Day != null )
        {
            WeatherDataPresentation item = new WeatherDataPresentation();
            item.City = citiesForecastData[ e.selectedItemIndex ].PlaceName;
            if ( data.Day != null )
                item.Day = data.Day.Split( ',' )[ 0 ];
            item.High = "Max:" + data.MaxTemperatureC;
            item.Low = "Min:" + data.MinTemperatureC;
            if ( data.WeatherImage != null )
                item.ConditionBitmap = new BitmapImage( new Uri( data.WeatherImage ) );
            cityWeekForecast.Add( item );
        }
    }
}

Download source

Conclusion

We tried to create a real life example of custom build control. In future we can improve the mouse dragging of the slider so there may be an article about that.

Reference

Part 1

http://www.silverlightshow.net/items/Custom-controls-Weather-forecast-control-Part-1-Introduction.aspx

Part 2

http://www.silverlightshow.net/items/Custom-Controls-Weather-control-Part-2-The-Slider.aspx


Subscribe

Comments

  • -_-

    RE: Custom controls: Weather control: Part 3 - Templates and web service


    posted by Todor Karkelanov on Nov 13, 2008 04:53

    Hi

    Very good article, but I have question.

     

    I saw in code that you subscribe for mouse event in your SelectableContentControl class like this.

     

    ContentPresenter.MouseEnter += new MouseEventHandler( ContentPresenter_MouseEnter );

    ContentPresenter.MouseLeave += new MouseEventHandler( ContentPresenter_MouseLeave );

                   

    Is any special reason to do this, instead to override

     

    protected override void  OnMouseEnter(MouseEventArgs e)

     

    Same for other mouse event 

  • lnikolov

    RE: Custom controls: Weather control: Part 3 - Templates and web service


    posted by lnikolov on Jan 14, 2011 11:22
    The article has been updated to the latest version of Silverlight and Visual Studio.

Add Comment

Login to comment:
  *      *