Pages

Wednesday, 7 September 2011

Key concepts in Silverlight in comparison with WPF

In this article I would like to show you how to use some of the key Silverlight features:
  • Dependency Properties,
  • Data Bindings,
  • Commands,
  • Implicit styles,
  • Explicit styles,
  • Visual State Manager,
  • Animations,
  • Value Converters,
  • CollectionViewSource,
  • Custom Controls.


1. Dependency Properties
If you want to understand the reason and the way how DP works please visit this link. I would like to mention differences between WPF and SL DPs. When you create DP in SL you can specify PropertyMetadata but as a result you will lack coercion and validation support which can be handled in FrameworkPropertyMetadata (WPF). These two can be useful especially if you want to create advanced controls. If you cannot imagine how coercion can be useful for you I can give an example - range IP address control.

   1:  public static readonly DependencyProperty RadiusProperty = DependencyProperty.Register("Radius", typeof(double), typeof(CelestialBodyControl), new PropertyMetadata(double.NaN));
   2:   
   3:          public double Radius
   4:          {
   5:              get { return (double)GetValue(RadiusProperty); }
   6:              set { SetValue(RadiusProperty, value); }
   7:          }

2. Data Bindings 
I describe data bindings as maximum functionality and minimum code, very useful for displaying and interacting with data. Bindings are even more useful when you use Model-View-ViewModel (MVVM) pattern because in case like that data bindings create these pipelines/streams of data between presentation layer (V) and logic behind it (VM) and no additional code is needed.

Example below shows how to use data bindings directly in your view (V)
   1:  <local:CelestialBodyControl BodyName="{Binding Name}" Radius="{Binding Radius}" AverageDistanceFromSun="{Binding AverageDistanceFromSun}" 
   2:                                                  RotationPeriod="{Binding RotationPeriod}" OrbitalPeriod="{Binding OrbitalPeriod}"/>

This example shows how to use a data binding inside a style
   1:  <TextBlock Text="{Binding Radius, RelativeSource={RelativeSource TemplatedParent}}" Foreground="White"/>

3. Commands
Commands are a way to handle user interface actions but the problem is that they are not widely supported in SL. They are a loosely coupled way to bind the UI to the logic that performs the action. This is the way how you can perform interactions between your View and a ViewModel.

   1:  <ToggleButton Content="Start" Command="{Binding StartCommand}" Width="100" Height="30" />

   1:          public ViewModel()
   2:          {
   3:              //...
   4:              StartCommand = new DelegateCommand<object>(HandleStartCommand);
   5:              //...
   6:          }
   7:   
   8:          public DelegateCommand<object> StartCommand
   9:          {
  10:              get { return _startCommand; }
  11:              set
  12:              {
  13:                  _startCommand = value;
  14:                  OnPropertyChanged("StartCommand");
  15:              }
  16:          }
  17:   
  18:          private void HandleStartCommand(object payload)
  19:          {
  20:              //...
  21:          }

4. Styles
Styles allows users/designers to apply consistent look & feel across multiple controls, some times very different controls. Styles can be defined globally or just locally if you define them inside resources of a control. Key elements of each style are:
  • Setter element - specifies key (property name) value (property value) pair,
  • TargetType attribute - specifies a type of a control,
  • x:Key or x:Name attributes - specify the name of a style for implicit styling. This is another difference between WPF and SL because only in SL you are able to use them interchangeably.
