Archive for the ‘WPF’ category

Advanced WPF ListBox Scenario: Representing and Selecting an XML Hierarchy

July 22nd, 2010

I’ve never found a use for the XmlDataProvider natively in Xaml for a shipping app, but today a @malevy had a situation that sounded interesting.  He has an Xml document structured as follows:

<Owners>

    <Owner name=”’”>

        <Pet name=”” />

        <Pet name=”” />

    <Owner name=””>

    …

</Owners>

He wanted to be able to represent this hierarchy in a ListBox, but actually select from the Pets.  Bonus points for the egregious expander as the owner container.  In the end it was a combination of XmlDataProvider pointing to the Pet nodes, a CollectionViewSource using a GroupDescription which walked up the XPath to Owner\@name, and the ListBox using an Expander as it’s GroupStyle container.

 

Here is what it looks like in the end:

image

 

Here’s the source.

SketchFlow: From Concept to Production

April 16th, 2010

I had a great time in Frankfort, KY yesterday.  We had a great turnout for the Expression SketchFlow: From Concept to Production session.  It is incredibly encouraging to see so many interested in building great, well designed apps for all industries and clients (even your LOB apps!).

Thanks again for those who made it.  You can find the slides and SketchFlow solution here.

Kentucky Briefings April 15th and 22nd

April 5th, 2010

Along with a great cast of MS partners, evangelists, specialists and more I will be bringing Sketchflow to the Frankfort, KY area Thursday, April 15th.  This will be Session I with Session II scheduled for April 22nd.  You can learn about everything from Forefront Suite to Silverlight to Windows Phone 7 and, of-course, Sketchflow

You can find registration and session schedules in here (pdf).

Looking forward to seeing everyone there.

Grouping is crazy easy in WPF

March 18th, 2010

Lots of people struggle with grouping data and when they do, the brut force method normally takes over and we start creating some crazy super structure to organize data and then do something like put a List inside of a List or a Grid inside of a list.  Just think of how many times you tried to put a GridView inside of a Repeater back when we didn’t know about jQuery or MVC and WebForms were all we knew.

Well it turns out WPF (and Silverlight 4.0) make this crazy easy.  It’s why the CollectionViewSource class exists.  The thing people don’t realize is that you don’t have to change anything about your data structure to make this work well (most times) and your designer (you have one right?) can make the decision on how best to display the information.

 

Step 1: Get Yourself Some Data

Don’t go crazy trying to group your data in your view model with Linq statements or anything like that.  You don’t (likely) need it.  Just get a list of your Animals with the necessary attributes for display in normal IEnumerable form.

Here’s how I’m doing to get my data for the sample:

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Page.Resources>
        <XmlDataProvider x:Key="data">
            <x:XData>
                <Animals xmlns="">
                    <Animal name="Dory" Species="Fish" />
                    <Animal name="Felix" Species="Cat" />
                    <Animal name="Fluffy" Species="Dog" />
                    <Animal name="Jake" Species="Snake" />
                    <Animal name="Mittens" Species="Cat" />
                    <Animal name="Murtle" Species="Turtle" />
                    <Animal name="Nemo" Species="Fish" />
                    <Animal name="Rex" Species="Dog" />
                    <Animal name="Rover" Species="Dog" />
                    <Animal name="Toonces" Species="Cat" />
                </Animals>
            </x:XData>
        </XmlDataProvider>
    </Page.Resources>
</Page>
Step 2: Group The Data with CollectionViewSource

We need to remember that Xaml is just a way to instantiate classes.  Knowing that, we can create a CollectionViewSource right in our xaml like this:

 

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Page.Resources>
        <XmlDataProvider x:Key="data">
            <x:XData>
                <Animals xmlns="">
                    <Animal name="Dory" Species="Fish" />
                    <Animal name="Felix" Species="Cat" />
                    <Animal name="Fluffy" Species="Dog" />
                    <Animal name="Jake" Species="Snake" />
                    <Animal name="Mittens" Species="Cat" />
                    <Animal name="Murtle" Species="Turtle" />
                    <Animal name="Nemo" Species="Fish" />
                    <Animal name="Rex" Species="Dog" />
                    <Animal name="Rover" Species="Dog" />
                    <Animal name="Toonces" Species="Cat" />
                </Animals>
            </x:XData>
        </XmlDataProvider>
        <CollectionViewSource x:Key="animalsBySpecies" Source="{Binding Source={StaticResource data}, XPath=Animals/Animal}">
            <CollectionViewSource.GroupDescriptions>
                <PropertyGroupDescription PropertyName="@Species" />
            </CollectionViewSource.GroupDescriptions>
        </CollectionViewSource>
    </Page.Resources>
</Page>

 

Step 3: Show The Data

This is where the fun happens.  We take something as simple as a ListView or, simplier yet, the ItemsControl and we hook it up to our CollectionViewSource like this:

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Page.Resources>
        <XmlDataProvider x:Key="data">
            <x:XData>
                <Animals xmlns="">
                    <Animal name="Dory" Species="Fish" />
                    <Animal name="Felix" Species="Cat" />
                    <Animal name="Fluffy" Species="Dog" />
                    <Animal name="Jake" Species="Snake" />
                    <Animal name="Mittens" Species="Cat" />
                    <Animal name="Murtle" Species="Turtle" />
                    <Animal name="Nemo" Species="Fish" />
                    <Animal name="Rex" Species="Dog" />
                    <Animal name="Rover" Species="Dog" />
                    <Animal name="Toonces" Species="Cat" />
                </Animals>
            </x:XData>
        </XmlDataProvider>
        <CollectionViewSource x:Key="animalsBySpecies" Source="{Binding Source={StaticResource data}, XPath=Animals/Animal}">
            <CollectionViewSource.GroupDescriptions>
                <PropertyGroupDescription PropertyName="@Species" />
            </CollectionViewSource.GroupDescriptions>
        </CollectionViewSource>
    </Page.Resources>
    <DockPanel>
        <ScrollViewer DockPanel.Dock="Bottom" VerticalScrollBarVisibility="Auto">
            <ItemsControl ItemsSource="{Binding Source={StaticResource animalsBySpecies}}">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding XPath=@name}" />
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </ScrollViewer>
    </DockPanel>
</Page>


This doesn’t give us anything we couldn’t do before:image 

So how do we represent this grouping? 

Step 3: Display the Grouping

Easy, we define the GroupStyle.  There is an awful lot we can do here, but we’ll go with putting each into a GroupBox with the Header being the Species.

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Page.Resources>
        <XmlDataProvider x:Key="data">
            <x:XData>
                <Animals xmlns="">
                    <Animal name="Dory" Species="Fish" />
                    <Animal name="Felix" Species="Cat" />
                    <Animal name="Fluffy" Species="Dog" />
                    <Animal name="Jake" Species="Snake" />
                    <Animal name="Mittens" Species="Cat" />
                    <Animal name="Murtle" Species="Turtle" />
                    <Animal name="Nemo" Species="Fish" />
                    <Animal name="Rex" Species="Dog" />
                    <Animal name="Rover" Species="Dog" />
                    <Animal name="Toonces" Species="Cat" />
                </Animals>
            </x:XData>
        </XmlDataProvider>
        <CollectionViewSource x:Key="animalsBySpecies" Source="{Binding Source={StaticResource data}, XPath=Animals/Animal}">
            <CollectionViewSource.GroupDescriptions>
                <PropertyGroupDescription PropertyName="@Species" />
            </CollectionViewSource.GroupDescriptions>
        </CollectionViewSource>
    </Page.Resources>
    <DockPanel>
        <ScrollViewer DockPanel.Dock="Bottom" VerticalScrollBarVisibility="Auto">
            <ItemsControl ItemsSource="{Binding Source={StaticResource animalsBySpecies}}">
                <ItemsControl.GroupStyle>
                    <GroupStyle>
                        <GroupStyle.ContainerStyle>
                            <Style TargetType="{x:Type GroupItem}">
                                <Setter Property="Template">
                                    <Setter.Value>
                                        <ControlTemplate TargetType="{x:Type GroupItem}">
                                            <GroupBox Header="{Binding Name}">
                                                <ItemsPresenter />
                                            </GroupBox>
                                        </ControlTemplate>
                                    </Setter.Value>
                                </Setter>
                            </Style>
                        </GroupStyle.ContainerStyle>
                    </GroupStyle>
                </ItemsControl.GroupStyle>
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding XPath=@name}" />
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </ScrollViewer>
    </DockPanel>
</Page>


 

We’ve told WPF, take all of the Groups out of the CollectionViewSource and put each into a Container (just like it would with individual items), that container being a GroupItem instead of a ListItem.  Style the GroupItem such that its Template is a GroupBox with an ItemsPresenter inside to display each individual item.  You can do a lot with this and it also works with the GridView form of a ListView.

Registering WPF “Views” with Windsor Fluent API

October 26th, 2009

I’ve had this nagging issue for some time now with WPF views that are registered for an interface.  The Views themselves are WPF UserControls:

namespace SomeApp.Views
{
    public partial class SearchView : UserControl, ISearchView
    {
        public SearchView()
        {
            InitializeComponent();
        }

        private void InitializeComponent()
        {
            throw new NotImplementedException();
        }
    }
}

 

I would love to have registered all views in one go by simply adding:

container.Register(AllTypes
                       .FromAssembly(assembly)
                       .Where(x => x.Name.EndsWith("View"))
                       .WithService.FirstInterface());

 

Unfortunately, this registers my Views for System.Windows.Media.Composition.DUCE+IResource implementations which I really couldn’t care less about.  The interface I’m looking for is the “nearest” interface for lack of a better term.  Fortunately, Windsor lets us provide a delegate selector for the interface.  Here’s what I’m using now:

container.Register(AllTypes
                       .FromAssembly(assembly)
                       .Where(x => x.Name.EndsWith(suffix))
                       .WithService.Select((t, bt) =>
                                               {
                                                   var serviceType = t
                                                       .GetInterfaces()
                                                       .Where(x => x.FullName.StartsWith("SomeApp"))
                                                       .FirstOrDefault();

                                                   if (serviceType != null)
                                                       return new[] {serviceType};
                                                   else
                                                       return new[] {t};
                                               }));

 

Use Select versus FirstInterface allows us to provide back the Service type(s?) that this class is implementing.  The StartsWith is obviously fragile, but I’m green right now, so I’ll see you on the other side.

Property Undo/Redo Support

July 4th, 2009

Undo/Redo support is one of those golden features that really differentiate a client app from many web apps.  There have been a number of methods/techniques to provide this support that I’ve run across from brute force, to the memento pattern, but none that lit any fire for me.

Here’s my answer to the problem using Anonymous methods and my first real attempt at creating (versus using) a Fluent Api.

The most common scenario for Undo/Redu support is form completion.  In your Domain or View Model this is often tied to Two Way bound properties.  Take the simple Person class:

public class Person
{
    public string Name { get; set; }
    public DateTime BirthDate { get; set; }
}

The question is how to provide undo/redo support for a form (WPF often in my case) bound to a Person.  First, I create a ViewModel or Presenter for the Person class:

public class PersonViewModel : ViewModelBase
{
    private readonly Person _person;
 
    public PersonViewModel(Person person)
    {
        _person = person;
    }
 
    public string Name
    {
        get { return _person.Name; }
        set
        {
            _person.Name = value;
            OnPropertyChanged("Name");
        }
    }
 
    public DateTime BirthDate
    {
        get { return _person.BirthDate; }
        set
        {
            _person.BirthDate = value;
            OnPropertyChanged("BirthDate");
            OnPropertyChanged("Age");
        }
    }
 
    public int Age
    {
        get
        {
            var now = DateTime.Now;
            var age = now.Year - _person.BirthDate.Year;
 
            if (now < _person.BirthDate.AddYears(age))
                age--;
 
            return age;
        }
    }
}

Notice that we have a calculated property Age based on the BirthDate.  Any time we change BirthDate we want to make sure to notify that the Age is also changed, obviously. 

