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

Windows Store apps with XAML and HTML: Controls to the max

(2 votes)
Andrea Boschin
>
Andrea Boschin
Joined Nov 17, 2009
Articles:   91
Comments:   9
More Articles
0 comments   /   posted on Apr 16, 2013
Categories:   Windows 8

Tweet

Every Windows developer knows the term "control" in the acception used into this operating system. This name is usually associated with piece of code, related to a visual component, which implements a reusable behavior. Since my first steps in Visual Basic, ages ago, these components have always permeated my work and ofter they made the success of a project, giving much more speed to the development, without losing in quality. So, Microsoft always payed full attention to this aspect which is often a parallel market for third party companies working on awesome controls to sell to people that needs effective interfaces, without paying the price of developing them directly. Both XAML and HTML in Windows Store apps allow developer to use and implement "controls", and in this article I would like to focus this topic to understand how each technology gives its interpretation.

Using a control in XAML and HTML

As you should expect, the very first aspect I would like to get in touch is to use existing controls. This apply both to native components that are shipped with the framework itself but also with third party controls you can buy from the vendors. Speaking about the technologies we are inspecting, these components have a number of points in common: they usually have properties that allow to customize the aspect or the behavior or to read the required result. Also they have events that notify about the interaction of the user with the component itself or whatever else it can happen during the normal lifecycle of the application which uses it. In my mind there is only one single difference between a XAML control versus its HTML counterpart. While the XAML markup usually directly represents the included control, with a special syntax to include the external namespaces needed to reference the component, the HTML control is something that is "attached" to an existing element, which it means an existing HTML tag that is used as an anchor into the markup for the given behavior. Just to be clear here is two snippets that shows how to add a control in markup for both the technologies. Let start with XAML:

   1: <Grid xmlns:cm="using:Elite.Html4Xaml.XamlApp.Common">
   2:     <cm:TilePad x:Name="tileTable" Width="1000" Height="500" Columns="50" Rows="25" 
   3:                 NormalTileStyle="{StaticResource TileNormal}"
   4:                 HoverTileStyle="{StaticResource TileHover}" />
   5: </Grid>

This example is related to the second part of the article where I will show how to create a control. For now focus on the current topic. The first line, just into the <Grid> tag, declares a namespace "cm" that is a shortcut for the Assembly where the control implementation resides. Usually this declaration is given at the top of the XAML file but nothing prevents to make the declaration close to the usage, also inside the tag of the control itself. Then using the "cm" prefix I added the tag <cm:TilePad /> that represents the position where the control will be rendered inside of the Visual Tree. Each property is set directly as an attribute on the tag an this il really easy and readable. Two things must be in your mind: first, the tag does not represents the control but it "IS" the instance of the control. Second, just being into the xaml means that the control will be rendered during the layout pass. Let go with the HTML version now:

   1: <!-- WinJS -->
   2: <script src="http://Microsoft.WinJS.1.0/js/base.js"></script>
   3: <script src="http://Microsoft.WinJS.1.0/js/ui.js"></script>
   4: 
   5: <!-- control specific -->
   6: <link href="controls.css" rel="stylesheet" />
   7: <script src="controls.js"></script>
   8: <div id="tileTable"
   9:     style="width: 1000px; height: 500px;"
  10:     data-win-control="Elite.Html4Xaml.Controls.TilePad"
  11:     data-win-options="{ columns: 50, rows: 25, tileClass: 'tile-normal' }">
  12: </div>

In the first part, a number of script tags add the required source code for WinJS and for the control itself. Then, a <div> tag acquire a set of "data" attributes that determine the way it should be rendered. The "data-win-control" attribute determine the associated control and the "data-win-options" contains the initial values of the properties. This latter value is expressed with a special notation called Json that every Javascript programmer knows. So the difference is clear, the behavior "TilePad" is attached to the <div> element in the markup. The other difference from XAML is that, this sole code does not suffice to have the control rendered. In the javacript of the page you are required to trigger the rendering calling almost the WinJS.UI.processAll() method.

   1: ready: function (element, options)
   2: {
   3:     WinJS.UI.processAll();
   4: },

Once the control is in the page you can always get a reference to it in the code to change its properties. This is done automaticcally in XAML when you assign the "x:Name" property but in HTML you have to call the document.getElementById method as you would do in a normal html page:

   1: ready: function (element, options)
   2: {
   3:     var tileTable = document.getElementById('tileTable').winControl;
   4:     tileTable.addEventListener('tileClick', this.handleTileClick.bind(this));
   5:     
   6:     WinJS.UI.processAll();
   7: },

The winControl property contains the instance of the class that represents the control, so you can attach events and set properties as shwon in the snippet. In XAML the tileTable variable is automatically assigned by the runtime in the InitializeComponent. Nothing needs to be done.

   1: public ControlsPage()
   2: {
   3:     this.InitializeComponent();
   4:     this.tableTile.TileClick += HandleTileClick;
   5: }

Create your own controls