TraditionalListBox and CanvasListBox are the best examples of different approaches in styling because the blue ListBox uses an implicit style and the green one uses an explicit style.

   1:   <!--Implicit style-->
   2:          <Style TargetType="local:CelestialBodyControl">
   3:              <Setter Property="Template">
   4:                  <Setter.Value>
   5:                      <ControlTemplate TargetType="local:CelestialBodyControl">
   6:                          <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
   7:                              <Grid.ColumnDefinitions>
   8:                                  <ColumnDefinition Width="*"/>
   9:                                  <ColumnDefinition Width="*"/>
  10:                              </Grid.ColumnDefinitions>
  11:                              <StackPanel Orientation="Vertical" Grid.Column="0">
  12:                                  <TextBlock x:Name="PART_Name" Text="{TemplateBinding BodyName}" Foreground="White"/>
  13:                                  <TextBlock Text="{Binding Radius, RelativeSource={RelativeSource TemplatedParent}}" Foreground="White"/>
  14:                                  <TextBlock Text="{Binding AverageDistanceFromSun, RelativeSource={RelativeSource TemplatedParent}}" Foreground="White"/>
  15:                                  <TextBlock Text="{Binding RotationPeriod, RelativeSource={RelativeSource TemplatedParent}}" Foreground="White"/>
  16:                                  <TextBlock Text="{Binding OrbitalPeriod, RelativeSource={RelativeSource TemplatedParent}}" Foreground="White"/>
  17:                              </StackPanel>
  18:                              <Border BorderThickness="0" Width="70" Height="70" RenderTransformOrigin="0.5,0.5" Grid.Column="1">
  19:                                  <Border.Resources>
  20:                                      <local:ImageConverter x:Key="ImageConverter"/>
  21:                                  </Border.Resources>
  22:                                  <Border.Background>
  23:                                      <ImageBrush ImageSource="{Binding BodyName, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource ImageConverter}}"/>
  24:                                  </Border.Background>
  25:                              </Border>
  26:                          </Grid>
  27:   
  28:                      </ControlTemplate>
  29:                  </Setter.Value>
  30:              </Setter>
  31:          </Style>
  32:   
  33:          <!--Explicit style-->
  34:          <Style TargetType="local:CelestialBodyControl" x:Key="CanvasStyle">
  35:              <Setter Property="Template">
  36:                  <Setter.Value>
  37:                      <ControlTemplate TargetType="local:CelestialBodyControl">
  38:                          <Border BorderThickness="2" Width="100" Height="100" RenderTransformOrigin="0.5,0.5" x:Name="border">
  39:   
  40:                              <VisualStateManager.VisualStateGroups>
  41:                                  <VisualStateGroup x:Name="SelectionStates">
  42:                                      <VisualState x:Name="Unselected"/>
  43:                                      <VisualState x:Name="Selected">
  44:                                          <Storyboard>
  45:                                              <DoubleAnimation Duration="0" To="0.5" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="border"/>
  46:                                          </Storyboard>
  47:                                      </VisualState>
  48:                                  </VisualStateGroup>
  49:                              </VisualStateManager.VisualStateGroups>
  50:                              <Border.Background>
  51:                                  <ImageBrush ImageSource="{Binding BodyName, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource ImageConverter}}"/>
  52:                              </Border.Background>
  53:                              <Border.RenderTransform>
  54:                                  <TransformGroup>
  55:                                      <ScaleTransform ScaleX="{Binding Radius, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource ValueConverter}, ConverterParameter=0.00003}" ScaleY="{Binding Radius, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource ValueConverter}, ConverterParameter=0.00003}"  CenterX="0.5" CenterY="0.5"/>
  56:                                      <RotateTransform CenterX="0.5" CenterY="0.5" Angle="{Binding RotationAngle, RelativeSource={RelativeSource TemplatedParent}}"/>
  57:                                      <TranslateTransform X="{Binding AverageDistanceFromSun, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource ValueConverter}, ConverterParameter=100}" Y="{Binding AverageDistanceFromSun, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource ValueConverter}, ConverterParameter=100}"/>
  58:                                      <RotateTransform  Angle="{Binding OrbitalAngle, RelativeSource={RelativeSource TemplatedParent}}"/>
  59:                                      <TranslateTransform X="800" Y="0"/>
  60:                                  </TransformGroup>
  61:                              </Border.RenderTransform>
  62:                          </Border>
  63:   
  64:                      </ControlTemplate>
  65:                  </Setter.Value>
  66:              </Setter>
  67:          </Style>

5. Visual State Manager
VSM manages states and the logic for transitioning between states for controls. Interesting fact is that VSM has been added to WPF 4.0 but SL was actually the first one where VSM has been implemented.

   1:   <VisualStateManager.VisualStateGroups>
   2:                                  <VisualStateGroup x:Name="CommonStates">
   3:                                      <VisualState x:Name="Normal"/>
   4:                                      <VisualState x:Name="MouseOver">
   5:                                      </VisualState>
   6:                                      <VisualState x:Name="Disabled">
   7:                                          <Storyboard>
   8:                                              <DoubleAnimation Duration="0" To=".55" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="contentPresenter"/>
   9:                                          </Storyboard>
  10:                                      </VisualState>
  11:                                  </VisualStateGroup>
  12:                                  <VisualStateGroup x:Name="SelectionStates">
  13:                                      <VisualState x:Name="Unselected"/>
  14:                                      <VisualState x:Name="Selected">
  15:                                          <Storyboard>
  16:                                              <DoubleAnimation Duration="0:0:0.3" To=".3" AutoReverse="True" RepeatBehavior="Forever" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="contentPresenter"/>
  17:                                              <!--<ColorAnimation Duration="0" To="#FF0096FF" Storyboard.TargetProperty="(BorderBrush).(SolidColorBrush.Color)" Storyboard.TargetName="..." />-->
  18:                                          </Storyboard>
  19:                                      </VisualState>
  20:                                  </VisualStateGroup>
  21:                                  <VisualStateGroup x:Name="FocusStates">
  22:                                      <VisualState x:Name="Focused">
  23:                                      </VisualState>
  24:                                      <VisualState x:Name="Unfocused"/>
  25:                                  </VisualStateGroup>
  26:                              </VisualStateManager.VisualStateGroups>

