Welcome to MSDN Blogs Sign in | Join | Help

VB.NET version of Planerator posted

Sean Cullinan, of blendblog.net, recently posted a VB.NET port of the Planerator

Posted by Greg Schechter | 1 Comments
Filed under:

Some implementation details of the Planerator

My previous two posts (here and here) discuss usage of the Planerator control.  There are some unique issues that needed to be resolved in the implementation that WPF geeks might be interested in.  If you just are interested in using it, and not finding out how the sausage is made, you might want to just skip this.

Actually, though, the sausage-making in this case is pretty nice, and the Planerator implementation is quite clean.  It's contained in about 280 lines of C# code in Planerator.cs.  There are some tricky aspects, though. Below are the salient points.

Interactive Content on 3D

One of the new features in WPF 3.5 is Viewport2DVisual3D, which is a way of getting a UIElement in as a material in 3D, and yet have it remain interactive.  This is used in the Planerator for the front face of the plane.  The back face of the plane just uses a VisualBrush for its material, and thus isn't interactive.

Caching of brush realizations

One critical performance optimization that the developer must opt into is to allow caching of the bitmaps that result from rasterizing a VisualBrush or Viewport2DVisual3D.  Without doing this, each frame renders the material anew, which can be quite expensive.  There are static methods off of RenderOptions that control caching in WPF.  In Planerator.cs, these are called from the "SetCachingForObject" private method.

Layout invalidation

When the contents of the Planerator change internally resulting in layout changes to those contents (for instance, a Label in a horizontal StackPanel gets longer, making the entire content wider), then the Planerator itself should be remeasured.  This, though, poses a problem because the contents are embedded in a Viewport2DVisual3D, and layout doesn't flow in and out of that. 

The solution pursued here is that when we set up the Planerator, we wrap the child in a custom Decorator defined in the project and called LayoutInvalidationCatcher.  LayoutInvalidationCatcher overrides MeasureOverride and ArrangeOverride, and results in invalidating the Planerator instance's measure and arrange, respectively, thus forcing the Planerator to be re-laid out, and getting the results we want.

Databinding passthrough

A problem similar to the Layout Invalidation problem above is with databinding.  Because the children are embedded in Viewport2DVisual3D, databinding processing doesn't flow into it.  Therefore, we make the logical child of the Planerator be the content itself, while the visual child is the 3D construct that get built up, including the use of Viewport2DVisual3D.  By doing this, we allow databinding to flow to the Planerator's child properly, through the logical tree.  Then everything is set up to render it as part of the visual tree.

Proper sizing

Finally, a key aspect to making Planerator meaningful and friction-free is that it sizes itself precisely to the size of the underlying content, as if the Planerator just weren't there (at least until you start changing the rotation angles).  There are two relevant pieces to this.  One is camera setup, and this is described in detail in this previous post of mine. 

The second is in determining the proper size of the child element itself.  It's not sufficient to use the results of Measure(), since those aren't the "ink bounds"... that is, it includes whitespace that doesn't get rendered to because, for instance, a Canvas is made explicitly wider than all the stuff that renders into it.  This is problematic because when such a visual is applied as a VisualBrush (or as Viewport2DVisual3D) it just uses the rendered bounds of the Visual, and thus the result would be distorted if we used Measure() results.  Thus, in order to get the correct "ink bounds", we use VisualTreeHelper.GetDescendantBounds().

 

Put all those together, and you get a pretty cool Planerator control.

Posted by Greg Schechter | 2 Comments
Filed under:

Planerator comments and posted XBAP

My previous post introduced a simple, but very powerful custom WPF control called a Planerator.  That post contained some screenshots, but there's nothing like a live demo.  So here's an XBAP demonstrating the Planerator in action.  Click on "Go" to start it animating on its own.  Or adjust the angles yourself.  Note that the content inside the Planerator remains interactive throughout.

This is a .NET 3.5 application, so you need the latest 3.5 runtime, which you can get here.  One could consider writing this in 3.0 with the 3D Tools package...  that's an exercise left for the reader :-).

I've also updated the source code drop for the Planerator control and some example uses of it.