The matter of creating controls is something that is so much hard than someone could expect, because it does not only involves a technical knowledge but also the ability of abstracting behaviors in reusable artifacts. In the following rows I do not suppose to give a full understand of the matter that could take an entire series. As an example, in XAML the controls are strictly related with the understanding of the Visual Tree inner working, with the dependency property system and this directly permeates the way you think your control to have it working the better.

The first thing to implement when you create a control, both in WinJS that in XAML, is to create a class. In Javascript you get an additional help from WinJs that gives a number of methods to implement object oriented paradigms.

   1: WinJS.Namespace.define("Elite.Html4Xaml.Controls",
   2: {
   3:     TilePad: WinJS.Class.define(function (element, options)
   4:     {
   5:         // constructor
   6:     },
   7:     {
   8:         // members
   9:     }
  10: });

In XAML, using C#as language, you do not have any problem to implement classes but you have to decide from which base class derive. You use Control for leaf components that does not contains Visual Tree elements, otherwise from ContentControl. In this case Control is good:

   1: public sealed class TilePad : Control
   2: {
   3:     public TilePad()
   4:     {
   5:         this.DefaultStyleKey = typeof(TilePad);
   6:     }
   7: }

In this snippet, inside the constructor, the DefaultStyleKey property is set to connect the class with its template that is located in the Themes/Generic.xaml file. This is the default template of the control an it is made of a chunk of XAML that describes the inner organization of elements. Thanks to this structure a XAML control can be manipulated with blend to give a totally different look & feel without modifing the behavior. In a WinJs control this concept is missing but you have to create the inner elements by code when the control is attached. The best is to build the elements just after the constructor is called.

   1: WinJS.Namespace.define("Elite.Html4Xaml.Controls",
   2: {
   3:     TilePad: WinJS.Class.define(function (element, options)
   4:     {
   5:         if (element == null || element.tagName.toLowerCase() !== "div")
   6:             throw "control must be attached to a div element";
   7:  
   8:         this.element = element;
   9:         this.columns = options.columns;
  10:         this.rows = options.rows;
  11:         this.tileClass = options.tileClass;
  12:         this.buildTiles();
  13:     },
  14:     {
  15:         element:
  16:         {
  17:             set: function (value) { this._element = value; },
  18:             get: function () { return this._element; }
  19:         },
  20:         columns:
  21:         {
  22:             set: function (value) { this._columns = value; },
  23:             get: function () { return this._columns; }
  24:         },
  25:         rows:
  26:         {
  27:             set: function (value) { this._rows = value; },
  28:             get: function () { return this._rows; }
  29:         },
  30:         tileClass:
  31:         {
  32:             set: function (value) { this._tileClass = value; },
  33:             get: function () { return this._tileClass; }
  34:         },
  35:         buildTiles: function ()
  36:         {
  37:             var fw = ''; for (i = 0; i < this.columns; i++, fw += '1fr ');
  38:             var fh = ''; for (i = 0; i < this.rows; i++, fh += '1fr ');
  39:  
  40:             this.element.style.display = '-ms-grid';
  41:             this.element.style.msGridColumns = fw;
  42:             this.element.style.msGridRows = fh;
  43:  
  44:             for (var x = 1; x <= this.columns; x++)
  45:             {
  46:                 for (var y = 1; y <= this.rows; y++)
  47:                 {
  48:                     var _this = this;
  49:                     var tile = document.createElement("div");
  50:                     tile.className = this.tileClass;
  51:                     tile.style.msGridColumn = x;
  52:                     tile.style.msGridRow = y;
  53:                     tile.onclick = this.handleClick.bind(this);
  54:                     this.element.appendChild(tile);
  55:                 }
  56:             }
  57:         },
  58:         handleClick: function (ev)
  59:         {
  60:             if (ev.srcElement != null)
  61:                 this.dispatchEvent('tileClick', { element: ev.srcElement });
  62:         }
  63:     })
  64: });
  65:  
  66: WinJS.Class.mix(Elite.Html4Xaml.Controls.TilePad,
  67:     WinJS.Utilities.createEventProperties("tileClick"),
  68:     WinJS.UI.DOMEventMixin);

When your class is instantiated the constructor gets the element to which it is attached and a value that contains the options specified in data-win-options. These values are copied to a number of properties and finally the layout is build according to the requested values.  Watch at the buildTiles method in the class. The XAML imolementation behave exactly the same; a bunch of dependency properties collect the values from the XAML markup. So in the OnApplyTemplate method it gets a reference to the RootElement (a Grid in the template) and then creates the elements of the table. In a real control this should be done using a itemscontrol but for the sake of simplicity is is done by code.

   1: [TemplatePart(Name = TilePad.RootElementName, Type = typeof(Grid))]
   2: public sealed class TilePad : Control
   3: {
   4:     public event EventHandler TileClick;
   5:  
   6:     private const string RootElementName = "RootElement";
   7:     public Grid RootElement { get; set; }
   8:  
   9:     #region Columns
  10:  
  11:     public static readonly DependencyProperty ColumnsProperty =
  12:         DependencyProperty.Register("Columns", typeof(int), typeof(TilePad), new PropertyMetadata(1));
  13:  
  14:     public int Columns
  15:     {
  16:         get { return (int)GetValue(ColumnsProperty); }
  17:         set { SetValue(ColumnsProperty, value); }
  18:     }
  19:  
  20:     #endregion
  21:  
  22:     #region Rows
  23:  
  24:     public static readonly DependencyProperty RowsProperty =
  25:         DependencyProperty.Register("Rows", typeof(int), typeof(TilePad), new PropertyMetadata(1));
  26:  
  27:     public int Rows
  28:     {
  29:         get { return (int)GetValue(RowsProperty); }
  30:         set { SetValue(RowsProperty, value); }
  31:     }
  32:  
  33:     #endregion
  34:  
  35:     #region NormalTileStyle
  36:  
  37:     public static readonly DependencyProperty NormalTileStyleProperty =
  38:         DependencyProperty.Register("NormalTileStyle", typeof(Style), typeof(TilePad), new PropertyMetadata(null));
  39:  
  40:     public Style NormalTileStyle
  41:     {
  42:         get { return (Style)GetValue(NormalTileStyleProperty); }
  43:         set { SetValue(NormalTileStyleProperty, value); }
  44:     }
  45:  
  46:     #endregion
  47:  
  48:     #region HoverTileStyle
  49:  
  50:     public static readonly DependencyProperty HoverTileStyleProperty =
  51:         DependencyProperty.Register("HoverTileStyle", typeof(Style), typeof(TilePad), new PropertyMetadata(null));
  52:  
  53:     public Style HoverTileStyle
  54:     {
  55:         get { return (Style)GetValue(HoverTileStyleProperty); }
  56:         set { SetValue(HoverTileStyleProperty, value); }
  57:     }
  58:  
  59:     #endregion
  60:  
  61:     public TilePad()
  62:     {
  63:         this.DefaultStyleKey = typeof(TilePad);
  64:     }
  65:  
  66:     protected override void OnApplyTemplate()
  67:     {
  68:         this.RootElement = this.GetTemplateChild(TilePad.RootElementName) as Grid;
  69:         this.BuildTiles();
  70:         base.OnApplyTemplate();
  71:     }
  72:  
  73:     private void BuildTiles()
  74:     {
  75:         if (this.RootElement != null)
  76:         {
  77:             this.RootElement.Children.Clear();
  78:  
  79:             this.RootElement.ColumnDefinitions.Clear();
  80:             foreach(var column in from i in Enumerable.Range(0, this.Columns)
  81:                                   select new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) })
  82:                 this.RootElement.ColumnDefinitions.Add(column);
  83:  
  84:             this.RootElement.RowDefinitions.Clear();
  85:             foreach (var row in from i in Enumerable.Range(0, this.Rows)
  86:                                   select new RowDefinition { Height = new GridLength(1, GridUnitType.Star) })
  87:                 this.RootElement.RowDefinitions.Add(row);
  88:  
  89:             for (int x = 0; x < this.Columns; x++)
  90:             {
  91:                 for (int y = 0; y < this.Rows; y++)
  92:                 {
  93:                     Rectangle tile = new Rectangle();
  94:                     tile.Style = this.NormalTileStyle;
  95:                     Grid.SetColumn(tile, x);
  96:                     Grid.SetRow(tile, y);
  97:                     tile.PointerReleased += HandlePointerReleased;
  98:                     tile.PointerEntered += HandleToggleStyle;
  99:                     tile.PointerExited += HandleToggleStyle;
 100:                     this.RootElement.Children.Add(tile);
 101:                 }
 102:             }
 103:         }
 104:     }
 105:  
 106:     private void HandlePointerReleased(object sender, PointerRoutedEventArgs e)
 107:     {
 108:         Rectangle tile = sender as Rectangle;
 109:  
 110:         if (tile != null && this.TileClick != null)
 111:             this.TileClick(tile, EventArgs.Empty);
 112:     }
 113:  
 114:     private void HandleToggleStyle(object sender, PointerRoutedEventArgs e)
 115:     {
 116:         Rectangle tile = sender as Rectangle;
 117:  
 118:         if (tile != null)
 119:         {
 120:             if (Object.Equals(tile.Style, this.NormalTileStyle))
 121:                 tile.Style = this.HoverTileStyle;
 122:             else if (Object.Equals(tile.Style, this.HoverTileStyle))
 123:                 tile.Style = this.NormalTileStyle;
 124:         }
 125:     }
 126: }

Both the controls exposes an event to notify when a tile is clicked. This is done as usual in C# but is slightly more complex in HTML. The expose an event you have to create a mix between the control itself and the WinJs.UI.DomEventMixin. This enable the raising of the event and the attach from outside the control.

It's someway complex in XAML

Watching ad the classes the feel may be that the XAML version is much more complex than the HTML. This is for sure true because the templating system that is underway in XAML requires to pay some attention to get a working control. For a simple control the best choice is to write a simple user control, but when you need to encapsulate complex behaviors the templated control becomes a big advantage and its complexity is justified by the benefits.


Subscribe

Comments

No comments

Add Comment

Login to comment:
  *      *       

From this series