6. Animations
Only DPs can be animated. Types of animations:
  • DoubleAnimation,
  • ColorAnimation,
  • PointAnimation.
7. Value Converters (IValueConverter interface)
Allow modifying data as it passes through the binding engine. The difference between WPF and SL is that SL does not support multi-value converters (IMultiValueConverter interface).

   1:  public class ValueConverter : IValueConverter
   2:      {
   3:          public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
   4:          {
   5:              double multiplier = 1;
   6:              double position = value != null ? (double)value : 0;
   7:              if (parameter != null)
   8:                  double.TryParse((string)parameter, out multiplier);
   9:              return position * multiplier;
  10:          }
  11:   
  12:          public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  13:          {
  14:              throw new NotImplementedException();
  15:          }
  16:      }

8. CollectionViewSource
CollectionViewSource is a proxy of a collection view class, can be used for sorting, grouping and filtering data. If you would like to know how to implement filtering in MVVM architecture please visit the following link: http://pavzav.blogspot.com/2011/07/filtering-in-mvvm-architecture.html.

   1:          <CollectionViewSource x:Name="SortedCelestialBodies" Source="{Binding CelestialBodies}">
   2:              <CollectionViewSource.SortDescriptions>
   3:                  <compMod:SortDescription PropertyName="Name"/>
   4:              </CollectionViewSource.SortDescriptions>
   5:          </CollectionViewSource>
   6:   
   7:   
   8:          <!--Usage of CollectionViewSource-->
   9:          <ListBox ItemsSource="{Binding Source={StaticResource SortedCelestialBodies}}" HorizontalAlignment="Right" VerticalAlignment="Bottom" Background="Black" Width="150" SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
  10:              <ListBox.ItemTemplate>
  11:                  <DataTemplate>
  12:                      <local:CelestialBodyControl BodyName="{Binding Name}" Radius="{Binding Radius}" AverageDistanceFromSun="{Binding AverageDistanceFromSun}" 
  13:                                                  RotationPeriod="{Binding RotationPeriod}" OrbitalPeriod="{Binding OrbitalPeriod}"/>
  14:                  </DataTemplate>
  15:              </ListBox.ItemTemplate>
  16:          </ListBox>

9. Custom Controls (Skinnable/Faceless Controls)
The most important feature of Custom Controls is a fact that their UI representation can be easily replaced and this it the reason why developers call them faceless controls.

   1:   public class CelestialBodyControl : Control
   2:      {
   3:          public static readonly DependencyProperty BodyNameProperty = DependencyProperty.Register("BodyName", typeof(string), typeof(CelestialBodyControl), new PropertyMetadata(string.Empty));
   4:          public static readonly DependencyProperty RadiusProperty = DependencyProperty.Register("Radius", typeof(double), typeof(CelestialBodyControl), new PropertyMetadata(double.NaN));
   5:          public static readonly DependencyProperty AverageDistanceFromSunProperty = DependencyProperty.Register("AverageDistanceFromSun", typeof(double), typeof(CelestialBodyControl), new PropertyMetadata(double.NaN));
   6:          public static readonly DependencyProperty RotationPeriodProperty = DependencyProperty.Register("RotationPeriod", typeof(double), typeof(CelestialBodyControl), new PropertyMetadata(double.NaN));
   7:          public static readonly DependencyProperty OrbitalPeriodProperty = DependencyProperty.Register("OrbitalPeriod", typeof(double), typeof(CelestialBodyControl), new PropertyMetadata(double.NaN));
   8:          public static readonly DependencyProperty OrbitalAngleProperty = DependencyProperty.Register("OrbitalAngle", typeof(double), typeof(CelestialBodyControl), new PropertyMetadata(double.NaN));
   9:          public static readonly DependencyProperty RotationAngleProperty = DependencyProperty.Register("RotationAngle", typeof(double), typeof(CelestialBodyControl), new PropertyMetadata(double.NaN));
  10:   
  11:          public CelestialBodyControl()
  12:          {
  13:              this.DefaultStyleKey = typeof(CelestialBodyControl);
  14:          }
  15:          
  16:          //...
  17:       }

Source code: KeyConceptsInSilverlight.rar

No comments:

Post a Comment