There were some comments on the last post that I'd like to address here:

  • FKruesch asked about exposing Field Of View... that is indeed exposed by the control.
  • BBowers asked about the content inside the Planerator being more dynamic.  In fact, it is, and my example that had hardcoded dimensions was misleading.  It's fully dynamic and layout is run on the contents and the size taken from that.  You'll see that the example I provide in the source code very much is dynamically sized.
  • Marlon Smith asked about putting elements on the back of the plane.  Just follow the lead of what's done in the CreateVisualChild() method in Planerator.cs for setting up the Viewport2DVisual3D.  That's used there for the front of the plane.  You can use the same technique for the back of the plane if you want those elements to be interactive (I just used a VisualBrush for the back, so they wouldn't be interactive).

Next post is on some of the implementation details that users of Planerator needn't concern themselves with.

Have fun!

Posted by Greg Schechter | 9 Comments
Filed under:

Enter The Planerator - Dead-simple 3D in WPF, with a stupid name

[UPDATED: November 26, 2007 - updated source code zip one last time.] 

[UPDATED: November 7, 2007 - updated source code zip and made a few clarifications.] 

When incorporating 3D support into WPF, we strived for integration with the rest of the system, and sufficient flexibility that will support lots of different scenarios.  So, even though 3D in WPF is typically far easier to use and higher-level than other 3D platform APIs, there are still a whole bunch of concepts and object model you need to learn to effectively use it.

But what about the developer that just wants to "make this thing tilt backwards into 3D"?  Or "give this element a 3D perspective"?  Currently, to do that, you need to know about Viewport3D, cameras, lights, models, meshes, texture coordinates, triangle indices, materials, visual brushes, 3d transformations, and caching hints.  (And yes, this still is substantially easier than most 3D platform APIs.)  And you also need to understand, re-derive, or steal the math necessary for aligning 3D frontal projections with 2D (as described in this previous blog entry).  Just for these very simple, constrained scenarios, it shouldn't be that difficult to do.

Those thoughts motivate construction of the custom WPF control I'll describe here.  I'm calling it the Planerator control, because it takes a WPF element and puts it on a flat plane that you can manipulate in 3D.  The original element remains interactive as it's transformed in 3D.  (This is thanks to our WPF 3.5 - Orcas - feature called Viewport2DVisual3D... Lester shows a usage of it here.)

All you need to do to use the Planerator is wrap it around some other WPF element, and set (or animate, or databind) its exposed RotationX, RotationY, and RotationZ properties, and you're done.  The element is now living in 3D, still fully interactive, and the Planerator itself will participate in 2D layout.  Here's a simple example:

 

    <!-- Excluding backgrounds and list box items for brevity -->

        <pl:Planerator RotationY="35">

           <StackPanel Orientation="Horizontal" ... >

                <StackPanel>

                    <Label  FontSize="24" Content=" " Foreground="#FF000000"/>

                    <Border ... >

                        <MediaElement x:Name="myMediaElement" />

                    </Border>

                </StackPanel>

                <ListBox ... />

            </StackPanel>

        </pl:Planerator>

The above results in this:

image

Again, no cameras or meshes or lights to set up, and no complicated math to align the 3D projection with the original 2D sources.  Drop dead simple perspective projections into 3D (that remain interactive, too!)

By the way, in case you were wondering what it looks like behind this plane... it's just what you'd expect :-)  :

image

Get Yours

I've attached a Visual Studio solution to this blog post that contains the Planerator custom control, and two sample apps -- one a windowed application, and the other an XBAP.  Both apps demonstrate the video browser demo panel shown above with controls for changing the angles ("field of view" can also be modified on the Planerator, to change the amount of perspective).  Note that the solution is for Visual Studio 2008, and uses C# 3.0 features and WPF 3.5 features.  I built it on Visual Studio 2008 Beta 2.

Have fun... if you create some cool uses, send 'em along...

I'll make a subsequent post that talks about some of the more interesting aspects of getting the Planerator working.

Posted by Greg Schechter | 12 Comments
Filed under:

Attachment(s): PlaneratorSolution.zip

Making use of multiprocessing in WPF

There was a query on the MSDN forums for WPF the other day that asked about leveraging multiple processors in WPF applications.  I responded, and am basically repeating that response here, with a little bit of extension:

WPF 3.0 (and the version coming out with Orcas, 3.5) is definitely an STA(*)-based model, but here are a some observations/comments on how multiple processors can and should be used in WPF applications.

Serialized/Atomic programming model for UI programming is essential:

  • Since time immemorial (i.e. Windows 3.1 ) the way developers have written event-based UI programs has been fairly constant:  register event handlers that are invoked through user interface actions, and have those event handlers modify the UI as well as maintain private state.  There's a fundamental assumption of, if not single-threadedness, at least atomicity and serialization of operations.  STA definitely provides that.  Rental-thread (**) could provide that as well, and does so in ASP.NET (but without any multicore benefits for a single application).  Free threaded definitely doesn't provide those guarantees and would result in havoc for UI developers.

  • In fact, we've seen such havoc in Windows Forms.  Unlike WPF, where access from a separate thread to UI objects results in explicit exceptions, Windows Forms is looser.  As a result, apps have gotten away, temporarily, with accessing UI objects from separate threads in Windows Forms.  The problem is that it sometimes works, sometimes doesn't.  It's one of the most widely reported programming problems with Windows Forms (and one of the motivations for making it more iron-clad in WPF).

  • Thus, we don't believe that, as long as developers are writing stateful and state-modifying logic for manipulating their UIs (and there's no sign of that changing), that making the event handlers be free threaded is the right way to go.  However, there are alternative ways to use those cores.

WPF Implementation Use of Multithreading

  • There is a degree of multithreading support built into WPF, with the rendering/composition happening on a second thread from the UI thread.  Thus there's a natural two-thread division of labor.  However, it doesn't naturally extend beyond that.

  • While we don't do so today, we're definitely interested in subdividing internal computation across cores.  Layout and databinding are both potential examples (though both also have potential pitfalls with invocation of stateful user code).  Rendering can also potentially benefit from multiple cores.  (Though not necessarily as much as one would initially think, since so much of the rendering is typically done on the GPU, which itself is massively parallelized.)  Silverlight's software renderer, for example, takes great advantage of multiple cores.

  • We'll be looking into the above in the context of more broadly looking at performance wins that can be had.

Making application operations multithreaded

This leads to the final aspect here:  None of the caveats above should prevent the application itself from being multithreaded.  There's great support in WPF and in the .NET Framework itself for doing so.  The trick is to be sure that when it comes time to manipulate WPF objects, that that manipulation is done on the UI thread.  Here are some techniques:

  • Use BackgroundWorker.  This component invokes a "DoWork" event on a separate thread, and when that work is completed, raises a completion event back on the UI thread.  Progress notification and cancellation are also supported.

  • The System.Net.WebClient class also provides support for this sort of asynchronous model, where file and data downloads occur asynchronously, but completion is reported back on the UI thread.

  • Web services (both Whidbey "web references" and Orcas "service references") provide similar asynchronous support in the proxies that are automatically generated by VisualStudio.  They provide methods that follow the general "Asynchronous Pattern For Components" model, which basically means that for a each synchronous web service call named Foo, there will also be a FooAsync() method and a FooCompleted event generated that provide asynchronous invocation of the web service.  FooCompleted will be raised on the UI thread with the results of the web service invocation available in the EventArgs.

  • Use the WPF Dispatcher.  The WPF Dispatcher can be used explicitly for finer-grained control of the message queue being processed by the WPF application.  Other threads can post delegates to be invoked on the UI thread with a high degree of control.

  • Use multiple UI threads driving separate windows.  A somewhat more advanced technique involves creating multiple UI threads each talking to its own Window.  Each UI thread is STA, but if your application is suitably compartmentalized, this can be an effective technique.

  • Use multiple UI threads targeting the same window.  A refinement of the technique above lets you create multiple UI threads, and bring their results together via HostVisual for hosting cross-thread visuals.  Dwayne Need has a great blog post on doing just that.

Anyhow...  hope this helps with some context and ideas.  We think this opens lots of doors for creative use of multiprocessing within WPF applications.  Interested in your experiences with all of this stuff!

(*) STA stands for "Single Threaded Apartment", which basically means that only the thread that creates a UI object is able to manipulate (i.e., get or set properties, or call methods) it.

(**) Rental-threaded, or RTA (for "Rental Threaded Apartment"), means that any thread can take ownership and manipulate a UI object, but only one can do so at a time.  So operations are guaranteed to be serialized, even if they execute on different threads.

Posted by Greg Schechter | 10 Comments
Filed under:

Silverlight 1.1 VirtualEarth Viewer

Microsoft Live Search Maps (aka Virtual Earth) at http://maps.live.com, is fantastic web technology that just keeps on getting better and better with birds-eye views, persistent collections, directions, etc.  However, given that its rendering is straight HTML and DHTML, the visual experience when zooming and panning is sometimes a little jarring in that you see black patches while tiles load, and tile layers pop instantly from one to the other during zooming.

The underlying graphics functionality in Silverlight provides an opportunity for a smoother viewing experience.  And I've had fun writing a Silverlight 1.1 (note Silverlight 1.1 is still in its Alpha stage) that provides just that.  You can find it, along with the source, at the Community Gallery on silverlight.net.  Click on the screenshot to visit the app:

VEViewer

(Extra points if you can tell me where screenshot is from - other than Rob and Dave!)

This app asynchronously requests tiles from the VirtualEarth tile servers (important note about this below).  Upon completion of the load, an animation is set up to fade the image from fully transparent to fully opaque, while the tiles below the image remain in place.  This results in a very nice fade-in effect as you zoom in and out and move around.  That and a continuous make for a great map browsing experience.  (And going fullscreen in IE with F11 is just beautiful.)

Important Note:  The URLs used to access the tile servers are not guaranteed to be supported by Microsoft and at any point are subject to change where they would no longer work.  Production code should not take any dependency on these tile servers.

Have fun!

Posted by Greg Schechter | 7 Comments
Filed under:

WPF, Silverlight and C# 3.0 object initializers

XAML is definitely the way to go whenever possible when you're writing WPF and Silverlight apps, due to its amenability to tooling, analyzability, side-effect-free-ness, etc.  However, there are occasionally (or often, depending on what you're doing) times when you need to construct WPF and Silverlight objects via code, and pure XAML expression doesn't do the trick.  A common example is when you're in a programmatic loop, and you need to generate an object on each iteration of the loop; or when you have something dynamically varying at runtime and you create based on that.  Some of those situations may still work with XAML and databinding, but there are many times where you just want to back off to writing it straight in code.

Since XAML is really an object-initialization-and-property-setting language, consider how we write the following XAML in code:

      <Canvas>

            <Rectangle StrokeThickness="30" RadiusX="97.5" RadiusY="97.5" Width="396" Height="312" >

                  <Rectangle.Fill>

                        <LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">

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

                              <GradientStop Color="Blue" Offset="0.5"/>

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

                        </LinearGradientBrush>

                  </Rectangle.Fill>

                  <Rectangle.Stroke>

                        <LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">

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

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

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

                        </LinearGradientBrush>

                  </Rectangle.Stroke>

            </Rectangle>

            <TextBlock Width="172" Height="80" Canvas.Left="109" Canvas.Top="109" TextWrapping="Wrap"

                  FontFamily="Baskerville Old Face" FontSize="72" Foreground="Orange">Hello

            </TextBlock>

      </Canvas>

The canonical means for writing something like this in code is to go from the inside out, creating objects and store them in local variables, and then assigning them into the containing objects.  So, the above would be constructed like this in C#:

        public UIElement CreateMyObject(double cornerRadius, double strokeThickness,

                                        string textString)

        {

            LinearGradientBrush fillBrush = new LinearGradientBrush();

            fillBrush.StartPoint = new Point(0, 0.5);

            fillBrush.EndPoint = new Point(1, 0.5);

            fillBrush.GradientStops.Add(new GradientStop(Colors.White, 0.0));

            fillBrush.GradientStops.Add(new GradientStop(Colors.Blue, 0.5));

            fillBrush.GradientStops.Add(new GradientStop(Colors.Black, 1.0));

 

            LinearGradientBrush strokeBrush = new LinearGradientBrush();

            strokeBrush.StartPoint = new Point(0, 0.5);

            strokeBrush.EndPoint = new Point(1, 0.5);

            strokeBrush.GradientStops.Add(new GradientStop(Colors.Black, 0.0));

            strokeBrush.GradientStops.Add(new GradientStop(Colors.Red, 0.5));

            strokeBrush.GradientStops.Add(new GradientStop(Colors.White, 1.0));

 

            Rectangle r = new Rectangle();

            r.Height = 312;

            r.Width = 396;

            r.Fill = fillBrush;

            r.Stroke = strokeBrush;

            r.StrokeThickness = strokeThickness;

            r.RadiusX = cornerRadius;

            r.RadiusY = cornerRadius;

 

            TextBlock t = new TextBlock();

            t.TextWrapping = TextWrapping.Wrap;

            t.FontFamily = new FontFamily("Baskerville Old Face");

            t.FontSize = 72;

            t.Foreground = Brushes.Orange;

            t.Text = textString;

 

            t.SetValue(Canvas.LeftProperty, 109.0);

            t.SetValue(Canvas.TopProperty, 109.0);

 

            Canvas c = new Canvas();

            c.Children.Add(r);

            c.Children.Add(t);

 

            return c;

        }

