RSS

Category Archives: PivotViewer

CustomActions (ItemAdorners) in PivotViewer v2

You can download the example solution here.

PivotViewer v1 introduced the concept of CustomActions on tiles. Enabling the behaviour was a 3-stage process. Firstly, you had to implement your own PivotViewer control and override the GetCustomActionsForItem method. Secondly, you had to write some CustomAction-derived classes. Thirdly, you had to handle the ItemActionExecuted event that the PivotViewer control raised in order to evaluate which CustomAction had been executed. This was not an ideal approach.

PivotViewer v2 has a much richer framework for adding custom functionality to your tiles. I’ll cover some advanced techniques in my next post but, for now, I’ll show you how CustomActions have evolved in this version.

The new version also takes a 3-stage approach, but it’s a lot less invasive of the PivotViewer control. For one, you dont’t have to implement your own custom class any more.

Step 1: Creating the ItemAdorner Style

The new PivotViewer control allows us to define a style for the CustomActions; now called ItemAdorners. This is just a standard Silverlight style, but the trick is to include a PivotViewerDefaultItemAdorner control.

<p:PivotViewer.ItemAdornerStyle>
  <Style TargetType="p:PivotViewerItemAdorner">
    <Setter Property="Template">
      <Setter.Value>
        <ControlTemplate TargetType="p:PivotViewerItemAdorner">
          <p:PivotViewerDefaultItemAdorner 
            IsTabStop="False"
            DataContext="{TemplateBinding Content}"
            CommandsRequested="GetCommands"
            IsItemSelected="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsItemSelected}" />
        </ControlTemplate>
      </Setter.Value>
    </Setter>
  </Style>
</p:PivotViewer.ItemAdornerStyle>

Notice how we’ve bound the IsItemSelected property. This allows the control to hide itself when the tile doesn’t have focus. Later, we’ll be wiring up the GetCommands event but first we have to create some commands.

Stage 2: Creating Commands

Any command associated with the adorner must implement a new interface IPivotViewerUICommand. This extends the, now familiar, ICommand interface and retains some familiar members from CustomActions in v1…

using System;
using System.Windows.Input;

namespace System.Windows.Controls.Pivot
{
    public interface IPivotViewerUICommand : ICommand
    {
        string DisplayName { get; }
        Uri Icon { get; }
        object ToolTip { get; }
    }
}

So, let’s create a simple Hello World command. We’ll pass in a reference to the underlying item that is the data source of our tile so that the command has access to its caller…

public class HelloWorldCommand : IPivotViewerUICommand
{
  private readonly Person _person;

  public HelloWorldCommand(Person person)
  {
    _person = person;
  }

  public string DisplayName
  {
    get { return "Hello World"; }
  }

  public Uri Icon
  {
    get { return new Uri("http://dev.opera.com/articles/view/opera-extensions-hello-world/hello.png"); }
  }

  public object ToolTip
  {
    get { return "Click this to say hello"; }
  }

  public bool CanExecute(object parameter)
  {
    return true;
  }

  public event EventHandler CanExecuteChanged;

  public void Execute(object parameter)
  {
    MessageBox.Show(_person.Name + " says 'Hello'");
  }
}

Stage 3: Connecting the Commands

Lastly we’ll add a handler for the PivotViewerItemAdorner’s GetCommands event where we’ll instantiate and add the commands…

public partial class MainPage : UserControl
{
  private void GetCommands(object sender, PivotViewerCommandsRequestedEventArgs e)
  {
    e.Commands.Add(new HelloWorldCommand(e.Item as Person));
  }
}

This is the result:

In my next post I’ll show you how you can get creative with ItemAdorners in ways that weren’t possible in v1.

You can download the example solution here.

 
25 Comments

Posted by on October 28, 2011 in PivotViewer

 

Tags: , , , , , , ,

Efficiency Strategies for PivotViewer

Joris Dries started a Twitter conversation this morning about getting decent performance out of the PivotViewer in SL5RC. So, I thought now would be a good time to gather a bunch of ideas I’ve had over the past few months and put them all in one post. This might not answer Joris’ question directly, but it does satisfy the title.

You can download the complete source code of the examples here.

What I’ll do is build a number of scenarios and compare them against a baseline. Measurements will not be empirical but rather based on when the user thinks the PivotViewer is ready for use. For background info this is the laptop I’m running these tests on:

  • Windows 7 Ultimate x64 SP1
  • Chrome 13
  • WEI: 5.0
  • Intel i5 2.27GHz
  • 4GB RAM
  • Non-SSD HDD

