Peter McGrattan’s Weblog

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

Archive for the ‘WPF’ Category

LINQ to Visual Tree (Beta)

Posted by petermcg on March 4, 2009

(Code Download)

Introduction

This post presents an early prototype of ‘LINQ to Visual Tree’ for Silverlight 2 and WPF applications.  The ultimate goal is to provide applications with the ability to quickly and easily query the Silverlight 2 or WPF Visual Tree via LINQ to Objects.

The prototype provides this functionality by enabling enumeration of an application’s in-memory Visual Tree through the IEnumerable<T> interface.  This is distinct to implementing a custom query provider: the prototype does not implement IQueryable<T> or execute and translate expression trees.

This post demonstrates the basics so far and makes the prototype available for download, it’s aim is to get feedback and ideas on how the simple API can be improved.

Trees in Silverlight and WPF

The one fundamental tree of objects that make up a Silverlight 2 or WPF application is the object tree.  WPF provides two conceptual views of the object tree called the logical tree and the visual tree that if combined would expose the full object tree.  The two conceptual trees are exposed via the LogicalTreeHelper and VisualTreeHelper static classes respectively.  The logical tree is a map of elements in the object tree’s element hierarchy (defined in Xaml or in code) that make up an applications user interface, including inferred elements not explicitly typed by the programmer.  The visual tree is a map of elements in the object tree’s element hierarchy that derive from Visual or Visual3D, each element is exploded into it’s constituent visual ingredient elements.

Silverlight does not expose the full object tree, there is more than likely a logical tree behind the scenes but there is currently no LogicalTreeHelper static class to expose it.  Silverlight does however expose a visual tree through VisualTreeHelper but the Visual and Visual3D types do not exist in the API.  The criteria for an object’s inclusion in the Silverlight visual tree according to MSDN is that the object has a "rendering implication".

LINQ to Visual Tree

So every Silverlight and WPF application has a Visual Tree exposed through VisualTreeHelper.  ‘LINQ to Visual Tree’ extends this model by enabling enumeration of the results returned from VisualTreeHelper for an object and it’s descendants.  The prototype is exactly two days old – I’m still experimenting at this stage, it currently consists of the following two extension methods:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Media;
 
namespace LinqToVisualTree.Common
{
    public static class VisualEnumerable
    {
        /// <summary>
        /// Gets the Visual Tree filtered by Type for a DependencyObject with that DependencyObject as the root.
        /// </summary>
        public static IEnumerable<T> GetVisualOfType<T>(this DependencyObject element)
        {
            return GetVisualTree(element).Where(t => t.GetType() == typeof(T)).Cast<T>();
        }
 
        /// <summary>
        /// Gets the Visual Tree for a DependencyObject with that DependencyObject as the root.
        /// </summary>
        public static IEnumerable<DependencyObject> GetVisualTree(this DependencyObject element)
        {
            int childrenCount = VisualTreeHelper.GetChildrenCount(element);
 
            for (int i = 0; i < childrenCount; i++)
            {
                var visualChild = VisualTreeHelper.GetChild(element, i);
 
                yield return visualChild;
 
                foreach (var visualChildren in GetVisualTree(visualChild))
                {
                    yield return visualChildren;
                }
            }
        }
    }
}

            

Although there are currently only two extension methods, they provide an abundance of scope for querying the Visual Tree in Silverlight or WPF.  Let’s take a look at some usage scenarios from the code download to see how ‘LINQ to Visual Tree’ can be used in practice to query the Visual Tree of a Silverlight 2 application consisting of the following simple Xaml:

<UserControl x:Class="LinqToVisualTree.Sil.Tasks.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Loaded="Page_Loaded">
    <UserControl.Resources>
        <SolidColorBrush x:Key="ForegroundBrush" Color="#FFFF0000"/>
    </UserControl.Resources>
 
    <Grid x:Name="GridOne" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
 
        <TextBox x:Name="TextBoxOne" Text="One" Foreground="{StaticResource ForegroundBrush}" Grid.Row="0" />
 
        <StackPanel x:Name="StackPanelOne" Grid.Row="1">
            <TextBox x:Name="TextBoxTwo" Text="Two" />
 
            <Grid x:Name="GridTwo">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                </Grid.RowDefinitions>
 
                <TextBox x:Name="TextBoxThree" Text="Three" Grid.Row="0" />
 
                <StackPanel x:Name="StackPanelTwo" Grid.Row="1">
                    <TextBox x:Name="TextBoxFour" Text="Four" Foreground="{StaticResource ForegroundBrush}" />
                </StackPanel>
            </Grid>
        </StackPanel>
    </Grid>
