Peter McGrattan’s Weblog

Silverlight, WCF, ASP.NET, AJAX, Graphics, RIA

Archive for the ‘XAML’ Category

Customizing the Silverlight 2 ItemsControl with Templates

Posted by petermcg on February 15, 2009

(Code download)

The ItemsControl in Silverlight 2 is the base type for a number of list based UI controls:

ItemsControl Derived Types

It’s at a place in it’s class hierarchy that means it provides just enough to get you going with list output. Controls further down the inheritance hierarchy from ItemsControl tend to specialise it’s functionality by providing the ability to select or edit items in the list in slightly distinct ways. The items in an ItemsControl can be defined in Xaml or generated dynamically from the ItemsSource property in code.

Every aspect of the ItemsControl’s appearance from the exterior of the control, the panel that surrounds it’s items and the items themselves can be customized based on a pre-defined pattern or ‘template’ that you specify. Three properties on the ItemsControl allow you to specify three different types of template to describe three different aspects of the appearance of the final list:

Property Name

Type

Default Content

Template System.Windows.Controls.ControlTemplate ItemsPresenter containing StackPanel
ItemsPanel System.Windows.Controls.ItemsPanelTemplate StackPanel containing ContentPresenter per item
ItemTemplate System.Windows.DataTemplate ContentPresenter containing Grid

 
These defaults can be visualised in the following control hierarchy:

Default ItemsControl Template Output

The ‘Template’ property is inherited from Control and appropriately allows you to specify a ControlTemplate to define the appearance of everything about the ItemsControl outside it’s ItemsPresenter. The other two properties ‘ItemsPanel’ and ‘ItemTemplate’ are defined by the ItemsControl class itself and are concerned with the appearance of items inside the ItemsControl. More specifically the ‘ItemsPanel’ property controls the appearance of the panel that surrounds all the item’s ContentPresenters, the ‘ItemTemplate’ property customizes the appearance of the contents of each ContentPresenter.

Customizing the Default Templates

The following example shows how to customize the value of each of these three properties to produce a different control hierarchy. The sample application attached to this post makes use of the ItemsControl to position images not vertically in a StackPanel but at specific coordinates within a Canvas. The Xaml from the sample application is reproduced below along with the subsequent control hierarchy:

<UserControl x:Class="ItemsControlImages.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:app="clr-namespace:ItemsControlImages">

    <UserControl.Resources>
        <ControlTemplate x:Key="ControlTemplateKey">
            <Border x:Name="ControlTemplate_ValueOf_Template">
                <ItemsPresenter />
            </Border>
        </ControlTemplate>

        <ItemsPanelTemplate x:Key="ItemsPanelKey">
            <Grid x:Name="ItemsPanelTemplate_ValueOf_ItemsPanel" />
        </ItemsPanelTemplate>

        <DataTemplate x:Key="ItemTemplateKey">
            <Canvas x:Name="DataTemplate_ValueOf_ItemTemplate" >
                <Image Canvas.Top="{Binding Location.Y}"
                       Canvas.Left="{Binding Location.X}"
                       Source="{Binding FlagImage}" />

                <StackPanel Canvas.Top="{Binding Location.Y}"
                            Canvas.Left="{Binding Location.X}">
                    <TextBlock Text="{Binding Title}" />
                    <TextBlock Text="{Binding Location}" />

                    <StackPanel.RenderTransform>
                        <TranslateTransform Y="-32.0" />
                    </StackPanel.RenderTransform>
                </StackPanel>
            </Canvas>
        </DataTemplate>
    </UserControl.Resources>

    <Canvas x:Name="LayoutRoot">
        <Canvas.Background>
            <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                <GradientStop Color="#FFB2C6D5"/>
                <GradientStop Color="#FF1483D9" Offset="1"/>
            </LinearGradientBrush>
        </Canvas.Background>

        <ItemsControl Template="{StaticResource ControlTemplateKey}"
                      ItemsPanel="{StaticResource ItemsPanelKey}"
                      ItemTemplate="{StaticResource ItemTemplateKey}">

            <app:Country Title="Argentina" Location="56,218" FlagImage="Images/ARG.png" />
            <app:Country Title="China" Location="368,66" FlagImage="Images/CHN.png" />
            <app:Country Title="Ireland" Location="192,90" FlagImage="Images/IRE.png" />
            <app:Country Title="New Zealand" Location="404,225" FlagImage="Images/NZ.png" />
            <app:Country Title="United States" Location="40,80" FlagImage="Images/USA.png" />

        </ItemsControl>
    </Canvas>
</UserControl>

This Xaml produces the following control hierarchy:

Customized ItemsControl Template Output

The contents of the three custom templates are defined in the Resources section of the UserControl and bound to the relevant properties of the instance of ItemsControl. Firstly the ControlTemplate specified in the Template property has been used to surround the ItemsPresenter with a blank Border. Secondly the ItemsPanelTemplate specified in the ItemsPanel property means the ItemsPresenter now has a Grid as it’s content rather than a StackPanel. Finally the DataTemplate specified in the ItemTemplate property has completely replaced the default Grid with a Canvas containing an Image and a StackPanel. The contents of the DataTemplate are interspersed with Binding expressions to extract data from each item in the ItemsControl, in this case instances of the simple Country class shown below:

using System.ComponentModel;
using System.Windows;

namespace ItemsControlImages
{
    public class Country : INotifyPropertyChanged
    {
        private string title;
        private string flagImage;
        private Point location;

        public string Title
        {
            get
            {
                return this.title;
            }
            set
            {
                if ((object.ReferenceEquals(this.title, value) != true))
                {
                    this.title = value;
                    this.RaisePropertyChanged("Title");
                }
            }
        }

        public string FlagImage
        {
            get
            {
                return this.flagImage;
            }
            set
            {
                if ((object.ReferenceEquals(this.flagImage, value) != true))
                {
                    this.flagImage = value;
                    this.RaisePropertyChanged("FlagImage");
                }
            }
        }

        public Point Location
        {
            get
            {
                return this.location;
            }
            set
            {
                if ((this.location.Equals(value) != true))
                {
                    this.location = value;
                    this.RaisePropertyChanged("Location");
                }
            }
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        protected void RaisePropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler propertyChanged = this.PropertyChanged;

            if (propertyChanged != null)
            {
                propertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion
    }
}

This all conspires to produce the following UI:

Positioned ItemsControl Images

Note even though the Images are in an ItemsControl they are positioned at the coordinates shown by setting the value of the Left and Top attached properties of their parent Canvas to the value of the X and Y coordinates from the custom Location property via the Bindings specified in the DataTemplate assigned to the ItemsControl’s ItemTemplate property.

The images used in the sample application are from this article on WikiMedia and the control hierarchy images were produced from screenshots of the excellent Silverlight Spy 2 which I highly recommend.

Posted in ItemsControl, Silverlight, XAML | Tagged: , , , , , | 14 Comments »

Stock List Demo Optimised

Posted by petermcg on July 4, 2008

Edited on 20/October/2008 : Code download updated to support Silverlight 2 RTW

(Code download)

I’ve recently addressed some comments on the blog and also optimised the Stock List Demo sample application, particularly the serialization.  The updates or deltas can now be pushed to clients at intervals from approx 1 millisecond.  If you haven’t seen this demo or haven’t downloaded it in a while it’s worth taking a look at the latest version.  Here is a screenshot of the server and one client application running :

Application Running

The sample comprises a Silverlight application with a DataGrid bound to an ObservableCollection<T> initially populated from a call to a Wcf Service and then updated by deltas received over Tcp Sockets.  The sample as a whole provides code that demonstrates some interesting different areas of Silverlight 2 development.  Silverlight Sockets support is used to push updates to one or many connected clients now at intervals from approx 1 millisecond upwards (the sample defaults to 50 milliseconds but feel free to change this to speed the sample up or slow it down) :

// Server
private void ServeDeltas()
{
    while (true)
    {
        if (synchClients.Count > 0)
        {
            // Send latest delta to all subscribed clients
            synchClients.SendAll(GetLatestDelta());

            // Set this to anything from 1.0 millisecond
            Thread.Sleep(TimeSpan.FromMilliseconds(50.0));
        }
        else
        {
            clientEvent.WaitOne();
        }
    }
}

The Silverlight UI remains responsive and notes can still be added against stocks in the DataGrid at these speeds.  The serialization has been optimised to use the DataContractSerializer and MemoryStream classes to more efficiently and reliably serialize and deserialize Stock objects from server to client :

// Server
public void Send(Stock stock)
{
    byte[] bytes = new byte[1024];

    using (MemoryStream memStream = new MemoryStream(bytes))
    {
        xmlSerializer.WriteObject(memStream, stock);

        try
        {
            memStream.WriteTo(netStream);
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex.Message);
        }
    }
}

// Client
private void OnSocketAsyncEventCompleted(object sender, SocketAsyncEventArgs e)
{
    Socket socket = e.UserToken as Socket;
    byte[] buffer = new byte[1024];

    switch (e.LastOperation)
    {
        case SocketAsyncOperation.Connect:
            // Set up receive buffer
            e.SetBuffer(buffer, 0, buffer.Length);
            // Asynchronously request to receive data from the server
            socket.ReceiveAsync(e);
            break;
        case SocketAsyncOperation.None:
            break;
        case SocketAsyncOperation.Receive:
            // Deal with received data from the server in our buffer
            int bytesTransferred = e.Buffer.TakeWhile(b => b != byte.MinValue).ToArray().Length;

            using (MemoryStream memStream = new MemoryStream(e.Buffer, 0, bytesTransferred, false))
            {
                try
                {
                    Stock stock = xmlSerializer.ReadObject(memStream) as Stock;

                    Action<Stock> action = UpdateStockList;
                    owner.BeginInvoke(action, stock);
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(ex.ToString());
                }
            }

            // Clear receive buffer
            e.SetBuffer(buffer, 0, buffer.Length);

            // Asynchronously request to receive more data from the server
            socket.ReceiveAsync(e);
            break;
        case SocketAsyncOperation.Send:
            break;
        default:
            break;
    }
}

The sample also shows how to use the Silverlight Dispatcher and Action<T> to queue work for execution on the UI thread (see client code after ReadObject above) and also how to apply an Action<T> to each element in a List<T> using ForEach :

// Client
private void OnGetStocksCompleted(object sender, GetStocksCompletedEventArgs e)
{
    Action<Stock> action = delegate(Stock delta)
    {
        StockDecorator stock = stockList.FirstOrDefault(s => s.Symbol == delta.Symbol);

        if (stock == default(StockDecorator))
        {
            stockList.Add(new StockDecorator(delta));
        }
    };

    e.Result.ForEach(action);
}

The Silverlight Socket policy requirements are handled on a separate worker thread in the same server that sends the Stock updates :

// Server
private void ServePolicy()
{
    while (true)
    {
        Console.WriteLine("Accepting Policy Requests...");

        // Read policy file
        byte[] policyBytes = File.ReadAllBytes("PolicyResponse.xml");

        using (Socket client = policyListener.AcceptSocket())
        {
            Console.WriteLine("Policy Request Accepted...");

            // Get poilicy request header
            byte[] buffer = new byte[1024];
            int bytesReceived = client.Receive(buffer);

            // Basic check of request header
            string header = Encoding.UTF8.GetString(buffer, 0, bytesReceived);

            if (header == "<policy-file-request/>")
            {
                client.Send(policyBytes, 0, policyBytes.Length, SocketFlags.None);
            }
        }
    }
}

The animation of the Stock Updates (green for positive, red for negative) is created programmatically and the sample also shows how to bind to those animations from Xaml :

<data:DataGrid x:Name="StocksGrid"
               AutoGenerateColumns="False"
               Grid.Row="1"
               Grid.ColumnSpan="2"
               GridlinesVisibility="Horizontal"
               HeadersVisibility="Column"
               RowBackground="AliceBlue"
               IsReadOnly="True"
               CanUserResizeColumns="False"
               Margin="0,5,0,0">
    <data:DataGrid.Columns>
        <data:DataGridTemplateColumn Header="Symbol">
            <data:DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Symbol}" />
                </DataTemplate>
            </data:DataGridTemplateColumn.CellTemplate>
        </data:DataGridTemplateColumn>
        <data:DataGridTemplateColumn Header="Bid">
            <data:DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <StackPanel Background="{Binding BidHighlight}">
                        <TextBlock Text="{Binding Bid}" />
                    </StackPanel>
                </DataTemplate>
            </data:DataGridTemplateColumn.CellTemplate>
        </data:DataGridTemplateColumn>
        <data:DataGridTemplateColumn Header="Ask">
            <data:DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <StackPanel Background="{Binding AskHighlight}">
                        <TextBlock Text="{Binding Ask}" />
                    </StackPanel>
                </DataTemplate>
            </data:DataGridTemplateColumn.CellTemplate>
        </data:DataGridTemplateColumn>
        <data:DataGridTemplateColumn MinWidth="150" Header="Notes">
            <data:DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Notes}" />
                </DataTemplate>
            </data:DataGridTemplateColumn.CellTemplate>
        </data:DataGridTemplateColumn>
    </data:DataGrid.Columns>
</data:DataGrid>

Multiple clients from different browsers can be connected by firing up a new browser and copy/pasting the address, they will receive the same updates (notice the highlights in the screenshot below) :

Multiple Clients Connected

The server will pause using an AutoResetEvent if there are no clients connected and resume when at least one client connects (you can also pause the server by pressing the pause key on your keyboard and resume by pressing Enter).  If you are working on a proof on concept for Silverlight 2 hopefully this is an example of a substantial application which may be helpful.

Posted in Silverlight, Sockets, WCF, XAML | Tagged: , , , , , | 12 Comments »

Beta 2 Xaml Namespaces

Posted by petermcg on June 16, 2008

The default XML namespace used by XAML in Silverlight 2 has changed in the transition from Beta 1 to Beta 2.  The two namespaces defined by default in Beta 1 were as follows :

<UserControl xmlns="http://schemas.microsoft.com/client/2007"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

In Beta 2 the default namespace has changed :

<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

The change has more than likely occurred as part of the work to make the Silverlight 2 API more compatible with WPF, although WPF 3.5 does define a new XML namespace :

xmlns="http://schemas.microsoft.com/netfx/2007/xaml/presentation"

Compatibility should still be maintained however as this namespace or the namespace defined in WPF 3.0 can be used when building WPF 3.5 applications.

XML Namespaces

An XML namespace must be a valid Uniform Resource Identifier (URI), a URI can either be a Uniform Resource Locator (URL) as in the above examples or a Uniform Resource Name (URN) as in the example below :

xmlns:charts="clr-namespace:Charts;assembly=Charts"

These URI’s do not resolve to any useful resource if you put them in the address bar in Internet Explorer for example, their only job is to be unique.  A common way to guarantee that uniqueness throughout the XAML’s travels is to start by using a domain name previously registered with an Internet Naming Authority such as schemas.microsoft.com and add a custom suffix such as netfx/2007/xaml/presentation.

The Default Namespace

There can be only one default XML namespace for an element, any additional namespaces must use a prefix.  In the default XAML markup below, the URI specified by the xmlns identifier without any prefix is the default namespace for the UserControl element and it’s descendant elements (the namespace’s scope) but default namespaces have no affect on attributes.  This means that all unqualified element names within the default namespace’s scope (UserControl and Grid) are implicitly associated with that namespace’s URI.

<UserControl x:Class="SilverlightApplication1.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="400" Height="300">
    <Grid x:Name="LayoutRoot" Background="White">

    </Grid>
</UserControl>

This is important because it is this implicit namespace URI that the XAML processor uses to search referenced assemblies to find the matching CLR Type for these unqualified elements.  The image below shows the default references for a standard Silverlight Application containing the above markup :

Default References

These referenced assemblies are searched by the XAML processor for a predefined, hardcoded attribute (the XmlnsDefinition attribute) defining this implicit URI as it’s ‘key’ :

System.Windows Assembly Attributes

When found, as many are in the System.Windows.dll assembly only, the name of a CLR namespace is retrieved from the ‘value’ of the same attribute.  This CLR namespace is then returned as a scope for the XAML processor to search within to find the matching CLR Type for the XAML element.  Both the UserControl and the Grid types are found in the System.Windows.Controls CLR namespace in this way :

UserControl Grid

You can see from the illustration above that twelve CLR namespaces in the System.Windows.dll assembly are mapped to the Silverlight Beta 2 default XAML namespace and that the Beta 1 namespace is currently still supported.  Note that this process is also the method by which the CLR Application type, instantiated by the the Silverlight plug-in control, is mapped :

<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
             x:Class="SilverlightApplication1.App"
             >

</Application>

Using the default XML namespace on the Application element in App.xaml, this fundamental CLR type is found in the System.Windows namespace again in the System.Windows.dll assembly :

Application

Prefixed Namespaces

To recap, there can be only one default XML namespace for an element, any additional namespaces must use a prefix and default namespaces have no affect on attributes.  In order to affect an attribute with a namespace a prefix must be used, the prefix then becomes an abbreviation for that namespace.  In this way the x prefix is used by convention with it’s corresponding pre-defined URI to fulfil Silverlight’s contract as an implementation of the XAML language :

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

The XAML language expects implementers of it’s syntax to expose certain fundamental attributes such as x:Class and x:Name (as show in the examples above) so the processor can perform duties such as joining a code-behind file to a XAML file through a partial class and creating members of that class with meaningful names.  This functionality is manifest in the System.Windows.Markup namespace also in the System.Windows.dll assembly.

For more information on XML namespaces in general and how they affect the family of XML technologies, have a look here and here.

Posted in Silverlight, WPF, XAML | Tagged: , , , , | Comments Off

 
Follow

Get every new post delivered to your Inbox.