Setting the Baseline

I’m going to start with a data class and a method for creating a sample set from it. This will allow us to stress test the new PivotViewer control. I’m not going to include the code for this here as it’s rather long-winded and not the focus of this post. It’s all in the sample solution.

public class Person : INotifyPropertyChanged
{
    public string Forename;
    public string Surname;
    public int Age
    public string Address1;
    public string Address2;
    public string Address3;
    public string Address4;
    public string Postcode;
    public string Sex;
}

And here’s the footprint of the method for creating a pseudo-random set at runtime that we can databind with:

public static ObservableCollection<Person> GetSampleData(int sampleSize);

Now let’s build some simple xaml so we can show our data. This example uses the new PivotProperties and PivotViewerItemTemplate markup and just needs us to specify an ItemsSource at runtime:

<UserControl x:Class="XamlTileEfficiency.NoEfficiencies"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:pivot="clr-namespace:System.Windows.Controls.Pivot;assembly=System.Windows.Controls.Pivot"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

    <Grid x:Name="LayoutRoot" Background="White">
        <pivot:PivotViewer x:Name="pv">
            <pivot:PivotViewer.ItemTemplates>
                <pivot:PivotViewerItemTemplate>
                    <StackPanel Orientation="Vertical" Height="150" Width="100" Background="LightBlue">
                        <TextBlock Text="{Binding Forename}" />
                        <TextBlock Text="{Binding Surname}" />
                        <TextBlock Text="{Binding Age}" />
                        <TextBlock Text="{Binding Sex}" />
                        <TextBlock Text="{Binding Address1}" />
                        <TextBlock Text="{Binding Address2}" />
                        <TextBlock Text="{Binding Address3}" />
                        <TextBlock Text="{Binding Address4}" />
                        <TextBlock Text="{Binding Postcode}" />
                    </StackPanel>
                </pivot:PivotViewerItemTemplate>
            </pivot:PivotViewer.ItemTemplates>
            <pivot:PivotViewer.PivotProperties>
                <pivot:PivotViewerStringProperty Binding="{Binding Forename}" DisplayName="First Name" Id="ForenameId" Options="CanFilter" />
                <pivot:PivotViewerStringProperty Binding="{Binding Surname}" DisplayName="Last Name" Id="SurnameId" Options="CanFilter" />
                <pivot:PivotViewerNumericProperty Binding="{Binding Age}" DisplayName="Age" Id="AgeId" Options="CanFilter" />
                <pivot:PivotViewerStringProperty Binding="{Binding Sex}" DisplayName="Sex" Id="SexId" Options="CanFilter" />
                <pivot:PivotViewerStringProperty Binding="{Binding Address1}" DisplayName="Address1" Id="Address1Id" Options="CanFilter" />
                <pivot:PivotViewerStringProperty Binding="{Binding Address2}" DisplayName="Address2" Id="Address2Id" Options="CanFilter" />
                <pivot:PivotViewerStringProperty Binding="{Binding Address3}" DisplayName="Address3" Id="Address3Id" Options="CanFilter" />
                <pivot:PivotViewerStringProperty Binding="{Binding Address4}" DisplayName="Address4" Id="Address4Id" Options="CanFilter" />
                <pivot:PivotViewerStringProperty Binding="{Binding Postcode}" DisplayName="Postcode" Id="PostcodeId" Options="CanFilter" />
            </pivot:PivotViewer.PivotProperties>
        </pivot:PivotViewer>
        <Button HorizontalAlignment="Left" VerticalAlignment="Top" Margin="5,2,0,0" Width="100" Height="25" Content="Stop the Clock!" Click="Button_Click" />
    </Grid>
</UserControl>

Lastly let’s write some client code to create the sample set and bind it to our PivotViewer. Note that I’ve also written a rudimentary stopwatch that we’ll use to time when our PivotViewer is ready for a user to interact with it.

public partial class NoEfficiencies : UserControl
    {
        private const int SAMPLE_SIZE = 1000;

        private DateTime _start;

        public NoEfficiencies()
        {
            InitializeComponent();

            Loaded += (sender, e) =>
                {
                    var data = Person.GetSampleData(SAMPLE_SIZE);
                    _start = DateTime.Now;
                    pv.ItemsSource = data;
                };
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            var timespan = TimeSpan.FromTicks(DateTime.Now.Ticks - _start.Ticks);

            MessageBox.Show(string.Format("Elapsed time is {0} seconds.", timespan.TotalSeconds.ToString()));
        }
    }
}