</UserControl>

             

Xaml similar to the above is also used for the test WPF application in the code download, for testing it’s really the hierarchy of the elements in the Xaml that is important – this can be visualized as follows:

Example Xaml Hierarchy

The code download defines seven tasks that demonstrate how to use the ‘LINQ to Visual Tree’ API, starting off with basic queries and progressing to slightly more involved ones.  Each task shows the code to produce the required result using both Extension Method syntax and LINQ syntax.  Each task is represented by a method called from the Page_Loaded event handler defined in the above Xaml:

          

/// <summary>
/// Task 1: Get the Visual Tree of this UserControl
/// </summary>
private void Task1()
{
    // Extension Method syntax
    var pageTree1 = this.GetVisualTree();
 
    // Linq syntax
    var pageTree2 = from v in this.GetVisualTree()
                    select v;
 
    Debug.Assert(pageTree1.SequenceEqual(pageTree2));
}

          

Silverlight Result WPF Result
Silverlight Task 01 Result Wpf Task 01 Result

          

/// <summary>
/// Task 2: Get all the TextBox controls in this UserControl's Visual Tree
/// </summary>
private void Task2()
{
    // Extension Method syntax
    var pageTextBoxes1 = this.GetVisualOfType<TextBox>();
 
    // Linq syntax
    var pageTextBoxes2 = from v in this.GetVisualOfType<TextBox>()
                        select v;
 
    Debug.Assert(pageTextBoxes1.SequenceEqual(pageTextBoxes2));
}

          

Silverlight Result WPF Result
Silverlight Task 02 Result Wpf Task 02 Result

          

/// <summary>
/// Task 3: Get the Visual Tree of GridTwo
/// </summary>
private void Task3()
{
    // Extension Method syntax
    var gridTwoTree1 = GridTwo.GetVisualTree();
 
    // Linq syntax
    var gridTwoTree2 = from v in GridTwo.GetVisualTree()
                       select v;
 
    Debug.Assert(gridTwoTree1.SequenceEqual(gridTwoTree2));
}

          

Silverlight Result WPF Result
Silverlight Task 03 Result Wpf Task 03 Result

          

/// <summary>
/// Task 4: Get all the TextBox controls in GridTwo's Visual Tree 
/// </summary>
private void Task4()
{
    // Extension Method syntax
    List<TextBox> gridTwoTextBoxes1 = GridTwo.GetVisualOfType<TextBox>().ToList();
 
    // Linq syntax
    List<TextBox> gridTwoTextBoxes2 = (from v in GridTwo.GetVisualOfType<TextBox>()
                                       select v).ToList();
 
    Debug.Assert(gridTwoTextBoxes1.SequenceEqual(gridTwoTextBoxes2));
}

          

Silverlight Result WPF Result
Silverlight Task 04 Result Wpf Task 04 Result

          

/// <summary>
/// Task 5: Get all TextBox controls with a red foreground in this UserControl's Visual Tree
/// </summary>
private void Task5()
{
    // Extension Method syntax
    List<TextBox> redTextBoxes1 = this.GetVisualOfType<TextBox>()
                                  .Where(t => t.Foreground == this.Resources["ForegroundBrush"])
                                  .ToList();
 
    // Linq syntax
    List<TextBox> redTextBoxes2 = (from t in this.GetVisualOfType<TextBox>()
                                   where t.Foreground == this.Resources["ForegroundBrush"]
                                   select t).ToList();
 
    Debug.Assert(redTextBoxes1.SequenceEqual(redTextBoxes2));
}

          

