David Sackstein's Blog

"The more that you learn, the more places you'll go.”, Dr. Seuss
Top Tips for Rapid Application Development

In recent weeks I have been busy on a number of projects with very tight, almost impossible schedules.

When schedules are tight, there is simply not enough time to implement all the steps of a formal development procedure. But I have found that some good practices shine through as time savers every time. Not only do they save time, but they also help produce exensible, maintainable code – which also saves time in the long run.

So here are my top tips for Rapid Application Development.

  1. Document the requirements succintly. Avoid using document templates that you can’t fill.
  2. Define and assign responsibilities, develop by contract and work in parallel.
  3. Keep interfaces simple. Use patterns commonly found in the framework you are using.
  4. Beware of generalizing unnecessarily.
  5. Provide prototypes frequently.
  6. Use functionality provided by the framework (e.g. Base Class Library in .Net, STL in C++)
  7. Unit test rigorously. You should have at least as much testing code as production code.
  8. Provide succint, useful inline documentation.
  9. Refactor constantly. Review, simplify, reorganize, rewrite, throw away.
  10. Document the features, bug fixes and known bugs of each version you provide and provide adequate installation utilities.
  11. Encourage tester feedback and provide quick, reliable responses to each report.

It is important to note that these tips only work well for proficient programmers, for whom the development environment is a tool and not a challenge. You need to know the environment well for points 3 and 6 and, if rewrites take too long, point 9 will not just not be practical.

The good news is that Visual Studio 2008 and the .Net framework provide inherent support for RAD.

You can learn so much about the environment from intellisense without even leaving your project. You can use snippets and autocompletions to boost your typing speed 10x and you can use Visual Studio’s refactoring tools to relieve you of the more tedious tasks associated with rewrites.

These tools are easily accessible and extremely reliable.

Good luck on your rapid application development!

Received my MCT Credentials Today

Earlier this year I completed the MCPD certification - today I earned my MCT !

Over the year I have trained classes on C++, .Net 2.0, WPF, WF, WCF, Silverlight and Design Patterns.

This has just got to be the fastest and most effective way to learn : )

WPF Explorer View with TreeView

In this post we will see how to use WPF’s TreeView with the WPF Tookit’s DataGrid to present an Explorer-like view of a file system folder. We will use databinding with the HierarchicalDataTemplate, a sprinkling of LINQ and as little code as possible.

You can download the source code here.

User Interface Layout

As in Explorer, we will present a window with two vertical panes. The left pane will show the directory structure as a tree; the right pane will show the files in the folder that is selected in the left pane.

The layout is very simple. It contains a Grid with three columns: the left and right panels and a GridSplitter in the middle. The left panel contains a TreeView (directoryView) to represent the directory structure, the right panel contains a DataGrid (fileView), from the WPF Toolkit, to represent the list of files in the selected directory.

Left Pane : Directory View

Here is the markup for the TreeView:

      <TreeView Grid.Column="0" Name="directoryView">

         <TreeView.Resources>

            <HierarchicalDataTemplate DataType="{x:Type local:DirectoryRecord}"

                                     ItemsSource="{Binding Directories}" >

               <TextBlock Text="{Binding Info.Name}"/>

            </HierarchicalDataTemplate>

         </TreeView.Resources>

      </TreeView>

As you can see I have used the HierarchicalDataTemplate to format the nodes of the TreeView. See also this post about HierarchicalDataTemplate. Each node binds to an instance of the DirectoryRecord, which is a wrapper of the DirectoryInfo class. The wrapper is required in order to expose the GetFiles and GetDirectories methods as properties.

Here it is:

    class DirectoryRecord

    {

        public DirectoryInfo Info { get; set; }

 

        public IEnumerable<FileInfo> Files

        {

            get

            {

                return Info.GetFiles();

            }

        }

        public IEnumerable<DirectoryRecord> Directories

        {

            get

            {

                return from di in Info.GetDirectories("*", SearchOption.AllDirectories)

                       select new DirectoryRecord { Info = di };

            }

        }

    }

Right Pane: File View

This is the markup for the DataGrid

      <wf:DataGrid Grid.Column="2"

          Name="fileView"

          ItemsSource="{Binding ElementName=directoryView,

                        Path=SelectedItem.Files}"></wf:DataGrid>

The databinding is interesting. I have bound the ItemsSources to the Files property of the DirectoryRecord that is selected in the directoryView.

The default value of AutoGenerateColumns is true, but I filter the columns with the following delegate which is attached to the AutoGeneratingColumn event of directoryView

        private void fileView_AutoGeneratingColumn(

            object sender, DataGridAutoGeneratingColumnEventArgs e)

        {

            List<string> requiredProperties = new List<string>

            {

                "Name", "Length", "LastWriteTime"

            };

            if (!requiredProperties.Contains(e.PropertyName))

                e.Cancel = true;

            else

            {

                e.Column.Header = e.Column.Header.ToString().MixedCaseToSpace();

            }

        }

Finally, I added some styling for the DataGrid as follows:

   <Window.Resources>

      <Style TargetType="{x:Type wf:DataGrid}">

         <Setter Property="Background" Value="White"></Setter>

         <Setter Property="GridLinesVisibility" Value="Vertical"></Setter>

         <Setter Property="RowHeaderWidth" Value="0"></Setter>

         <Setter Property="VerticalGridLinesBrush"

                Value="{Binding ElementName=splitter, Path=Background}"></Setter>

      </Style>

   </Window.Resources>

Putting It All Together

In the constructor of the Window we install events for Loaded and the AutoGeneratingColumn of the fileView.

        public Window1()

        {

            InitializeComponent();

 

            this.Loaded += Window_Loaded;

            fileView.AutoGeneratingColumn += fileView_AutoGeneratingColumn;

        }

 

The folder to be viewed in the window is currently hard coded. You can easily add a browse button to allow the user to select the folder to view.

In the Loaded event we create the root node and bind it to the directoryView.

        string folder = @"..\..\";

 

        void Window_Loaded(object sender, RoutedEventArgs e)

        {

            var root = new ObservableCollection<DirectoryRecord>{

                new DirectoryRecord {

                    Info = new DirectoryInfo(folder)

                }

            };

            directoryView.ItemsSource = root;

            Title = "Explorer View (" + Path.GetFullPath(folder) + ")";

        }

 

Da Da!

Explorer View with TreeView

Performance

I think you will agree that the we have achieved quite a lot with very little code.

However, if you try to use the application to view the contents of your entire c: drive, you will wait a long time until the TreeView is filled.

Does anyone have any simple ideas to improve the performance?

Silverlight Client With Sockets - Barebones Sample

Dan Wahlin wrote an excellent article on Silverlight Client with Sockets - accompanied by a sample application.

My objective in this post is simply to reduce Dan’s application to the bare minimum so that it is easier to reuse.

You can download my reduced sample here.

Changes

These are the differences between my sample and Dan’s.

  1. There are now only three projects: The Silverlight control, the hosting Web site and the server.
  2. I reduced the Silverlight client to contain a single text box displaying a counter whose value is pushed from the server.
  3. Accordingly I deleted all business objects (Teams, Scores etc) from the client and the server.
  4. Then I merged the PolicySocketServer (which provides the cross-domain policy file) and the CounterSocketServer (which does the real work of pushing new counter values to clients) into one executable. As they both block while listening for connections, I ran them both in separate threads.
  5. I reduced all customized logging to good old Console.Write.
  6. I fixed a bug (I think it was a bug).
    In PolicySocketServer.OnSendComplete there was a call to client.Client.EndSendFile. I replaced it with a call to client.Client.EndSend to correspond with client.Client.BeginSend.

Summary

I recommend you read Dan’s article to get the complete picture. I offer you use of this reduced sample to make it easier to reuse the techniques.

Cross-Domain Policy Files for Flash and Silverlight with WCF

Introduction

In an earlier blog I described my motivation for upgrading an ASMX Web Service to WCF in a particular project.

In that project the client was a Flash client hosted in a web page. The upgrade was simple and, as long as I ran the client from within Flex Builder, it had no problem consuming the new self-hosted WCF service instead of the ASMX.

Allowing Cross-Domain Access

However, once I deployed the package outside the Flex Builder, all calls to the service failed. The reason was that the domain from which I had loaded the web page was not the same as the domain of the WCF service.

RIA applications like Flash and Silverlight regard such cross-domain accesses as potential security risks and block them - unless cross-domain policies on the server explicitly allow them. This requirement is relaxed in the development environment and enforced in a production environment, which explains why I had no problem during development.

So what if, like me, you would like certain services to serve clients even if they are loaded from other domains?

Well, the solution is to specify such permissions in a file called a Cross Domain Policy File and to make it accessible to clients at the domain URL. For instance, if your service resides at www.mycompany.com/Services/ProductsService.svc the file should be accessible as www.mycompany.com/crossdomain.xml.

Both Flash and Silverlight try to download such a file before accessing applications in the domain. By default they will look for a file called crossdomain.xml at the root of the domain. The Adobe cross-domain file specification can be found here. Tim Heuer shows how to create policy files for Silverlight here.

Problem: Exposing Cross-Domain Policy Files for a Self-Hosted WCF Service

When your service is being hosted by IIS it is easy to make the crossdomain.xml file accessible to clients, because it is straightforward to expose files using IIS. But, alas if you are using a self-hosted WCF service, it would seem that you are out of luck. Unless the WCF host delivers the crossdomain.xml file itself, it will not be accessible to remote clients.

Solution: Using WCF’s REST Support

Think about it, all we really need is to be able to respond to an HTTP Get request for the URL (such as www.mycompany.com/crossdomain.xml) by sending an HTTP response containing the contents of the cross-domain file.

WCF can easily support this with its support for RESTful services that I described in an earlier post.

Here are the configuration file and code snippets adapted where necessary for the task at hand.

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

  <system.serviceModel>

    <services>

      <service name="ProductsService"

               behaviorConfiguration="ProductsServiceBehavior">

        <endpoint address="ProductsService.svc"

                  binding="basicHttpBinding"

                  contract="IProductsService" />

        <endpoint address=""

                  binding="webHttpBinding"

                  behaviorConfiguration="WebBehavior"

                  contract="IRestfulOperations" />

        <host>

          <baseAddresses>

            <add baseAddress="http://www.mycompany.com/" />

          </baseAddresses>

        </host>

      </service>

    </services>

    <behaviors>

      <endpointBehaviors>

        <behavior name="WebBehavior">

          <webHttp />

        </behavior>

      </endpointBehaviors>

    </behaviors>

  </system.serviceModel>

</configuration>

and here is the code (adapted from an answer by Carlos Figueira here).

    public partial class ProductsService : IProductsService, IPolicyRetriever

    {

        #region IPolicyRetriever Members

 

        public Stream GetPolicy()

        {

            string result = @"<?xml version=""1.0""?>

                <!DOCTYPE cross-domain-policy SYSTEM

                     ""http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd"">

                <cross-domain-policy>

                    <allow-access-from domain=""*"" />

                    <allow-http-request-headers-from domain=""*"" headers=""SOAPAction""/>

                </cross-domain-policy>";

            WebOperationContext.Current.OutgoingResponse.ContentType = "application/xml";

            return new MemoryStream(Encoding.UTF8.GetBytes(result));

        }

 

        #endregion

    }

Summary

In this post I described how you can use WCF’s REST support to have a self-hosted service expose the cross-domain policy file required by Silverlight and Flash when calling into Web services.

This is one more proof that WCF is extremely versatile interoperable and can be used in applications with a variety of requirements, including RIA in applications such as Flash and Silverlight.

Enabling RESTful Services with WCF

I came across a number of sources that describe how to support RESTful Services with WCF, but unfortunately, I couldnt find one source that had all the small print. So, here it is – all in one place.

Objective

Let’s say you have a WCF Web Service exposed at an endpoint with this address: www.mycompany.com/Services/ProductsService.svc. And let’s say that in addition to speaking sophisticated SOAP with some clients you would also like to respond to GET requests from simple clients that need to retrieve all the information in XML (or JSON) form. For instance, you might like to respond to this URL: www.mycompany.com/productlist.xml with the contents of the productlist.xml file.

Solution

This is possible using WCF, and as you might expect, it involves defining an additional endpoint. There are actually three things you need to do:

  1. Define an additional end point using the webHttpBinding with a webHttp behavior.
  2. Add a ServiceContract with an OperationContract decorated with the [WebGet] attribute.
  3. Implement the OperationContract by returning a Stream with the contents of the required file.

1. Additional EndPoint with a webHttp Behavior

A configuration file is worth a thousand lines of code, so here is a rather minimilistic service-side configuration file that shows what you need.

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

  <system.serviceModel>

    <services>

      <service name="ProductsService"

               behaviorConfiguration="ProductsServiceBehavior">

        <endpoint address="Services/ProductsService.svc"

                  binding="basicHttpBinding"

                  contract="IProductsService" />

        <endpoint address=""

                  binding="webHttpBinding"

                  behaviorConfiguration="WebBehavior"

                  contract="IRestfulOperations" />

        <host>

          <baseAddresses>

            <add baseAddress="http://www.mycompany.com/" />

          </baseAddresses>

        </host>

      </service>

    </services>

    <behaviors>

      <endpointBehaviors>

        <behavior name="WebBehavior">

          <webHttp />

        </behavior>

      </endpointBehaviors>

    </behaviors>

  </system.serviceModel>

</configuration>

2. Add an OperationContract with the [WebGet] Attribute

The code in this and the next section is based on responses by Carlos Figueira here.

Here is the new ServiceContract with the decorated OperationContract:

    [ServiceContract]

    public partial interface IRestfulMethods

    {

        [OperationContract, WebGet(UriTemplate = "/ProductList.xml")]

        Stream GetProductList();

    }

I advise you take a look at the documentation for more information on WebGet parameters. In particular, take a look at the UriTemplate which allows you to pass in parameters for the decorated method as part of the URL. WCF will extract the parameters and pass them as arguments to the method.

3. Implement the OperationContract

    public partial class ProductsService : IProductsService, IRestfulMethods

    {

        #region IRestfulMethods Members

 

        public Stream GetProductList()

        {

            string result = @"<?xml version=""1.0""?>

                <Products>

                    <Garden Chair>

                        <Quantity>2</Quantity>

                        <Price>100</Price>

                    <Garden Chair>

                    <Magic Wand>

                        <Quantity>1</Quantity>

                        <Price>50</Price>

                    </Magic Wand>

                </Products>";

            WebOperationContext.Current.OutgoingResponse.ContentType = "application/xml";

            return new MemoryStream(Encoding.UTF8.GetBytes(result));

        }

 

        #endregion

    }

For this to work you need to add a reference to the System.ServiceModel.Web assembly as well as to System.ServiceModel and to add a using statement for the System.ServiceModel.Web namespace.

Putting It All Together

Now, when a client makes a GET request to the URL www.mycompany.com/ProductList.xml, the new endpoint with the webHttpBinding responds by calling the GetProductList method. The GetProductList retrieves the data from the file (or generates it some other way), encapsulates it as a Stream and returns the Stream. WCF then writes the contents of the Stream to the HTTP Response.

Summary

In this post I demonstrated the three basic steps you need to implement to have a WCF service serve RESTful clients. You do this by mapping the RESTful requests to an OperationContract decorated with the WebGet attribute and that is exposed at an endpoint that uses the webHttpBinding binding with a webHttp behavior.

Case Study: Upgrading from ASMX to WCF

Introduction

In this post I will share my considerations for upgrading an ASMX Web Service to WCF in an existing product. Following the upgrade I encountered a little hitch with Flash and cross domain policies. In this later post I demonstrate how overcome the hitch with WCF.

Motivation for the Upgrade

When I started working on the project, the architecture had been set and a first version of the product had already been released. A Flash client interacted with an ASMX Web Service hosted by ASP.NET. My part was to make additions to the UI, but I couldn’t help reviewing the architecture to see what I could learn and improve.

An important first observation was that the Flash-ASMX combination works. Microsoft and Adobe are fierce competitors, so I think that deservers mention. Having said that, there are some limitations in the Flash support for SOAP Web Services which were disappointing.

For instance, in a Web Service you can define enums, and through the WSDL, these can be treated as enums on a Microsoft client. However enums are not supported by Flash clients. See the bug report here. Trivial as that may seem, the impact on design is significant. It encouraged the original designers to use strings and integers in the interface. With very little strong-typing in the API, maintenance and debugging became difficult indeed.

Quite early on I decided to upgrade the ASPX Web Service to WCF. I had three reasons for this:

  1. WCF supports multiple concurrent end-points.
  2. WCF implements singleton behavior with a mere attribute, saving coding.
  3. WCF can be self-hosted and doesn’t require IIS.

Multiple end-points

The need for multiple end-points in this project was due to the internal architecture on the Server. The service actually had two clients. One was the RIA hosted in Web page, invoked from a remote client, another was a Windows Service running on the Server. The original solution involved an additional Remoting server with the same interface as the Web Service with which the Web Server and the local client interacted. Managing multiple servers, contracts and implementations was tough.

This problem is a natural fit for WCF: All I needed to do was implement one server with WCF and to add endpoint definitions to a configuration file. I defined a standard endpoint for the Flash client using basicHttpBinding and an efficient, proprietary endpoint for the local client using NetNamedPipeBinding.

One contract, one service implementation – just two endpoints.

Singleton Behavior

In the original design, the designers created the service as a Singleton and syncronized accesses to the service from the two clients in code. Rather than clutter the code with locks I preferred to use WCF’s ServiceBehavior attribute with InstanceContextMode.Single and ConcurrencyMode.Single. WCF handles the implementation.

Self-Hosting - No Need for IIS

Actually, this reason was not in my original list. But then, as I approached the design review for the new GUI, the Product Manager of the product and the Technical Writer asked me if they could get a mock-up of the application to review and to document following the review.

This wouldn’t usually be a problem, because, following good practice in GUI development, I was developing against an interface and not a real server. I had already implemented the Web Service contract with a Mock so there would be no need to install any business layer components with the mock-up.

But there was still one hitch. In order to run the mock-up, I needed IIS to host the ASMX web service. My clients would have to install IIS, or I would have to do it for them. What a pain : )

Well, with WCF that was easy to solve – a simple Console Application could host the service in 1-2-3 (or should I say A-B-C).

Summary

In this post I described some very practical reasons why I upgraded an ASMX Web Service to WCF in an existing product. In the next post I will show you how I used WCF to solve a cross-domain problem that I encountered with the Flash client.

Flash and Silverlight

I mentioned some time back that I am doing some development work with Flash. It has given me a great opportunity to get a broader perspective on what Microsoft has accomplished with Silverlight (and WCF) and I would like to share some of the insights gained in my next few posts.

Naturally I will be making some comparisons between the technologies, but I will begin with a humble disclaimer.

I am not an expert on Flash and my experience with this one project is limited. If I do either technology an injustice, please feel free to comment and correct me. My objective is for us to learn by perspective, not to criticize.

I haven’t considered any particular order for these posts, but I will index them later if this leads to any confusion.

So, let’s begin.

My first post is about handling cross-domain policies from the Flash RIA. It’s easy to do with WCF as we will see.

Hello World Silverlight on a Blog Post

This is a test page that I wrote to check that I can run Silverlight applications in a blog post. This way I will be able to demonstrate Silverlight and WPF (not all) stuff without you having to necessarily download and compile it.

It works! and the possibilities are endless : )

Get Microsoft Silverlight

Here is the XAML for this Hello World blog-hosted Silverlight application:

<UserControl x:Class="HelloWorld.MainPage"

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

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

   Width="400" Height="300"

   >

   <UserControl.Resources>

      <Storyboard x:Key="storyBoard">

         <DoubleAnimation

                 Storyboard.TargetName="button"

                 Storyboard.TargetProperty="FontSize"

                 To="30"

                 Duration="0:0:2"

                 AutoReverse="True"

           >

         </DoubleAnimation>

      </Storyboard>

   </UserControl.Resources>

   <Grid x:Name="LayoutRoot" Background="White">

      <Button Name="button" Height="100" Width="200" FontSize="20"

             Click="Button_Click">

         <TextBlock>Hello World</TextBlock>

      </Button >

   </Grid>

</UserControl>

In order to start the animation I used this code behind implementation for Button_Click:

    private void Button_Click(object sender, RoutedEventArgs e)

    {

        Storyboard storyBoard = this.Resources["storyBoard"] as Storyboard;

        storyBoard.Begin();

    }

I was able to load the Silverlight control into this page simply by editing the HTML inserting the following:

   <div id="silverlightControlHost">

      <object data="data:application/x-silverlight-2,"

             type="application/x-silverlight-2"

             width="100%" height="100%">

         <param name="source"

          value="http://blogs.microsoft.co.il/blogs/davids/../HelloSilverlightOnBlog.zip/>

         <param name="background" value="white" />

         <param name="minRuntimeVersion" value="3.0.40624.0" />

         <param name="autoUpgrade" value="true" />

         <a href=http://go.microsoft.com/fwlink/?LinkID=149156&v=3.0.40624.0

           style="text-decoration:none">

            <img src=http://go.microsoft.com/fwlink/?LinkId=108181

                alt="Get Microsoft Silverlight" style="border-style:none"/>

         </a>

      </object>

  </div>

WPF Simple ControlTemplate Sample

Here is a sample of using a ControlTemplate to customize a button.

The result is a circular button with a radial brush that, when clicked, animates the origin of the brush from the center by a small offset - and then back.

I put it in a page so you can copy this snippet to a text file and open with Internet Explorer.

<Page

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

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

  Title="Page1" Height="300" Width="300">

   <Page.Resources>

      <Style TargetType="{x:Type Button}">

         <Setter Property="Template">

            <Setter.Value>

               <ControlTemplate>

                  <Ellipse Width="{TemplateBinding Width}" Height="{TemplateBinding Height}">

                     <Ellipse.Fill>

                        <RadialGradientBrush x:Name="radialBrush" SpreadMethod="Pad">

                           <GradientStop Offset="1" Color="Red"></GradientStop>

                           <GradientStop Offset="0" Color="White"></GradientStop>

                        </RadialGradientBrush>

                     </Ellipse.Fill>

                  </Ellipse>

                  <ControlTemplate.Triggers>

                     <EventTrigger RoutedEvent="ButtonBase.Click">

                        <BeginStoryboard>

                           <Storyboard

                            TargetName="radialBrush"

                            TargetProperty="GradientOrigin">

                              <PointAnimation

                               From="0.5,0.5"

                               To="0.4,0.4"

                               Duration="0:0:1"

                               AutoReverse="true">

                              </PointAnimation>

                           </Storyboard>

                        </BeginStoryboard>

                     </EventTrigger>

                  </ControlTemplate.Triggers>

               </ControlTemplate>

            </Setter.Value>

         </Setter>

      </Style>

   </Page.Resources>

   <Button Height="300" Width="300"/>

</Page>

Here are the highlights:

  1. The page contains one button and a style (in Page.Resources).
  2. The style applies to Button controls without a key – and therefore applies by default to all buttons in the Page.
  3. The style contains only one Setter which is the one that sets the Template property of the Button.
  4. The value of the Setter is a ControlTemplate that contains two parts: content – an Ellipse, and one trigger – for the animation.
  5. The Ellipse uses the TemplateBinding markup extension to grab the actual Width and Height of the button.
  6. The Ellipse’s Fill property is a RadialGradientBrush called radialBrush that is centered at (0.5, 0.5). This is interpreted as the center of the Ellipse’s bounding rectangle.
  7. radialBrush goes from White at the center (Offset 0) to Red at the edge (Offset 1).
  8. SpreadMethod is set to Pad so that when the center of the brush is moved away from the center of the Ellipse, the area of the Ellipse beyond Offset 1 is colored Red.
  9. The Trigger of the ControlTemplate is an EventTrigger, triggered by a click on the Button (ButtonBase.Click)
  10. When the event is triggered, a PointAnimation is executed.
  11. The PointAnimation moves the GradientOrigin from (0.5, 0.5) (center) to (0.4, 0.4) over a 1 second interval – and then reverses the operation.
Rendering Video with VMR9 on a Windows Form

This week I taught a class on video and DirectShow and sat down to prepare some demos.

In this demo I show how to use the Video Mixing Render 9 (VMR9) in Windowless mode to render video on the Form of a Windows Forms application. As in my posts on MSXML I found that the smart pointers of comdef.h and comip.h really helped to clean up the code.

Why, programming the VMR9 in C++ can be as elegant as C# !

You can find the complete source code here.

High Level Design

For this demo I started with the “windowless” sample provided with DirectShow, which is written in C++ against the Win32 native API’s.

With all due respect for the usefullness of the sample, the programming style is difficult to follow. I set out to separate the DirectShow code from the battle field of Win32 native programming and to make the DirectShow code as elegant as C#.

The result is two projects, one a dll written in C++ to handle the DirectShow code and the other a simple Windows Forms application. The Windows Forms application calls the C++ dll with good old Platform Invoke.

I didnt port all the functionality of the “windowless” sample, but that shouldn’t be difficult to do.

Let’s take a closer look at the C++ Library (WindowlessVMR9) and the Windows Forms Application.

The C++ Library: WindowlessVMR9

The API

The API of the library is a set of C-style functions that will be called by the Windows Form application using Platform Invoke. Here they are:

bool VmrInitialize(HWND hwnd);

void VmrCleanUp(HWND hwnd);