Here is the Fluent way to register Undo/Redo support for our PersonViewModel.Name property:

   1: public string Name
   2: {
   3:     get { return _person.Name; }
   4:     set
   5:     {
   6:         var oldName = _person.Name;
   7:  
   8:         _undoRedoManager
   9:             .WithUndo(() => _person.Name = oldName)
  10:             .RepeatAfterBoth(() => OnPropertyChanged("Name"))
  11:             .Do(() => _person.Name = value);
  12:     }
  13: }

Cool, but what is that doing?  Well, it’s pretty simply.  We are simply maintaining two queues; An Undo and a Redo queue.  Here is the longer syntax, without the Fluent helper Api that makes that a little clearer:

public string Name
{
    get { return _person.Name; }
    set
    {
        var oldName = _person.Name;
 
        _undoRedoManager.Push(() =>
                                  {
                                      _person.Name = oldName;
                                      OnPropertyChanged("Name");
                                  },
                                  () =>
                                  {
                                      _person.Name = value;
                                      OnPropertyChanged("Name");
                                  });
    }
}

This may not seem like much more, but if your Repeat part is more than one line, it gets a bit fragile.  Being able to specify the “After” stuff once is important.

From here you can use the IUndoRedoManager.CanUndo(), IUndoRedoManager.CanRedo(), IUndoRedoManager.Redo() methods to go back and forth.  Hook this up to your favorite ICommand implementation and you have most of what you might need.

 

Here is the full project with a WPF Sample and a VB sample.  The VB version is a little ugly since it doesn’t support anonymous methods without a return, but it works.

If you have suggestions (on the Fluent Api especially) let me know.  I’d really like to hear them.

Scaling ItemsControl Items for Explicit Visible Items in WPF

March 12th, 2009

Another part of the overall solution we were trying to come up with in conjunction my previous post on Scrolling Multiple Content Areas, we also needed to come up with a way to only show X number of items in the visible region of an ItemsControl wrapped in a ScrollViewer.  This was more tricky than I had initially anticipated, but the solution seems to work for us and its got relatively few moving parts. 

The end result of the concept can be visualize in this Xbap example.  By moving the slider at the bottom, you effectively change the width of the ItemsControl containing the colored Ellipses.  In order to do this as a reflection of the number of items contained within, you need another IMultiValueConverter.

Here is the meat of the code for the RelativeSizeConverter I came up with:

public class RelativeSizeConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        if( values.Length != 3 )
            throw new InvalidOperationException("Don't do that!");

        double containerSize = System.Convert.ToDouble(values[0]);
        int totalItems = System.Convert.ToInt32(values[1]);
        int visibleItems = System.Convert.ToInt32(values[2]);

        var visibleSubgroupSize = containerSize / visibleItems;

        return totalItems * visibleSubgroupSize;
    }
}

Basically we figure out how wide things would be if we only had the visible items and then we apply that to all the items return.  In order for this to work, there cannot be any sizing between the ItemsControl and the ScrollViewer, but its a small price to pay.  That would look odd any way in my opinion, but maybe I am simply justifying the solution!

Anyways, putting this RelativeSizeConverter to use, we get:

<ScrollViewer x:Name="container" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
    <ItemsControl x:Name="itemsToScale">
        <ItemsControl.Width>
            <MultiBinding Converter="{StaticResource sizeConverter}">
                <Binding ElementName="container" Path="ActualWidth" />
                <Binding ElementName="itemsToScale" Path="Items.Count" />
                <Binding ElementName="scaleSize" Path="Value" />
            </MultiBinding>
        </ItemsControl.Width>
    </ItemsControl>
</ScrollViewer>

<Slider x:Name="scaleSize" Width="100" HorizontalAlignment="Center"
        Minimum="2" Maximum="{Binding ElementName=itemsToScale, Path=Items.Count}"
        Value="{Binding ElementName=itemsToScale, Path=Items.Count, Mode=OneWay}"
        IsSnapToTickEnabled="True" TickFrequency="1" />

Notice we use the ScrollViewer’s ActualWidth as our “containerSize”.  The ItemsControl itself has the “totalItems” count, and finally we are using a Slider with the name “scaleSize” to adjust the “visibleItems” count.