Silverlight Result WPF Result
Silverlight Task 05 Result Wpf Task 05 Result

          

/// <summary>
/// Task 6: Get all Grid controls in a StackPanel in this UserControl's Visual Tree
/// </summary>
private void Task6()
{
    // Extension Method syntax
    List<Grid> gridsInSp1 = (this.GetVisualOfType<StackPanel>()
                            .SelectMany(s => s.GetVisualOfType<Grid>())
                            ).ToList();
 
    // Linq syntax
    List<Grid> gridsInSp2 = (from s in this.GetVisualOfType<StackPanel>()
                            from g in s.GetVisualOfType<Grid>()
                            select g).ToList();
 
    Debug.Assert(gridsInSp1.SequenceEqual(gridsInSp2));
}

          

Silverlight Result WPF Result
Silverlight Task 06 Result Wpf Task 06 Result

          

/// <summary>
/// Task 7: Get all TextBox controls in a Grid in a StackPanel in this UserControl's Visual Tree
/// </summary>
private void Task7()
{
    // Extension Method syntax
    List<TextBox> textBoxesInGridInSp1 = (this.GetVisualOfType<StackPanel>()
                                        .SelectMany(s => s.GetVisualOfType<Grid>())
                                        .SelectMany(g => g.GetVisualOfType<TextBox>())
                                        ).ToList();
 
    // Linq syntax
    List<TextBox> textBoxesInGridInSp2 = (from s in this.GetVisualOfType<StackPanel>()
                                          from g in s.GetVisualOfType<Grid>()
                                          from t in g.GetVisualOfType<TextBox>()
                                          select t).ToList();
 
    Debug.Assert(textBoxesInGridInSp1.SequenceEqual(textBoxesInGridInSp2));
}

          

Silverlight Result WPF Result
Silverlight Task 07 Result Wpf Task 07 Result

          

Feel free to download the code and run the tasks to see how the API is used from both Silverlight and WPF.  As I say I’m still experimenting with this so I’d love to hear your suggestions and get feedback and ideas on how the simple API can be improved.

Posted in LINQ to Visual Tree, Silverlight, WPF | Tagged: | 5 Comments »

Silverlight 2 WCF Polling Duplex Support – Part 4: Adding a WPF Client

Posted by petermcg on November 19, 2008

(Code download)

Introduction

This post attempts to highlight some of the intricacies involved in connecting a WPF Client to the existing Silverlight 2 Polling Duplex demo previously published on this blog.  To recap, the demo consists of a WCF service designed to push Stock data to previously connected Silverlight 2 clients over Http.  In a similar scenario to a chat application, the Silverlight 2 clients can also send ‘Note’ messages back to the server and have them propagated to other connected clients.

The aim of this post is to make the demo capable of also supporting connections from WPF clients.  Success is defined by enabling WPF clients to connect to the same WCF service, receive the same Stock updates at the same time as the Silverlight 2 clients and having Notes synchronized across all connected clients regardless of their underlying technology.  Below is a link to a screenshot of the final version of this demo running, shown is the Console Application hosting the WCF service with two Silverlight clients and one WPF client connected:

Server and All Clients Running

PollingDuplexHttpBinding and WPF clients

Up until this point the demo application has solely supported pushing data to Silverlight 2 clients, defining a PollingDuplexHttpBinding endpoint on the server side and creating a channel factory as part of the Silverlight 2 client as shown below:

// Create a channel factory capable of producing a channel of type IDuplexSessionChannel
IChannelFactory<IDuplexSessionChannel> factory = new PollingDuplexHttpBinding().BuildChannelFactory<IDuplexSessionChannel>();

However, attempting to use PollingDuplexHttpBinding for this purpose in a WPF application (as in the code above) currently results in a NotSupportedException exception being raised with a full explanation and even some architectural advice:

Polling Duplex NotSupportedException

PollingDuplexHttpBinding cannot currently be used in non-Silverlight clients without running into this exception.  In order to extend the demo to support pushing Stock data to a WPF client through Http the guidance from the above exception is to consider WSDualHttpBinding.  This binding predates Silverlight and is the closest alternative for achieving equivalent results to PollingDuplexHttpBinding in a non-Silverlight client application.  A look at the BindingElement objects each binding encapsulates reveals WSDualHttpBinding is actually a much more mature binding than PollingDuplexHttpBinding:

