The World’s Leading Microsoft .NET Magazine
   
 
The .NET Addict's Blog

My Top Tags

                                                           

My RSS Feeds








I heart FeedBurner

Latest Diggs - Programming

Computers Blogs - Blog Top Sites

Site Hits

Total: 4,868,426
since: 19 Jan 2005

3D Data Visualization in WPF Step 2 - Combining Hit Testing with Data Binding

posted Tue 07 Mar 06

This post is a follow-up to the following two articles:
    Rotating the Camera in WPF
    3D Hit Testing with WPF

A lot of people are doing some really great things with WPF. On the other hand, a lot of what I've seen so far would fall under Alan Cooper's categorization of a "painted corpse".
Slapping gradients and spinning rotating objects into an interface just for the sake of doing it doesn't really add much value to the customer.
Don't get me wrong: it's fabulous that this kind of technology is now commoditized for us in the form of WPF, and no longer the exclusive domain of "Direct3D Programmers",
but we can easily abuse this technology. A friend of mine once told me that the .NET Framework was like taking the little deringer that used to be COM
and converting it into a .44 magnum. My response was "So now we have a larger gun with which to shoot ourselves in the foot."
The more power our tools give us, the more we need to be aware of abusing that power and abusing the people who use our applications.

One of the things I've always been interested in is presenting data in new and more useful ways. I'm pretty tired of the same old story: "Got data? ok, slap a DataGrid on it and call it done.". It's getting pretty sad and pretty tired. When you think about it - programmers are really the only kinds of people who just love having their data displayed to them in DataGrids. In this post, I'm going to play around with some WPF 3D techniques as well as some 2D techniques to get data to appear as a result of clicking an object in a 3D space. My proof of concept is actually a space game, and the cubes represent star ports (they will eventually be more complex meshes, but I wanted the interaction fleshed out before spending time modelling). When you click a star port, the final game will actually go out and communicate with the player that is hosting that star port (via WCF self-hosted services) and grab information on the port such as an image for the port, the port's name, description, how many players are docked at it, etc. This doesn't have to be a gaming-only concept. You could easily devise some scheme whereby the parent information is laid out in 3D meshes in an immersive environment. When you click the parent node/object/mesh/whatever, the client then fetches the raw detail information and then displays it in a stylish 2D panel.

To get started, the first thing we need is a data source. There are a dozen different ways you can create a data source in WPF, but I only need one object (not a list of objects), and I've only got one instance of that object (the currently selected starport cube), so I'm going to use an ObjectDataProvider. You can set one up in your XAML by first creating an XML namespace mapping to a CLR namespace with the following line of XAML:


<Window x:Class="Cube1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"   
   xmlns:src="clr-namespace:DataLibrary;assembly=DataLibrary"
....
....

This essentially now gives me the freedom to refer to any class contained in the DataLibrary namespace in the DataLibrary Assembly directly within my XAML. I can't even begin to touch on how ridiculously useful this feature is, especially for those of us used to making really large apps with multiple libraries. Next, you need to create the ObjectDataProvider, which can be in any .Resources element. In this case, I chose Window.Resources to make it available to the entire window:

<ObjectDataProvider x:Key="odpPort">
</ObjectDataProvider>

Not much here, right? Well, I could choose to specify the data type of the underlying object data source, in which case the data provider would then take care of creating the instance for me. I don't want that:  I want this object data provider to point to a member variable that is part of the Window's code-behind, so I'll rig that up programmatically. First, add a member variable of type ObjectDataProvider:

private ObjectDataProvider odp;
private DataLibrary.PortData currentPort = new DataLibrary.PortData();

Then, at the end of the Window_Loaded event handler, set the ObjectInstance property of the data provider to the currentPort member:

odp = (ObjectDataProvider)this.FindResource("odpPort");
currentPort.PortName = "-- No Port --";
odp.ObjectInstance = currentPort;

There's one more thing that needs to be done in order for this whole thing to work properly. Custom classes that you write will not be able to inform the WPF GUI subsystem that properties have changed unless those objects implement the INotifyPropertyChanged interface. Here's a listing of the DataLibrary.PortData class:

namespace DataLibrary
{
    public class PortData : INotifyPropertyChanged
    {
        private string portName;
        private string image;
        private string longDesc;
        public string PortName
        {
            get { return portName; }
            set { portName = value; OnPropertyChanged("PortName"); }
        }
        public string LongDescription
        {
            get { return longDesc; }
            set { longDesc = value; OnPropertyChanged("LongDescription"); }
        }
        public string Image
        {
            get { return image; }
            set { image = value; OnPropertyChanged("Image"); }
        }
        #region INotifyPropertyChanged Members
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string prop)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(prop));
            }
        }
        #endregion
    }
}