Code for the Xbap sample at the top can be downloaded here.

Scrolling Multiple Content Areas with a single ScrollBar in WPF

March 12th, 2009

I struggled to solve a UI dilemma while at my current client.  For various reasons, we came across a situation in which we needed to scroll two areas of the view based on a single Scrollbar.  You can see what we were attempting and finally achieved here.

The end result is smooth, yet not quite intuitive so I thought I would share the results and steps taken.  The idea is to use a surrogate ScrollViewer as the source for the offset of a TranslateTransform being applied to content within a seperate ScrollViewer.

Unfortunately, the offset values exposed by the ScrollViewer are both readonly and opposite in force to which we would need to apply to the surrogate.  That is, if we scroll 20 units to the right on our ScrollViewer, we would need to offset the surrogate ScrollViewer by that amount in the opposite direction.  To resolve this problem, we use an IValueConverter to multiple the offset by –1.

Here we can see the Xaml used to layout and hookup this scenario:

<StackPanel>
    <ScrollViewer HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden">
        <Grid>
            <Grid.RenderTransform>
                <TranslateTransform X="{Binding ElementName=mainScroller, Path=HorizontalOffset, Converter={StaticResource multiplyConv}, ConverterParameter=-1}" />
            </Grid.RenderTransform>
            <!-- Content Scrolled by another goes here -->
        </Grid>
    </ScrollViewer>
    <ScrollViewer x:Name="mainScroller" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
        <!-- Content Scrolled on its own accord goes here-->
    </ScrollViewer>
</StackPanel>

You can see that we have two ScrollViewers and the one which will be controlled (versus the controller) has a container element with a TranslateTransform applied to its RenderTransform property.  This is what we will be using to move the controlled content in the opposite direction of the ScrollBar.

If you find that your content for each are not the same size, you can always use a MultiBinding to accomodate the relative scrolling distance as applied to the offset.

You can find the source to the sample Xbap here.

The WPF Mindset

November 3rd, 2008

WPF is awesome, but it requires the developer to make a pretty big shift in their thinking of UI.  I’ve had a hard time explaining this to people, but last week, I had an opportunity to work through an example with Steve Gentile.  That’s the basis of this walkthrough.

 

The Scenerio

Steve has an ASP.Net website he maintains called ForgottenSkies.com.  One of the pages displays a map from the game with events that were captured from the server.  You can see the ASP.Net version here.  Steve was looking to create a Silverlight version of this page.

The Wrong Mindset

The initial response from most developers would be to put an image on the “form” and then figure out a way to place some composite control or other image that would denote each event’s location.  If they had the inclination they might use the DataContext, but they likely would also iterate each Event in some collection and manually place these controls within some container.  I believe Steve was trying to place them into a really big grid with lots of Rows and Columns to represent the Latitude and Longitude of the map.

The WPF Mindset

Decomposing this scenario is vital to using WPF properly.  When it comes down to it, the map is really just a way to visualize a collection of Events. If we wanted we could easily make this a list, couldn’t we?  So lets start there:

For the purposes of this demo, the Window.DataContext is set to an instance of an AttackService, which looks like the following:

public class AttackService
{
    public ObservableCollection<Attack> Attacks { get; private set; }
    public int MapSize { get; set; }

    public AttackService()
    {
        this.MapSize = 160;
        this.Attacks = new ObservableCollection<Attack>();

        Random random = new Random();

        for(int index =0; index < 10;index++)
        {
            var attack = new Attack
            {
                Description = string.Format("Attack {0}", index),
                Latitude = random.Next(0, MapSize),
                Longitude = random.Next(0, MapSize),
                Type = (AttackType)random.Next(0, 2)
            };


            this.Attacks.Add(attack);
        }
    }
}

Our goal is to display the Attacks collection, so let’s do that:

<Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid>
        <ListView ItemsSource="{Binding Attacks}" />
    </Grid>
</Window>

imageWe have a ListView which has it’s ItemsSource bound to our Attacks collection.  Nothing special.  WPF doesn’t know how to display an Attack class so if we would just see a 10 instances of our class name listed.  We’ll get there though.

 

For starters, lets look at these as if they were listed in a table (or GridView). To do this, we’ll tell the ListView to use a style, attacksList.  We’ll place this style definition in our Window’s Resources collection for convenience of our demo.

<Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Window.Resources>
        <Style x:Key="attacksList" TargetType="ListView">
            <Setter Property="View">
                <Setter.Value>
                    <GridView>
                        <GridViewColumn DisplayMemberBinding="{Binding Description}" />
                        <GridViewColumn Header="Latitude" DisplayMemberBinding="{Binding Latitude}" />
                        <GridViewColumn Header="Longitude" DisplayMemberBinding="{Binding Longitude}" />
                    </GridView>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>

    <Grid>
        <ListView x:Name="attacksList" ItemsSource="{Binding Attacks}" Style="{StaticResource attacksList}" />
    </Grid>
</Window>

What we’ve done is given ourselves a way to set values or properties on the ListView.  imageFor the sample you see here, we set the type of View for the ListView to be a GridView. 

The Map

The ultimate visualization of this information is to display it contextually in a map.  It’s important to remember that this is still a list, we just want to visualize the list differently.  To do this we need to change our attacksListStyle.

<Style x:Key="attacksList" TargetType="ListView">
    <Setter Property="ItemsPanel">
        <Setter.Value>
            <ItemsPanelTemplate>
                <Canvas>
                    <Canvas.Background>
                        <ImageBrush ImageSource="Images\Map.jpg" />
                    </Canvas.Background>
                </Canvas>
            </ItemsPanelTemplate>
        </Setter.Value>
    </Setter>

    <Setter Property="ItemContainerStyle" Value="{StaticResource attackContainerStyle}" />
</Style>

Instead of setting the View of our ListView, we are now setting the ItemsPanel to a Canvas.  This tells the ListView to place all of the items its charged with listing into the Canvas.  We have chosen canvas, because we will want to place each item via coordinates (X/Y, Left/Type) rather than stacking, row/column, etc.  The background of our Canvas is going to be a JPG version of our map.  Backgrounds and Foregrounds are always brushes, so we are using an ImageBrush to draw our map on the Canvas background.

The second property we are setting is the ItemContainerStyle to the attackContainerStyle.  The ListView places each item into a container rather than displaying it directly.  In order to place it correctly, we need to set the Top and Left properties of this ItemContainer.

<Style x:Key="attackContainerStyle">
    <Setter Property="Canvas.Top" Value="{Binding Latitude}" />
    <Setter Property="Canvas.Left" Value="{Binding Longitude}" />
</Style>

This binds the Canvas.Top and Canvas.Left DependencyProperties to the Attack.Latitude and Attack.Longitude properties respectively.  To bring this together, we have our ListView bound to a collection of Attacks and the ItemContainer contains each Attack and has its DataContext set to that Attack it is meant to contain.

At this point we have most of what we want.  imageEach little black image you see below represents an attack.  This is done by setting the DataTemplate for the Attack type.

<DataTemplate DataType="{x:Type local:Attack}">
    <Image Source="Images\AlliedTerritory.gif" />
</DataTemplate>

The problem you will find is that if you allow the Window to be scaled beyond the size of the map size as it existed when creating the Attack Latitude and Longitude, the points on the map will not scale or move with the map.  I dealt with this by implementing an IMultiValueConverter.  I won’t go into it here as it doesn’t pertain to our Mindset discussion, but I’ve included it in the attacked source code.

Summary

If you come into WPF trying to create cool graphics or visualizations from the outset, you will miss the power and productivity of the platform.  WPF and the oft-sited Designer/Developer scenario actually thrive when you begin to simplify your application into the basics and then apply visualizations.

Get the source for this project here:  Source Code.  The Xaml for this is literally only 67 lines with spaces.  There is 1 line of custom code in code behind, and you saw the AttackService above.  It’s dead simple.

Why WPF? This is why…

July 2nd, 2008

http://dnrtv.com/default.aspx?showID=115

Just awesome!  I didn’t see anything far fetched other than they must have a great designer on staff.  I wonder if the designer was doing the Blend work?