WSDualHttpBinding PollingDuplexHttpBinding
TransactionFlowBindingElement PollingDuplexBindingElement
ReliableSessionBindingElement TextMessageEncodingBindingElement
SymmetricSecurityBindingElement HttpTransportBindingElement
CompositeDuplexBindingElement  
OneWayBindingElement  
TextMessageEncodingBindingElement  
HttpTransportBindingElement  
The WSDualHttpBinding Endpoint

The outcome of acting on the aforementioned advice in the demo application is the addition of a new endpoint to the WCF service exposed from the StockServer Console Application.  This could easily be specified entirely in a configuration file (see this post for how to configure the Silverlight policy and duplex endpoints) but in the download is achieved in code as shown below:

using System;
using System.ServiceModel;
using System.ServiceModel.Description;

namespace StockServer
{
    public class StockServiceHost : ServiceHost
    {
        public StockServiceHost(object singletonInstance, params Uri[] baseAddresses)
            : base(singletonInstance, baseAddresses)
        {
        }

        public StockServiceHost(Type serviceType, params Uri[] baseAddresses)
            : base(serviceType, baseAddresses)
        {
        }

        protected override void InitializeRuntime()
        {
            this.AddServiceEndpoint(
                typeof(IPolicyProvider),
                new WebHttpBinding(),
                new Uri("http://localhost:10201/")).Behaviors.Add(new WebHttpBehavior());

            this.AddServiceEndpoint(
                typeof(IStockService),
                new PollingDuplexHttpBinding(),
                new Uri("http://localhost:10201/SilverlightStockService"));

            this.AddServiceEndpoint(
                typeof(IStockService),
                new WSDualHttpBinding(WSDualHttpSecurityMode.None),
                new Uri("http://localhost:10201/WpfStockService"));

            base.InitializeRuntime();
        }
    }
}

Although the WSDualHttpBinding endpoint is registered using a different URI, the endpoint is simply just another entry point into the same WCF service called by the existing Silverlight 2 clients using PollingDuplexHttpBinding.  Before you can push data from a WCF duplex service to a client, the client must initiate the session by calling the service.  The Register method serves this purpose in the demo application and the only change to the code in the WCF service itself (although there are no method signature changes) is in this method as shown below:

public void Register(Message message)
{
    client = new StockClient(OperationContext.Current.GetCallbackChannel<IStockClient>(),
                                  "StockClient/IStockService/Receive",
                                  message.Version);
    client.Faulted += OnClientFaulted;

    InitialReply();

    StockGenerator.Instance.DeltaReceived += OnDeltaReceived;
}

The change is minor but essential: extracting the MessageVersion from the initial message sent by the client and passing it to the constructor of the StockClient object along with a reference to the channel back to that client.  The variety of MessageVersion used by each of the bindings in the demo application is as follows:

Binding MessageVersion
WSDualHttpBinding
MessageVersion.Soap12WSAddressing10
PollingDuplexHttpBinding
MessageVersion.Soap11

Messages sent between WSDualHttpBinding endpoints use the SOAP 1.2 protocol; between PollingDuplexHttpBinding endpoints the SOAP 1.1 protocol is employed.  There is a clue in the name of MessageVersion. Soap12WSAddressing10 that WSDualHttpBinding also requires addressing headers to support reliable messaging sessions via ReliableSessionBindingElement.  In the case of these two bindings, attempting to use a mismatch of MessageVersion and binding, for example MessageVersion.Soap11 for messages sent to a WSDualHttpBinding endpoint, results in an InvalidOperationException as shown below:

MessageVersion InvalidOperationException

Storing the message version along with the channel reference in an instance of the StockClient class (see code download) ensures the reply messages are pushed back to the client in the format the relevant binding expects.  The rest of the WCF service remains the same: a new instance of StockService is created per session regardless of the endpoint used.  Each session registers it’s interest in the DeltaReceived event of the singleton StockGenerator class.  This event is raised approx every 250 milliseconds and each session’s handler (OnDeltaReceived) sends the same generated delta to its respective client via the stored channel reference using the appropriate message protocol.