This certainly works, but it has the downside of taking something that's fundamentally declarative, and adding assignment to it solely because we need to refer to the objects we just created as we assign its properties.  This approach also encourages the inside-out construction of objects, making it harder to see the forest through the trees.

Enter C# 3.0 (coming in the "Orcas" release), with its "object initializer" feature.  Object initializers are particularly good for the sorts of objects that get built up in WPF and Silverlight -- namely objects that are created and then have properties assigned.  Not coincidentally, this is precisely the pattern used for XAML-exposed objects as well.

Here's a canonical example of construction of a Point without and with object initializers (not using any parameterized constructors):

Without:

        Point pt = new Point();

        pt.X = 3.0;

        pt.Y = 4.0;

With:

        Point pt = new Point() { X = 3.0, Y = 4.0 };

Now we can take our C# and convert it to use object initializers, resulting in this:

        public UIElement CreateMyObject(double cornerRadius, double strokeThickness,

                                        string textString)

        {

            return new Canvas()

               {

                   Children =

                   {

                       new Rectangle()

                       {

                           Height = 312,

                           Width = 396,

                           Fill = new LinearGradientBrush()

                                  {

                                      StartPoint = new Point(0,0.5),

                                      EndPoint = new Point(1, 0.5),

                                      GradientStops =

                                      {

                                          new GradientStop(Colors.White, 0),

                                          new GradientStop(Colors.Blue, 0.5),

                                          new GradientStop(Colors.Black, 1),

                                      }

                                  },

 

                           Stroke = new LinearGradientBrush()

                                  {

                                      StartPoint = new Point(0,0.5),

                                      EndPoint = new Point(1, 0.5),

                                      GradientStops =

                                      {

                                          new GradientStop(Colors.Black, 0),

                                          new GradientStop(Colors.Red, 0.5),

                                          new GradientStop(Colors.White, 1),

                                      }

                                  },

 

                           StrokeThickness = strokeThickness,

                           RadiusX = cornerRadius,

                           RadiusY = cornerRadius

                       },

                       new TextBlock()

                       {

                           TextWrapping = TextWrapping.Wrap,

                           FontFamily = new FontFamily("Baskerville Old Face"),

                           FontSize = 72,

                           Foreground = Brushes.Orange,

                           Text = textString,

                       }

                   }

               };

        }

Here, objects can be created outside-in, there's no possibility of creating unwanted "side-effects" here by needing to deal with temporary local variables.  Finally, C# Intellisense in Visual Studio helps out even more in object initializer expressions -- by only displaying properties that can be set (no methods), and by not displaying properties that have already been set in the expression.  So for instance when I'm typing FontSize in the above, Intellisense won't prompt with FontFamily, since this has already been set the line before.

There is one major problem with the object-initializer version I show above: it doesn't deal with the attached properties (Canvas.Top and Canvas.Left) that both the XAML and the first C# example have.  C# doesn't have direct language support for attached properties, so this is a situation where we do need to have a compromise approach -- using object initializers to create as large a chunk as possible, then setting attached properties imperatively, then using the results in another object initializer expression.  The following takes that approach as it creates a TextBlock with object initialization syntax, sets some attached properties, and then uses the result in another object initializer expression.

        public UIElement CreateMyObject(double cornerRadius, double strokeThickness,

                                        string textString)

        {

            TextBlock textBlock = new TextBlock()

                       {

                           TextWrapping = TextWrapping.Wrap,

                           FontFamily = new FontFamily("Baskerville Old Face"),

                           FontSize = 72,

                           Foreground = Brushes.Orange,

                           Text = textString,

                       };

 

            textBlock.SetValue(Canvas.LeftProperty, 109.0);

            textBlock.SetValue(Canvas.TopProperty, 109.0);

 

            return new Canvas()

               {

                   Children =

                   {

                       new Rectangle()

                       {

                           Height = 312,

                           Width = 396,

                           Fill = new LinearGradientBrush()

                                  {

                                      StartPoint = new Point(0,0.5),

                                      EndPoint = new Point(1, 0.5),

                                      GradientStops =

                                      {

                                          new GradientStop(Colors.White, 0),

                                          new GradientStop(Colors.Blue, 0.5),

                                          new GradientStop(Colors.Black, 1),

                                      }

                                  },

                           Stroke = new LinearGradientBrush()

                                  {

                                      StartPoint =