On my laptop it takes about 12 seconds for the items to appear in the PivotViewer.

So, what’s happening under the covers when we set the ItemsSource? First of all the control displays the Filter Panel on the left-hand side. This happens almost instantly and gives a good user experience. The delay is in producing the xaml tiles (or trading cards). This is because the runtime has to render static images from the databound markup. This is expensive.

Strategy 1: Use Pre-generated DeepZoom Images

You may have a situation where you can pre-build all your visuals and store them on a server. If you’ve spent any time working with PivotViewer v1 this will be very familiar territory. If your collections are going to be fairly static you can then just use the new CxmlCollectionSource class in combination with some dynamic features that I talked about in my previous post: Add, Remove & Change Cxml Items in PivotViewer.

Alternatively you can you the new PivotViewerMultiSizeImage control in your PivotViewerItemTemplates to render your pre-generated visuals. Either way, you’re swapping the cost of rendering the images from xaml with that of downloading them from the web.

Strategy 1 works well if your visuals are not very dynamic.

Strategy 2: Use Multiple Visual Templates

The PivotViewerItemTemplate I’ve used in my example is very inefficient for the number of tiles. Before I can interact with the control I have to wait for it to render 1000 tiles of information that I can’t possibly read at the default zoom level. So strategy 2 suggests defining multiple visual templates of increasing complexity. This helps by allowing the control to render simpler visual elements first and then the complex ones asynchronously. In the meantime, however, the user has been able to do some work.

            <pivot:PivotViewer.ItemTemplates>
                <!-- This new template will be shown up to a zoomed width of 100px -->
                <pivot:PivotViewerItemTemplate MaxWidth="100">
                    <StackPanel Orientation="Vertical" Height="75" Width="50" Background="LightGreen">
                        <TextBlock Text="{Binding Forename}" />
                        <TextBlock Text="{Binding Surname}" />
                    </StackPanel>
                </pivot:PivotViewerItemTemplate>
                <pivot:PivotViewerItemTemplate>
                    <StackPanel Orientation="Vertical" Height="150" Width="100" Background="LightBlue">
                        <TextBlock Text="{Binding Forename}" />
                        <TextBlock Text="{Binding Surname}" />
                        <TextBlock Text="{Binding Age}" />
                        <TextBlock Text="{Binding Sex}" />
                        <TextBlock Text="{Binding Address1}" />
                        <TextBlock Text="{Binding Address2}" />
                        <TextBlock Text="{Binding Address3}" />
                        <TextBlock Text="{Binding Address4}" />
                        <TextBlock Text="{Binding Postcode}" />
                    </StackPanel>
                </pivot:PivotViewerItemTemplate>
            </pivot:PivotViewer.ItemTemplates>

Once loaded, select an item by clicking on it. You’ll see the higher res template render as you zoom in. Now use the cursor keys to navigate. You’ll see flashes of green as new tiles come into view. Then, and only then, does the runtime render the higher res image.

Scenario 2 take about 8 seconds to load on my machine. 30% faster than our baseline!

Scenario 3: Stagger your loading

Hitting PivotViewer with 1000 items at once can be a bit overwhelming for it. It also means that your user has to wait until ALL 1000 items have been rendered before they can work. In the pre-beta versions of PivotViewer the team had investigated the idea of a BatchObservableCollection class. Unfortunately, this didn’t make it into the RC but we can still replicate the idea. Basically, we can drip feed batches of the whole over a period of time. If your scenario is very visual, this can be a really cool feature as the user gets to see data being assembled in front of them. Ultimately it takes more time, but might give your users the impression that it’s actually quicker!

First up, let’s add a new static method that will add items to an already existing collection.

public static void PopulateWithSampleData(ObservableCollection<Person> data, int sampleSize);

Next we’ll create a class that will handle the drip-feeding of the items. We need to specify an interval between batches so that a) PivotViewer thinks we’ve finished and starts to refresh the UI and b) so that the UI finishes its refresh before the next batch.

using System;
using System.Collections.ObjectModel;
using System.Windows.Threading;

namespace XamlTileEfficiency
{
    public class DripFeeder<T>
    {
        private readonly DispatcherTimer _timer;
        private readonly ObservableCollection<T> _data;
        private readonly int _sampleSize;
        private readonly int _batchSize;
        private readonly Action<ObservableCollection<T>, int> _populateAction;

        private int _counter;