The WPF Client Application

In a good advertisement for how nearly all of what you write in Silverlight 2 can be used without modification in WPF, the code in the WPF client application is extremely similar to that contained in the Silverlight 2 client project.  The markup in StockWindow.xaml and code in StockWindow.xaml.cs is virtually identical to Page.xaml and Page.xaml.cs in the Silverlight 2 project.

One difference between the WPF and Silverlight client code is the WPF DataGrid which is from the October 2008 Release of the WPF Toolkit available from codeplex.  Another difference already highlighted is that PollingDuplexHttpBinding cannot be used in a non-Silverlight client application.  In its place we use WSDualHttpBinding on the WPF client side also and due to this binding’s maturity the code in StockTicker.cs is less verbose as shown below:

using System;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Windows.Threading;
using Common;

namespace WpfStockClient
{
    public sealed class StockTicker : IStockClient
    {
        // Reference to layout instance that created the StockTicker
        private readonly Dispatcher owner = null;

        // Serializes instances of the Stock type before they are sent on the wire
        private readonly DataContractSerializer stockSerializer = new DataContractSerializer(typeof(Stock));

        // Proxy for communication to WCF service
        private IStockService proxy;

        // List of stocks designed to be bound to a UI control
        private readonly StockList stockList = new StockList();

        public StockList StockList
        {
            get { return stockList; }
        }

        public StockTicker(Dispatcher owner)
        {
            if (owner == null)
            {
                throw new ArgumentNullException("owner");
            }

            this.owner = owner;
        }

        public void SubscribeDeltas()
        {
            EndpointAddress endPoint = new EndpointAddress("http://localhost:10201/WpfStockService");

            proxy = new DuplexChannelFactory<IStockService>(this,
                                                            new WSDualHttpBinding(WSDualHttpSecurityMode.None),
                                                            endPoint).CreateChannel();

            proxy.Register(Message.CreateMessage(MessageVersion.Soap12WSAddressing10, "StockClient/IStockService/Register"));
        }

        public void Receive(Message message)
        {
            Stock stock = message.GetBody<Stock>();

            // Queue a call to UpdateStockList on the Dispatcher of the thread that created this instance
            Action<Stock> action = UpdateStockList;
            owner.BeginInvoke(action, stock);
        }

        public void Sync(Stock stock)
        {
            // Create a message with the appropriate SOAPAction and asynchronously send it via the proxy with the serialized Stock as the body of the envelope
            Message message = Message.CreateMessage(MessageVersion.Soap12WSAddressing10, "StockClient/IStockService/Sync", stock, stockSerializer);
            proxy.Sync(message);
        }

        private void UpdateStockList(Stock delta)
        {
            // NOTE : CheckAccess is there but intellisense doesn't see it because of the [EditorBrowsable(EditorBrowsableState.Never)] attribute
            if (!owner.CheckAccess())
            {
                throw new InvalidOperationException("The calling thread cannot access this method because a different thread owns it.");
            }

            // Check if this Stock is already in the collection
            Stock existing = stockList.FirstOrDefault(s => s.Symbol == delta.Symbol);

            if (existing == default(Stock))
            {
                // This delta is a new Stock
                stockList.Add(new StockHighlight(delta));
            }
            else
            {
                // This delta is an existing Stock
                existing.Ask = delta.Ask;
                existing.Bid = delta.Bid;

                if (!String.IsNullOrEmpty(delta.Notes))
                {
                    existing.Notes = delta.Notes;
                }
            }
        }
    }
}

There are many similarities between the above code and the Silverlight 2 version; the changes are all centred on the body of the SubscribeDeltas method.  This is where the client instance of the WSDualHttpBinding class is instantiated and used to establish a composite duplex channel (two one-way channels) with the new endpoint at the address exposed in the WCF service.  The first parameter passed to the DuplexChannelFactory<IStockService>  constructor specifies ‘this’ as the instance of the IStockService interface to receive the pushed messages from the WCF service in the Receive method.  After the channel is created the initial message is sent using the correct MessageVersion as described earlier.