bool VmrPlay (LPCWSTR lpcwstrFile);

bool VmrStop ();

void VmrOnPaint (HWND hwnd);

void VmrDisplayModeChanged ();

void VmrMoveVideoWindow(HWND hwnd);

VmrInitialize creates a filter graph manager and a VMR9 renderer filter. It  adds the VMR9 filter to the graph manager and sets its rendering mode to “windowless”. This means that renderer will not create its own window, rather it will use the one it is given by the application.

VmrInitialize then retrieved the IVMRWindowlessControl9 interface from the VMR9 filter and uses it to provide the application window handle to the filter. This function will typically be called when the window loads.

VmrCleanUp releases the resources allocated by VmrInitialize. This function will typically be called when the window closes.

VmrPlay uses the RenderFile method of the filter graph manager’s IGraphBuilder interface to build the graph, based on the VMR9 filter previously added. It then retrieves the IMediaControl interface of the filter graph manager and calls the Run method through it.

VmrStop retrieves the IMediaControl interface of the filter graph manager and calls the Stop method through it.

VmrOnPaint retrieves a device context from the window and passes it to the RepaintVideo method of the IVMRWindowlessControl9 interface of the filter graph manager. This function should be called whenever the window handles a WM_PAINT message.

VmrMoveVideoWindow uses the IVMRWindowlessControl9 interface to reposition the rendered video in the client area of the hosting window’s new coordinates following a WM_SIZE message.

VmrDisplayModeChanged uses the IVMRWindowlessControl9 interface to notify the VMR9 filter that the display mode (e.g. the display resolution or the DPI) has changed. This function will rarely be called.

Smart Pointers Revisited

Before we dive into the implementation of the WindowlessVMR9 library, lets introduce some smart pointers for our DirectShow interfaces.

I started with this:

#include <comdef.h>

#define COM_SMARTPTR_TYPEDEF(Interface) _COM_SMARTPTR_TYPEDEF(Interface, IID_ ## Interface)

My COM_SMARTPTR_TYPEDEF macro exploits the _COM_SMARTPTR_TYPEDEF macro of comdef.h and the COM naming conventions for interfaces and their GUIDS to allow us to define smart pointers based on the _com_ptr_t template like so:

COM_SMARTPTR_TYPEDEF(IGraphBuilder);

and like so

COM_SMARTPTR_TYPEDEF(IMediaControl);

These statements define smart pointers IGraphBuilderPtr and IMediaControlPtr for the IGraphBuilder and IMediaControl interfaces respectively.

Of course, for these to compile you need to have previously included the headers that declare the interfaces themselves, but once done, you can similarly define smart pointers for all the interfaces you need.

Here is the full text of the DirectShowPtr.h include file that I used for this sample:

#pragma once

 

#include <comdef.h>

#include <dshow.h>

#include <d3d9.h>

#include <vmr9.h>

 

#define COM_SMARTPTR_TYPEDEF(Interface) _COM_SMARTPTR_TYPEDEF(Interface, IID_ ## Interface)

 

COM_SMARTPTR_TYPEDEF(IGraphBuilder);

COM_SMARTPTR_TYPEDEF(IFilterGraph);

COM_SMARTPTR_TYPEDEF(IMediaControl);

COM_SMARTPTR_TYPEDEF(IMediaFilter);

COM_SMARTPTR_TYPEDEF(IMediaEvent);

COM_SMARTPTR_TYPEDEF(IEnumFilters);

COM_SMARTPTR_TYPEDEF(IVideoWindow);

COM_SMARTPTR_TYPEDEF(IVMRWindowlessControl9);

COM_SMARTPTR_TYPEDEF(IBaseFilter);

COM_SMARTPTR_TYPEDEF(IVMRFilterConfig9);

Now, to say that _com_ptr_t is a smart pointer is a bit of an understatement.

Apart from having the all the assignment operators, copy constructors and conversion operators you need to make it safe in almost any context, _com_ptr_t also has some very smart COM specific constructors:

Take this statement for example:

        IGraphBuilderPtr graphBuilder (CLSID_FilterGraph);

Here, the constructor of IGraphBuilderPtr will call CoCreateInstance for the co class with the specified CLSID to retrieve a IGraphBuilder interface and store it in graphBuilder. All that in one line!

And look at this one:

        IMediaControlPtr mediaControl(graphBuilder);

Here, the constructor of IMediaControlPtr will call QueryInterface on the graphBuilder for an IMediaControl interface and store it in mediaControl.

With those arrows in our quiver, it would even be safe to write this:

        IMediaControlPtr mediaControl (CLSID_FilterGraph);

        IGraphBuilderPtr graphBuilder (CLSID_FilterGraph);

        mediaControl = IMediaControlPtr(graphBuilder);

and once you get your confidence up, you will even feel comfortable writing this:

        HRESULT hr = IMediaControlPtr(m_GraphBuilder)->Run();

Wow!

_com_ptr_t is defined in comip.h which I included via comdef.h. These files are located in the VC/Include directory of Visual Studio 2008.

Now _com_ptr_t isnt the only smart pointer we could use. ATL also provides smart pointers for COM (ComPtr and ComQIPtr), but I preferred not to include the ATL headers just for these classes.

Also, the Samples\Multimedia\DirectShow\Common folder under the Windows SDK provides a smart pointer class in the SmartPtr.h file, but it isnt half as powerful as _com_ptr_t.

Implementation

Having separated the DirectShow code from the Win32 Window code, and having hidden the complexities of COM behind smart pointers, I think the implementation explains itself:

static IGraphBuilderPtr             m_GraphBuilder;

static IVMRWindowlessControl9Ptr    m_WindowlessControl;

 

bool VmrInitialize(HWND hwnd)

{

    try

    {

        m_GraphBuilder = IGraphBuilderPtr (CLSID_FilterGraph);

 

        IBaseFilterPtr vmrFilter (CLSID_VideoMixingRenderer9);

 

        HRESULT hr = m_GraphBuilder->AddFilter(vmrFilter, L"Video Mixing Renderer 9");

        if (! SUCCEEDED(hr))

            return false;

 

        IVMRFilterConfig9Ptr(vmrFilter)->SetRenderingMode(VMR9Mode_Windowless);

 

        m_WindowlessControl = IVMRWindowlessControl9Ptr(vmrFilter);

        if(! m_WindowlessControl)

            return false;

 

        m_WindowlessControl->SetVideoClippingWindow(hwnd);

    }

    catch (_com_error)

    {

        assert (false);

        return false;

    }

    return true;

}

 

void VmrCleanUp(HWND hwnd)

{

    try

    {

        m_WindowlessControl.Release();

        m_GraphBuilder.Release();

    }

    catch (_com_error)

    {

        assert (false);

    }

}

 

bool VmrPlay (LPCWSTR lpcwstrFile)

{

    if (! m_GraphBuilder)

        return false;

 

    try

    {

        HRESULT hr = m_GraphBuilder->RenderFile(lpcwstrFile, NULL);

        if (! SUCCEEDED(hr))

            return false;

 

        hr = IMediaControlPtr(m_GraphBuilder)->Run();

        return SUCCEEDED (hr);

    }

    catch (_com_error)

    {

        assert (false);

    }

 

    return false;

}

 

bool VmrStop ()

{

    if (! m_GraphBuilder)

        return false;

 

    try

    {

        HRESULT hr = IMediaControlPtr(m_GraphBuilder)->Stop();

        return SUCCEEDED (hr);

    }

    catch (_com_error)

    {

        assert (false);

    }

    return false;

}

 

void VmrOnPaint (HWND hwnd)

{

    if (! m_WindowlessControl)

        return;

 

    PAINTSTRUCT ps;

    HDC         hdc;

    hdc = BeginPaint(hwnd, &ps);

 

    // When using VMR Windowless mode, you must explicitly tell the

    // renderer when to repaint the video in response to WM_PAINT

    // messages.  This is most important when the video is stopped

    // or paused, since the VMR won't be automatically updating the

    // window as the video plays.

 

    try

    {

        HRESULT hr = m_WindowlessControl->RepaintVideo(hwnd, hdc);

    }

    catch (_com_error)

    {

        assert (false);

    }

 

    EndPaint(hwnd, &ps);

}

 

void VmrDisplayModeChanged ()

{

    if (! m_WindowlessControl)

        return;

 

    try

    {

        m_WindowlessControl->DisplayModeChanged();

    }

    catch (_com_error)

    {

        assert (false);

    }

}

 

void VmrMoveVideoWindow(HWND hwnd)

{

    if (! m_WindowlessControl)

        return;

 

    try

    {

        RECT g_rcDest;

 

        GetClientRect(hwnd, &g_rcDest);

        HRESULT hr = m_WindowlessControl->SetVideoPosition(NULL, &g_rcDest);

    }

    catch (_com_error)

    {

        assert (false);

    }

}

I think you will agree that this looks more like C# than native COM coding.

 

The Windows Forms Application

In the Windows Forms application I wrote this Platform Invoke class to enable me to call the WindowlessVMR9 unmanaged code:

    public class Vmr9

    {

        [DllImport("WindowlessVmr9", EntryPoint = "VmrInitialize")]

        public static extern bool Initialize(IntPtr hwnd);

 

        [DllImport("WindowlessVmr9", EntryPoint = "VmrCleanUp")]

        public static extern bool CleanUp();

 

        [DllImport("WindowlessVmr9", EntryPoint = "VmrPlay", CharSet = CharSet.Unicode)]

        public static extern bool Play(string fileName);

 

        [DllImport("WindowlessVmr9", EntryPoint = "VmrStop")]

        public static extern bool Stop();

 

        [DllImport("WindowlessVmr9", EntryPoint = "VmrOnPaint")]

        public static extern void OnPaint(IntPtr hwnd);

 

        [DllImport("WindowlessVmr9", EntryPoint = "VmrDisplayModeChanged")]

        public static extern void DisplayModeChanged();

 

        [DllImport("WindowlessVmr9", EntryPoint = "VmrMoveVideoWindow")]

        public static extern void MoveVideoWindow(IntPtr hwnd);

    }

WinForms allows us to retrieve the native HWND of a Form and Platform Invoke allows us to marshall it to the unmanaged code. The Form code itself has now been reduced to this:

    public partial class Form1 : Form

    {

        public Form1()

        {

            InitializeComponent();

        }

 

        private void Form1_Load(object sender, EventArgs e)

        {

            Vmr9.Initialize(panelVideo.Handle);

 

            buttonPlayStop.Text = "Play";

            buttonPlayStop.Enabled = false;

        }

 

        protected override void OnPaint(PaintEventArgs e)

        {

            base.OnPaint(e);

            Vmr9.OnPaint(panelVideo.Handle);

        }

        protected override void OnResize(EventArgs e)

        {

            base.OnResize(e);

            Vmr9.MoveVideoWindow(panelVideo.Handle);

        }

 

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)

        {

            Vmr9.CleanUp();

        }

 

        private void buttonOpen_Click(object sender, EventArgs e)

        {

            DialogResult result = openFileDialog1.ShowDialog();

            if (result == DialogResult.OK)

            {

                textBox1.Text = openFileDialog1.FileName;

                buttonPlayStop.Enabled = true;

            }

        }

 

        private void buttonPlayStop_Click(object sender, EventArgs e)

        {

            if (buttonPlayStop.Text == "Play")

            {

                buttonPlayStop.Text = "Stop";

                buttonOpen.Enabled = false;

                Vmr9.Play(openFileDialog1.FileName);

                Vmr9.MoveVideoWindow(panelVideo.Handle);

            }

            else

            {

                buttonPlayStop.Text = "Play";

                buttonOpen.Enabled = true;

                Vmr9.Stop();

            }

        }

    }

Summary

In this post I demonstrated how to use the Video Mixing Renderer 9 in a Windows Forms Application in windowless mode.

The code is based on the “windowless” sample of the Windows SDK but has been simplified by separating the VMR9 code into an unmanaged dll, replacing the Win32 GUI with Windows Forms and relying on smart pointers to hide the complexities of COM programming.

Mapping MediaElement onto a Sphere with WPF

In my previous post I created a 3D cube, mapped a MediaElement onto each of its faces and animated the camera around the cube. In this post, I will replace the 3D cube with a sphere and wrap the video onto the surface of the sphere. I omit the camera animation for simplicity, but you can add it back, of course.

You can download the source code here.

In order to create the sphere mesh I made use of Charles Petzold’s elegant SphereMeshGenerator.

In Chapter 6 of his book (3D Programming for Windows: Three-Dimensional Graphics Programming for the Windows Presentation Foundation) Petzold explains the technique very clearly - a highly recommended read. In this post, I will just provide the highlights.

The SphereMeshGenerator class is a factory that generates the MeshGeometry3D in code. Here it is:

//-----------------------------------------------------

// SphereMeshGenerator.cs (c) 2007 by Charles Petzold

//-----------------------------------------------------

using System;

using System.Windows;

using System.Windows.Media;

using System.Windows.Media.Media3D;

namespace Petzold.SphereResourceDemo

{

    public class SphereMeshGenerator

    {

        // Four private initialized fields.

        int slices = 32;

        int stacks = 16;

        Point3D center = new Point3D();

        double radius = 1;

        // Four public properties allow access to private fields.

        public int Slices

        {

            set { slices = value; }

            get { return slices; }

        }

        public int Stacks

        {

            set { stacks = value; }

            get { return stacks; }

        }

        public Point3D Center

        {

            set { center = value; }

            get { return center; }

        }

        public double Radius

        {

            set { radius = value; }

            get { return radius; }

        }

        // Get-only property generates MeshGeometry3D.

        public MeshGeometry3D Geometry

        {

            get

            {

                // Create a MeshGeometry3D.

                MeshGeometry3D mesh = new MeshGeometry3D();

                // Fill the vertices, normals, and textures collections.

                for (int stack = 0; stack <= Stacks; stack++)

                {

                    double phi = Math.PI / 2 - stack * Math.PI / Stacks;

                    double y = Radius * Math.Sin(phi);

                    double scale = -Radius * Math.Cos(phi);

                    for (int slice = 0; slice <= Slices; slice++)

                    {

                        double theta = slice * 2 * Math.PI / Slices;

                        double x = scale * Math.Sin(theta);

                        double z = scale * Math.Cos(theta);

                        Vector3D normal = new Vector3D(x, y, z);

                        mesh.Normals.Add(normal);

                        mesh.Positions.Add(normal + Center);

                        mesh.TextureCoordinates.Add(

                                    new Point((double)slice / Slices,

                                              (double)stack / Stacks));

                    }

                }

                // Fill the indices collection.

                for (int stack = 0; stack < Stacks; stack++)

                {

                    int top = (stack + 0) * (Slices + 1);

                    int bot = (stack + 1) * (Slices + 1);

                    for (int slice = 0; slice < Slices; slice++)

                    {

                        if (stack != 0)

                        {

                            mesh.TriangleIndices.Add(top + slice);

                            mesh.TriangleIndices.Add(bot + slice);

                            mesh.TriangleIndices.Add(top + slice + 1);

                        }

                        if (stack != Stacks - 1)

                        {

                            mesh.TriangleIndices.Add(top + slice + 1);

                            mesh.TriangleIndices.Add(bot + slice);

                            mesh.TriangleIndices.Add(bot + slice + 1);

                        }

                    }

                }

                return mesh;

            }

        }

    }

}

A slice is the area of the sphere surface that lies between two lines of longitude. The lines of longitude are circles centered at the center of the sphere that pass through the North Pole and South Pole. A stack is the area of the sphere surface that lies between two lines of latitude. The lines of latitude are parallel to the plane of the equator; only the equator itself is centered about the center of the sphere.

Properties of the SphereMeshGenerator allow you to specify the center and radius of the sphere as well as the number of slices and stacks that will be used. The higher the number of slices and stacks the smoother the image and the greater the load on the CPU and GPU.

Using the SphereMeshGenerator is straightforward. Just create a static instance of the SphereMeshGenerator as a resource of the Window, and bind the Geometry property of the ModelVisual3D to the Geometry factory property of SphereMeshGenerator.

Here is the XAML I used.

<Window x:Class="MediaElementOnSphere.Window1"

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

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

       xmlns:local="clr-namespace:Petzold.SphereResourceDemo"

       Title="Window1"

       Width="500"

       Height="400"

       Loaded="Window_Loaded">

   <Window.Resources>

      <local:SphereMeshGenerator x:Key="sphereMeshGenerator"

        Center="0 0 0"

        Radius="2" />

   </Window.Resources>

   <Grid>

      <Viewport3D>

         <Viewport3D.Camera>

            <PerspectiveCamera x:Name="camera"

                              UpDirection="0,1,0"

                              LookDirection="-4,-4,-4"

                              Position="4,4,4" />

         </Viewport3D.Camera>

         <ModelVisual3D>

            <ModelVisual3D.Content>

               <AmbientLight Color="White"></AmbientLight >

            </ModelVisual3D.Content>

         </ModelVisual3D>

         <ModelVisual3D>

            <ModelVisual3D.Content>

               <GeometryModel3D Geometry=

                    "{Binding Source={StaticResource sphereMeshGenerator}, Path=Geometry}">

                  <GeometryModel3D.Material>

                     <DiffuseMaterial>

                        <DiffuseMaterial.Brush>

                           <VisualBrush>

                              <VisualBrush.Visual>

                                 <MediaElement>

                                    <MediaElement.Triggers>

                                       <EventTrigger RoutedEvent="MediaElement.Loaded">

                                          <EventTrigger.Actions>

                                             <BeginStoryboard>

                                                <Storyboard>

                                                   <MediaTimeline x:Name="mediaTimeline"

                                                                  RepeatBehavior="Forever" />

                                                </Storyboard>

                                             </BeginStoryboard>

                                          </EventTrigger.Actions>

                                       </EventTrigger>

                                    </MediaElement.Triggers>

                                 </MediaElement>

                              </VisualBrush.Visual>

                           </VisualBrush>

                        </DiffuseMaterial.Brush>

                     </DiffuseMaterial>

                  </GeometryModel3D.Material>

               </GeometryModel3D>

            </ModelVisual3D.Content>

         </ModelVisual3D>

      </Viewport3D>

   </Grid>

</Window>

Finally, here is a snapshot of my spherical cinema:

image

Summary:

In this post I demonstrated how you can map a MediaElement onto the surface of a 3D spherical mesh in WPF. I used Charles Petzold’s SphereMeshGenerator to generate the mesh and set the DiffuseMaterial.Brush property to a VisualBrush that references the MediaElement.

Animating the Position of a 3D Camera in WPF

In this post we will use Point3DAnimationUsingPath from the previous post to rotate the viewpoint around a 3D cube while playing a video on all six of its faces.

You can download the complete source, with the Point3DAnimationUsingPath class from here.

Point3DAnimationUsingPath

"Look at me!
Look at me now!" said the cat.
"With a cup and a cake
On the top of my hat!
I can hold up TWO books!
I can hold up the fish!
And a litte toy ship!
And some milk on a dish!
And look!
I can hop up and down on the ball!
But that is not all!
Oh, no.
That is not all... “,  Dr.Seuss

 

Motivation

It is fairly straightforward to rotate 3D objects in WPF using the various Transform3D derived classes. But sometimes what you need is to rotate the viewer around the scene and not to rotate individual objects. That is, you need to animate the Position property of the Camera object over a path in three dimensions.

The problem is that the type of the Position property is Point3D and WPF doesnt provide a Point3DAnimationUsingPath class. I partially filled that gap with a class of that name in this previous post.

In addition, as we modify the Position of the Camera, we need to change the LookDirection so that the Camera always points to the same point in space.

To demonstrate the solution, I will place a cube at the center of the 3D coordinate system and play video on each side so you can clearly see how we are rotating around the object.

“Look at me! Look at me now!”, say I, “With a cube in the center and a video on SIX faces, I can rotate around it and look back to the center! But that is not all! Oh no, that is not all…”

Solution

I will build this solution in 8 steps:

  1. Describe the cube.
  2. Create a MediaElement that plays video continuously.
  3. Map the MediaElement on to each of the six faces of the cube.
  4. Define the light source.
  5. Define the camera and bind the LookDirection to a function of its Position.
  6. Describe the path over which we will animate the Position.
  7. Apply the Point3DAnimationUsingPath to the Position property of the Camera
  8. Put it all together.

Describe the Cube

We describe the cube as a MeshGeometry3D and place it in a GeometryModel3D. This can then be placed inside a ModelVisual3D which will be added to the Viewport3D.

In the MeshGeometry3D we define the Positions and TriangleIndices as follows:

   <GeometryModel3D.Geometry>

      <MeshGeometry3D Positions=

               "-1,-1,-1    1,-1,-1   1, 1,-1   -1, 1,-1

                -1,-1,-1   -1, 1,-1  -1, 1, 1   -1,-1, 1  

                 1,-1,-1   -1,-1,-1  -1,-1, 1    1,-1, 1

                 1, 1,-1    1,-1,-1   1,-1, 1    1, 1, 1   

                 1,-1, 1   -1,-1, 1  -1, 1, 1    1, 1, 1

                -1, 1,-1    1, 1,-1   1, 1, 1   -1, 1, 1"

      TriangleIndices=

               "3,2,1       1,0,3

                7,6,5       5,4,7

                11,10,9     9,8,11

                15,14,13    13,12,15

                19,18,17    17,16,19

                23,22,21    21,20,23"

      </MeshGeometry3D>

   </GeometryModel3D.Geometry>

There are more than one ways to do this, but I chose the more verbose way in order to make it easier to define the mapping between the MediaElement and the faces of the cube.

In the Positions collection there are six lines, one for each face of the cube, four points for each face.

In the TriangleIndices there are six lines, one for each face of the cube, two triangles for each face. Note that the order of the points in the triangles is counterclockwise around the outfacing norm to the triangle’s plane.

Here is a diagram of the cube and its vertices:

3D Cube in XAML

Create the MediaElement

The MediaElement itself is not new, but this XAML snippet also shows how it is used as the Visual property of a VisualBrush that we will use to paint the Material of our GeometryModel3D.

   <GeometryModel3D.Material>

      <DiffuseMaterial>

         <DiffuseMaterial.Brush>

            <VisualBrush>

               <VisualBrush.Visual>

                  <MediaElement>

                     <MediaElement.Triggers>

                        <EventTrigger RoutedEvent="MediaElement.Loaded">

                           <EventTrigger.Actions>

                              <BeginStoryboard>

                                 <Storyboard>

                                    <MediaTimeline x:Name="mediaTimeline"

                                      RepeatBehavior="Forever" />

                                 </Storyboard>

                              </BeginStoryboard>

                           </EventTrigger.Actions>

                        </EventTrigger>

                     </MediaElement.Triggers>

                  </MediaElement>

               </VisualBrush.Visual>

            </VisualBrush>

         </DiffuseMaterial.Brush>

      </DiffuseMaterial>

   </GeometryModel3D.Material>

This MediaElement has a MediaTimeline that plays forever. There is something missing, of course - the Uri identifying the source of the media. But, no fear, we will set that in code. That’s why the MediaTimeline has been given a name.

Map the MediaElement on to the Cube

There are two parts to this mapping.

The first part, completed in the previous step, is to set the Visual property of the VisualBrush for the Material. The second part is to provide a relative coordinate in the 2D space of the VisualBrush for each 3D coordinate in the Positions collection. I defined the Positions collection in a consistent way for each face, so it turns out that the texture mapping is identical for each of the six faces.

We will add this XAML snippet to the properties of the MeshGeometry3D (after TriangleIndices, for example):

TextureCoordinates=

         "1,1     0,1     0,0     1,0

          1,1     0,1     0,0     1,0

          1,1     0,1     0,0     1,0

          1,1     0,1     0,0     1,0

          1,1     0,1     0,0     1,0    

          1,1     0,1     0,0     1,0">

Define the Light Source

This one is simple. We will use an ambient white light to make sure that there is no shading on the cube.

   <ModelVisual3D>

      <ModelVisual3D.Content>

         <AmbientLight Color="White"></AmbientLight >

      </ModelVisual3D.Content>

   </ModelVisual3D>

Define the Camera

We will use a PerspectiveCamera in order to achieve the illusion of depth.

   <Viewport3D.Camera>

      <PerspectiveCamera

         x:Name="camera"

         UpDirection="0,0,1"

         LookDirection="{

               Binding RelativeSource={RelativeSource Self},

                       Path=Position,

                       Converter={StaticResource lookBackConverter}}"

         Position="0,0,0" />

   </Viewport3D.Camera>

The value of the Position property is meaningless because we are going to control this property with an animation. But, the LookDirection is interesting. In order to move the Camera and still look at the cube, we need to modify the look direction as the Position changes. This is how:

Camera.LookDirection = CenterPointOfInterest – Camera.Position

We could choose to animate the LookDirection concurrently with the Position property, but I chose a simpler, and in my opinion, a more elegant way.

I defined a converter from Camera.Position to Camera.LookDirection and apply it to a binding of LookDirection to Position.

This is the code for the converter:

    class LookBackConverter : IValueConverter

    {

        #region IValueConverter Members

 

        public object Convert(

            object value, Type targetType,

            object parameter,

            System.Globalization.CultureInfo culture)

        {

            return new Point3D(0,0,0) - (Point3D)value;

        }

 

        public object ConvertBack(

            object value, Type targetType,

            object parameter,

            System.Globalization.CultureInfo culture)

        {

            return null;

        }

 

        #endregion

    }

We could expose the center of interest as a public property of the LookBackConverter and set it in XAML, but for this demo, I will just assume its the origin of the coordinate system.

Describe the Path over which we will Animate the Position

You will recall from the previous post that Point3DAnimationUsingPath is lacking in that it doesn’t accept a description of a path in 3D space, but rather a constant Z coordinate and a path in 2D space.

So, we need to define a circle around the Z axis, with a diameter larger than the side of the cube, but not too much larger. The length of each side of the cube is 2 units, so 4 will do.

Unfortunately there is no EllipseGeometry for 2D, so I created my own with 2 ArcSegments. Here is the XAML:

   <PathGeometry x:Key="circlePath">

      <PathGeometry.Figures>

         <PathFigure StartPoint="-4, -4" IsClosed="False">

            <ArcSegment Point="4,4" Size="4, 4" />

            <ArcSegment Point="-4,-4" Size="4,4" />

         </PathFigure>

      </PathGeometry.Figures>

   </PathGeometry>

Apply the Point3DAnimationUsingPath to the Camera Position

Armed with the Point3DAnimationUsingPath and the circlePath PathGeometry it is now easy to define the animation we need:

   <Viewport3D.Triggers>

      <EventTrigger RoutedEvent="FrameworkElement.Loaded">

         <BeginStoryboard>

            <Storyboard>

               <local:Point3DAnimationUsingPath

                   Storyboard.TargetName="camera"

                   Storyboard.TargetProperty="Position"

                   Duration="0:0:20"

                   Z="2"

                   PathGeometry="{StaticResource circlePath}"

                   RepeatBehavior="Forever">

               </local:Point3DAnimationUsingPath>

            </Storyboard>

         </BeginStoryboard>

      </EventTrigger>

   </Viewport3D.Triggers>

As you can see, the camera named “camera” is the animation target and Position is the name of the target property. We set the circlePath PathGeometry as the PathGeometry and Z=2 so we rotate around the cube, but above it.

Putting It All Together

Here is the entire XAML for the solution:

<Window x:Class="RotatingCamera.Window1"

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

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

       xmlns:local="clr-namespace:RotatingCamera"

       Title="Window1"

       Width="500"

       Height="400"

       Loaded="Window_Loaded">

   <Grid>

      <Viewport3D>

         <Viewport3D.Resources>

            <local:LookBackConverter x:Key="lookBackConverter" />

            <PathGeometry x:Key="circlePath">

               <PathGeometry.Figures>

                  <PathFigure StartPoint="-4, -4" IsClosed="False">

                     <ArcSegment Point="4,4" Size="4, 4" />

                     <ArcSegment Point="-4,-4" Size="4,4" />

                  </PathFigure>

               </PathGeometry.Figures>

            </PathGeometry>

         </Viewport3D.Resources>

         <Viewport3D.Triggers>

            <EventTrigger RoutedEvent="FrameworkElement.Loaded">

               <BeginStoryboard>

                  <Storyboard>

                     <local:Point3DAnimationUsingPath

                         Storyboard.TargetName="camera"

                         Storyboard.TargetProperty="Position"

                         Duration="0:0:20"

                         Z="2"

                         PathGeometry="{StaticResource circlePath}"

                         RepeatBehavior="Forever">

                     </local:Point3DAnimationUsingPath>

                  </Storyboard>

               </BeginStoryboard>

            </EventTrigger>

         </Viewport3D.Triggers>

         <Viewport3D.Camera>

            <PerspectiveCamera

               x:Name="camera"

               UpDirection="0,0,1"

               LookDirection="{

                     Binding RelativeSource={RelativeSource Self},

                             Path=Position,

                             Converter={StaticResource lookBackConverter}}"

               Position="0,0,0" />

         </Viewport3D.Camera>

         <ModelVisual3D>

            <ModelVisual3D.Content>

               <AmbientLight Color="White"></AmbientLight >

            </ModelVisual3D.Content>

         </ModelVisual3D>

         <ModelVisual3D>

            <ModelVisual3D.Content>

               <GeometryModel3D>

                  <GeometryModel3D.Geometry>

                     <MeshGeometry3D Positions=

                             "-1,-1,-1    1,-1,-1   1, 1,-1   -1, 1,-1

                               -1,-1,-1   -1, 1,-1  -1, 1, 1   -1,-1, 1  

                                1,-1,-1   -1,-1,-1  -1,-1, 1    1,-1, 1

                                1, 1,-1   1,-1,-1    1,-1, 1    1, 1, 1   

                                1,-1, 1   -1,-1, 1  -1, 1, 1    1, 1, 1

                               -1, 1,-1   1, 1,-1    1, 1, 1   -1, 1, 1"

                     TriangleIndices=

                             "3,2,1       1,0,3

                               7,6,5       5,4,7

                               11,10,9     9,8,11

                               15,14,13    13,12,15

                               19,18,17    17,16,19

                               23,22,21    21,20,23"

                     TextureCoordinates=

                             "1,1     0,1     0,0     1,0

                               1,1     0,1     0,0     1,0

                               1,1     0,1     0,0     1,0

                               1,1     0,1     0,0     1,0

                               1,1     0,1     0,0     1,0    

                               1,1     0,1     0,0     1,0">

                     </MeshGeometry3D>

                  </GeometryModel3D.Geometry>

                  <GeometryModel3D.Material>

                     <DiffuseMaterial>

                        <DiffuseMaterial.Brush>

                           <VisualBrush>

                              <VisualBrush.Visual>

                                 <MediaElement>

                                    <MediaElement.Triggers>

                                       <EventTrigger RoutedEvent="MediaElement.Loaded">

                                          <EventTrigger.Actions>

                                             <BeginStoryboard>

                                                <Storyboard>

                                                   <MediaTimeline x:Name="mediaTimeline"

                                                     RepeatBehavior="Forever" />

                                                </Storyboard>

                                             </BeginStoryboard>

                                          </EventTrigger.Actions>

                                       </EventTrigger>

                                    </MediaElement.Triggers>

                                 </MediaElement>

                              </VisualBrush.Visual>

                           </VisualBrush>

                        </DiffuseMaterial.Brush>

                     </DiffuseMaterial>

                  </GeometryModel3D.Material>

               </GeometryModel3D>

            </ModelVisual3D.Content>

         </ModelVisual3D>

      </Viewport3D>

   </Grid>

</Window>

and this is what my cube looked like in one of my orbits around it.

Point3DAnimationUsingPath

Summary

In this post I demonstrated the use of my Point3DAnimationUsingPath to animate the Position of a Camera around a 3D object.

Its amazing how with WPF you can have so much going on in parallel, video, animations and databinding. Like the Cat in The Hat, really.

A Custom Animation Class - Point3DAnimationUsingPath

For 2D graphics WPF provides three classes for animating points: PointAnimation (for linear interpolation), PointAnimationUsingKeyFrames and PointAnimationUsingPath.

For 3D graphics WPF provides only two: Point3DAnimation (for linear interpolation) and Point3DAnimationUsingKeyFrames. There is no built in class for animating points over a path in three dimensions.

In this post I will show you a custom animation class that I wrote to partially fill that gap. In the next post I will put it to use in a 3D animation demo.

Requirement

Provide an animation class that enables the animation of a 3D point over a 3D path such that the path is defined by a 2D path and a constant value of Z.

You can probably extend this to include more exciting paths in 3D, but it does not directly support animation of a 3D point over any 3D path.

The Point3DAnimationUsingPath Class

public class Point3DAnimationUsingPath : AnimationTimeline

{

    #region AnimationTimeline abstract overrides

 

    public override Type TargetPropertyType

    {

        get { return typeof (Point3D); }

    }

 

    protected override Freezable CreateInstanceCore()

    {

        return new Point3DAnimationUsingPath { Z = this.Z };

    }

 

    #endregion

 

    #region AnimationTimeline virtual overrides

 

    public override object GetCurrentValue(

        object defaultOriginValue, object defaultDestinationValue,

        AnimationClock animationClock)

    {

        PathGeometry path = this.PathGeometry;

 

        Point point;

        Point tangent;

 

        path.GetPointAtFractionLength(

            animationClock.CurrentProgress.Value, out point, out tangent);

 

        return new Point3D(point.X, point.Y, Z);

    }

 

    #endregion

 

    public double Z { get; set; }

 

    public PathGeometry PathGeometry

    {

        get { return (PathGeometry)GetValue(PathGeometryProperty); }

        set { SetValue(PathGeometryProperty, value); }

    }

 

    public static readonly DependencyProperty PathGeometryProperty =

        DependencyProperty.Register(

            "PathGeometry",

            typeof(PathGeometry),

            typeof(Point3DAnimationUsingPath));

}

Point3DAnimationUsingPath has two public properties, a 2D PathGeometry and a double called Z. These two define a coplanar path in 3D space and need to be set on objects of this type before starting an animation.

As a custom animation class, Point3DAnimationUsingPath must also derive from AnimationTimeline and override the abstract CreateInstanceCore method and TargetPropertyType property.

As you can see, TargetPropertyType returns the type of Point3D indicating that the target of the animation created by this class will be a property of type Point3D. CreateInstanceCore simply returns a clone of this class.

The magic of this class happens in the GetCurrentValue method which is an override of a virtual method. It’s actually quite simple. I delegate the animation task to the 2D path, and return a 3D point with the Z value set to the Z property.

In the next post I will provide an application that uses Point3DAnimationUsingPath.

Instrumentation Tip for ASMX Web Services

This post describes a simple way to trace all method invocations on an ASMX Web Service. The source code for the demo can be downloaded here.

Problem

I am currently adding some features to a customer’s application that exposes its core functionality as an ASMX Web Service.

About every mistake in the book was made in the design of this product, so I was having a rough time understanding the API of the service, never mind the implementation. For a start, the methods are not strongly typed, mostly accepting strings as parameters, and there are over 20 of them! Oh, and some of the API functions have as many as 15 parameters!

In order to understand the API I decided to trace each of the method invocations by the client (which seems to know how to consume the service). For each call I will trace the name of the method and the values of each of its parameters.

As I couldnt stand the humiliation of writing a macro to create tens of lines of verbose tracing code in the service, I looked for a more elegant way of achieving this goal.

Solution 1: Migrate to WCF

The first solution that came to mind was to upgrade the ASMX service to WCF with a basicHttpBinding. Then, use the extensive tracing capabilities that come with WCF with just a few lines in the configuration file.

Upgrading to WCF is easy and I have every intention of doing this eventually, but if you are familiar with WCF tracing you might agree that this alone was not a compelling reason to do so now. From my experience, WCF tracing offers too much information for simple tasks and, what bothers me most is that in order to make constructive use of the information it generates you need to use a dedicated tool – SvcTraceViewer.

Solution 2: Soap Extensions

For ASMX Web Services, ASP.NET allows you to intercept SOAP messages on the server. You can catch incoming messages (operation invocations) just after they are deserialized and outgoing messages (operation return values) just before they are serialized.

This solution has two pieces:

  1. Define a class that derives from SoapExtension (named MethodTraceExtension). In the ProcessMessage method, handle incoming messages only. Extract the name and parameters of the method being invoked and trace this information.
  2. Configure the application (in web.config) to inject MethodTraceExtension on every web method.

In piece two, I will demonstrate three different ways to extract the information from the SoapMessage. You only need one way - the other two are for fun. For the tracing I will be using the tip from my last post.

Implementation

This is the MethodTraceExtension class. You can compile it and place it in a class library or you can place the source file in the App_Code folder:

The MethodTraceExtension class

namespace MethodTrace

{

    public class MethodTraceExtension : SoapExtension

    {

        #region Dont Care Overrides

 

        public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attr)

        {

            return attr;

        }

 

        public override object GetInitializer(Type obj)

        {

            return obj;

        }

 

        public override void Initialize(object initializer)

        {

        }

 

        #endregion

 

        public override void ProcessMessage(SoapMessage message)

        {

            // On the server side, this condition is true when processing an incoming message

 

            if (message.Stage == SoapMessageStage.AfterDeserialize)

            {

                Trace("Using Message Members", TraceFromMessageMembers(message));

 

                //Trace("Using Linq over SOAP", TraceFromSoapWithLinq(message));

                //Trace("Using DOM with XPath", TraceFromSoapWithXPath(message));

            }

        }

 

        private void Trace(string category, string message)

        {

            HttpContext.Current.Trace.Write (category, message);

        }

 

        private string TraceFromMessageMembers(SoapMessage message)

        {

            LogicalMethodInfo method = message.MethodInfo;

            StringBuilder sb = new StringBuilder();

 

            sb.AppendFormat("{0} (", method.Name);

 

            int paramCount = method.InParameters.Length;

 

            for (int i = 0; i < paramCount; i++)

            {

                sb.Append(method.InParameters[i].Name);

                sb.Append(" = ");

                sb.Append(message.GetInParameterValue(i).ToString());

 

                if (i != paramCount - 1)

                {

                    sb.Append(", ");

                }

            }

 

            sb.Append(")");

 

            return sb.ToString();

        }

    }

}

As you can see, its only the ProcessMessage function that requires a non-trivial implementation.

The TraceFromMessageMembers uses SoapMessage members to extract the name of the method and the values of its parameters and returns the information as a string. The Trace function uses HttpContext.Current.Trace.Write as described in the previous post.

Configuring the Soap Extension

If you want to apply the SoapExtension to methods selectively, you can use a SoapExtensionAttribute as follows:

Define an attribute that derives from SoapExtensionAttribute, and override its ExtensionType property to return typeof (MethodTraceExtension). Then apply this attribute to any method on which you would like to apply the soap extension.

But, in my case, I want to apply the soap extension to all methods – and, you will remember, I wanted to avoid writing repetetive code for no good reason, even it its just an attribute : )

So I applied the other option – configuration through web.config:

  <system.web>

    . . .

    <webServices>

      <soapExtensionTypes>

        <add type="MethodTrace.MethodTraceExtension, App_Code"/>

      </soapExtensionTypes>

    </webServices> 

    . . .

  </system.web>

This will apply the MethodTraceExtension to every Web Method in the service.

Dont forget to configure tracing as recommended in the previous post.

Thats it.

The client in the source code calls each method of the service. Every call is now traced to the Traces.txt file on the server.

In the rest of this post, I just want to show you two other methods you can use to extract the information we need from an incoming SoapMessage. They both rely on knowledge of the SOAP schema, which can change in subtle ways if a few attributes are changed on the service, so I would not recommend using them instead of the one above.

Still, I included them here for fun. One uses LINQ to XML, and the other use DOM and XPath to extract the information we need from the incoming SOAP message.

Other Methods to Analyze the Soap Message

This method extracts the information using LINQ to XML.

        private string TraceFromSoapWithLinq(SoapMessage message)

        {

            message.Stream.Position = 0;

 

            XmlReader reader = XmlReader.Create(message.Stream);

            XDocument doc = XDocument.Load(reader);

 

            var body =

                (from element in doc.Root.Elements()

                 where element.Name.LocalName == "Body"

                 select element).Single();

 

            StringBuilder sb = new StringBuilder();

 

            XElement methodElement = body.Elements().Single();

 

            sb.AppendFormat("{0} (", methodElement.Name.LocalName);

 

            var parameters =

                (from element in methodElement.Descendants()

                 select new {

                     Name = element.Name.LocalName,

                     Value = element.Value }).ToArray();

 

            foreach (var p in parameters)

            {

                sb.Append(p.Name);

                sb.Append(" = ");

                sb.Append(p.Value);

 

                if (p != parameters.Last())

                {

                    sb.Append(", ");

                }

            }

 

            sb.Append(")");

 

            return sb.ToString();

        }

and this method extracts the information using the DOM model and XPath.

        private string TraceFromSoapWithXPath(SoapMessage message)

        {

            message.Stream.Position = 0;

 

            XmlReader reader = XmlReader.Create(message.Stream);

            XmlDocument doc = new XmlDocument();

            doc.Load(reader);

 

            XmlNamespaceManager nsm = new XmlNamespaceManager(doc.NameTable);

            nsm.AddNamespace("soap", "http://schemas.xmlsoap.org/soap/envelope/");

 

            XmlNode methodNode = doc.SelectNodes("// soap:Body/*", nsm)[0];

 

            StringBuilder sb = new StringBuilder();

 

            sb.AppendFormat("{0} (", methodNode.Name);

 

            XmlNodeList nodes = methodNode.ChildNodes;

 

            int paramCount = nodes.Count;

 

            for (int i = 0; i < paramCount; i++)

            {

                XmlNode node = nodes[i];

 

                sb.Append(node.Name);

                sb.Append(" = ");

                sb.Append(node.InnerText);

 

                if (i != paramCount - 1)

                {

                    sb.Append(", ");

                }

            }

 

            sb.Append(")");

            return sb.ToString();

        }

    }

Summary

In this post I demonstrated how to use a SoapExtension to trace every invocation of an ASMX Web Service in a centralized way.

I think that with this tool I will now find it easier to fathom the API of the service. I hope you find it useful too.

More Posts Next page »