        public DripFeeder(ObservableCollection<T> data, int sampleSize, int batchSize, TimeSpan interval, Action<ObservableCollection<T>, int> populateAction)
        {
            _timer = new DispatcherTimer { Interval = interval };
            _timer.Tick += _timer_Tick;

            _data = data;
            _sampleSize = sampleSize;
            _batchSize = batchSize;
            _populateAction = populateAction;
        }

        void _timer_Tick(object sender, EventArgs e)
        {
            _timer.Stop();

            if (_counter < _sampleSize)
            {
                // This won't take account of any remainders, but hey.
                _populateAction(_data, _batchSize);
                _counter += _batchSize;

                _timer.Start();
            }
        }

        public void Start()
        {
            _timer_Tick(null, EventArgs.Empty);
        }
   }
}

Now that we have all our building blocks we just need to change the client code from our baseline example like this. Notice that we do our databinding BEFORE we start to populate the collection.

            Loaded += (sender, e) =>
            {
                var data = new ObservableCollection<Person>();
                _start = DateTime.Now;
                pv.ItemsSource = data;

                var feeder = new DripFeeder<Person>(data, SAMPLE_SIZE, BATCH_SIZE, TimeSpan.FromSeconds(INTERVAL_SECONDS), Person.PopulateWithSampleData);
                feeder.Start();
            };

So, scenario 3 is great for situations where the gradual loading of data accompanied with the visual changes is OK. In public demos this might be impressive!

Scenario 4: Lazy Load your Facets

In combination with Scenario 2 this can be very powerful. It is also a great way to use PivotViewer with high-latency data. e.g. stuff gathered from multiple sources across the web. As long as our data items implement INotifyPropertyChanged we can lazy load the data asynchronously or triggered when we need it.

In this last example (LazyLoader.xaml) we’ll just load the Forename and Surname properties. Then, as the user selects items in the PivotViewer we’ll go and fetch the rest of the data. The control will handle the PropertyChanged events and re-render the visual elements for us. It’s not the most elegant example, but you should get the potential from it.

Wrap Up

The PivotViewer control is very powerful and flexible but you have to think about loading data in the right way for your situation. I’d love to hear of other scenarios that I haven’t covered here.

Finally, you can download the complete source code of the examples here.

 
11 Comments

Posted by on September 8, 2011 in PivotViewer

 

Tags: , , , ,

Breaking Changes in PivotViewer SL5RC

I’ve been wondering why a bunch of our collections on PhotoPivot haven’t been working in my SL5RC examples. After much scratching of various heads I’ve figured it out. PivotViewer in SL4 tolerated empty Description elements in <Item> tags in cxml files. PivotViewer in SL5 does not. Unfortunately, no errors were raised during loading. Seems like the Parse Exceptions from SL4 aren’t currently in SL5.

Here’s an example:

    <Item Img="#0" Id="0" Href="http://photopivot.com" Name="An Item">
      <!-- Remove this tag completely if you want to run this in SL5 -->
      <Description />
    </Item>
 
Leave a comment

Posted by on September 5, 2011 in PivotViewer

 

Tags: , , ,

Add, Remove & Change Cxml Items in PivotViewer

The first thing that most people will do with PivotViewer v2 in SL5RC is look at an existing static collection that they’ve made. The mechanism for loading an existing cxml file in Silverlight 5 has changed since the EAP releases, but is still very straight forward. Assuming that you already have a PivotViewer control called pv in your xaml, this is all you need (I’m using one of Tony Champion‘s test collections)…

var collection = new CxmlCollectionSource(new Uri("http://labs.championds.com/MIX10/MIX10Collection.cxml"));
collection.StateChanged += (sender, e) =>
{
  if (e.NewState == Loaded)
  {
    pv.PivotProperties = (IList)collection.ItemProperties;
    pv.ItemTemplates = collection.ItemTemplates;
    pv.ItemsSource = collection.Items;
  }
}

Now, Silverlight 5 promised some dynamic control over these collections, but we need to do a little work first. Firstly, the Items property of CxmlCollectionSource is of type ReadOnlyObservableCollection<PivotViewerItem>. This stops us adding and removing any items. What we need to do is construct an ObservableCollection<PivotViewerItem> for our items and then bind that to the control. We’ll keep a reference to this new collection so that we don’t have to keep casting the ItemsSource. This changes our code to:

public partial class MainPage : UserControl
{
  private ObservableCollection<PivotViewerItem> _items;

  public MainPage()
  {
    InitialiseComponent();

    Loaded += LoadCollection();
  }

  private void LoadCollection()
  {
    var collection = new CxmlCollectionSource(new Uri("http://labs.championds.com/MIX10/MIX10Collection.cxml"));

    collection.StateChanged += (sender, e) =>
    {
      if (e.NewState == Loaded)
      {
        _items = new ObservableCollection<PivotViewerItem>(collection.Items);

        pv.PivotProperties = collection.ItemProperties;
        pv.ItemTemplates = collection.ItemTemplates;
        pv.ItemsSource = _items;
      }
    }
  }

Removing Items

Now that we have our Observable Collection, removing items is trivially simple. Assuming you add a Button to your xaml, this is how we would remove the currently selected item:

private void btnRemove_Click(object sender, RoutedEventArgs e)
{
  if (pv.SelectedIndex >= 0)
    _items.RemoveAt(pv.SelectIndex);
}

Adding Items

Adding new items is harder than removing them. There are a number of required properties that need to be specified and then there are the optional ones that the collection’s designer has specified. First off, we’ll create an extension method that will make it easier to find properties on a PivotViewer:

public static class PivotViewerExtensions
{
  public static PivotViewerProperty FindProperty(this PivotViewer pv, string displayName)
  {
    var query = pv.PivotProperties.AsQueryable().OfType<PivotViewerProperty>();

    return query.Where(q => q.DisplayName == displayName).FirstOrDefault();
  }
}

Next we’ll add a Factory class that will help us create a PivotViewerItem with the required properties. The ‘visualCollectionSource’ parameter specifies the xml file for a single DeepZoom image (either .dzi or .xml):

public static class PivotViewerItemFactory
{
  public static PivotViewerItem Create(PivotViewer parentViewer, string id, string name, string description, string visualCollectionSource)
  {
    var item = new PivotViewerItem(id);

    item.Add(parentViewer.FindProperty("Name"), new List<string> { name });
    item.Add(parentViewer.FindProperty("Description"), new List<string> { description });
    item.Add(parentViewer.FindProperty("VisualCollectionSource"), new List<string> { visualCollectionSource });

    return item;
  }
}

Finally, we need to write some client code that will create a ‘standard’ PivotViewerItem and then add any other properties we want:

var newItem = PivotViewerItemFactory.Create(pv, (_data.Count + 1).ToString(), "New Name", "New Description", "http://labs.championds.com/MIX10/MIXCollectionTest_files2/lcy4icqv.3r5.xml");

// Now add the optional properties...
newItem.Add(pv.FindProperty("Code"), new List<string> { "A New Code" });
newItem.Add(pv.FindProperty("Room"), new List<string> { "A New Room" });
newItem.Add(pv.FindProperty("Speakers"), new List<string> { "A New Speaker" });
newItem.Add(pv.FindProperty("Video"), new List<PivotViewerHyperlink> {  new PivotViewerHyperlink("My video", new Uri("http://a.com/b.wmv")) });

// Finally, add our new item to the collection...
_data.Add(newItem);

Changing Items

The issue we have here is that PivotViewerItem gives us no way to access or change the value of any PivotViewerProperty values. What we have to do is remove the property we want to change and then re-add it with the new value. Again we’ll create a convenient extension method for this:

public static class PivotViewerItemExtensions
{
  public static void Replace(this PivotViewerItem item, PivotViewer pivotViewer, string displayName, IList newValues)
  {
    var property = pivotViewer.FindProperty(displayName);

    if (property == null)
      throw new PropertyNotFoundException(displayName);

    item.Remove(property);

    item.Add(property, newValues);
  }

  public class PropertyNotFoundException : Exception
  {
    public readonly string DisplayName;

    public PropertyNotFoundException(string displayName)
      : base("Failed to find a property with Displayname of " + displayName)
    {
      DisplayName = displayName;
    }
  }
}

Calling this from client code then only requires us to know the name of the property and the new value:

if (pv.SelectedIndex >= 0)
  _data[pv.SelectedIndex].Replace(pv, "Room", new List<string> { "A New Room" });

You can download the complete source code here and follow me @GoodCoffeeCode for more blog posts on PivotViewer.

 
4 Comments

Posted by on September 4, 2011 in PivotViewer

 

Tags: , , ,

Fixing Corrupt Image Metadata in DeepZoom

During the past few months PhotoPivot has created over 1 million DeepZoom images. Given these numbers, we were bound to encounter some problems. One of the most common is source images with corrupt colour palette information. The DeepZoomTools just can’t cope with this and you’ll get an exception like this…

System.ArgumentException: The image has corrupted metadata header.
	System.Runtime.InteropServices.COMException: Exception from HRESULT: 0x88982F63

at System.Windows.Media.ColorContextsHelper(GetColorContextsDelegate getColorContexts)
at System.Windows.Media.Imaging.BitmapFrameDecode.get_ColorContexts()
at System.Windows.Media.Imaging.BitmapSource.CreateCachedBitmap(BitmapFrame frame, BitmapSourceSafeMILHandle wicSource, BitmapCreateOptions createOptions, BitmapCacheOption cacheOption, BitmapPalette palette)
at System.Windows.Media.Imaging.BitmapFrameDecode.FinalizeCreation()
at System.Windows.Media.Imaging.BitmapDecoder.SetupFrames(BitmapDecoder decoder, ReadOnlyCollection'1 frames)
at System.Windows.Media.Imaging.BitmapDecoder.get_Frames()
at Microsoft.DeepZoomTools.BitmapTransformer.CreateInputFrame(InputNode inputNode, Int32Rec rectNeeded, Double scaleFactor)
at Microsoft.DeepZoomTools.BitmapTransformer.GetPixelSize(String fileName)
at Microsoft.DeepZoomTools.SparseImageCreator.Create(ICollection'1 images, String destination)
at Microsoft.DeepZoomTools.ImageCreator.Create(String source, String destination)

The approach we took to fixing this was to catch the exception and then remove the palette information from the image before re-submitting. Here’s the first class you’ll need to implement. Setting the second parameter to false when loading the image tells the runtime to ignore any embedded colour management. This is the key.


using System;
using System.Drawing;

namespace GoodCoffeeGoodCode
{
  internal static class ImageColorManagementCorrector
  {
    internal static void Correct(string inputFile, string outputFile)
    {
      using (var bitmap = Bitmap.FromFile(intputFile, false))
      {
        bitmap.Save(outputFile);
      }
    }
  }
}

Next, we extended the standard ImageCreator class found in the DeepZoomTools namespace. This encapsulates the catch, retry routines.


using System;
using System.IO;
using Microsoft.DeepZoomTools;

namespace GoodCoffeeGoodCode
{
  internal class PaletteSafeImageCreator : ImageCreator
  {
    public new void Create(string source, string destination)
    {
      try
      {
        base.Create(source, destination);
      }
      catch (SystemException)
      {
        var cleanSource = GetTempFileName(source);

        ImageColorManagementCorrector.Correct(source, cleanSource);

        base.Create(cleanSource, destination);
      }
    }

    private static string GetTempFileName(string source)
    {
      var fileName = Path.GetFileNameWithoutExtension(source) + "_temp";

      return Path.Combine(Path.GetDirectoryName(source), fileName + Path.GetExtension(source));
    }
  }
}

And that’s it. Just ‘new-up’ a PaletteSafeImageCreator in place of an ImageCreator and you now have the ability to automatically cope with corrupt colour palette metadata in images.

 
3 Comments

Posted by on August 31, 2011 in PivotViewer

 

Tags: , ,

Inside PivotViewer v2. Episode 1, Legacy Support

Welcome to the first in a series of video blog posts where we’ll be looking at what’s new for PivotViewer v2 in Silverlight 5.

In this episode we’ll take a deep-dive into PivotViewer v2 and look at what legacy support is available to all those that have created static, server-side cxml collections. We’ll also see some great dynamic benefits that can be applied to these existing collections with v2.

Please leave comments, feedback and any suggestions for things you’d like to see in this series.

Inside PivotViewer v2. Episode 1, Legacy Support from Chris Arnold on Vimeo.

Here are the links to the references I make at the end of the video:

Tony Champion (.Net Rocker)
http://tonychampion.net

Xpert360
http://xpert360.com

Kingsley Uyi Idehen
http://twitter.com/kidehen

 
11 Comments

Posted by on May 13, 2011 in PivotViewer

 

Tags: , , , , , ,

Pivot API for .Net

Firstly, I don’t know whether this is strictly an API, a wrapper or an object model for Pivot. So, shall we agree to gloss over the semantics and just say that it is a bunch of related classes that will allow you to easily create your Pivot collections in a .Net environment and then save them as .cxml files? OK? Good.

Once you’ve read through all this there are links to download the source code and binaries at the end of this post.

If you don’t already know Microsoft Live Labs is in the process of releasing a pretty cool piece of data “analysis” called Pivot. At the time of writing this you need to download their browser to experience the ‘goodness’ but by summer 2010 we’ve been assured a Silverlight plugin.

Let’s get another thing straight: you could just go write your own xml files for this; after all, that is the product we ultimately need to create. However, why not let someone else (me) do the hard work so that all you need to do is just new-up some classes?

Finally, I need to assume that you have a reasonable understanding of the architecture and components involved in the Pivot application. You can get all you need from here.

The School Class Collection

The Pivot API contains very little documentation or comments. This is because, I hope, the classes and methods document themselves with their names. So, the best way I could think to demonstrate the behaviour and usage was through a worked example.

For this example let’s assume that I have a pre-populated collection of Pupil objects (IList<Pupil>) that represents the raw data that I wish to create a Pivot Collection from.

class Pupil
{
  public long ID { get; set; }
  public string Name { get; set; }
  public DateTime DateOfBirth { get; set; }
  public string Class { get; set; }
  public string ReportCard { get; set; }
  public string Description { get; set; }
  public Pupil BestFriend { get; set; }
}

Our starting object will be an instance of the PivotCollection class. You’ll need to supply this with a name, a version and a reference to an already created Deep Zoom Collection. I will not cover how to perform the latter but there’s plenty of information out there to help you with this. You can also, optionally, add some copyright information to the collection.

var collection = new PivotCollection("School Collection", "schoolCollection.dzc", "1.0")
{
  Copyright = new CollectionCopyright { Name = "Chris Arnold", Href = "http://goodcoffeegoodcode.blogspot.com/" }
};

Next we have to define what information (Facets) the items in our collection will expose. The API defines a number of different Facet Category classes that encapsulate some inherent functionality.

collection.FacetCategories.Add(new StringFacetCategory("Class"));
collection.FacetCategories.Add(new DateTimeFacetCategory("Date of Birth"));
collection.FacetCategories.Add(new LongStringFacetCategory("Report Card"));
collection.FacetCategories.Add(new LinkFacetCategory("Best Friend"));

You could also encapsulate all of this into your own class to make your code more readable:

class SchoolPivotCollection : PivotCollection
{
  public SchoolPivotCollection()
    : base("School Collection", "schoolCollection.dzc", "1.0")
  {
    Copyright = new CollectionCopyright { Name = "Chris Arnold", Href = "http://goodcoffeegoodcode.blogspot.com/" };
    FacetCategories.Add(new DateTimeFacetCategory("Date of Birth"));
    FacetCategories.Add(new LongStringFacetCategory("Report Card"));
    FacetCategories.Add(new LinkFacetCategory("Best Friend"));
  }
}

We’ve now set up all of the pre-requisites for our collection. Time to start populating it. To do this we just need to iterate over our collection of Pupils, create a Pivot Item for each one and add it to the Pivot collection…

var pupils = GetPupils();
foreach (var pupil in pupils)
  AddPupilToCollection(pupil, collection);
void AddPupilToCollection(Pupil pupil, PivotCollection collection)
{
  string PUPIL_URL = "http://www.myschool.com/pupils?id={0}";
  string PUPIL_COLLECTION_URL = "http://www.myschool.com/collection/pupil_{0}.cxml";
  var item = new Item()
  {
    Name = pupil.Name,
    Href = string.Format(PUPIL_URL, pupil.ID),
    Description = pupil.Description,
    Img = "#" + imageCounter, /* This assumes that the order of the pupils is the same as their images in the deep zoom collection */
    Id = pupil.ID
  };

  item.Facets.Add(FacetFactory.Create(collection.FacetCategories["Class"], pupil.Class));
  item.Facets.Add(FacetFactory.Create(collection.FacetCategories["Date of Birth"], pupil.DateOfBirth.ToShortDateString()));
  item.Facets.Add(FacetFactory.Create(collection.FacetCategories["Report Card"], pupil.ReportCard));

  var facet = FacetFactory.Create(collection.FacetCategories["Best Friend"], string.Format(PUPIL_COLLECTION_URL, pupil.BestFriend.ID)) as LinkFacet;
  facet.Name = pupil.BestFriend.Name;

  collection.Items.Add(item);

  imageCounter++;
}

The final step in the process is to save the collection to an xml document. This is handled using the classes in the PivotAPI.XmlWriter namespace…

using (var writer = new XmlTextWriter("pupils.cxml", System.Text.Encoding.Default) { Formatting = Formatting.Indented })
{
  var collectionWriter = new PivotAPI.XmlWriters.PivotCollectionXmlWriter(writer, collection);
  collectionWriter.Write();
}

And that’s the only client code you’ll need to create a collection document (.cxml).

Here’s the complete code example. Create a new console application, reference the PivotAPI library and replace the Program.cs with the following…

using System;
using System.Collections.Generic;
using System.Xml;
using PivotAPI;

namespace BlogPivotExample
{
  class Program
  {
    static int imageCounter;

    static void Main()
    {
      var collection = new SchoolPivotCollection();

      var pupils = GetPupils();

      foreach (var pupil in pupils)
        AddPupil(pupil, collection);

      using (var writer = new XmlTextWriter("pupils.cxml", System.Text.Encoding.Default) { Formatting = Formatting.Indented })
      {
        var collectionWriter = new PivotAPI.XmlWriters.PivotCollectionXmlWriter(writer, collection);

        collectionWriter.Write();
      }
    }

    static void AddPupil(Pupil pupil, PivotCollection collection)
    {
      string PUPIL_URL = "http://www.myschool.com/pupils?id={0}";
      string PUPIL_COLLECTION_URL = "http://www.myschool.com/collection/pupil_{0}.cxml";

      var item = new Item()
      {
        Name = pupil.Name,
        Href = string.Format(PUPIL_URL, pupil.ID),
        Description = pupil.Description,
        Img = "#" + imageCounter, /* This assumes that the order of the pupils is the same as their images in the deep zoom collection */
        Id = pupil.ID
      };

      item.Facets.Add(FacetFactory.Create(collection.FacetCategories["Class"], pupil.Class));
      item.Facets.Add(FacetFactory.Create(collection.FacetCategories["Date of Birth"], pupil.DateOfBirth.ToShortDateString()));
      item.Facets.Add(FacetFactory.Create(collection.FacetCategories["Report Card"], pupil.ReportCard));

      var facet = FacetFactory.Create(collection.FacetCategories["Best Friend"], string.Format(PUPIL_COLLECTION_URL, pupil.BestFriend.ID)) as LinkFacet;
      facet.Name = pupil.BestFriend.Name;

      collection.Items.Add(item);

      imageCounter++;
    }
    static IList GetPupils()
    {
      var pupils = new List();

      //TODO: this is just a test pupil. You'll have to write your own routine if you want more data than this!
      var testPupil = new Pupil
      {
        Name = "Chris Arnold",
        ID = 34873,
        Class = "Upper 5A",
        Description = "Lovely child",
        DateOfBirth = DateTime.Parse("1/1/1973"),
        ReportCard = "Chris has worked very hard this year. Well done!"
      };

      pupils.Add(testPupil);

      return pupils;
    }
  }
}

Now create 2 new classes in you application…

using System;
using PivotAPI;

namespace BlogPivotExample
{
  class SchoolPivotCollection : PivotCollection
  {
    public SchoolPivotCollection()
      : base("School Collection", "schoolCollection.dzc", "1.0")
    {
      Copyright = new CollectionCopyright { Name = "Chris Arnold", Href = "http://goodcoffeegoodcode.blogspot.com/" };

      FacetCategories.Add(new DateTimeFacetCategory("Date of Birth"));
      FacetCategories.Add(new LongStringFacetCategory("Report Card"));
      FacetCategories.Add(new LinkFacetCategory("Best Friend"));
    }
  }
}
using System;

namespace BlogPivotExample
{
  internal class Pupil
  {
    public long ID { get; set; }
    public string Name { get; set; }
    public DateTime DateOfBirth { get; set; }
    public string Class { get; set; }
    public string ReportCard { get; set; }
    public string Description { get; set; }
    public Pupil BestFriend { get; set; }
  }
}

Caveats

  • The API uses LINQ and so you’ll need version 3.5 or 4.0 of the .Net framework.
  • The API is v1.0.0 and is not complete. Missing functions include the Supplement file, BrandImage, AdditionalSearchText, Icon. Also, some of the extensions have not been implemented yet e.g. DateRange, SortOrder and SortValue. These will all follow very quickly.

 

Tweet Pivot

Just to demonstrate that this isn’t just all smoke and mirrors I wrote a website called Tweet Pivot. This utilises the Pivot API to get Twitter data and create dynamic collections. This is also where I’ve hosted the API for convenience.

So, if you like Tweet Pivot or the Pivot API please re-tweet them! Thanks.

Download Pivot API Source Code and Binaries

 
8 Comments

Posted by on May 23, 2010 in PivotViewer

 

Tags: , , ,