Linked Common Files

Those class files that are identical across both client projects are added to the Common Solution folder and are referenced using links from the respective projects where they are used:

Common Files Linked

The highlighted classes are written in one place and as such are assured of being the same structure throughout the solution (and where relevant on both sides of the wire as a consequence).  As the files are linked, the code they contain will be compiled into IL that targets the respective version of the CLR implied by the type of project the classes are linked from.  For the WPF project the linked classes will be compiled into an assembly that targets the desktop CLR, for the Silverlight project the linked classes will be compiled into an assembly that targets the more compact CoreCLR.  Linking common files in this manner is one solution to the problem normally solved by using a class library when a common CLR is targeted by all projects.

Animating the Stock Price changes

Illustrating changes to Stock price instances displayed in the DataGrid by creating the illusion that the DataGrid cell background is changing color takes less code in WPF than in Silverlight.  The code for this animation for both WPF and Silverlight 2 is contained in the StockHighlight class.  The entire contents of the StockHighlight.cs file is shown below, the code between the #if and #endif directives is compiled only if the SILVERLIGHT symbol is defined as it is in the Build tab of project properties on all default Silverlight 2 projects:

using System;
using System.Runtime.Serialization;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Animation;

namespace Common
{
    [DataContract(Name = "Stock", Namespace = "urn:petermcg.wordpress.com")]
    public class StockHighlight : Stock
    {
        public SolidColorBrush AskHighlight { get; private set; }
        public SolidColorBrush BidHighlight { get; private set; }

#if SILVERLIGHT
        private Storyboard askPosStory = new Storyboard();
        private Storyboard askNegStory = new Storyboard();
        private Storyboard bidPosStory = new Storyboard();
        private Storyboard bidNegStory = new Storyboard();
#endif

        private static readonly Duration DefaultDuration = new Duration(TimeSpan.FromSeconds(2.0));
        private static readonly Color DefaultColor = Colors.Transparent;

        public StockHighlight(Stock stock)
        {
            base.Ask = stock.Ask;
            base.Bid = stock.Bid;
            base.Notes = stock.Notes;
            base.Symbol = stock.Symbol;

            InitializeAnimation();
        }

        private void InitializeAnimation()
        {
            // Default highlight color to Transparent
            this.AskHighlight = new SolidColorBrush(DefaultColor);
            this.BidHighlight = new SolidColorBrush(DefaultColor);

#if SILVERLIGHT
            InitializeAnimation(AskHighlight, askPosStory, askNegStory);
            InitializeAnimation(BidHighlight, bidPosStory, bidNegStory); 
#endif
        }

#if SILVERLIGHT
        private static void InitializeAnimation(SolidColorBrush brush, Storyboard pos, Storyboard neg)
        {
            // Set up a unique Positive and Negative ColorAnimationUsingKeyFrames for  price
            ColorAnimationUsingKeyFrames posAnim = new ColorAnimationUsingKeyFrames();
            posAnim.KeyFrames.Add(new LinearColorKeyFrame() { KeyTime = TimeSpan.FromSeconds(0.0), Value = Colors.Green });
            posAnim.KeyFrames.Add(new LinearColorKeyFrame() { KeyTime = DefaultDuration.TimeSpan, Value = DefaultColor });
            posAnim.Duration = DefaultDuration;

            ColorAnimationUsingKeyFrames negAnim = new ColorAnimationUsingKeyFrames();
            negAnim.KeyFrames.Add(new LinearColorKeyFrame() { KeyTime = TimeSpan.FromSeconds(0.0), Value = Colors.Red });
            negAnim.KeyFrames.Add(new LinearColorKeyFrame() { KeyTime = DefaultDuration.TimeSpan, Value = DefaultColor });
            negAnim.Duration = DefaultDuration;

            // Add animations to storyboard
            pos.Children.Add(posAnim);
            neg.Children.Add(negAnim);

            // Target the  color animations to the  brush
            Storyboard.SetTarget(posAnim, brush);
            Storyboard.SetTarget(negAnim, brush);

            // Target the property of the  brush to animate
            Storyboard.SetTargetProperty(posAnim, new PropertyPath(SolidColorBrush.ColorProperty));
            Storyboard.SetTargetProperty(negAnim, new PropertyPath(SolidColorBrush.ColorProperty));
        }

        private void BeginHighlight(Storyboard storyBoard)
        {
            storyBoard.Begin();
        }
#else
        private void BeginHighlight(SolidColorBrush brush, Color startColor)
        {
            ColorAnimationUsingKeyFrames anim = new ColorAnimationUsingKeyFrames();
            anim.KeyFrames.Add(new LinearColorKeyFrame() { KeyTime = TimeSpan.FromSeconds(0.0), Value = startColor });
            anim.KeyFrames.Add(new LinearColorKeyFrame() { KeyTime = DefaultDuration.TimeSpan, Value = DefaultColor });
            anim.Duration = DefaultDuration;

            brush.BeginAnimation(SolidColorBrush.ColorProperty, anim);
        }
#endif
        public override double Ask
        {
            get
            {
                return base.Ask;
            }
            set
            {
                if ((base.Ask.Equals(value) != true))
                {
#if SILVERLIGHT
                    BeginHighlight((value > this.Ask) ? askPosStory : askNegStory);
#else
                    BeginHighlight(AskHighlight, (value > this.Ask) ? Colors.Green : Colors.Red);
#endif

                    base.Ask = value;

                    this.RaisePropertyChanged("Ask");
                }
            }
        }

        public override double Bid
        {
            get
            {
                return base.Bid;
            }
            set
            {
                if ((base.Bid.Equals(value) != true))
                {
#if SILVERLIGHT
                    BeginHighlight((value > this.Bid) ? bidPosStory : bidNegStory);
#else
                    BeginHighlight(BidHighlight, (value > this.Bid) ? Colors.Green : Colors.Red);
#endif

                    base.Bid = value;

                    this.RaisePropertyChanged("Bid");
                }
            }
        }
    }
}

The targets of the animation are the two public SolidColorBrush properties, AskHighlight and BidHighlight.  The Color dependency property of these brushes is animated using color key frame animation from green for a positive change or red for a negative change.  The animation for both WPF and Silverlight begins with a call to the BeginHighlight method when the corresponding Ask or Bid price property changes.  The SILVERLIGHT debug symbol makes it clear to see the extra Storyboard objects and extra initialization required in the InitializeAnimation method to achieve the desired effect in Silverlight 2.  You can see the references to these AskHighlight and BidHighlight properties in the WPF XAML below, also notice the xmlns:data for the DataGrid from the WPF Toolkit:

<Window x:Class="WpfStockClient.StockWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:data="http://schemas.microsoft.com/wpf/2008/toolkit"
    Title="Stock Window" Width="640" Height="520">

    <Grid x:Name="LayoutRoot" Background="White"  Margin="5,5,5,5">
        <Grid.RowDefinitions>
            <RowDefinition MaxHeight="30" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width=".80*" />
            <ColumnDefinition Width=".20*" />
        </Grid.ColumnDefinitions>

        <TextBox x:Name="Notes" Grid.Row="0" Grid.Column="0" Margin="0,0,5,0" />
        <Button x:Name="AddNotes" Click="AddNotes_Click" Content="Add Notes" Grid.Row="0" Grid.Column="1" Margin="5,0,0,0" />

        <data:DataGrid x:Name="StocksGrid"
                       AutoGenerateColumns="False"
                       Grid.Row="1"
                       Grid.ColumnSpan="2"
                       GridLinesVisibility="Horizontal"
                       HeadersVisibility="Column"
                       RowBackground="AliceBlue"
                       CanUserResizeColumns="False"
                       SelectionChanged="StocksGrid_SelectionChanged"
                       SelectionMode="Single"
                       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>
    </Grid>
</Window>

For more detail and to see the application running the entire solution can be downloaded using the link at the start of this post.

Posted in PollingDuplex, Silverlight, WCF, WPF | Tagged: , , , , , , , , | 9 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.