Now that the data-bound class is able to communicate property changes to the GUI, we're ready to rig up a panel that will display the information obtained by clicking on a cube. In my sample, I'm fabricating the information rather than really getting it from a WCF service just to keep things easy to read. The following is the XAML for the DockPanel that contains the port information:

<!-- PORT VIEWER -->
<DockPanel Visibility="Hidden" x:Name="dpPortInfo" Width="0" Height="300" VerticalAlignment="Top" Margin="2">
  <DockPanel.DataContext>
    <Binding Source="{StaticResource odpPort}"></Binding>
  </DockPanel.DataContext>
  <DockPanel.Background>
    <LinearGradientBrush>
      <LinearGradientBrush.GradientStops>
        <GradientStop Color="DarkBlue" Offset="0"/>
        <GradientStop Color="LightBlue" Offset="1" />
      </LinearGradientBrush.GradientStops>
    </LinearGradientBrush>
  </DockPanel.Background>
  <DockPanel.BitmapEffect>
    <DropShadowBitmapEffect Color="AliceBlue"></DropShadowBitmapEffect>
  </DockPanel.BitmapEffect>
  <Grid Width="180" ShowGridLines="False">
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="100" />
      <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
      <RowDefinition Height="100" />
      <RowDefinition Height="30" />
      <RowDefinition Height="30" />
    </Grid.RowDefinitions>
    <Border BorderBrush="Silver" CornerRadius="5" Width="110" Grid.Column="0" Grid.Row="0">
      <Rectangle Width="100" Height="100">
        <Rectangle.Fill>
          <ImageBrush x:Name="imgPortIcon" ImageSource="{Binding Image}"></ImageBrush>
        </Rectangle.Fill>
      </Rectangle>
    </Border>
    <TextBlock Text="{Binding PortName}" Foreground="Yellow" Margin="5" Grid.Column="0" Grid.Row="1"></TextBlock>
    <TextBlock Text="{Binding LongDescription}" Foreground="White" Margin="5" Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="2"></TextBlock>
  </Grid>
</DockPanel>

The fact that its a completely mindless task to add a colored drop-shadow to any WPF element is just pretty staggering to me. I love little details like that.

There's one more thing before I rig up the hit testing code. I want to animate the expansion of the port info panel with a storyboard, so I'll put a storyboard in the Window.Resources element as shown below:

<Storyboard Storyboard.TargetName="dpPortInfo" Storyboard.TargetProperty="Width" x:Key="sbWidthExpander">
  <DoubleAnimation From="0" To="170" Duration="0:0:00.5"></DoubleAnimation>
</Storyboard>


This storyboard will animate the Width property from 0 to 170 in half a second. Its a pretty decent window expansion time - anything longer than that and users are going to be tapping their fingers or, worse, they might be wondering why your app is running so slow.

Now take a look at the hit-test code from the previous blog. The difference is that this time I have added code that locates the width-expansion storyboard and activates it, and changes values on the currentPort object instance. Note that I never have to re-set the ObjectInstance property or do any refreshing of the data source. The GUI is immediately aware of changes to my data-bound object!

public HitTestResultBehavior HTResult(System.Windows.Media.HitTestResult rawresult)
{
RayHitTestResult rayResult = rawresult as RayHitTestResult;
if (rayResult != null)
{
    RayMeshGeometry3DHitTestResult rayMeshResult = rayResult as RayMeshGeometry3DHitTestResult;
    if (rayMeshResult != null)
    {
        GeometryModel3D hitgeo = rayMeshResult.ModelHit as GeometryModel3D;
        tbHitCheck.Text = "You hit mesh (" + meshes[hitgeo] + ") at " + DateTime.Now.ToString();
       
        dpPortInfo.Visibility = Visibility.Visible;
        currentPort.PortName = meshes[hitgeo];
        currentPort.Image = @"C:\documents and settings\kevin\my documents\my pictures\penfold.jpg";
        currentPort.LongDescription = "Star port floating in space.";
       
        Storyboard s = (Storyboard)this.FindResource("sbWidthExpander");
        this.BeginStoryboard(s);
    }
}
return HitTestResultBehavior.Continue;
}

The only thing that irked me so far about data binding is that I can't seem to bind an in-memory, loaded Image to the ImageSource property of a brush. Oh well - small sacrifice to pay I suppose. Now we'll finish this whole thing off with a screenshot of what the application looks like with its fresh new data visualization panel:

Data Visualization in 2D and 3D

tags:                

links: digg this    del.icio.us    technorati    reddit

AddThis Social Bookmark Button




1. Christopher Bennage left...
Thu 18 May 06 12:41 pm :: http://dev.bennage.com

I just stumbled onto this post and I was pretty excited because I have been planning a spiritual successor to the old BBS game, Trade Wars, for some time. My partners in the effort and I had only recently decided to abandon Flash as our client platform in favor of WPF (a more limited audience, but much easier to manage).

Anyway, it is interesting to see how you are solving some of the exact same problems that we've been working on. We are planning to have a playable version of the our game in beta by the end of the year. I am curious to know, are you are planning on turning your "proof of concept" into a full blown game? Not that some friendly competition would be so bad.

(I attempted to leave a private message also, but the mechanism for doing so appeared not to work.)


2. Epsilone3 left...
Tue 06 Nov 07 6:45 pm :: http://epsilone3.spaces.live.com

Any one got a solution for this problem ?

http://epsilone3.spaces.live.com/blog/cns!CCE93A915EF34C04!274.entry


3. Alionka left...
Mon 16 Mar 09 1:27 am :: http://techzone.enterra-inc.com/

Just want to share with you article on using WPF in visualisating cartographical objects. here is direct link http://techzone.enterra-inc.com/architecture/cartograph ical-objects-visualization-using-wpf/


Tag Related Posts

WPF Control Development Unleashed

Wed 25 Mar 09 2:26 P GMT-05

My first day using Windows 7 Beta 1

Wed 25 Feb 09 1:58 P GMT-05

Smart, Deep Property Notifications in CLINQ v2.0

Tue 07 Oct 08 1:15 P GMT-05
tags:          

Microsoft's Lofty Direction

Sun 05 Oct 08 2:30 P GMT-05

So I'm in the LA Times ;)

Wed 27 Aug 08 2:51 P GMT-05
tags:                  

MobileMe vs. Live Mesh Throwdown - Round 1

Wed 16 Jul 08 10:33 A GMT-05

Building Model Classes in C# and Cocoa

Sun 15 Jun 08 3:13 P GMT-05
tags:            

NYC SharePoint Developer Needed

Mon 12 May 08 12:09 P GMT-05

CLINQ v1.1.0.0 Released!

Fri 02 May 08 5:38 P GMT-05
tags:          

iPhone Underrated as a Gaming Device?

Fri 14 Mar 08 1:50 P GMT-05
tags:        

One Framework to Rule them All

Mon 25 Feb 08 6:49 P GMT-05

CLINQ v1 Demo - Network Message Filtering

Wed 09 Jan 08 7:47 P GMT-05
tags:        

Building a Ledger Style for WPF Grids

Tue 21 Aug 07 3:30 P GMT-05
tags:    

Continuous LINQ - Can I write games with it?

Mon 13 Aug 07 3:09 P GMT-05
tags:        

My 2008 Wishlist : A Transformers MMORPG

Tue 26 Jun 07 12:04 P GMT-05
tags:        

The dreaded language bleed-over has begun

Tue 19 Jun 07 6:23 P GMT-05
tags:        

My first "Acropolis" Application

Mon 04 Jun 07 1:40 P GMT-05
tags:      

Exploring the Delegate Design Pattern

Mon 14 May 07 6:30 P GMT-05

Core Data - Almost too Easy?

Wed 18 Apr 07 2:23 P GMT-05

Will Silverlight be DOA?

Mon 16 Apr 07 8:02 P GMT-05

Exploring the MVC Pattern in WPF

Tue 10 Apr 07 12:51 P GMT-05
tags:                      

My Little Pony .NET Unleashed 2007

Fri 30 Mar 07 1:59 P GMT-05

An experience with the Leopard beta

Mon 26 Mar 07 7:45 P GMT-05
tags:                

Authorness

Thu 15 Mar 07 1:44 P GMT-05

WPF Bindings == WTF Bindings?

Mon 12 Mar 07 6:31 P GMT-05

On MUDs

Thu 08 Mar 07 5:00 A GMT-05
tags:                    

Cocoa Programming vs. WPF : NIB vs XAML

Tue 20 Feb 07 2:09 P GMT-05

Cocoa Bindings vs. WPF Binding

Thu 15 Feb 07 5:41 P GMT-05
tags:                

Ulysses Agenda Makes Redmond Developer News

Wed 29 Nov 06 7:10 P GMT-05
tags: