How to Create XAML WinRT Components for Windows 8 Metro Applications

[Infragistics] Mihail Mateev / Friday, March 23, 2012

Metro applications have an innovative new interface and a component-based architecture.

Many developers created components for XAML-related technologies like Silverlight / WPF and developers of jQuery components they would like to create WinRT controls for Windows Metro applications.One of the new challenges for developers is to work on Windows 8 Metro applications. This article is about how to start creating XAML WinRT components.You will

You will learn how to create a simple XAML control, using C# and how to use it in a demo Metro application.

Developers will be excited from the significant improvements in Windows 8 Consumer Preview and Visual Studio 11 Beta, related to Metro applications development.

In September 2011 Microsoft announced Windows 8 Developer Preview and Visual Studio 2011 Developer Preview. There was some missing features in the Developer Preview:

  • Visual Studio Developer Preview 11 had no templates for custom controls
  • ObservableCollection<T> didn’t support INotifyCollectionChanged (developers must create custom collections, that implement IObservableVector<T>
  • XAML Dependency Properties had a different syntax from the Silverlight / WPF implementation
  • In Visual Studio 11 Developer Preview developers should set Generic.xaml build type as Content (in Silverlight it is Page)
  • XAML Metro components had some issues in design mode and often it was not possible programmers to see the UI at design time.

Dependency Property definition

   1: // Using a DependencyProperty as the backing store for Value.  
   2: //This enables animation, styling, binding, etc...
   3: public static readonly DependencyProperty ValueProperty =
   4:     DependencyProperty.Register("Value", "Object", typeof(SimpleSlider).FullName, 
   5: new PropertyMetadata(0.0, OnValueChanged));

 

In Visual Studio Beta 11 there  is a significant improvement for the component developers. They now have all of the above missing features and can quickly begin development of Metro components.

In this article you will learn how to create step by step a simple Metro component (simple slider). The blog could help component developers to ensure that they could reuse almost everything from WPF/Silverlight controls. Users without an experience in component development also could understand that is pretty easy to create XAML components for Metro applications.

How to start:

We will create a simple Metro application, that display information about mobile phones . In the project will be added a Templated  Control item, used to implement slider functionalities.

Prerequisites:

Application

In Visual Studio 11 Beta create a new application: Visual C# –> Windows Metro Style –> Blank Application. Set the name of the application to “SimpleSliderDemo”.

Visual Studio will create the application structure using the amazing new metro interface.

Sample application will show a list with mobile phones. When you select a specified phone you could see the hone properties in the frame, located in the right side of the layout. In this application will be implemented custom slider control, that customer could use to change the number of phones in stock.

SimpleSlider Control Implementation

Add new item-> Templated Control and name it “SimpleSlider”.

Generated Generic.xaml is in the Themes folder as you expect.

Create a folder, named controls and move in this folder SimpleSlider.cs file.

SimpleSlider.cs  has the well known structure if you have experience with Silverlight/WPF components.  There are only different namespaces for UI elements.

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using Windows.UI.Xaml;
   5: using Windows.UI.Xaml.Controls;
   6: using Windows.UI.Xaml.Data;
   7: using Windows.UI.Xaml.Documents;
   8: using Windows.UI.Xaml.Input;
   9: using Windows.UI.Xaml.Media;
  10:  
  11: // The Templated Control item template is documented at http://go.microsoft.com/fwlink/?LinkId=234235
  12:  
  13: namespace SimpleSliderDemo
  14: {
  15:     public sealed class SimpleSlider : Control
  16:     {
  17:         public SimpleSlider()
  18:         {
  19:             this.DefaultStyleKey = typeof(SimpleSlider);
  20:         }
  21:     }
  22: }

 

Visual Studio generates also a basic template in Generic.xaml

   1: <ResourceDictionary
   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:     xmlns:local="using:SimpleSliderDemo">
   5:  
   6:     <Style TargetType="local:SimpleSlider">
   7:         <Setter Property="Template">
   8:             <Setter.Value>
   9:                 <ControlTemplate TargetType="local:SimpleSlider">
  10:                     <Border
  11:                         Background="{TemplateBinding Background}"
  12:                         BorderBrush="{TemplateBinding BorderBrush}"
  13:                         BorderThickness="{TemplateBinding BorderThickness}">
  14:                     </Border>
  15:                 </ControlTemplate>
  16:             </Setter.Value>
  17:         </Setter>
  18:     </Style>
  19: </ResourceDictionary>

 

Update the SimpleSlider control template adding a thumb and  a rectangle for the slider track:

   1: <ResourceDictionary
   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:     xmlns:ctrl="using:SimpleSliderDemo.Controls">
   5:  
   6:     <Style TargetType="ctrl:SimpleSlider">
   7:         <Setter Property="Template">
   8:             <Setter.Value>
   9:                 <ControlTemplate TargetType="ctrl:SimpleSlider">
  10:                     <Border
  11:                         Background="{TemplateBinding Background}"
  12:                         BorderBrush="{TemplateBinding BorderBrush}"
  13:                         BorderThickness="{TemplateBinding BorderThickness}">
  14:                         <Grid>
  15:                             <Border Height="8"
  16:                                 VerticalAlignment="Stretch"
  17:                                 Background="LightGray" />
  18:                             <Canvas Margin="0"
  19:                                 MinHeight="8">
  20:                                 <Rectangle x:Name="PART_Track"
  21:                                        Height="8"
  22:                                        Fill="Yellow" />
  23:                                 <Thumb x:Name="PART_Thumb" Background="Blue"
  24:                                    Width="8"
  25:                                    Height="8" />
  26:                             </Canvas>
  27:                         </Grid>
  28:                     </Border>
  29:                 </ControlTemplate>
  30:             </Setter.Value>
  31:         </Setter>
  32:     </Style>
  33: </ResourceDictionary>

 

Implement the slider functionalities in SimpleSlider.cs file:

  • Add dependency properties for Value, MinValue, MaxValue
  • Implement thumb dragging support.

You could see that the approach is like in other XAML technologies. In Visual Studio 11 Beta Microsoft offers to XAML developers the same structure for components that they know from the past. You could use the overrides like OnApplyTemplate() in the same way like in WPF/Silverlight

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using Windows.UI.Xaml;
   5: using Windows.UI.Xaml.Controls;
   6: using Windows.UI.Xaml.Controls.Primitives;
   7: using Windows.UI.Xaml.Shapes;
   8: using Windows.UI.Xaml.Data;
   9: using Windows.UI.Xaml.Documents;
  10: using Windows.UI.Xaml.Input;
  11: using Windows.UI.Xaml.Media;
  12:  
  13: // The Templated Control item template is documented at http://go.microsoft.com/fwlink/?LinkId=234235
  14:  
  15:  
  16: namespace SimpleSliderDemo.Controls
  17: {
  18:     [TemplatePart(Name = ThumbPartName, Type = typeof(Thumb))]
  19:     [TemplatePart(Name = TrackPartName, Type = typeof(Rectangle))]
  20:     public sealed class SimpleSlider : Control
  21:     {
  22:  
  23:         #region Fields
  24:  
  25:         private const string ThumbPartName = "PART_Thumb";
  26:         private const string TrackPartName = "PART_Track";
  27:  
  28:         private Thumb thumb;
  29:         private Rectangle rectangle;
  30:  
  31:         #endregion //Fields
  32:  
  33:         #region Constructor
  34:         public SimpleSlider()
  35:         {
  36:             this.DefaultStyleKey = typeof(SimpleSlider);
  37:         }
  38:         #endregion //Constructor
  39:  
  40:         #region Properties
  41:  
  42:         #region MinimumValue
  43:  
  44:         // Using a DependencyProperty as the backing store for MinimumValue.  This enables animation, styling, binding, etc...
  45:         public static readonly DependencyProperty MinimumValueProperty =
  46:             DependencyProperty.Register("MinimumValue", typeof(object), typeof(SimpleSlider), new PropertyMetadata(0.0));
  47:  
  48:         /// <summary>
  49:         /// Gets or sets the minimum.
  50:         /// </summary>
  51:         public double MinimumValue
  52:         {
  53:             get { return (double)GetValue(MinimumValueProperty); }
  54:             set { SetValue(MinimumValueProperty, value); }
  55:         }
  56:  
  57:         #endregion //MinimumValue
  58:  
  59:         #region MaximumValue
  60:  
  61:         // Using a DependencyProperty as the backing store for MaximumValue.  This enables animation, styling, binding, etc...
  62:         public static readonly DependencyProperty MaximumValueProperty =
  63:             DependencyProperty.Register("MaximumValue", typeof(object), typeof(SimpleSlider), new PropertyMetadata(0.0));
  64:  
  65:         /// <summary>
  66:         /// Gets or sets the maximum.
  67:         /// </summary>
  68:         public double MaximumValue
  69:         {
  70:             get { return (double)GetValue(MaximumValueProperty); }
  71:             set { SetValue(MaximumValueProperty, value); }
  72:         }
  73:  
  74:         #endregion //MaximumValue
  75:  
  76:         #region Value
  77:  
  78:         // Using a DependencyProperty as the backing store for Value.  This enables animation, styling, binding, etc...
  79:         public static readonly DependencyProperty ValueProperty =
  80:             DependencyProperty.Register("Value", typeof(object), typeof(SimpleSlider), new PropertyMetadata(0.0, OnValueChanged));
  81:  
  82:         /// <summary>
  83:         /// Gets or sets the value.
  84:         /// </summary>
  85:         public double Value
  86:         {
  87:             get { return (double)GetValue(ValueProperty); }
  88:             set
  89:             {
  90:                 SetValue(ValueProperty, value);
  91:             }
  92:         }
  93:         #endregion //Value
  94:  
  95:         #endregion //Properties
  96:  
  97:         #region Methods
  98:  
  99:         #region OnApplyTemplate
 100:         /// <summary>
 101:         /// When overridden in a derived class, is invoked whenever application code or internal processes call <see cref="M:System.Windows.FrameworkElement.ApplyTemplate"/>.
 102:         /// </summary>
 103:         protected override void OnApplyTemplate() // OnApplyTemplateCore()
 104:         {
 105:             //base.OnApplyTemplateCore();
 106:  
 107:             base.OnApplyTemplate();
 108:  
 109:             this.thumb = this.GetTemplateChild(ThumbPartName) as Thumb;
 110:             if (this.thumb != null)
 111:             {
 112:                 this.thumb.DragDelta += this.Thumb_DragDelta;
 113:             }
 114:  
 115:             this.rectangle = this.GetTemplateChild(TrackPartName) as Rectangle;
 116:  
 117:             this.SizeChanged += new SizeChangedEventHandler(SimpleSlider_SizeChanged);
 118:         }
 119:         #endregion //OnApplyTemplate
 120:  
 121:         #region SimpleSlider_SizeChanged
 122:         /// <summary>
 123:         /// Called when size changed.
 124:         /// </summary>
 125:         private void SimpleSlider_SizeChanged(object sender, SizeChangedEventArgs e)
 126:         {
 127:             if (e.NewSize.Width != e.PreviousSize.Width)
 128:             {
 129:                 this.UpdateControlParts();
 130:             }
 131:         }
 132:         #endregion //SimpleSlider_SizeChanged
 133:  
 134:         #region OnValueChanged
 135:         /// <summary>
 136:         /// Called when value changed.
 137:         /// </summary>
 138:         private static void OnValueChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
 139:         {
 140:             var customSlider = (SimpleSlider)dependencyObject;
 141:             customSlider.UpdateControlParts();
 142:         }
 143:         #endregion //OnValueChanged
 144:  
 145:         #region Thumb_DragDelta
 146:         /// <summary>
 147:         /// Handles the DragDelta event of the Thumb control.
 148:         /// </summary>
 149:         private void Thumb_DragDelta(object sender, DragDeltaEventArgs e)
 150:         {
 151:             var pixelDiff = e.HorizontalChange;
 152:             var currentLeft = Canvas.GetLeft(this.thumb);
 153:  
 154:             // trying to drag too far left
 155:             if ((currentLeft + pixelDiff) < 0)
 156:             {
 157:                 this.Value = 0;
 158:             }
 159:             // trying to drag too far right
 160:             else if ((currentLeft + pixelDiff + this.thumb.ActualWidth) > this.ActualWidth)
 161:             {
 162:                 this.Value = this.MaximumValue;
 163:             }
 164:             else
 165:             {
 166:                 var totalSize = this.ActualWidth;
 167:                 var ratioDiff = pixelDiff / totalSize;
 168:                 var rangeSize = this.MaximumValue - this.MinimumValue;
 169:                 var rangeDiff = rangeSize * ratioDiff;
 170:                 this.Value += rangeDiff;
 171:             }
 172:         }
 173:         #endregion //Thumb_DragDelta
 174:  
 175:         #region UpdateControlParts
 176:         /// <summary>
 177:         /// Updates the control parts.
 178:         /// </summary>
 179:         private void UpdateControlParts()
 180:         {
 181:             double halfTheThumbWith = 0;
 182:  
 183:             if (this.thumb != null)
 184:             {
 185:                 halfTheThumbWith = this.thumb.ActualWidth / 2;
 186:             }
 187:  
 188:             double totalSize = this.ActualWidth - halfTheThumbWith * 2;
 189:  
 190:             double ratio = totalSize / (this.MaximumValue - this.MinimumValue);
 191:  
 192:             if (this.thumb != null)
 193:             {
 194:                 Canvas.SetLeft(this.thumb, ratio * this.Value);
 195:             }
 196:  
 197:             if (this.rectangle != null)
 198:             {
 199:                 this.rectangle.Width = ratio * this.Value + halfTheThumbWith;
 200:             }
 201:         }
 202:         #endregion //UpdateControlParts
 203:  
 204:         #endregion //Methods
 205:     }
 206: }

 

Data

This sample is focused mainly on Metro user interface and component development, but each application needs data.

Add two classes:

  • MobilePhone : to maintain phone data.
  • MobilePhoneData: to implement a collection with a sample mobile phone data

MobilePhone implementation

   1: class MobilePhone : INotifyPropertyChanged
   2:  {
   3:      #region Events
   4:  
   5:      public event PropertyChangedEventHandler PropertyChanged;
   6:  
   7:      #endregion // Events
   8:  
   9:      #region Properties
  10:  
  11:      #region Id
  12:  
  13:      private long _id;
  14:  
  15:      public long Id
  16:      {
  17:          get
  18:          {
  19:              return _id;
  20:          }
  21:  
  22:          set
  23:          {
  24:              if (value != _id)
  25:              {
  26:                  _id = value;
  27:                  this.RaisePropertyChanged("Id");
  28:              }
  29:          }
  30:      }
  31:  
  32:      #endregion // Id
  33:  
  34:      #region Brand
  35:  
  36:      private string _brand;
  37:  
  38:      public string Brand
  39:      {
  40:          get
  41:          {
  42:              return _brand;
  43:          }
  44:  
  45:          set
  46:          {
  47:              if (value != _brand)
  48:              {
  49:                  _brand = value;
  50:                  this.RaisePropertyChanged("Brand");
  51:              }
  52:          }
  53:      }
  54:  
  55:      #endregion // Brand
  56:  
  57:      #region Model
  58:  
  59:      private string _model;
  60:  
  61:      public string Model
  62:      {
  63:          get
  64:          {
  65:              return _model;
  66:          }
  67:  
  68:          set
  69:          {
  70:              if (value != _model)
  71:              {
  72:                  _model = value;
  73:                  this.RaisePropertyChanged("Model");
  74:              }
  75:          }
  76:      }
  77:  
  78:      #endregion // Model
  79:  
  80:      #region Price
  81:  
  82:      private string _price;
  83:  
  84:      public string Price
  85:      {
  86:          get
  87:          {
  88:              return _price;
  89:          }
  90:  
  91:          set
  92:          {
  93:              if (value != _price)
  94:              {
  95:                  _price = value;
  96:                  this.RaisePropertyChanged("Price");
  97:              }
  98:          }
  99:      }
 100:  
 101:      #endregion // Price
 102:  
 103:      #region Quantity
 104:  
 105:      private int _quantity;
 106:  
 107:      public int Quantity
 108:      {
 109:          get
 110:          {
 111:              return _quantity;
 112:          }
 113:  
 114:          set
 115:          {
 116:              if (value != _quantity)
 117:              {
 118:                  _quantity = value;
 119:                  this.RaisePropertyChanged("Quantity");
 120:              }
 121:          }
 122:      }
 123:  
 124:      #endregion // Quantity
 125:  
 126:      #endregion // Properties
 127:  
 128:      #region Methods
 129:  
 130:      #region RaisePropertyChanged
 131:      private void RaisePropertyChanged(string propertyName)
 132:      {
 133:          if (this.PropertyChanged != null)
 134:          {
 135:              this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
 136:          }
 137:      }
 138:      #endregion //RaisePropertyChanged
 139:  
 140:      #endregion // Methods
 141:  }

 

MobilePhoneData implementation

 

   1: class MobilePhonesData
   2:  {
   3:      #region Events
   4:  
   5:      public event PropertyChangedEventHandler PropertyChanged;
   6:  
   7:      #endregion // Events
   8:  
   9:      #region Properties
  10:  
  11:      #region Phones
  12:  
  13:      private ObservableCollection<MobilePhone> _phones;
  14:  
  15:      public ObservableCollection<MobilePhone> Phones
  16:      {
  17:          get
  18:          {
  19:              return _phones;
  20:          }
  21:  
  22:          set
  23:          {
  24:              if (value != _phones)
  25:              {
  26:                  _phones = value;
  27:                  RaisePropertyChanged("Phones");
  28:              }
  29:          }
  30:      }
  31:  
  32:      #endregion // Phones
  33:  
  34:      #endregion // Properties
  35:  
  36:      #region Constructor
  37:  
  38:      public MobilePhonesData()
  39:      {
  40:          this.Phones = new ObservableCollection<MobilePhone>
  41:                            {
  42:                                new MobilePhone
  43:                                    {
  44:                                        Id = 0,
  45:                                        Brand = "Motorola",
  46:                                        Model = "GSM C139",
  47:                                        Price = "42 $", 
  48:                                        Quantity = 100
  49:                                    },
  50:                                new MobilePhone
  51:                                    {
  52:                                        Id = 1,
  53:                                        Brand = "Motorola",
  54:                                        Model = "GSM C123",
  55:                                        Price = "46 $", 
  56:                                        Quantity=120
  57:                                    },
  58:                                new MobilePhone
  59:                                    {
  60:                                        Id = 2,
  61:                                        Brand = "LG",
  62:                                        Model = "GSM KG275",
  63:                                        Price = "48.99 $", 
  64:                                        Quantity=79
  65:                                    },
  66:  
  67:                                new MobilePhone
  68:                                    {
  69:                                        Id = 3,
  70:                                        Brand = "Sony Ericsson",
  71:                                        Model = "GSM J110i",
  72:                                        Price = "52 $", 
  73:                                        Quantity=35
  74:                                    }
  75:                            };
  76:      }
  77:  
  78:      #endregion // Constructor
  79:  
  80:      #region Methods
  81:  
  82:      private void RaisePropertyChanged(string propertyName)
  83:      {
  84:          if (this.PropertyChanged != null)
  85:          {
  86:              this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
  87:          }
  88:      }
  89:  
  90:      #endregion // Methods
  91:  }

 

Mobile Phone Details

Add a blank page for Mobile Phone Details. In this page application will display details about the selected mobile phone

   1: <Page
   2:     x:Class="SimpleSliderDemo.PhoneDetailsPage"
   3:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   4:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   5:     xmlns:local="using:SimpleSliderDemo"
   6:     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
   7:     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
   8:     mc:Ignorable="d">
   9:  
  10:     <Grid Background="{StaticResource ApplicationPageBackgroundBrush}">
  11:         <StackPanel Orientation="Vertical">
  12:             <TextBlock Text="Mobile Phone:" FontSize="36"/>
  13:             <TextBlock Text="{Binding Brand}" FontSize="32"/>
  14:             <TextBlock Text="{Binding Model}" FontSize="32"/>
  15:             <StackPanel Orientation="Horizontal">
  16:                 <TextBlock Text="Price=" FontSize="32"/>
  17:                 <TextBlock Text="{Binding Price}" FontSize="32"/>
  18:             </StackPanel>
  19:  
  20:             <StackPanel Orientation="Horizontal">
  21:                 <TextBlock Text="Quantity=" FontSize="32"/>
  22:                 <TextBlock Text="{Binding Quantity}" FontSize="32"/>
  23:             </StackPanel>
  24:         </StackPanel>
  25:     </Grid>
  26: </Page>

 

When navigate to this page you could set Parameter (selected mobile phone) as a DataContext for PhoneDetailsPage.

   1: protected override void OnNavigatedTo(NavigationEventArgs e)
   2: {
   3:     this.DataContext = e.Parameter;
   4: }

 

Application usage

Run the application. You could see the list with phones. When you select a phone details about the specified product appears in the frame where is loaded the phone details page. Slider thumb shows the number of items on stock (Quantity).

User could change the number of available phones when you drag the slider thumb

Finally you have a real XAML WinRT component for Metro applications. It is easy, very similar to other XAML platforms and could give an opportunity to be one of the first on this market.

Source code you could download here.

 Follow news from Infragistics in http://infragistics.com/ and twitter: @infragistics for more information about new Infragistics products.